/*
 * ethernet frame generator. (C) 2000-2002 Willy Tarreau <willy@ant-computing.com>
 * Use and redistribute under terms of GPL.
 * 
 * WARNING: no not use when you don't know what this is intended to do, this could
 * kill your network within a few tens of seconds.
 * 
 * Best results seen with :
 *
 *  - DEC 21143 (DEC Tulip, Dlink DFE570TX) : >148 kpps, no error.
 *  - SIS900 : idem, but high system overhead.
 *  - Adaptec Starfire ANA62044 (aha6915) : >148kpps, some errors, particularly 
 *    when link goes down.
 *  - Intel EEPro100 : slightly less, a few unpredictable errors.
 *  - 3Com 3c905C : 82 kpps, no error, little load on the system.
 *  - Realtek RTL8139 : system hangs immediately, never use it !!!
 */

// please read "man 7 packet"
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <features.h>    /* for the glibc version number */
#include <net/if.h>
#include <sys/types.h>
#include <linux/sockios.h>
#if __GLIBC__ >= 2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h>     /* the L2 protocols */
#else
#include <linux/if_packet.h>
#include <linux/if_ether.h>   /* The L2 protocols */
#include <sys/time.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip_icmp.h>
#include <sys/ioctl.h>


#define ETH_HW_ADDR_LEN 6
#define IP_ADDR_LEN 4
#define ETHER_HW_TYPE 1
#define IP_PROTO_TYPE 0x0800
#define ICMP_DATA_LEN 56

#if (ICMP_DATA_LEN<22)
#define PADDING (22-ICMP_DATA_LEN)
#else
#define PADDING 0
#endif

#define DEFAULT_DEVICE "eth0"

#define MAX_FRAME_SIZE 9018	/* up to jumbo frames */

struct s_eth_hdr {
    /* ETHERNET header */
    u_char dst[ETH_HW_ADDR_LEN];
    u_char src[ETH_HW_ADDR_LEN];
};


struct s_vlan {
    u_short	proto;	/* 0x8100 */
    u_short	id;	/* 4 flags + vlan_id */
};


struct s_ip_hdr {
    /* IP header */
    u_char	verihl;	/* 0x45 */
    u_char	tos;
    u_short	totlen;
    u_short	id;
    u_short	fragoff;
    u_char	ttl;
    u_char	protocol;
    u_short	check;
    u_char	src[IP_ADDR_LEN];
    u_char	dst[IP_ADDR_LEN];
};


struct s_icmp_hdr {
    u_char	type;				/* 0x08 = ICMP_ECHOREQUEST */
    u_char	code;				/* 0x00 (ignored) */
    u_short	icmpchk;
    union {
	struct {
	    u_short	id;
	    u_short	sequence;
	} echo;
	u_char	gw_addr[IP_ADDR_LEN];
    } un;
};


struct s_udp_hdr {
    u_short	src;
    u_short	dst;
    u_short	len;
    u_short	chk;
};

void *p_frame;	/* pointer to the frame itself */
void *p_l2;	/* pointer to the end of the encapsulation if any (vlan...) */
void *p_l3;	/* pointer to the L3 protocol header */
void *p_l4;	/* pointer to the L4 protocol header (tcp, udp, icmp -although not l4- ) */
void *p_data;	/* L4 proto data */

void die(char *);
void get_ip_addr(struct in_addr *, char *);
void get_hw_addr(char *, char *);

void die(char* str){
    fprintf(stderr,"%s\n",str);
    exit(1);
}

void get_ip_addr(struct in_addr* in_addr, char* str){
    struct hostent *hostp;

    in_addr->s_addr = inet_addr(str);
    if (in_addr->s_addr == -1) {
	if ((hostp = gethostbyname(str)))
	    bcopy (hostp->h_addr, in_addr, hostp->h_length);
	else {
	    *(unsigned long *)in_addr = -1;  /* 255.255.255.255 */
	}
    }
}

void get_hw_addr(char* buf, char* str){

    int i;
    char c, val;

    for(i = 0; i < ETH_HW_ADDR_LEN; i++){
	while ((*str == ':') || (*str == '.'))
	    str++;
	if (!(c = tolower(*str++)))
	    die("Invalid hardware address");
	if (isdigit(c))
	    val = c-'0';
	else if(c >= 'a' && c <= 'f')
	    val = c-'a'+10;
	else {
	    die("Invalid hardware address");
	    return;
	}

	*buf = val << 4;
	if( !(c = tolower(*str++))) die("Invalid hardware address");
	if(isdigit(c)) val = c-'0';
	else if(c >= 'a' && c <= 'f') val = c-'a'+10;
	else die("Invalid hardware address");

	*buf++ |= val;

	if(*str == ':')str++;
    }
}

