Commit 60591ea4 authored by yorn's avatar yorn

Merge branch 'automake'

Conflicts:
	crude/main.c
	include/rude.h
	rude/parse.c
parents ab985946 985733f1
*.o
crude/crude
rude/rude
autom4te.cache/
autoconf/config.hin
configure
crude/Makefile
include/config.h
include/stamp.h
rude/Makefile
Makefile
......@@ -39,6 +39,11 @@ INTRODUCTION (very short):
COMPILATION AND INSTALLATION INSTRUCTIONS:
------------------------------------------
In order to compile this software, you need a compiler. How you can
obtain a compiler depends on your operating system. on Debian,
for example, you need to run
`apt-get install build-essential git automake autoconf`.
1. This package you have received should contain the automatic 'configure'
script in the source code root directory. If this is true you can jump
into the step 4. Otherwise read also the steps 2. and 3. that explain
......@@ -49,22 +54,22 @@ COMPILATION AND INSTALLATION INSTRUCTIONS:
2. So you have the very clean version. Alright. Before you can compile
the program you should make the configuration script(s) and other
files with the GNU autoconf utilities. In other words you require
properly installed 'autoheader' and 'autoconf' programs. At least
properly installed `autoheader` and `autoconf` programs. At least
version 2.13 of the GNU autoconf tools seems to work fine.
[ After those programs are installed you can move on.... ]
3. You need to generate some header file(s) and the configuration script
with the following commands (you must be in the program's source code
root directory):
with the following commands (you must be in the project's root
directory, right outside the DOC folder):
autoheader -l ./autoconf
autoheader autoconf/configure.in
autoconf autoconf/configure.in > configure
chmod 755 configure
4. Now you are ready to go on and configure this software package. As
usual, you can say './configure --help' to get the full list of
usual, you can say `./configure --help` to get the full list of
configuring switches. Here are only the usually required options:
--prefix=PREFIX = Installation directory root (/usr/local/)
......@@ -77,9 +82,14 @@ COMPILATION AND INSTALLATION INSTRUCTIONS:
./configure --enable-wall --with-debug-lvl=3
The configure command will give a warning about ignoring
--datarootdir setting. You can safely ignore this warning.
5. 'make all'
This command will create the executables
`rude/rude` and `crude/crude`.
So far this program is tested on Linux and Solaris2.6 OS's and
seems to compile/operate without any major drawbacks. Your mileage
may vary...
......@@ -87,6 +97,7 @@ COMPILATION AND INSTALLATION INSTRUCTIONS:
6. 'make install'
This command will install the executables on the filesystem.
USE MANUAL INSTALLATION IF THIS FAILS. SORRY 'BOUT THAT :)
Only ROOT can successfully execute this command.
......
......@@ -22,10 +22,20 @@ START NOW
## 1000 packets/second with 1000 bytes/packet = 1000kbytes/sec
##
## 1 second from that the flow is turned off...
##
1000 0030 ON 3002 10.1.1.1:10001 CONSTANT 200 250
3000 0030 MODIFY CONSTANT 400 500
4000 0030 MODIFY CONSTANT 1000 1000
1000 0030 ON 1030 192.168.1.1:1031 CONSTANT 200 250
#2000 0030 MODIFY CONSTANT 400 500
#4000 0030 MODIFY CONSTANT 1000 1000
# IPv4 multicast address
#1000 0030 ON 1030 224.0.2.123:1031 CONSTANT 10 256
# IPv6 unicast address
#1000 0030 ON 1030 2001:718:1:b:21f:d0ff:feae:64b2:1031 CONSTANT 10 512
# ipv6 multicast address
#1000 0030 ON 1030 FF3E:040:2001:718:2:981:ACAC:ACCA:1031 CONSTANT 10 1024
5000 0030 OFF
## FLOW 2: (flow ID = 25)
......@@ -36,16 +46,16 @@ START NOW
## Sets the TOS for this flow to LOW_DELAY (0x10)
##
## 9 seconds after that the flow is turned off...
##
0000 0025 ON 3001 10.1.1.1:10001 CONSTANT 400 100
TOS 0025 0x10
9000 0025 OFF
#
#0000 0025 ON 1025 hostname.domain.com:1026 CONSTANT 400 100
#TOS 0025 0x10
#9000 0025 OFF
## FLOW 3: (flow ID = 1)
##
## This flow acts as specified in the TRACE configuration file.
## Read the README.rude file for the command and file syntax
##
0000 1 ON 3111 10.1.1.1:10001 TRACE trace_file.txt
9999 1 OFF
#0000 1 ON 1001 10.1.1.1:1002 TRACE trace_file.txt
#9999 1 OFF
## ... and that's it.
# $Id$
# $Id: Makefile.in,v 1.1 2005/05/31 07:12:38 ubik Exp $
#
# autoconf/Makefile.in - the main Makefile template for RUDE and CRUDE
#
......
This diff is collapsed.
This diff is collapsed.
# $Id$
# $Id: configure.in,v 1.1 2005/05/31 07:12:38 ubik Exp $
#
# autoconf/configure.in for RUDE and CRUDE
#
......
This diff is collapsed.
# $Id$
# $Id: Makefile.in,v 1.2 2005/06/30 12:02:49 juranek Exp $
#
# Makefile.in for CRUDE
#
......@@ -29,7 +29,7 @@ DEFS = @DEFS@
LIBS = @LIBS@
MAN8 = crude.8
CRUDE_OBJS = main.o
CRUDE_OBJS = main.o ../rude/mcast.o
##############################################################################
# Rules for make
......
This diff is collapsed.
/*
* C Implementation: %{$MODULE}
*
* Description:
*
*
*
* Copyright: See COPYING file that comes with this distribution
*
*/
#include <mcast.h>
#include <rude.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <net/if.h>
#include <sys/ioctl.h>
int
isMulticastAddr(struct sockaddr_storage *addr)
{
int retVal;
retVal=-1;
switch (addr->ss_family) {
case AF_INET: {
struct sockaddr_in *addr4=(struct sockaddr_in *)addr;
retVal = IN_MULTICAST(ntohl(addr4->sin_addr.s_addr));
} break;
case AF_INET6: {
struct sockaddr_in6 *addr6=(struct sockaddr_in6 *)addr;
retVal = IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr);
} break;
default:
;
}
return retVal;
}
int joinGroup(int sockfd, int loopBack, unsigned int mcastTTL,struct sockaddr_storage *addr,int interface)
{
int r1, r2, r3, retval;
retval=-1;
switch (addr->ss_family) {
case AF_INET: {
struct ip_mreq mreq;
//zjistit adresu interfacu(v parametru je jeho index),INADDR_ANY je pro defaultni
mreq.imr_multiaddr.s_addr=
((struct sockaddr_in *)addr)->sin_addr.s_addr;
if(interface < 0){
mreq.imr_interface.s_addr= INADDR_ANY;
RUDEBUG7("joinGroup: using inaddr_any interface\n");
}
else{
struct ifreq ifr;
char name[IF_NAMESIZE];
RUDEBUG7("joinGroup: using interface %i\n",interface);
if(!if_indextoname(interface,name)){
RUDEBUG1("joinGroup: interface doesnt exist.\n");
return retval;
}
strcpy(ifr.ifr_name, name);
if(ioctl(sockfd,SIOCGIFADDR,&ifr)==-1){
RUDEBUG1("joinGroup() - ioctl failed for getting interface address: %s\n",strerror(errno));
return retval;
}
struct sockaddr_in *si = (struct sockaddr_in*)&ifr.ifr_addr;
RUDEBUG7("joinGroup(): interface address: %s\n",inet_ntoa(si->sin_addr));
//mreq.imr_interface.s_addr = si->sin_addr.s_addr;
memcpy(&mreq.imr_interface,&(si->sin_addr),sizeof(struct in_addr));
//mreq.imr_interface = si->sin_addr;
}
r1= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
RUDEBUG1("joinGroup:: IP_MULTICAST_LOOP:: ");
r2= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
RUDEBUG1("joinGroup:: IP_MULTICAST_TTL:: ");
r3= setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(const void *)&mreq, sizeof(mreq));
if (r3<0)
RUDEBUG1("joinGroup:: IP_ADD_MEMBERSHIP:: ");
} break;
case AF_INET6: {
struct ipv6_mreq mreq6;
memcpy(&mreq6.ipv6mr_multiaddr,
&(((struct sockaddr_in6 *)addr)->sin6_addr),
sizeof(struct in6_addr));
if(interface < 0){
mreq6.ipv6mr_interface= 0;
RUDEBUG7("joinGroup: using default interface\n");
}else{
mreq6.ipv6mr_interface= interface;
RUDEBUG7("joinGroup: using interface with index: %d\n",interface);
}
r1= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
RUDEBUG1("joinGroup(): IPV6_MULTICAST_LOOP error");
r2= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
RUDEBUG1("joinGroup() IPV6_MULTICAST_HOPS error ");
r3=setsockopt(sockfd,IPPROTO_IPV6,IPV6_JOIN_GROUP,&mreq6,sizeof(mreq6));
//r3=setsockopt(sockfd,IPPROTO_IPV6,IP_ADD_MEMBERSHIP,&mreq6,sizeof(mreq6));
if (r3<0)
RUDEBUG1("joinGroup() IPV6_JOIN_GROUP error ");
} break;
default:
r1=r2=r3=-1;
}
if ((r1>=0) && (r2>=0) && (r3>=0))
retval=0;
return retval;
}
//
// C++ Interface: %{MODULE}
//
// Description:
//
//
// Author: %{AUTHOR} <%{EMAIL}>, (C) %{YEAR}
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include <netinet/in.h> /* for struct sockaddr_in */
int isMulticastAddr(struct sockaddr_storage *addr);
int joinGroup(int sockfd, int loopBack, unsigned int mcastTTL,struct sockaddr_storage *addr,int interface);
......@@ -29,11 +29,12 @@
#define DNMAXLEN 128
#define TMAXLEN 32
#define PMINSIZE 20 /* Minimum accepted UDP-data field/packet size */
#define PMINSIZE 24 /* Minimum accepted UDP-data field/packet size, in previouse version it was 20 */
#define PMINSIZE_V6 40 /* Minimum accepted UDP-data field/packet size for ipv6 */
#define PMAXSIZE 32768 /* Maximum accepted UDP-data field/packet size */
#define MINDURAT 0.001 /* Minimum allowed flow duration in seconds (float) */
#define VERSION "0.70"
#define VERSION "0.8"
/*
* Enumeration definition for different (known) flow types
......@@ -51,8 +52,10 @@ typedef enum {
*/
struct cbr_params{
f_type ftype; /* Flow TRAFFIC TYPE */
int rate; /* Flow PACKET RATE (per second) */
int rate; /* Flow PACKET RATE - PACKAGE RATE per PERIOD */
int psize; /* Flow PACKET SIZE */
int package_size; /* Flow NUMBER OF PACKETS IN ONE PACKAGE*/
int time_period; /* Flow TIME_PERIOD */
};
/*
......@@ -78,9 +81,10 @@ struct trace_params{
struct flow_cfg {
struct flow_cfg *next; /* Pointer to NEXT flow */
struct flow_cfg *mod_flow; /* Next action-block for the flow */
struct sockaddr_in dst; /* Destination information */
struct sockaddr_storage dst; /* Destination information */
int send_sock; /* Socket to be used by this flow */
long int flow_id; /* Flow IDENTIFICATION number */
unsigned short flow_sport; /* Flow SOURCE PORT number */
struct timeval flow_start; /* Absolute flow cmd START TIME */
......@@ -94,7 +98,8 @@ struct flow_cfg {
int sequence_nmbr; /* */
int tos; /* IP TOS byte if positive */
char *localIf; /* local interface to be used with multicast */
char prefferedVersion; /*preffered ip version(4 or 6)*/
union {
f_type ftype;
struct cbr_params cbr;
......@@ -111,7 +116,7 @@ struct udp_data{
unsigned long tx_time_seconds;
unsigned long tx_time_useconds;
unsigned long flow_id;
unsigned long dest_addr;
struct sockaddr_storage dest_addr;
}__attribute__ ((packed));
......@@ -121,9 +126,10 @@ struct udp_data{
struct crude_struct{
unsigned long rx_time_seconds;
unsigned long rx_time_useconds;
unsigned long src_addr;
//struct in6_addr src_addr;
long pkt_size;
unsigned short src_port;
struct sockaddr_storage src;
//unsigned short src_port;
unsigned short dest_port;
};
......@@ -157,4 +163,17 @@ struct crude_struct{
} while (0)
#endif
#ifndef timespecsub
#define timespecsub(tsp, usp, vsp) \
do { \
(vsp)->tv_sec = (tsp)->tv_sec - (usp)->tv_sec; \
(vsp)->tv_nsec = (tsp)->tv_nsec - (usp)->tv_nsec; \
if ((vsp)->tv_nsec < 0) { \
(vsp)->tv_sec--; \
(vsp)->tv_nsec += 1000000000L; \
} \
} while (0)
#endif
#endif /* _RUDE_H */
# $Id$
# $Id: Makefile.in,v 1.2 2005/06/30 12:02:49 juranek Exp $
#
# Makefile.in for RUDE
#
......@@ -29,7 +29,7 @@ DEFS = @DEFS@
LIBS = @LIBS@
MAN8 = rude.8
RUDE_OBJS = flow_cntl.o flow_txmit.o main.o parse.o
RUDE_OBJS = flow_cntl.o flow_txmit.o main.o parse.o mcast.o
##############################################################################
# Rules for make
......
......@@ -43,12 +43,24 @@ extern struct flow_cfg *head;
extern struct udp_data *data;
extern char *buffer;
struct timespec pre_nano_sleep = {0,10005};//10us+5nanos...see man nanosleep
/*
* wait_for_xmit(): Wait for certain period of time in busy-loop
*/
__inline__ void wait_for_xmit(struct timeval *target, struct timeval *now)
{
//try wait with nanosleep, then active loop
gettimeofday(now,NULL);
if(timercmp(now,target,<)){
//nanosleep has a resolution 10ms on i386 architectures
struct timespec now_spec = {now->tv_sec,now->tv_usec*1000};
struct timespec target_spec = {target->tv_sec,target->tv_usec*1000};
struct timespec diff;
timespecsub(&target_spec,&now_spec,&diff); //dif = now-target - pre_nano_sleep
timespecsub(&diff, &pre_nano_sleep, &diff);
if(diff.tv_sec >= 0 && diff.tv_nsec >= 0)
nanosleep(&diff, NULL);
}
while(1){
gettimeofday(now,NULL);
if(timercmp(now,target,<)){
......@@ -71,9 +83,11 @@ void send_cbr(struct flow_cfg *flow)
struct cbr_params *p = &flow->params.cbr;
struct timeval now = {0,0};
int written = 0;
int i = 0;
/* Do the initialization and wait if necessary */
data->dest_addr = flow->dst.sin_addr.s_addr;
data->dest_addr = flow->dst;
//printf("size: %d\n",sizeof(flow->dst));
data->flow_id = htonl(flow->flow_id);
data->sequence_number = htonl(flow->sequence_nmbr);
wait_for_xmit(&flow->next_tx, &now);
......@@ -84,20 +98,34 @@ void send_cbr(struct flow_cfg *flow)
/* Write the data to the socket and check the result for errors */
/* Increase the status and sequence number counters. */
/* Write whole package - package_size * one packet */
for( i = 0; i < p -> package_size; i++){
//pokud budu posilat jeste dalsi v baliku tak bych mohl zvysit sequence number
if( i > 0){
data->sequence_number = htonl( ++(flow->sequence_nmbr) );
gettimeofday(&now,NULL);
/* ...and fill in the actual times */
data->tx_time_seconds = htonl(now.tv_sec);
data->tx_time_useconds = htonl(now.tv_usec);
}
written = sendto(flow->send_sock, buffer, p->psize, 0,
(struct sockaddr *)&flow->dst,sizeof(struct sockaddr));
(struct sockaddr *)&flow->dst,sizeof(struct sockaddr_storage));
if(written != p->psize){
flow->errors++;
RUDEBUG7("send_cbr(): sendto() error for flow=%ld (%d/%d): %s\n",
flow->flow_id,written,p->psize,strerror(errno));
RUDEBUG7("send_cbr(): sendto() error for flow=%ld (%d/%d/%d): %s\n",
flow->flow_id,written,p->psize,i,strerror(errno));
} else {
flow->success++;
}
}
flow->sequence_nmbr++;
/* Caclulate and store the next transmission time */
flow->next_tx.tv_usec += 1000000.0/p->rate;
if(flow->next_tx.tv_usec >= 1000000){
/* Calculate with time_period */
flow->next_tx.tv_usec += (1000000.0 * p->time_period)/p->rate;
while(flow->next_tx.tv_usec >= 1000000){
flow->next_tx.tv_usec -= 1000000;
flow->next_tx.tv_sec += 1;
}
......@@ -117,7 +145,7 @@ void send_trace(struct flow_cfg *flow)
int written = 0;
/* Do the initialization and wait if necessary... */
data->dest_addr = flow->dst.sin_addr.s_addr;
data->dest_addr = flow->dst;
data->flow_id = htonl(flow->flow_id);
data->sequence_number = htonl(flow->sequence_nmbr);
wait_for_xmit(&flow->next_tx, &now);
......@@ -129,7 +157,7 @@ void send_trace(struct flow_cfg *flow)
/* Write the data to the socket and check the result for errors */
/* Increase the status and sequence number counters. */
written = sendto(flow->send_sock, buffer, xmit_size, 0,
(struct sockaddr *)&flow->dst,sizeof(struct sockaddr));
(struct sockaddr *)&flow->dst,sizeof(struct sockaddr_in6));
if(written != xmit_size){
flow->errors++;
RUDEBUG7("send_trace(): sendto() error for flow=%ld (%d/%d): %s\n",
......
*** flow_txmit.c.orig 2009-07-17 13:00:25.000000000 +0200
--- flow_txmit.c 2009-07-17 13:31:14.000000000 +0200
***************
*** 43,49 ****
extern struct udp_data *data;
extern char *buffer;
! struct timespec pre_nano_sleep = {0,10000005};//10ms+5nanos...see man nanosleep
/*
* wait_for_xmit(): Wait for certain period of time in busy-loop
*/
--- 43,49 ----
extern struct udp_data *data;
extern char *buffer;
! struct timespec pre_nano_sleep = {0,10005};//10us+5nanos...see man nanosleep
/*
* wait_for_xmit(): Wait for certain period of time in busy-loop
*/
This diff is collapsed.
This diff is collapsed.
/*
* C Implementation: %{$MODULE}
*
* Description:
*
*
*
* Copyright: See COPYING file that comes with this distribution
*
*/
#include <mcast.h>
#include <rude.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <net/if.h>
#include <sys/ioctl.h>
int
isMulticastAddr(struct sockaddr_storage *addr)
{
int retVal;
retVal=-1;
switch (addr->ss_family) {
case AF_INET: {
struct sockaddr_in *addr4=(struct sockaddr_in *)addr;
retVal = IN_MULTICAST(ntohl(addr4->sin_addr.s_addr));
} break;
case AF_INET6: {
struct sockaddr_in6 *addr6=(struct sockaddr_in6 *)addr;
retVal = IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr);
} break;
default:
;
}
return retVal;
}
int joinGroup(int sockfd, int loopBack, unsigned int mcastTTL,struct sockaddr_storage *addr,int interface)
{
int r1, r2, r3, retval;
retval=-1;
switch (addr->ss_family) {
case AF_INET: {
struct ip_mreq mreq;
//zjistit adresu interfacu(v parametru je jeho index),INADDR_ANY je pro defaultni
mreq.imr_multiaddr.s_addr=
((struct sockaddr_in *)addr)->sin_addr.s_addr;
if(interface < 0){
mreq.imr_interface.s_addr= INADDR_ANY;
RUDEBUG7("joinGroup: using inaddr_any interface\n");
}
else{
struct ifreq ifr;
char name[IF_NAMESIZE];
RUDEBUG7("joinGroup: using interface %i\n",interface);
if(!if_indextoname(interface,name)){
RUDEBUG1("joinGroup: interface doesnt exist.\n");
return retval;
}
strcpy(ifr.ifr_name, name);
if(ioctl(sockfd,SIOCGIFADDR,&ifr)==-1){
RUDEBUG1("joinGroup() - ioctl failed for getting interface address: %s\n",strerror(errno));
return retval;
}
struct sockaddr_in *si = (struct sockaddr_in*)&ifr.ifr_addr;
RUDEBUG7("joinGroup(): interface address: %s\n",inet_ntoa(si->sin_addr));
//mreq.imr_interface.s_addr = si->sin_addr.s_addr;
memcpy(&mreq.imr_interface,&(si->sin_addr),sizeof(struct in_addr));
//mreq.imr_interface = si->sin_addr;
}
r1= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
RUDEBUG1("joinGroup:: IP_MULTICAST_LOOP:: ");
r2= setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
RUDEBUG1("joinGroup:: IP_MULTICAST_TTL:: ");
r3= setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(const void *)&mreq, sizeof(mreq));
if (r3<0)
RUDEBUG1("joinGroup:: IP_ADD_MEMBERSHIP:: ");
} break;
case AF_INET6: {
struct ipv6_mreq mreq6;
memcpy(&mreq6.ipv6mr_multiaddr,
&(((struct sockaddr_in6 *)addr)->sin6_addr),
sizeof(struct in6_addr));
if(interface < 0){
mreq6.ipv6mr_interface= 0;
RUDEBUG7("joinGroup: using default interface\n");
}else{
mreq6.ipv6mr_interface= interface;
RUDEBUG7("joinGroup: using interface with index: %d\n",interface);
}
r1= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
&loopBack, sizeof(loopBack));
if (r1<0)
RUDEBUG1("joinGroup(): IPV6_MULTICAST_LOOP error");
r2= setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
&mcastTTL, sizeof(mcastTTL));
if (r2<0)
RUDEBUG1("joinGroup() IPV6_MULTICAST_HOPS error ");
r3=setsockopt(sockfd,IPPROTO_IPV6,IPV6_JOIN_GROUP,&mreq6,sizeof(mreq6));
//r3=setsockopt(sockfd,IPPROTO_IPV6,IP_ADD_MEMBERSHIP,&mreq6,sizeof(mreq6));
if (r3<0)
RUDEBUG1("joinGroup() IPV6_JOIN_GROUP error ");
} break;
default:
r1=r2=r3=-1;
}
if ((r1>=0) && (r2>=0) && (r3>=0))
retval=0;
return retval;
}
This diff is collapsed.
#!/usr/local/bin/perl
#!/usr/bin/perl
###############################################################################
# crude_jitter.pl - refines the output from CRUDE.
#
......
#!/usr/local/bin/perl
#!/usr/bin/perl
###############################################################################
# crude_parse.pl - refines the output from CRUDE.
#
......
#!/usr/local/bin/perl
#!/usr/bin/perl
###############################################################################
# dump2trace.pl - converst tcpdump-output to TRACE file
# No documentation (yet) available...
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment