/* * TODO: * IP: * - IP options (security, source route, record route, timestamp) * - for some reason we can't ping the host, but it can ping us? * * TCP: * - TCP options (mss, sack, wscale, timestamp) * - "connection synchronization using data-carrying segments" * - URG * - Better initial seqno generation * - slow start * - congestion avoidance * - Grow/shrink windows * - retransmission/timers (Karn, Jacobson, exp. backoff, etc.) * - Deal better with lost received data * - delayed/selective ACKs * - header prediction * * General: * - argv parsing (specify device, user to run as, etc.) * - some way of guessing which IP address to use? * - make sure not using loopback device * - use link layer socket instead of raw socket * * And finally, all of the evil things I'd like to do: * - Increased control over remote window size */ #include #include #include #include /* LIST { */ typedef struct _list { struct _list *next; void *data; } list; typedef int (*cmpfnc)(const void *, const void *); static list * list_new(void *data) { list *l = malloc(sizeof (list)); if (!l) return NULL; l->next = NULL; l->data = data; return l; } static list * list_prepend(list *l, void *data) { list *s = list_new(data); s->next = l; return s; } static list * list_remove(list *l, void *data) { list *s = l, *p = NULL; if (!s) return NULL; if (s->data == data) { p = s->next; free(s); return p; } while (s->next) { p = s; s = s->next; if (s->data == data) { p->next = s->next; free(s); return l; } } return l; } static list * list_insert_sorted(list *l, void *data, cmpfnc f) { list *s = l; if (!s) return list_prepend(l, data); if (f(s->data, data) >= 0) return list_prepend(l, data); while (s->next) { if (f(s->next->data, data) < 0) { s = s->next; continue; } s->next = list_prepend(s->next, data); return l; } s->next = list_new(data); return l; } /* } */ /* TIMER { */ struct timer { struct timeval end; void (*func)(void *); void *arg; }; static list *timers = NULL; static int timer_cmp(const void *x, const void *y) { const struct timer *a = x, *b = y; return timercmp(&a->end, &b->end, -); } static struct timer * timer_start(int ms, void (*func)(void *), void *arg) { struct timer *timer; struct timeval tv; timer = malloc(sizeof (struct timer)); if (!timer) return NULL; gettimeofday(&tv, NULL); timer->func = func; timer->arg = arg; timer->end.tv_sec = tv.tv_sec + (ms / 1000); timer->end.tv_usec = tv.tv_usec + ((ms % 1000) * 1000); timers = list_insert_sorted(timers, timer, timer_cmp); return timer; } static void timer_cancel(struct timer *timer) { timers = list_remove(timers, timer); free(timer); } static struct timeval * timer_sleep_time(struct timeval *rtv) { if (timers) { /* timers are sorted by when they're going to expire (first to last), so * taking the first on the list should always be the first to expire and * so we can use it to figure out how long to tell select to sleep */ struct timer *timer = timers->data; struct timeval tv; gettimeofday(&tv, NULL); timersub(&timer->end, &tv, rtv); if (rtv->tv_sec < 0) timerclear(rtv); return rtv; } else { return NULL; } } static void timer_process_pending(void) { struct timeval tv; gettimeofday(&tv, NULL); /* we don't support periodic timers, and since timers are sorted by * expiration date, if we come across one that doesn't need processing, all * subsequent ones won't need processing, so we can return. */ while (timers) { struct timer *timer = timers->data; /* we get away with this because the timeval is the first element * of the timer */ if (timer_cmp(timer, &tv) <= 0) { if (timer->func) timer->func(timer->arg); timer_cancel(timer); } else { return; } } } /* } */ static const char *state_names[] = { "UNKNOWN", "ESTABLISHED", "SYN_SENT", "SYN_RECV", "FIN_WAIT1", "FIN_WAIT2", "TIME_WAIT", "TCP_CLOSE", "CLOSE_WAIT", "LAST_ACK", "LISTEN", "CLOSING", }; typedef struct tcp_session { uint32_t id; /* heh. a more appropriate name might be 'fd'. */ uint32_t state; /* these identify a unique session. the src_ip is global. libnet_name2addr4 * puts ip addresses in network byte order, so those will always be that * way. however, ports stored in the session will always be host byte order, * so when comparing against what comes off the wire, make sure to do a * proper translation */ uint16_t src_prt; uint16_t dst_prt; uint32_t dst_ip; /* these are stored in host byte order mostly by default */ uint32_t seqno; /* aka SND.NXT */ uint32_t iss; uint32_t ackno; /* aka RCV.NXT */ uint32_t irs; uint32_t unacked; /* aka SND.UNA */ /* I didn't actually start using Linux to get away from windows; I started * using it because I wanted to know more about computing in general */ uint32_t snd_win; /* aka SND.WND */ uint32_t rcv_win; /* aka RCV.WND */ struct timer *tmr; /* there's really a bunch of other stuff that I should be paying * attention to */ /* this is where we start getting evil: reimplementing sting */ int sting; int count; int sting_acks; int data_lost; #define STING_STRING "GET / HTTP/1.0\n" \ "Accept: text/plain\nAccept: */*\n" \ "User-Agent: Mozilla/4.0 " \ "(compatible; MSIE 5.0; Windows NT; DigExt; Sting)\n\n" #define STING_COUNT 100 #define STING_DELAY 100 } TCB; struct ip_pkt { struct libnet_ipv4_hdr *hdr; u_char *options; u_char *data; }; struct icmp_pkt { struct libnet_ipv4_hdr *ip; struct libnet_icmpv4_hdr *hdr; }; struct tcp_pkt { struct libnet_ipv4_hdr *ip; u_char *ip_options; struct libnet_tcp_hdr *hdr; u_char *tcp_options; u_char *data; uint32_t data_len; }; /* these are our global variables */ static libnet_t *lnh_link = NULL; static unsigned char src_hw[6]; static libnet_t *lnh_raw4 = NULL; static uint32_t src_ip; static u_int16_t ip_id; static const u_int8_t ip_ttl = 64; static list *sessions = NULL; static list *open_connections = NULL; static list *listeners = NULL; /* TCP UTIL { */ static int send_tcp(TCB *sess, int flags, u_char *data, uint32_t len) { libnet_clear_packet(lnh_raw4); if (libnet_build_tcp(sess->src_prt, sess->dst_prt, sess->seqno, sess->ackno, flags, sess->rcv_win, 0, 0, LIBNET_TCP_H + len, data, len, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } if (libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H + len, IPTOS_RELIABILITY, ip_id++, 0, ip_ttl, IPPROTO_TCP, 0, src_ip, sess->dst_ip, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } if (flags & TH_SYN) sess->seqno++; if (flags & TH_FIN) sess->seqno++; sess->seqno += len; return 0; } /* yeah, I probably should figure out some way of reusing send_tcp instead of * just copying it. but eh. it probably isn't an issue. at least not often. */ static int send_rst(uint32_t state, struct tcp_pkt *tcp) { uint32_t ackno = ntohl(tcp->hdr->th_seq) + 1; u_int8_t cntrl = TH_RST; if (!(tcp->hdr->th_flags & TH_ACK)) cntrl |= TH_ACK; else ackno = 0; libnet_clear_packet(lnh_raw4); if (libnet_build_tcp(ntohs(tcp->hdr->th_dport), ntohs(tcp->hdr->th_sport), ntohl(tcp->hdr->th_ack), ackno, cntrl, 0, 0, 0, LIBNET_TCP_H, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } if (libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_TCP_H, IPTOS_LOWDELAY, ip_id++, 0, ip_ttl, IPPROTO_TCP, 0, src_ip, tcp->ip->ip_src.s_addr, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return 1; } fprintf(stderr, "RST'ing (SRC = %s:%d, DPort = %d (%s), Cntrl = %02x)\n", libnet_addr2name4(tcp->ip->ip_src.s_addr, LIBNET_DONT_RESOLVE), ntohs(tcp->hdr->th_sport), ntohs(tcp->hdr->th_dport), state_names[state], tcp->hdr->th_flags); return 0; } static TCB * find_session(uint32_t dst_ip, uint32_t dst_prt, uint32_t src_prt) { list *l = open_connections; while (l && dst_ip && dst_prt) { TCB *sess = l->data; l = l->next; if (sess->src_prt != src_prt) continue; if (sess->dst_ip == dst_ip && sess->dst_prt == dst_prt) return sess; } l = listeners; /* we should be able to support listening for specific hosts and/or ports */ while (l) { TCB *sess = l->data; l = l->next; if (sess->src_prt == src_prt) return sess; } return NULL; } /* XXX there are probably several problems with this. */ static int get_port(uint32_t dst_ip, uint16_t dst_prt) { uint16_t src_prt; do { /* privileged ports, my ass */ src_prt = libnet_get_prand(LIBNET_PRu16); } while (find_session(dst_ip, dst_prt, src_prt)); return src_prt; } static int get_next_sess_id(void) { list *l = sessions; unsigned int i = 0; while (l) { TCB *s = l->data; l = l->next; if (s->id != i) return i; i++; } return i; } static int sess_cmp(const void *x, const void *y) { const TCB *a = x, *b = y; return a->id - b->id; } static TCB * session_setup(void) { TCB *sess = calloc(1, sizeof (TCB)); /* sess->state = TCP_CLOSE; */ /* XXX this isn't actually supposed to be random; it's supposed to be * somewhat internal-clock-based so that if we happen to be using the same * src_prt/dst_ip/dst_prt tuple that was used in a previous session, the * remote stack knows that it's for a new session. but I don't care. */ /* I hear I shouldn't use this function for anything real. oh well. */ sess->seqno = libnet_get_prand(LIBNET_PRu32); /* XXX at some point I'm going to have to consider what might happen if I * want to do something like queuing the data until someone tells me they * want to read it. at that point I may need to decrease this window */ sess->rcv_win = 0xffff; sess->id = get_next_sess_id(); sessions = list_insert_sorted(sessions, sess, sess_cmp); printf("creating socket %d\n", sess->id); return sess; } static TCB * accept_session(TCB *listener, struct tcp_pkt *pkt) { TCB *sess = session_setup(); if (!sess) return NULL; sess->dst_ip = pkt->ip->ip_src.s_addr; sess->dst_prt = ntohs(pkt->hdr->th_sport); sess->src_prt = listener->src_prt; sess->irs = ntohl(pkt->hdr->th_seq); sess->ackno = sess->irs + 1; send_tcp(sess, TH_SYN | TH_ACK, NULL, 0); sess->state = TCP_SYN_RECV; printf("%u: %s\n", sess->id, state_names[sess->state]); open_connections = list_prepend(open_connections, sess); return sess; } /* after this function, sess is no longer valid (obviously) */ static void remove_session(TCB *sess) { if (sess->state == TCP_LISTEN) { listeners = list_remove(listeners, sess); } else { open_connections = list_remove(open_connections, sess); } sessions = list_remove(sessions, sess); timer_cancel(sess->tmr); free(sess); } /* maybe eventually you can specify the port to connect from */ static TCB * create_session(char *host, uint16_t port) { TCB *sess; if (host) { if ((sess = session_setup()) == NULL) return NULL; sess->dst_ip = libnet_name2addr4(lnh_raw4, host, LIBNET_RESOLVE); if (sess->dst_ip == 0xffffffff) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); remove_session(sess); return NULL; } sess->dst_prt = port; sess->src_prt = get_port(sess->dst_ip, sess->dst_prt); /* send the SYN, and we're off! */ send_tcp(sess, TH_SYN, NULL, 0); sess->state = TCP_SYN_SENT; printf("%u: %s (port %u)\n", sess->id, state_names[sess->state], sess->src_prt); open_connections = list_prepend(open_connections, sess); } else { /* listening socket */ if ((sess = find_session(0, 0, port)) != NULL) { fprintf(stderr, "id %d already listening on port %d\n", sess->id, port); return sess; } if ((sess = session_setup()) == NULL) return NULL; sess->dst_ip = 0; sess->dst_prt = 0; sess->src_prt = port; sess->state = TCP_LISTEN; printf("%u: %s\n", sess->id, state_names[sess->state]); listeners = list_prepend(listeners, sess); } return sess; } /* } */ /* STING { */ static void sting(void *data) { TCB *sess = data; u_char str[] = STING_STRING; sess->count++; send_tcp(sess, TH_PUSH | TH_ACK, &str[sess->count], 1); if (sess->count != STING_COUNT) { sess->tmr = timer_start(STING_DELAY, sting, data); } else { /* so now we send the last byte */ sess->tmr = NULL; sess->seqno = sess->iss; send_tcp(sess, TH_PUSH | TH_ACK, &str[0], 1); sess->seqno = sess->iss + STING_COUNT + 1; } } static void sting_process(TCB *sess, struct tcp_pkt *pkt) { u_char str[] = STING_STRING; if (sess->state != TCP_ESTABLISHED) { fprintf(stderr, "sting session stopping?\n"); timer_cancel(sess->tmr); return; } if (ntohl(pkt->hdr->th_ack) == sess->iss) { /* they're still waiting for our first byte */ sess->sting_acks++; } else if (ntohl(pkt->hdr->th_ack) == sess->seqno) { printf("sting to %s:\n", libnet_addr2name4(pkt->ip->ip_src.s_addr, LIBNET_DONT_RESOLVE)); printf("remote received %d/%d packets\n", STING_COUNT - sess->data_lost, STING_COUNT); printf("we received %d/%d acks\n", sess->sting_acks, STING_COUNT - sess->data_lost); send_tcp(sess, TH_RST | TH_ACK, NULL, 0); remove_session(sess); } else { /* this indicates that they dropped something and need it resent */ sess->data_lost++; sess->seqno = ntohl(pkt->hdr->th_ack); send_tcp(sess, TH_PUSH | TH_ACK, &str[sess->seqno - sess->iss], 1); sess->seqno = sess->iss + STING_COUNT + 1; } } /* } */ /* TCP SM { */ static TCB * tcp_process_listen(TCB *sess, struct tcp_pkt *pkt) { if (pkt->hdr->th_flags & TH_RST) { /* we're in a listening state, just drop it */ } else if (pkt->hdr->th_flags & TH_ACK) { send_rst(sess->state, pkt); } else if (pkt->hdr->th_flags & TH_SYN) { return accept_session(sess, pkt); } else { /* And I quote: * * Any other control or text-bearing segment (not containing SYN) * must have an ACK and thus would be discarded by the ACK * processing. An incoming RST segment could not be valid, since it * could not have been sent in response to anything sent by this * incarnation of the connection. So you are unlikely to get here, * but if you do, drop the segment, and return. */ } return NULL; } static void tcp_process_syn_sent(TCB *sess, struct tcp_pkt *pkt) { if (pkt->hdr->th_flags & TH_ACK) { if (ntohl(pkt->hdr->th_ack) != sess->seqno) { fprintf(stderr, "Invalid ackno on %d\n", sess->id); send_rst(sess->state, pkt); remove_session(sess); return; } else { sess->unacked = ntohl(pkt->hdr->th_ack); } } /* by now we've already handled RST and ACK. any other packet without SYN * should just be dropped */ if (!(pkt->hdr->th_flags & TH_SYN)) return; sess->irs = ntohl(pkt->hdr->th_seq); sess->ackno = sess->irs + 1; if (sess->unacked == sess->seqno) { sess->state = TCP_ESTABLISHED; send_tcp(sess, TH_ACK, NULL, 0); } else { sess->state = TCP_SYN_RECV; /* we're resending our initial syn, so we have to decrement the * seqno to get at our initial seqno for the syn */ sess->seqno--; send_tcp(sess, TH_SYN | TH_ACK, NULL, 0); } printf("%u: %s\n", sess->id, state_names[sess->state]); /* we assume that sting will always go this route */ if ((sess->state == TCP_ESTABLISHED) && sess->sting) { /* bump seqno deliberately despite not having sent anything yet */ sess->seqno++; sting(sess); } } static int tcp_check_seqno(TCB *sess, struct tcp_pkt *pkt) { unsigned int len = ntohs(pkt->ip->ip_len) - (pkt->ip->ip_hl << 2) - (pkt->hdr->th_off << 2); uint32_t rcvnxt = sess->ackno, segseq = ntohl(pkt->hdr->th_seq); if (len == 0) { if ((0 <= (int32_t)(segseq - rcvnxt)) && /* if sess->rcv_win is 0 then the check is if * pkt->hdr->th_seq <= sess->ackno, which is acceptable, * since the above check makes it the same check * as checking if sess->ackno == pkt->hdr->th_seq */ (0 <= (int32_t)(rcvnxt + sess->rcv_win - segseq))) { return 0; } } else if (sess->rcv_win != 0) { /* if there's data and our window is 0 then it's unacceptable */ if (((0 <= (int32_t)(segseq - rcvnxt)) && (0 < (int32_t)(rcvnxt + sess->rcv_win - segseq))) || ((0 <= (int32_t)(segseq + len - 1 - rcvnxt)) && (0 < (int32_t)(rcvnxt + sess->rcv_win - (segseq + len - 1))))) { return 0; } } return 1; } static int tcp_process_syn_recv(TCB *sess, struct tcp_pkt *pkt) { if (sess->unacked > ntohl(pkt->hdr->th_ack) || ntohl(pkt->hdr->th_ack) > sess->seqno) { fprintf(stderr, "Invalid ackno on %d\n", sess->id); send_rst(sess->state, pkt); remove_session(sess); return 1; } sess->state = TCP_ESTABLISHED; printf("%u: %s\n", sess->id, state_names[sess->state]); /* and continue procesing */ return 0; } static void tcp_process_last_ack(TCB *sess, struct tcp_pkt *pkt) { if (ntohl(pkt->hdr->th_ack) == sess->seqno) { /* now we can remove the session */ sess->state = TCP_CLOSE; printf("%u: %s\n", sess->id, state_names[sess->state]); remove_session(sess); } else if (ntohl(pkt->hdr->th_ack) > sess->seqno) { /* they're acking more than we've sent? anyway, resend FIN (which means * decrement seqno before sending since it's sort of a retransmit) */ sess->seqno--; send_tcp(sess, TH_FIN | TH_ACK, NULL, 0); } } static int tcp_check_ackno(TCB *sess, struct tcp_pkt *pkt) { uint32_t snduna = sess->unacked, segack = ntohl(pkt->hdr->th_ack), sndnxt = sess->seqno; if ((0 <= (int32_t)(segack - snduna)) && (0 <= (int32_t)(sndnxt - segack))) { snduna = sess->unacked = segack; /* XXX "the send window should be updated" */ } else if (0 < (int32_t)(snduna - segack)) { /* the ACK is a duplicate and can be ignored */ fprintf(stderr, "duplicate ack on %d\n", sess->id); return 1; } else { /* the ACK acks something not yet sent */ fprintf(stderr, "ack for something not sent on %d\n", sess->id); send_tcp(sess, TH_ACK, NULL, 0); return 1; } if (snduna == sndnxt) { if (sess->state == TCP_FIN_WAIT1) { /* the FIN has been acked, we can move to FIN-WAIT-2 */ sess->state = TCP_FIN_WAIT2; printf("%u: %s\n", sess->id, state_names[sess->state]); } else if (sess->state == TCP_CLOSING) { /* the FIN has been acked, we can move to TIME-WAIT. but we don't * move to TIME-WAIT, we simply delete the TCB and go straight to * CLOSED without the 2 MSL delay */ sess->state = TCP_TIME_WAIT; printf("%u: %s\n", sess->id, state_names[sess->state]); remove_session(sess); return 1; } } return 0; } static void tcp_handle_data(TCB *sess, struct tcp_pkt *pkt) { unsigned int i; if (sess->sting) return; if (pkt->data_len == 0) return; printf("read %u: %u bytes\n", sess->id, pkt->data_len); for (i = 0; i < pkt->data_len; i++) { printf("%02x ", pkt->data[i]); if (i && (i + 1) % 16 == 0) printf("\n"); } printf("\n"); for (i = 0; i < pkt->data_len; i++) { if (isprint(pkt->data[i])) printf(" %c ", pkt->data[i]); else printf(" "); if (i && (i + 1) % 16 == 0) printf("\n"); } printf("\n"); /* XXX this is wrong */ sess->ackno += pkt->data_len; send_tcp(sess, TH_ACK, NULL, 0); } static void tcp_handle_fin(TCB *sess, struct tcp_pkt *pkt) { /* XXX this is wrong */ sess->ackno++; if ((sess->state == TCP_SYN_RECV) || (sess->state == TCP_ESTABLISHED)) { send_tcp(sess, TH_FIN | TH_ACK, NULL, 0); /* we send both the FIN and the ACK together, and move right through * CLOSE_WAIT to LAST_ACK */ sess->state = TCP_LAST_ACK; printf("%u: %s\n", sess->id, state_names[sess->state]); } else if (sess->state == TCP_FIN_WAIT1) { /* ACK the FIN, wait for our ACK */ send_tcp(sess, TH_ACK, NULL, 0); sess->state = TCP_CLOSING; printf("%u: %s\n", sess->id, state_names[sess->state]); } else if (sess->state == TCP_FIN_WAIT2) { /* we've got the FIN, send the ACK and we're done */ /* XXX if there's still data missing then we don't want to set ackno to * pkt->hdr->th_seq + 1; we want to receive that data still */ sess->ackno = ntohl(pkt->hdr->th_seq) + 1; send_tcp(sess, TH_ACK, NULL, 0); sess->state = TCP_TIME_WAIT; printf("%u: %s\n", sess->id, state_names[sess->state]); /* we should stay in TIME_WAIT for 2 MSL, but if the ack is received * it's not necessary. if it's lost and they resend FIN we'll send RST, * which isn't what's expected but will do about the right thing */ remove_session(sess); } else { /* anyone else just stays in their state. TIME-WAIT, if it were * possible for us, would restart the 2 MSL time-wait timeout. */ } } static void tcp_state_machine(TCB *sess, struct tcp_pkt *pkt) { /* here we can assume that the pkt is part of the session and for us */ if (sess->state == TCP_LISTEN) { sess = tcp_process_listen(sess, pkt); return; } if (pkt->hdr->th_flags & TH_RST) { fprintf(stderr, "Remote host sent RST, closing %d\n", sess->id); remove_session(sess); return; } if (sess->state == TCP_SYN_SENT) { tcp_process_syn_sent(sess, pkt); return; } /* at this point the state is not CLOSED, LISTEN, or SYN_SENT. also we can't * be in the CLOSE_WAIT state because we send the FIN with the ACK. also we * won't be in the TIME_WAIT state because we assume the other side received * our last ACK. */ if (tcp_check_seqno(sess, pkt)) { /* XXX should we be updating sess->unacked from hdr->th_ack here? */ fprintf(stderr, "unacceptable segment size on %d\n", sess->id); send_tcp(sess, TH_ACK, NULL, 0); return; } if (pkt->hdr->th_flags & TH_SYN) { fprintf(stderr, "SYN on %d\n", sess->id); send_rst(sess->state, pkt); remove_session(sess); return; } if (!(pkt->hdr->th_flags & TH_ACK)) { /* "if the ACK bit is off drop the segment and return" */ return; } if (sess->state == TCP_SYN_RECV) { if (tcp_process_syn_recv(sess, pkt)) return; } if (sess->state == TCP_LAST_ACK) { tcp_process_last_ack(sess, pkt); return; } /* at this point we're either ESTABLISHED, FIN_WAIT (1 or 2), or CLOSING. * all of these "Do the same processing as for the ESTABLISHED state" before * doing their own thing */ if (tcp_check_ackno(sess, pkt)) return; /* XXX check to see if we're missing any data. if we are then we should * queue the data that's been sent to us, but we don't. instead we drop the * new data and send an ack with what we think should have come next. then * when we receive the in-order data later, we'll need to re-request this * data as well. this is really inefficient, but it works. */ if (sess->ackno != ntohl(pkt->hdr->th_seq)) { send_tcp(sess, TH_ACK, NULL, 0); return; } tcp_handle_data(sess, pkt); if (pkt->hdr->th_flags & TH_FIN) tcp_handle_fin(sess, pkt); if (sess->sting) sting_process(sess, pkt); } static void process_tcp_packet(struct ip_pkt *ip) { struct tcp_pkt tcp; TCB *sess; uint16_t csum; int sum, len; if (ntohs(ip->hdr->ip_len) < (ip->hdr->ip_hl << 2) + LIBNET_TCP_H) { fprintf(stderr, "invalid tcp packet (src %s)\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } tcp.ip = ip->hdr; tcp.ip_options = ip->options; tcp.hdr = (struct libnet_tcp_hdr *)ip->data; csum = tcp.hdr->th_sum; tcp.hdr->th_sum = 0; sum = libnet_in_cksum((u_int16_t *)&ip->hdr->ip_src, 8); len = ntohs(ip->hdr->ip_len) - (ip->hdr->ip_hl << 2); sum += ntohs(IPPROTO_TCP + len); sum += libnet_in_cksum((u_int16_t *)&tcp.hdr->th_sport, len); tcp.hdr->th_sum = LIBNET_CKSUM_CARRY(sum); if (csum != tcp.hdr->th_sum) { fprintf(stderr, "checksum mismatch in TCP header (src %s)!\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } if ((tcp.hdr->th_off << 2) < LIBNET_TCP_H) { fprintf(stderr, "invalid TCP header (src %s)!\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } if (!(sess = find_session(ip->hdr->ip_src.s_addr, ntohs(tcp.hdr->th_sport), ntohs(tcp.hdr->th_dport)))) { if (!(tcp.hdr->th_flags & TH_RST)) { /* if they're sending us a RST then we don't need to send RST back, * we can safely drop it. */ send_rst(TCP_CLOSE, &tcp); } return; } if (ntohs(ip->hdr->ip_len) < (ip->hdr->ip_hl << 2) + (tcp.hdr->th_off << 2)) { fprintf(stderr, "invalid th_off (src %s)\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } tcp.tcp_options = (u_char *)tcp.hdr + LIBNET_TCP_H; /* XXX we don't actually do anything with the options, but eh */ tcp.data = (u_char *)&tcp.hdr->th_sport + (tcp.hdr->th_off << 2); tcp.data_len = ntohs(ip->hdr->ip_len) - (ip->hdr->ip_hl << 2) - (tcp.hdr->th_off << 2); /* this is where the real work is */ tcp_state_machine(sess, &tcp); } /* } */ /* UDP { */ static void process_udp_packet(struct ip_pkt *ip) { /* that's right, we don't handle udp! when's the last time you used udp. * honestly. and don't tell me you actually use dns! or ntp. or nfs. or * smb. *shudder* */ unsigned int len; printf("rejecting udp (src %s)\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); len = (ip->hdr->ip_hl << 2) + 64; if (len > ntohs(ip->hdr->ip_len)) len = ntohs(ip->hdr->ip_len); libnet_clear_packet(lnh_raw4); if (libnet_build_icmpv4_unreach(ICMP_UNREACH, ICMP_UNREACH_PORT, 0, (u_int8_t *)ip->hdr, len, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_UNREACH_H + len, 0, ip_id++, 0, ip_ttl, IPPROTO_ICMP, 0, src_ip, ip->hdr->ip_src.s_addr, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); } } /* } */ /* ICMP { */ static void icmp_echo_reply(struct icmp_pkt *icmp) { unsigned int len; if (ntohs(icmp->ip->ip_len) < (icmp->ip->ip_hl << 2) + LIBNET_ICMPV4_ECHO_H) { fprintf(stderr, "invalid icmp echo packet (src %s)\n", libnet_addr2name4(icmp->ip->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } printf("being pinged (src %s)\n", libnet_addr2name4(icmp->ip->ip_src.s_addr, LIBNET_DONT_RESOLVE)); len = ntohs(icmp->ip->ip_len) - (icmp->ip->ip_hl << 2) - LIBNET_ICMPV4_ECHO_H; libnet_clear_packet(lnh_raw4); if (libnet_build_icmpv4_echo(ICMP_ECHOREPLY, 0, 0, ntohs(icmp->hdr->icmp_id), ntohs(icmp->hdr->icmp_seq), (u_int8_t *)icmp->hdr->icmp_data, len, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H + len, icmp->ip->ip_tos, ip_id++, 0, ip_ttl, IPPROTO_ICMP, 0, src_ip, icmp->ip->ip_src.s_addr, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); } } static void process_icmp_echo_reply(struct icmp_pkt *icmp) { struct timeval tv, diff; int i; gettimeofday(&tv, NULL); if (ntohs(icmp->ip->ip_len) != (icmp->ip->ip_hl << 2) + 64) { fprintf(stderr, "improper echo reply (src %s)\n", libnet_addr2name4(icmp->ip->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } for (i = sizeof (struct timeval); i < 64 - LIBNET_ICMPV4_ECHO_H; i++) { if (icmp->hdr->icmp_data[i] != i) { fprintf(stderr, "byte %d is %02x, should be %02x\n", i, icmp->hdr->icmp_data[i], i); } } timersub(&tv, (struct timeval *)(icmp->hdr->icmp_data), &diff); printf("received ping response from %s (%ld.%.03ld ms)\n", libnet_addr2name4(icmp->ip->ip_src.s_addr, LIBNET_RESOLVE), (diff.tv_sec * 1000) + (diff.tv_usec / 1000), diff.tv_usec % 1000); } static void process_icmp_packet(struct ip_pkt *ip) { struct icmp_pkt icmp; uint16_t csum; int sum; icmp.ip = ip->hdr; icmp.hdr = (struct libnet_icmpv4_hdr *)ip->data; csum = icmp.hdr->icmp_sum; icmp.hdr->icmp_sum = 0; sum = libnet_in_cksum((u_int16_t *)icmp.hdr, ntohs(ip->hdr->ip_len) - (ip->hdr->ip_hl << 2)); icmp.hdr->icmp_sum = LIBNET_CKSUM_CARRY(sum); if (csum != icmp.hdr->icmp_sum) { fprintf(stderr, "invalid icmp checksum (src %s)\n", libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } switch (icmp.hdr->icmp_type) { case ICMP_ECHO: icmp_echo_reply(&icmp); break; case ICMP_ECHOREPLY: process_icmp_echo_reply(&icmp); break; default: fprintf(stderr, "received unhandled icmp type %d (src %s)\n", icmp.hdr->icmp_type, libnet_addr2name4(ip->hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); break; } } /* } */ /* IP { */ /* FRAGMENTS { */ struct ip_frag_bit { uint16_t offset; uint16_t len; int end; uint8_t *data; }; struct ip_frag { uint32_t ip_src; u_int16_t ip_id; u_int8_t ip_p; u_int8_t pad; list *bits; uint8_t *data; struct timer *timer; }; static list *fragments = NULL; static struct ip_frag * find_ip_frag(struct ip_pkt *pkt) { list *l = fragments; while (l) { struct ip_frag *frag = l->data; l = l->next; if ((frag->ip_src == pkt->hdr->ip_src.s_addr) && (frag->ip_id == pkt->hdr->ip_id) && (frag->ip_p == pkt->hdr->ip_p)) return frag; } return NULL; } static void free_ip_frag(struct ip_frag *frag) { if (!frag) return; fragments = list_remove(fragments, frag); while (frag->bits) { struct ip_frag_bit *bit = frag->bits->data; frag->bits = list_remove(frag->bits, bit); free(bit->data); free(bit); } if (frag->data) free(frag->data); free(frag); } static void timeout_ip_frag(void *arg) { struct ip_frag *frag = arg; fprintf(stderr, "fragment %s %d %d timed out\n", libnet_addr2name4(frag->ip_src, LIBNET_DONT_RESOLVE), frag->ip_id, frag->ip_p); /* we should be sending an ICMP message here but I don't care. odds are that * either the host that was sending us the fragmented packet was trying to * do something evil to us, or the network between us is a little wonky. * either way the ICMP message saying that the reassembly timed out will * probably not have any effect. */ free_ip_frag(frag); } static int frag_bit_cmp(const void *x, const void *y) { const struct ip_frag_bit *a = x, *b = y; return (a->offset - b->offset); } static struct ip_frag * reassemble_ip_frag(struct ip_frag *frag, struct ip_pkt *pkt) { int cur = 0; list *l = frag->bits; while (l) { struct ip_frag_bit *bit = l->data; l = l->next; /* if the offset is greater than our current pointer then return */ if (bit->offset > cur) return NULL; /* update our offset */ cur = bit->offset + bit->len; /* if this isn't the end but there are no more bits return */ if (!bit->end && !l) return NULL; /* if this is the end then stop processing bits */ if (bit->end) break; } /* at this point we have all the data we're told we're going to get and cur * is equal to the length of that data */ timer_cancel(frag->timer); frag->timer = NULL; frag->data = malloc(LIBNET_IPV4_H + cur); if (!frag->data) { free_ip_frag(frag); return NULL; } l = frag->bits; while (l) { struct ip_frag_bit *bit = l->data; l = l->next; memcpy(&frag->data[bit->offset + LIBNET_IPV4_H], bit->data, bit->len); if (bit->end) break; } memcpy(frag->data, pkt->hdr, LIBNET_IPV4_H); pkt->hdr = (struct libnet_ipv4_hdr *)frag->data; pkt->hdr->ip_len = ntohs(LIBNET_IPV4_H + cur); /* XXX we're supposed to support options, but eh */ pkt->hdr->ip_hl = (LIBNET_IPV4_H >> 2); pkt->options = NULL; pkt->data = (u_char *)pkt->hdr + LIBNET_IPV4_H; return frag; } static struct ip_frag * add_ip_frag(struct ip_pkt *pkt) { struct ip_frag *frag = find_ip_frag(pkt); struct ip_frag_bit *bit; uint16_t offset; uint16_t len; int more; if (!frag) { frag = malloc(sizeof (struct ip_frag)); if (!frag) return NULL; frag->ip_src = pkt->hdr->ip_src.s_addr; frag->ip_id = pkt->hdr->ip_id; frag->ip_p = pkt->hdr->ip_p; frag->bits = NULL; frag->data = NULL; frag->timer = timer_start(pkt->hdr->ip_ttl * 1000, timeout_ip_frag, frag); fragments = list_prepend(fragments, frag); } offset = (ntohs(pkt->hdr->ip_off) & IP_OFFMASK) << 3; len = ntohs(pkt->hdr->ip_len) - (pkt->hdr->ip_hl << 2); more = ntohs(pkt->hdr->ip_off) & IP_MF ? 1 : 0; bit = malloc(sizeof (struct ip_frag_bit)); if (!bit) return NULL; bit->offset = offset; bit->len = len; bit->end = !more; bit->data = malloc(len); if (!bit->data) { free(bit); return NULL; } memcpy(bit->data, pkt->data, len); /* XXX we should also be copying and verifying options, but eh */ frag->bits = list_insert_sorted(frag->bits, bit, frag_bit_cmp); return reassemble_ip_frag(frag, pkt); } /* } */ static void process_ip_packet(struct libnet_ethernet_hdr *enet, uint32_t len) { struct ip_frag *frag = NULL; struct ip_pkt ip; uint16_t csum; int sum; if (len < LIBNET_ETH_H + LIBNET_IPV4_H) { fprintf(stderr, "packet does not include full header\n"); return; } ip.hdr = (struct libnet_ipv4_hdr *)(enet + 1); if (ip.hdr->ip_hl < 5 || ip.hdr->ip_v != 4) { fprintf(stderr, "bad IP packet, len = %d, ver = %d\n", ip.hdr->ip_hl, ip.hdr->ip_v); return; } csum = ip.hdr->ip_sum; ip.hdr->ip_sum = 0; sum = libnet_in_cksum((u_int16_t *)ip.hdr, ip.hdr->ip_hl << 2); ip.hdr->ip_sum = LIBNET_CKSUM_CARRY(sum); if (csum != ip.hdr->ip_sum) { fprintf(stderr, "checksum mismatch in IP header!\n"); return; } /* if it's not for us, we ignore it. that's probably a bad thing since * it means we ignore broadcast as well. but who the hell cares. if we * ever want to support broadcast then we'll also have to change * init_pcap so that it gives us broadcast packets. if we ever want to * be really evil and do horrible things to other people's connections * then we'll have to modify this and init_pcap() below. */ if (ip.hdr->ip_dst.s_addr != src_ip) { return; } if (len < (uint32_t)(LIBNET_ETH_H + (uint16_t)ntohs(ip.hdr->ip_len))) { fprintf(stderr, "invalid ip_len (src %s)\n", libnet_addr2name4(ip.hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } if ((ip.hdr->ip_hl << 2) > ntohs(ip.hdr->ip_len)) { fprintf(stderr, "invalid ip_hl (src %s)\n", libnet_addr2name4(ip.hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); return; } /* now we know that there's at least as much data as ip_len says and that * ip_hl isn't invalid, so we can use those for validation from now on */ ip.options = (u_char *)ip.hdr + LIBNET_IPV4_H; /* XXX we don't actually do anything with the options, but eh */ ip.data = (u_char *)ip.hdr + (ip.hdr->ip_hl << 2); if (ntohs(ip.hdr->ip_off) & (IP_MF|IP_OFFMASK)) { frag = add_ip_frag(&ip); if (frag == NULL) return; } switch (ip.hdr->ip_p) { case IPPROTO_TCP: process_tcp_packet(&ip); break; case IPPROTO_UDP: process_udp_packet(&ip); break; case IPPROTO_ICMP: process_icmp_packet(&ip); break; default: fprintf(stderr, "received unhandled protocol %d (src %s)\n", ip.hdr->ip_p, libnet_addr2name4(ip.hdr->ip_src.s_addr, LIBNET_DONT_RESOLVE)); break; } free_ip_frag(frag); } /* } */ /* ARP { */ /* this probably wasn't entirely necessary but it's convenient */ struct arp_pkt { struct libnet_ethernet_hdr enet; struct libnet_arp_hdr hdr; /* everything from here down is IPv4 but that's all we need */ unsigned char src_hw[6]; uint32_t src_ip; unsigned char dst_hw[6]; uint32_t dst_ip; } __attribute__ ((__packed__)); /* since we're "faking" a client, we need to reply to arp requests as that * client. we shouldn't ever get an arp request from the host because we set up * a fake arp entry for ourselves, but we check to make sure it's not from the * host anyway. */ static void process_arp_packet(struct libnet_ethernet_hdr *enet, uint32_t len) { struct arp_pkt *arp = (struct arp_pkt *)enet; if (len < LIBNET_ETH_H + LIBNET_ARP_ETH_IP_H) return; if ((ntohs(arp->hdr.ar_hrd) != ARPHRD_ETHER) || /* ethernet*/ (ntohs(arp->hdr.ar_pro) != ETHERTYPE_IP) || /* ipv4 */ (ntohs(arp->hdr.ar_op) != ARPOP_REQUEST) || /* request */ (arp->dst_ip != src_ip) || /* for us */ memcmp(src_hw, arp->src_hw, 6) == 0) /* not host */ return; printf("replying to arp from %s (%02x:%02x:%02x:%02x:%02x:%02x)\n", libnet_addr2name4(arp->src_ip, LIBNET_DONT_RESOLVE), arp->src_hw[0], arp->src_hw[1], arp->src_hw[2], arp->src_hw[3], arp->src_hw[4], arp->src_hw[5]); libnet_clear_packet(lnh_link); if (libnet_build_arp(ARPHRD_ETHER, ETHERTYPE_IP, 6, 4, ARPOP_REPLY, src_hw, (u_char *)&src_ip, arp->src_hw, (u_char *)&arp->src_ip, NULL, 0, lnh_link, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_link)); return; } if (libnet_autobuild_ethernet(arp->src_hw, ETHERTYPE_ARP, lnh_link) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_link)); return; } if (libnet_write(lnh_link) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_link)); return; } } /* } */ /* INPUT { */ static void ping(char *host) { uint32_t dst_ip; u_char payload[64 - LIBNET_ICMPV4_ECHO_H]; unsigned int i; libnet_clear_packet(lnh_raw4); dst_ip = libnet_name2addr4(lnh_raw4, host, LIBNET_RESOLVE); if (dst_ip == 0xffffffff) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } for (i = 0; i < sizeof (payload); i++) payload[i] = i; gettimeofday((struct timeval *)&payload[0], NULL); if (libnet_build_icmpv4_echo(ICMP_ECHO, 0, 0, 0x42, 0, payload, sizeof (payload), lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_build_ipv4(LIBNET_IPV4_H + 64, IPTOS_LOWDELAY, ip_id++, 0, ip_ttl, IPPROTO_ICMP, 0, src_ip, dst_ip, NULL, 0, lnh_raw4, 0) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); } } /* to send a fragmented ping packet, to test fragmentation */ static void fping(char *host) { uint32_t dst_ip; struct libnet_icmpv4_hdr *echo; u_char payload[64]; unsigned int i; libnet_ptag_t tag; int sum; libnet_clear_packet(lnh_raw4); dst_ip = libnet_name2addr4(lnh_raw4, host, LIBNET_RESOLVE); if (dst_ip == 0xffffffff) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } for (i = 0; i < sizeof (payload) - LIBNET_ICMPV4_ECHO_H; i++) payload[i + LIBNET_ICMPV4_ECHO_H] = i; gettimeofday((struct timeval *)&payload[LIBNET_ICMPV4_ECHO_H], NULL); echo = (struct libnet_icmpv4_hdr *)payload; echo->icmp_type = ICMP_ECHO; echo->icmp_code = 0; echo->icmp_id = ntohs(0x42); echo->icmp_seq = 0; echo->icmp_sum = 0; sum = libnet_in_cksum((u_int16_t *)echo, sizeof (payload)); echo->icmp_sum = LIBNET_CKSUM_CARRY(sum); if ((tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_ICMPV4_ECHO_H, IPTOS_LOWDELAY, ip_id, IP_MF, ip_ttl, IPPROTO_ICMP, 0, src_ip, dst_ip, payload, LIBNET_ICMPV4_ECHO_H, lnh_raw4, 0)) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_build_ipv4(LIBNET_IPV4_H + sizeof (payload) - LIBNET_ICMPV4_ECHO_H, IPTOS_LOWDELAY, ip_id++, LIBNET_ICMPV4_ECHO_H / 8, ip_ttl, IPPROTO_ICMP, 0, src_ip, dst_ip, &payload[LIBNET_ICMPV4_ECHO_H], 64 - LIBNET_ICMPV4_ECHO_H, lnh_raw4, tag) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); return; } if (libnet_write(lnh_raw4) == -1) { fprintf(stderr, "%s", libnet_geterror(lnh_raw4)); } } static void fake_timeout(void *arg __attribute__((__unused__))) { struct timeval tv; gettimeofday(&tv, NULL); printf("processing timer at %ld.%06ld\n", tv.tv_sec, tv.tv_usec); } static void process_input(void) { char buf[80]; fgets(buf, sizeof (buf), stdin); if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = 0; if (!strncasecmp(buf, "ping ", strlen("ping "))) { char *arg1; arg1 = buf + strlen("ping "); ping(arg1); } else if (!strncasecmp(buf, "fping ", strlen("fping "))) { char *arg1; arg1 = buf + strlen("fping "); fping(arg1); } else if (!strncasecmp(buf, "sting ", strlen("sting "))) { TCB *sess; char *arg1, *arg2; uint16_t port = 80; arg1 = buf + strlen("sting "); arg2 = strchr(arg1, ' '); if (arg2) { *arg2++ = 0; port = atoi(arg2); } sess = create_session(arg1, port); sess->sting = 1; /* by this time seqno is iss+1 but that's fine for our purposes */ sess->iss = sess->seqno; } else if (!strncasecmp(buf, "connect ", strlen("connect "))) { char *arg1, *arg2; arg1 = buf + strlen("connect "); arg2 = strchr(arg1, ' '); if (!arg2) { fprintf(stderr, "need to specify port to connect to\n"); return; } *arg2++ = 0; create_session(arg1, atoi(arg2)); } else if (!strncasecmp(buf, "listen ", strlen("listen "))) { create_session(NULL, atoi(buf + strlen("listen "))); } else if (!strncasecmp(buf, "write ", strlen("write "))) { list *l = sessions; unsigned int id; char *arg1, *arg2; arg1 = buf + strlen("write "); arg2 = strchr(arg1, ' '); if (!arg2) { fprintf(stderr, "need to specify socket to write to\n"); return; } *arg2++ = 0; id = atoi(arg1); while (l) { TCB *sess = l->data; if (sess->id > id) { l = NULL; break; } if (sess->id < id) { l = l->next; continue; } if (sess->sting) { fprintf(stderr, "can't write to sting session!\n"); break; } if ((sess->state == TCP_ESTABLISHED) || /* it's not possible for us to be in CLOSE_WAIT but we check it * anyway, just to be pedantic */ (sess->state == TCP_CLOSE_WAIT)) { send_tcp(sess, TH_PUSH | TH_ACK, (u_char *)arg2, strlen(arg2)); /* XXX should add it to the send queue in case we need to * retransmit. in that case we should also set a timer. */ } break; } if (!l) printf("couldn't find %u\n", id); } else if (!strncasecmp(buf, "close ", strlen("close "))) { list *l = sessions; unsigned int id = atoi(buf + strlen("close ")); while (l) { TCB *sess = l->data; if (sess->id > id) { l = NULL; break; } if (sess->id < id) { l = l->next; continue; } if (sess->state == TCP_LISTEN || sess->state == TCP_SYN_SENT) { remove_session(sess); } else if (sess->state == TCP_ESTABLISHED || sess->state == TCP_SYN_RECV) { send_tcp(sess, TH_FIN | TH_ACK, NULL, 0); sess->state = TCP_FIN_WAIT1; printf("%u: %s\n", sess->id, state_names[sess->state]); } break; } if (!l) printf("couldn't find %u\n", id); } else if (!strcasecmp(buf, "netstat")) { list *l = sessions; while (l) { TCB *sess = l->data; l = l->next; printf("id %d: port %d with %s:%d state %s\n", sess->id, sess->src_prt, libnet_addr2name4(sess->dst_ip, LIBNET_DONT_RESOLVE), sess->dst_prt, state_names[sess->state]); } } else if (!strncasecmp(buf, "timer ", strlen("timer "))) { struct timeval tv; char *arg1; arg1 = buf + strlen("timer "); gettimeofday(&tv, NULL); printf("starting timer at %ld.%06ld\n", tv.tv_sec, tv.tv_usec); timer_start(atoi(arg1), fake_timeout, NULL); } else if (!strcasecmp(buf, "timerlist")) { list *l = timers; struct timeval tv; gettimeofday(&tv, NULL); printf("Current time: %ld.%06ld\n", tv.tv_sec, tv.tv_usec); while (l) { struct timer *t = l->data; l = l->next; printf("timer to expire at %ld.%06ld\n", t->end.tv_sec, t->end.tv_usec); } } else if (!strcasecmp(buf, "quit")) { /* XXX should send RST to all the sessions, and remove the bogus arp * entry from the host */ exit(0); } } /* } */ /* PCAP { */ static void process_packet(pcap_t *lph) { struct libnet_ethernet_hdr *enet; struct pcap_pkthdr *hdr; u_char *pkt; /* we pretend pkt is a const here! that's probably pretty evil. in fact we * never actually modify the contents of the packet (or shouldn't!), but * not everything (ahem, libnet) uses const. rather than copy the data to * avoid const, and sprinkling const all over the place, we'll just avoid * the warning. */ if (pcap_next_ex(lph, &hdr, (const u_char **)&pkt) != 1) { fprintf(stderr, "pcap_next fails!\n"); return; } if (hdr == NULL || pkt == NULL) { fprintf(stderr, "pcap_next returns bad data!\n"); return; } if (hdr->len < LIBNET_ETH_H) { fprintf(stderr, "pcap packet too short!\n"); return; } /* we assume everything pcap gives is is ethernet */ enet = (struct libnet_ethernet_hdr *)pkt; switch (ntohs(enet->ether_type)) { case ETHERTYPE_ARP: process_arp_packet(enet, hdr->len); break; case ETHERTYPE_IP: process_ip_packet(enet, hdr->len); break; default: fprintf(stderr, "received unhandled ethernet type %d\n", ntohs(enet->ether_type)); break; } } static pcap_t * init_pcap(char *dev) { char errbuf[PCAP_ERRBUF_SIZE]; pcap_t *lph; bpf_u_int32 net; bpf_u_int32 mask; char *filter_line; struct bpf_program filter; if (!(lph = pcap_open_live(dev, BUFSIZ, 0, 0, errbuf))) { fprintf(stderr, "%s\n", errbuf); return NULL; } if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) { fprintf(stderr, "%s\n", errbuf); return NULL; } filter_line = malloc(4 + 5 + (4*4)); /* this filter should give us everything we need (including arp requests) */ sprintf(filter_line, "dst host %s", libnet_addr2name4(src_ip, LIBNET_DONT_RESOLVE)); pcap_compile(lph, &filter, filter_line, 0, net); free(filter_line); if (pcap_setfilter(lph, &filter) == -1) { fprintf(stderr, "%s\n", errbuf); return NULL; } return lph; } /* } */ int main(int argc, char **argv) { struct passwd *pswd; fd_set set; struct timeval tv, *ptv; struct arpreq req; struct sockaddr_in *s_in; char errbuf[LIBNET_ERRBUF_SIZE]; pcap_t *lph; const char *user = "nobody"; char *dev = NULL; if (argc < 2) { printf("Usage: %s \n", argv[0]); return 1; } if (!(lnh_link = libnet_init(LIBNET_LINK, dev, errbuf))) { fprintf(stderr, "%s\n", errbuf); return 1; } dev = (char *)libnet_getdevice(lnh_link); libnet_seed_prand(lnh_link); memcpy(src_hw, libnet_get_hwaddr(lnh_link), 6); ip_id = libnet_get_prand(LIBNET_PRu16); /* please see RAWSOCKET_NON_SEQUITUR in libnet1 docs */ if (!(lnh_raw4 = libnet_init(LIBNET_RAW4, dev, errbuf))) { fprintf(stderr, "%s\n", errbuf); return 1; } /* you need to pick this IP address based on two characteristics: * * 1. it cannot be in use by another computer (including the host) * 2. it needs to be in the same subnet as the host * * of course, you could remove the ip address from the host and just use * that address, but then you'll probably have to modify this client to * handle all the routing details itself, and that's not fun. */ src_ip = libnet_name2addr4(lnh_raw4, argv[1], LIBNET_RESOLVE); if (src_ip == (uint32_t)-1) { fprintf(stderr, "invalid host name %s\n", argv[1]); return 1; } /* so this is quite the hack. before doing anything else, we feed the host a * fake ethernet address (I certainly hope no device actually exists that * uses it!). then if the host wants to talk to us, it will send it out over * the wire. the packet will go nowhere, but pcap will see it and we'll be * able to process it. in this way we can talk to the host. */ memset(&req, 0, sizeof (req)); req.arp_flags = ATF_PERM | ATF_COM; s_in = (struct sockaddr_in *)&req.arp_pa; s_in->sin_family = AF_INET; s_in->sin_port = 0; s_in->sin_addr.s_addr = src_ip; req.arp_ha.sa_family = ARPHRD_ETHER; memcpy(req.arp_ha.sa_data, "\x00\x00\x00\x00\x00\x01", 6); if (ioctl(libnet_getfd(lnh_raw4), SIOCSARP, &req) < 0) { perror("SIOCSARP"); return 1; } if (!(lph = init_pcap(dev))) return 1; /* now that we've created all of our sockets and opened up pcap, drop root * privileges and run as specified user (which defaults to 'nobody'). */ pswd = getpwnam(user); if (!pswd || setuid(pswd->pw_uid)) { fprintf(stderr, "I ain't %s! (can't drop root privs)\n", user); return 1; } while (1) { FD_ZERO(&set); FD_SET(0, &set); FD_SET(pcap_fileno(lph), &set); ptv = timer_sleep_time(&tv); if (select(pcap_fileno(lph) + 1, &set, NULL, NULL, ptv) < 0) exit(1); if (FD_ISSET(0, &set)) process_input(); if (FD_ISSET(pcap_fileno(lph), &set)) process_packet(lph); timer_process_pending(); } /* I don't think we'll ever get here, unless things go very wrong */ return 1; } /* vim:set ts=4 sw=4 noet ai tw=80: */