/* just like strcat, but with packets */
static inline u_char *pktcat(u_char *to, void *from, int len) {
    memcpy(to, from, len);
    return to+len;
}

/* returns a big-endian checksum of the buffer */
unsigned short checksum(unsigned short* addr, int len) {
    register long sum = 0;
  
    while(len > 1){
	sum += *addr++;
	len -= 2;
    }

    if (len & 1)
	sum += (*addr & 0xff);

    sum = (sum & 65535) + (sum >> 16);

    return ~sum;
}

/* returns a big-endian checksum of the buffer */
unsigned short l4_checksum(unsigned long src, unsigned long dst, u_char proto, unsigned short* addr, int len) {
    register long sum = 0;
  
    sum = (src & 0xffff) + (src >> 16) + (dst & 0xffff) + (dst >> 16);
    sum += (proto << 8); sum += htons(len);

    while(len > 1){
	sum += *addr++;
	len -= 2;
    }

    if (len & 1)
	sum += (*addr & 0xff);

    sum = (sum & 65535) + (sum >> 16);

    return ~sum;
}

void usage() {
    printf("ethforge : ethernet frame forge - Willy Tarreau - 20010523\n"
	   "Syntaxe: ethforge [ -g ] [ -r ] [ -h ] [ -n packets ] [ -w wait_us ] [ -t ttl ]\n"
	   "                  [ -i id ] [ -l len ] [ -S l2src ] [ -D l2dst] [ -e device ]\n"
	   "                  [ -v vlan ]* [ -p l3proto ] [ -s l3src ] [ -d l3dst ] [ -m ]\n"
	   "                  [ -u udp_dst_p ] [ -U udp_src_p ] [ -f ] [ -F ]\n"
	   "                  [ -W ] [ -E ] [ -o frag_offset ] [ -P l4proto ]\n"
	   "  -o sets frag offset. -1 for random ; same for id.\n"
	   "  -g means that data won't be generated, but 'l' bytes will be read from stdin\n"
	   "     and sent after vlan and l3proto.\n"
	   "  -r means to randomize a bit the wait time.\n"
	   "  -m sets random l3dst to multicast.\n"
	   "  -f = don't fragment.\n"
	   "  -F = more fragments.\n"
	   "  -W = wait via select()\n"
	   "  -E = ignore errors\n"
	   "  if no l2 addr is set, random values will be used for each frame.\n"
	   "\n");
    exit(1);
}

/* make a random addr, up to 64 bits long (len bytes) */
void rand_addr(void *to, int len) {
    unsigned long addr[2];
    addr[0] = random(); addr[1] = random();
    memcpy(to, addr, len);
}

int main(int argc,char** argv){

    int loops = 0;
    int waittime = 0;
    int datalen = ICMP_DATA_LEN;
    char *arg_l3_src = NULL, *arg_l3_dst = NULL,
	*arg_l2_src = NULL, *arg_l2_dst = NULL,
	*arg_device = DEFAULT_DEVICE;
    int arg_random_delay = 0, arg_get_data = 0, arg_mcast = 0, arg_fragoff = 0;
    int arg_dontfrag = 0, arg_morefrag = 0, arg_waitselect = 0, arg_ignore=0;
    int manual_l4 = 0;
    int pktid = -2, ttl = 64;
    int sudp = -1, dudp = -1;
    int l4proto = IPPROTO_ICMP;

    int vlan = 0;
    int proto = 0;
    struct sockaddr_ll sa;
    struct ifreq ifr;

    fd_set wfd;
    int sock;
    int i;
   
    srandom(getpid() * time(NULL));
 
    p_frame = (void *)calloc(1, MAX_FRAME_SIZE);
    p_l2 = p_frame + sizeof(struct s_eth_hdr);

    argc--; argv++;
    while (argc > 0) {
	char *flag;

	if (**argv == '-') {
	    flag = *argv+1;

	    /* 1 arg */
	    if (*flag == 'h')
		usage();
	    else if (*flag == 'r')
		arg_random_delay = 1;
	    else if (*flag == 'g')
		arg_get_data = 1;
	    else if (*flag == 'm')
		arg_mcast = 1;
	    else if (*flag == 'f')
		arg_dontfrag = 1;
	    else if (*flag == 'F')
		arg_morefrag = 1;
	    else if (*flag == 'W')
		arg_waitselect = 1;
	    else if (*flag == 'E')
		arg_ignore = 1;
	    else { /* 2+ args */
		argv++; argc--;
		if (argc == 0)
		    usage();

		switch (*flag) {
		case 'n' : loops = atol(*argv); break;
		case 'w' : waittime = atol(*argv); break;
		case 't' : ttl = atol(*argv); break;
		case 'i' : pktid = atol(*argv); break;
		case 'l' : datalen = atol(*argv); break;
		case 's' : arg_l3_src = *argv; break;
		case 'S' : arg_l2_src = *argv; break;
		case 'd' : arg_l3_dst = *argv; break;
		case 'D' : arg_l2_dst = *argv; break;
		case 'e' : arg_device = *argv; break;
		case 'o' : arg_fragoff = atol(*argv); break;
		case 'p' : proto = atol(*argv); break;
		case 'P' : {
		    l4proto = atol(*argv);
		    manual_l4 = 1;
		    break;
		}
		case 'u' : {
		    l4proto = IPPROTO_UDP;
		    dudp = atol(*argv);
		    break;
		}
		case 'U' : {
		    l4proto = IPPROTO_UDP;
		    sudp = atol(*argv);
		    break;
		}
		case 'v' : { /* allow multiple vlan encapsulations */
		    vlan = atol(*argv);
		    ((struct s_vlan *)p_l2)->proto = htons(0x8100);/* vlan protocol */
		    ((struct s_vlan *)p_l2)->id = htons(vlan);  	/* vlan id + flags */
		    p_l2 += sizeof(struct s_vlan);
		    break;
		}
		default: usage();
		}
	    }
	}
	else
	    usage();
	argv++; argc--;
    }

    if (arg_l2_dst)
	get_hw_addr(((struct s_eth_hdr *)p_frame)->dst, arg_l2_dst);
    else
	if (arg_mcast) /* in case of random addresses, perhaps we have to set multicast */
	    *(char *)((struct s_eth_hdr *)p_frame)->dst = 1;

    if (arg_l2_src)
	get_hw_addr(((struct s_eth_hdr *)p_frame)->src, arg_l2_src);

    if (pktid < -1)
	pktid = getpid();

    sock = socket(PF_PACKET, SOCK_RAW, (proto==0)?htons(ETH_P_IP):htons(proto));

    if (sock < 0) {
	perror("socket");
	exit(1);
    }

    bzero(&ifr, sizeof(ifr));
    strncpy(ifr.ifr_name, arg_device, sizeof(ifr.ifr_name));
    if (ioctl(sock, SIOCGIFINDEX, &ifr) == -1)
	perror("ioctl()");

    p_l3=NULL;

    if (proto == 0) { /* unspecified, let's send simple IP/ICMP datagrams */
	*(u_short *)p_l2 = htons(ETH_P_IP);
	if (p_l3 == NULL)
		p_l3 = (p_l2 += 2);

	((struct s_ip_hdr *)p_l3)->verihl   = 0x45;
	((struct s_ip_hdr *)p_l3)->tos      = 0x00;
	if (!manual_l4 && (l4proto == IPPROTO_ICMP))
	    ((struct s_ip_hdr *)p_l3)->totlen   = htons(sizeof(struct s_ip_hdr) + sizeof(struct s_icmp_hdr) + datalen);
	else if (!manual_l4 && (l4proto == IPPROTO_UDP))
	    ((struct s_ip_hdr *)p_l3)->totlen   = htons(sizeof(struct s_ip_hdr) + sizeof(struct s_udp_hdr) + datalen);
	else
	    ((struct s_ip_hdr *)p_l3)->totlen   = htons(sizeof(struct s_ip_hdr) + datalen);
	    
	if (pktid >= 0)
		((struct s_ip_hdr *)p_l3)->id       = htons(pktid);
	else
		((struct s_ip_hdr *)p_l3)->id       = htons(random());

	if (arg_fragoff >= 0)
		((struct s_ip_hdr *)p_l3)->fragoff  = htons((arg_dontfrag?0x4000:0) | (arg_morefrag?0x2000:0) | arg_fragoff);
	else
		((struct s_ip_hdr *)p_l3)->fragoff  = htons((arg_dontfrag?0x4000:0) | (arg_morefrag?0x2000:0) | (random() & 0x3fff));

	((struct s_ip_hdr *)p_l3)->ttl      = ttl;
	((struct s_ip_hdr *)p_l3)->protocol = l4proto;
	((struct s_ip_hdr *)p_l3)->check    = 0;
	
	if (arg_l3_src != NULL)
	    get_ip_addr(&((struct s_ip_hdr *)p_l3)->src, arg_l3_src);
	else {
	    rand_addr(&((struct s_ip_hdr *)p_l3)->src, sizeof(struct in_addr));
	    *(char *)(&((struct s_ip_hdr *)p_l3)->src) &= 0xDF;
	}

	if (arg_l3_dst != NULL)
	    get_ip_addr(&((struct s_ip_hdr *)p_l3)->dst, arg_l3_dst);
	else
	    rand_addr(&((struct s_ip_hdr *)p_l3)->dst, sizeof(struct in_addr));

	((struct s_ip_hdr *)p_l3)->check = checksum((unsigned short*)p_l3, 20);
	p_l4 = p_l3 + sizeof(struct s_ip_hdr);

	if (!manual_l4 && (l4proto == IPPROTO_ICMP)) {
	    /* initialization of ICMP packet */
	    ((struct s_icmp_hdr*)p_l4)->type = ICMP_ECHO;
	    ((struct s_icmp_hdr*)p_l4)->code = 0x00;
	    ((struct s_icmp_hdr*)p_l4)->icmpchk = 0;
	    ((struct s_icmp_hdr*)p_l4)->un.echo.id = pktid;
	    ((struct s_icmp_hdr*)p_l4)->un.echo.sequence = htons(0x0000);

	    p_data = p_l4 + sizeof(struct s_icmp_hdr);
	}
	else if (!manual_l4 && (l4proto == IPPROTO_UDP)) {
	    /* initialization of UDP packet */
	    ((struct s_udp_hdr*)p_l4)->dst = htons(dudp);
	    ((struct s_udp_hdr*)p_l4)->src = htons(sudp);
	    ((struct s_udp_hdr*)p_l4)->len = htons(datalen + sizeof(struct s_udp_hdr));
	    ((struct s_udp_hdr*)p_l4)->chk = 0;

	    p_data = p_l4 + sizeof(struct s_udp_hdr);
	}
	else
	    p_data = p_l4;

	if (arg_get_data)
	    for (i = 0; i < datalen; i++)
		*(u_char *)p_data++ = getchar();
	else
	    for (i = 0; i < datalen; i++)
		*(u_char *)p_data++ = i;
	
	if (!manual_l4 && (l4proto == IPPROTO_ICMP))
	    ((struct s_icmp_hdr*)p_l4)->icmpchk = checksum((unsigned short*)p_l4, p_data - p_l4);
	else if (!manual_l4 && (l4proto == IPPROTO_UDP)) { /* this needs to include parts of IP hdr, see rfc768 */
	    ((struct s_udp_hdr*)p_l4)->chk = l4_checksum(
							 *(unsigned long *)((struct s_ip_hdr *)p_l3)->src,
							 *(unsigned long *)((struct s_ip_hdr *)p_l3)->dst,
							 l4proto,
							 (unsigned short*)p_l4, p_data - p_l4);
	}
    }
    else {
	*(u_short *)p_l2 = htons(proto);
	p_l3 = (p_l2 += 2);
	p_l4 = p_l3;
	p_data = p_l4;
	if (arg_get_data)
	    for (i = 0; i < datalen; i++)
		*(u_char *)p_data++ = getchar();
	else {
	    for (i = 0; i < datalen; i++)
		*(u_char *)p_data++ = random();
	}
    }
    
    /* padding */
#if PADDING
    bzero(pkt.padding,sizeof(pkt.padding));
#endif

    bzero(&sa, sizeof(sa));
    sa.sll_family=AF_PACKET;
    sa.sll_ifindex=ifr.ifr_ifindex;
    bind(sock, (struct sockaddr *)&sa, sizeof(sa));

    /* loop indefinitely if loops<0, or "loops" times */
    while (1) {
	if (arg_l2_src == NULL)
	    rand_addr(((struct s_eth_hdr *)p_frame)->src + 1, ETH_HW_ADDR_LEN - 1);

	if (arg_l2_dst == NULL)
	    rand_addr(((struct s_eth_hdr *)p_frame)->dst + 1, ETH_HW_ADDR_LEN - 1);

	/* painfully slow interfaces sometimes need select... */
	if (arg_waitselect) {
	    FD_ZERO(&wfd);
	    FD_SET(sock, &wfd);
	    select(sock+1, NULL, &wfd, NULL, NULL); /* wait for the socket buffers to be free */
	}
	if ((send(sock, p_frame, p_data - p_frame, 0) < 0) && !arg_ignore) {
	    perror("sendto");
	    exit(1);
	}
	if (loops == 1)
	    break;
	else if (loops)
	    loops--;

	if (waittime > 0)
	    usleep(waittime);
    }
    
    exit(0);
}
