/* tcp.c: tcp state machine and signaling. */ /* created for ICS 651 by: esb */ /* interoperates with implementations by: */ #include "ipsim.h" #include #include /* for gettimeofday */ #include /* for gettimeofday */ #define TCP_CLOSED 100 #define TCP_LISTEN 101 #define TCP_SYN_RCVD 102 #define TCP_SYN_SENT 103 #define TCP_ESTABLISHED 104 #define TCP_FIN_WAIT1 105 #define TCP_FIN_WAIT2 106 #define TCP_CLOSING 107 #define TCP_TIME_WAIT 108 #define TCP_CLOSE_WAIT 109 #define TCP_LAST_ACK 110 struct tcp_header { unsigned short src_port; unsigned short dst_port; unsigned long seq_num; unsigned long ack_num; unsigned char offset; unsigned char flags; unsigned short window; unsigned short checksum; unsigned short urgent_pointer; }; #define URG_FLAG 0x20 #define ACK_FLAG 0x10 #define PSH_FLAG 0x08 #define RST_FLAG 0x04 #define SYN_FLAG 0x02 #define FIN_FLAG 0x01 struct tcb { int tcp_state; unsigned short local_port; unsigned short remote_port; unsigned long remote_ip; int remote_port_specified; unsigned long iss; unsigned long irs; unsigned long snd_una; unsigned long snd_nxt; unsigned long rcv_nxt; unsigned long rcv_wnd; }; #define le32(a, b) ((((((unsigned long) (b)) - ((unsigned long)(a)))) & ((unsigned long) 0xffffffff)) < ((unsigned long) 0x7fffffff)) #define g32(a, b) (! (le32 (a, b))) #define MAX_BUF 2000 static struct tcb tcbs [MAX_CONNECTIONS]; static char * print_state (int state) { switch (state) { case TCP_CLOSED: return "TCP_CLOSED"; case TCP_LISTEN: return "TCP_LISTEN"; case TCP_SYN_RCVD: return "TCP_SYN_RCVD"; case TCP_SYN_SENT: return "TCP_SYN_SENT"; case TCP_ESTABLISHED: return "TCP_ESTABLISHED"; case TCP_FIN_WAIT1: return "TCP_FIN_WAIT1"; case TCP_FIN_WAIT2: return "TCP_FIN_WAIT2"; case TCP_CLOSING: return "TCP_CLOSING"; case TCP_TIME_WAIT: return "TCP_TIME_WAIT"; case TCP_CLOSE_WAIT: return "TCP_CLOSE_WAIT"; case TCP_LAST_ACK: return "TCP_LAST_ACK"; default: printf ("error in print_state(%d)\n", state); return "illegal state"; } } static int tcp_send (int IP_address, unsigned short src_port, unsigned short dst_port, unsigned long seq_num, unsigned long ack_num, unsigned int ack, unsigned int rst, unsigned int syn, unsigned int fin) { char buf [MAX_BUF]; struct tcp_header * header = (struct tcp_header *) buf; header->src_port = src_port; header->dst_port = dst_port; header->seq_num = seq_num; header->ack_num = ack_num; header->offset = 0x50; header->flags = (((ack) ? ACK_FLAG : 0) | ((rst) ? RST_FLAG : 0) | ((syn) ? SYN_FLAG : 0) | ((fin) ? FIN_FLAG : 0)); header->window = htons(4096); header->checksum = 0; /* not implemented */ header->urgent_pointer = 0; if (ip_send (IP_address, buf, sizeof(struct tcp_header)) != 0) { struct in_addr ia; ia.s_addr = IP_address; printf ("tcp_send: ip_send returned -1 to destination %s\n", inet_ntoa (ia)); return -1; } return 0; } static int tcp_receive (const char * name, char * buf, int size, int * address, int timeout) { int count; if ((count = ip_receive (buf, MAX_BUF, address, timeout)) != 20) { if (count > 0) { #ifdef DEBUG printf ("tcp/%s: got %d bytes from ip_receive\n", name, count); #endif DEBUG } else { printf ("tcp/%s: ip_receive returned no data\n", name); } return -1; } return 0; } static unsigned long tcp_iss () { struct timeval t; unsigned long iss; gettimeofday (&t, NULL); iss = t.tv_sec * 250000 + t.tv_usec / 4; #ifdef DEBUG printf ("tcp_iss: iss value %lu/0x%lx\n", iss, iss); #endif DEBUG return (t.tv_sec * 250000 + t.tv_usec / 4); } static int free_connection () { int connection; for (connection = 1; connection < MAX_CONNECTIONS; connection++) { if (tcbs [connection].tcp_state == TCP_CLOSED) { return connection; } } if (connection >= MAX_CONNECTIONS) { printf ("no connection IDs available\n"); return -1; } } static void process_packet (struct tcb * tcb, struct tcp_header * header, int from_listen) { char * state_str = print_state (tcb->tcp_state); /* since we don't have any data except for syn and fin, we only accept packets if seg.seq == rcv.nxt. This means we never have to "trim" the packet. */ #ifdef DEBUG printf ("process_packet: starting in state %s\n", state_str); #endif DEBUG if (ntohl (header->seq_num) != tcb->rcv_nxt) { /* bad sequence number */ #ifdef DEBUG printf ("process_packet/%s: received packet with bad sequence number\n", state_str); #endif DEBUG if (! (header->flags & RST_FLAG)) { /* not reset, send ack */ if (tcp_send (htonl (tcb->remote_ip), header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 0) < 0) { printf ("process_packet/%s: error sending ack for out-of-window\n", state_str); } } return; } if (header->flags & RST_FLAG) { /* reset */ switch (tcb->tcp_state) { case TCP_SYN_RCVD: if (from_listen) { tcb->tcp_state = TCP_LISTEN; return; } else { printf ("process_packet/%s: error, connection refused\n", state_str); tcb->tcp_state = TCP_CLOSED; return; } case TCP_ESTABLISHED: /* note, this should be impossible */ case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: case TCP_CLOSE_WAIT: printf ("process_packet/%s: error, connection refused\n", state_str); /* fall through to the next cases */ case TCP_CLOSING: case TCP_LAST_ACK: case TCP_TIME_WAIT: tcb->tcp_state = TCP_CLOSED; return; default: printf ("process_packet/%s: reset received\n", state_str); return; } } if (header->flags & SYN_FLAG) { switch (tcb->tcp_state) { case TCP_SYN_RCVD: case TCP_ESTABLISHED: /* note, this should be impossible */ case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: case TCP_CLOSE_WAIT: case TCP_CLOSING: case TCP_LAST_ACK: case TCP_TIME_WAIT: printf ("process_packet: error, SYN in state %d\n", state_str); if (tcp_send (htonl(tcb->remote_ip), header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 1, /* S */ 0, /* F */ 0) < 0) { printf ("tcp_close: error sending reset for illegal syn\n"); } tcb->tcp_state = TCP_CLOSED; return; default: printf ("process_packet/%s: syn received\n", state_str); return; } } if (! (header->flags & ACK_FLAG)) { printf ("process_packet: error, no ACK\n"); return; } switch (tcb->tcp_state) { /* step 5, check ack */ case TCP_SYN_RCVD: if (le32 (tcb->snd_una, ntohl (header->ack_num)) && le32 (ntohl (header->ack_num), tcb->snd_nxt)) { #ifdef DEBUG printf ("process_packet/%s: transition to established from syn_rcvd\n", state_str); #endif DEBUG tcb->snd_una = ntohl (header->ack_num); tcb->tcp_state = TCP_ESTABLISHED; } else { printf ("sending reset, snd.una %x, seg.ack %x, snd.nxt %x\n", tcb->snd_una, ntohl (header->ack_num), tcb->snd_nxt); if (tcp_send (htonl(tcb->remote_ip), header->dst_port, header->src_port, header->ack_num, header->seq_num, /* A */ 0, /* R */ 1, /* S */ 0, /* F */ 0) < 0) { printf ("process_packet/%s: error sending reset bad SYN_RCVD ack\n", state_str); } return; } /* fall through to ESTABLISHED */ case TCP_ESTABLISHED: case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: case TCP_CLOSE_WAIT: case TCP_CLOSING: if (g32 (tcb->snd_una, ntohl (header->ack_num))) { printf ("process_packet/%s: ignoring duplicate ack %x, expecting %x\n", state_str, ntohl (header->ack_num), tcb->snd_una); } else if (g32 (ntohl (header->ack_num), tcb->snd_nxt)) { printf ("process_packet/%s: got illegal ack\n", state_str); if (tcp_send (htonl(tcb->remote_ip), header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 0) < 0) { printf ("process_packet: error sending ack for illegal ack\n"); } return; /* ignore the rest of this packet */ } else { /* valid ack */ tcb->snd_una = ntohl (header->ack_num); } switch (tcb->tcp_state) {/* special cases "in addition to ESTABLISHED" */ case TCP_FIN_WAIT1: if (ntohl (header->ack_num) == tcb->snd_nxt) { /* fin is acked */ tcb->tcp_state = TCP_FIN_WAIT2; }; break; case TCP_CLOSING: if (ntohl (header->ack_num) == tcb->snd_nxt) { /* fin is acked */ tcb->tcp_state = TCP_TIME_WAIT; }; break; default: break; /* no further action needed */ } /* end special cases */ break; /* end ack processing for these states */ case TCP_LAST_ACK: if (ntohl (header->ack_num) == tcb->snd_nxt) { /* fin is acked */ tcb->tcp_state = TCP_CLOSED; } break; case TCP_TIME_WAIT: /* should not happen in this implementation */ if (tcp_send (htonl(tcb->remote_ip), header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 0) < 0) { printf ("process_packet: error sending ack in state time_wait\n"); } break; default: printf ("process_packet: unexpected state %s\n", tcb->tcp_state); return; } /* skip step six, URG bit */ /* skip step seven, segment text*/ if (header->flags & FIN_FLAG) { /* step 8, FIN bit */ if ((tcb->tcp_state == TCP_CLOSED) || (tcb->tcp_state == TCP_LISTEN) || (tcb->tcp_state == TCP_SYN_SENT)) { printf ("process_packet/%s: ignoring illegal fin\n", state_str); return; } tcb->rcv_nxt = ntohl (header->seq_num) + 1; /* skip over FIN */ if (tcp_send (htonl(tcb->remote_ip), header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 0) < 0) { printf ("process_packet/%s: error sending ack for fin\n", state_str); } switch (tcb->tcp_state) { case TCP_SYN_RCVD: case TCP_ESTABLISHED: tcb->tcp_state = TCP_CLOSE_WAIT; break; case TCP_FIN_WAIT1: /* our fin not acked, or we'd be in FW2 */ tcb->tcp_state = TCP_CLOSING; break; case TCP_FIN_WAIT2: tcb->tcp_state = TCP_TIME_WAIT; break; case TCP_CLOSE_WAIT: case TCP_CLOSING: case TCP_LAST_ACK: case TCP_TIME_WAIT: break; default: printf ("process_packet: unexpected state %s\n", state_str); return; } } #ifdef DEBUG printf ("process_packet: returning in state %s\n", print_state (tcb->tcp_state)); #endif DEBUG } static int tcp_syn_rcvd (char * caller, struct tcb * tcb, int from_listen) { char buf [MAX_BUF]; struct tcp_header * header = (struct tcp_header *) buf; int from; while (tcb->tcp_state == TCP_SYN_RCVD) { /* wait for ack */ #ifdef DEBUG printf ("%s: waiting for ack in state syn_rcvd\n", caller); #endif DEBUG if (tcp_receive ("SYN_RCVD", buf, MAX_BUF, &from, 10000) < 0) { return -1; } process_packet (tcb, header, from_listen); if (tcb->tcp_state == TCP_CLOSED) { return -1; } } return 0; } int tcp_connect(int IP_address, short local_port, short remote_port) { char buf [MAX_BUF]; struct tcp_header * header = (struct tcp_header *) buf; struct tcb * tcb; int from, connection; /* find a free connection */ if ((connection = free_connection ()) < 1) { return -1; } tcb = &(tcbs [connection]); #ifdef DEBUG printf ("tcp_connect: entering state syn_sent on connection %d\n", connection); #endif DEBUG tcb->tcp_state = TCP_SYN_SENT; tcb->iss = tcp_iss (); tcb->local_port = local_port; tcb->remote_port = remote_port; tcb->remote_ip = ntohl (IP_address); tcb->snd_una = tcb->iss; tcb->snd_nxt = tcb->iss + 1; if (tcp_send (IP_address, htons(local_port), htons(remote_port), htonl(tcb->iss), htonl(0), /* A */ 0, /* R */ 0, /* S */ 1, /* F */ 0) < 0) { printf ("tcp_connect: error sending SYN packet\n"); return -1; } do { #ifdef DEBUG printf ("tcp_connect: calling tcp_receive\n"); #endif DEBUG if (tcp_receive ("SYN_SENT", buf, MAX_BUF, &from, 10000) < 0) { return -1; } #ifdef DEBUG printf ("tcp_connect: received response to syn packet\n"); #endif DEBUG if ((ntohs (header->dst_port) != local_port) || (ntohs (header->src_port) != remote_port) || (from != IP_address)) { printf ("tcp_connect: got response for someone else, not implemented\n"); continue; /* return -1; */ } if (header->flags & ACK_FLAG) { if ((le32 (ntohl(header->ack_num), tcb->iss)) || (g32 (ntohl(header->ack_num), tcb->snd_nxt))) { printf ("tcp_connect: got illegal sequence number\n"); if (! (header->flags & RST_FLAG)) { if (tcp_send (from, header->dst_port, header->src_port, header->ack_num, header->seq_num, /* A */ 1, /* R */ 1, /* S */ 0, /* F */ 0) < 0) { printf ("tcp_connect: error sending reset packet for bad ack\n"); } } return -1; } } if (header->flags & RST_FLAG) { if (header->flags & ACK_FLAG) { printf ("tcp_connect: error, connection reset\n"); tcb->tcp_state = TCP_CLOSED; return -1; } else { printf ("tcp_connect: error, no ack on reset segment\n"); continue; } } if (header->flags & SYN_FLAG) { #ifdef DEBUG printf ("tcp_connect: received syn flag\n"); #endif DEBUG tcb->rcv_nxt = ntohl(header->seq_num) + 1; tcb->irs = ntohl(header->seq_num); tcb->rcv_wnd = ntohs(header->window); if ((header->flags & ACK_FLAG) && (g32 (ntohl(header->ack_num), tcb->iss))) { /* SND.UNA > ISS */ #ifdef DEBUG printf ("tcp_connect: entering state established\n"); #endif DEBUG tcb->snd_una = ntohl(header->ack_num); tcb->tcp_state = TCP_ESTABLISHED; if (tcp_send (from, header->dst_port, header->src_port, htonl (tcb->snd_nxt), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 0) < 0) { printf ("tcp_connect: error sending ack for received syn\n"); } } else { /* not valid ack */ #ifdef DEBUG printf ("tcp_connect: entering state syn_rcvd\n"); #endif DEBUG tcb->tcp_state = TCP_SYN_RCVD; if (tcp_send (from, header->dst_port, header->src_port, htonl (tcb->iss), htonl(tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 1, /* F */ 0) < 0) { printf ("tcp_connect: error sending syn-ack for received syn\n"); } } } } while (! ((header->flags & SYN_FLAG) || (header->flags & RST_FLAG))); if (tcp_syn_rcvd ("tcp_connect", tcb, 0) < 0) { return -1; } if (tcb->tcp_state == TCP_ESTABLISHED) { #ifdef DEBUG printf ("tcp_connect: connection %d established\n", connection); #endif DEBUG return connection; } else { printf ("tcp_connect: in state %d\n", tcb->tcp_state); return -1; } } int tcp_listen(short local_port, int * IP_address, int timeout) { char buf [MAX_BUF]; struct tcp_header * header = (struct tcp_header *) buf; int from, connection; struct tcb * tcb; /* find a free connection */ if ((connection = free_connection ()) < 1) { return -1; } tcb = &(tcbs [connection]); tcb->tcp_state = TCP_LISTEN; tcb->local_port = local_port; #ifdef DEBUG printf ("tcp_listen: opening connection %d\n", connection); #endif DEBUG do { #ifdef DEBUG printf ("tcp_listen: calling receive\n"); #endif DEBUG if (tcp_receive ("LISTEN", buf, MAX_BUF, &from, timeout) < 0) { return -1; /* no connection */ } if (header->flags & RST_FLAG) { #ifdef DEBUG printf ("tcp_listen: ignoring reset packet in LISTEN state\n"); #endif DEBUG continue; /* received reset packet, ignore */ } if (header->flags & ACK_FLAG) { #ifdef DEBUG printf ("tcp_listen: ignoring ack packet in LISTEN state\n"); #endif DEBUG if (tcp_send (from, header->dst_port, header->src_port, header->ack_num, header->seq_num, /* A */ 0, /* R */ 1, /* S */ 0, /* F */ 0) < 0) { printf ("tcp_connect: error sending reset packet\n"); return -1; } continue; /* received reset packet, ignore */ } if (ntohs (header->dst_port) != local_port) { printf ("tcp_listen: got message for different port %d(%d), ignoring\n", ntohs (header->dst_port), local_port); continue; /* return -1; */ } if (header->flags & SYN_FLAG) { #ifdef DEBUG printf ("tcp_listen: changing to syn_rcvd state\n"); #endif DEBUG tcb->irs = ntohl (header->seq_num); tcb->rcv_nxt = tcb->irs + 1; tcb->iss = tcp_iss (); tcb->snd_nxt = tcb->iss + 1; tcb->snd_una = tcb->iss; tcb->remote_port = ntohs(header->src_port); tcb->remote_ip = ntohl(from); *IP_address = tcb->remote_ip; tcb->tcp_state = TCP_SYN_RCVD; if (tcp_send (from, header->dst_port, header->src_port, htonl (tcb->iss), htonl (tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 1, /* F */ 0) < 0) { printf ("tcp_listen: error sending syn/ack packet\n"); return -1; } } } while (tcb->tcp_state != TCP_SYN_RCVD); /* now wait for ack to our syn */ if (tcp_syn_rcvd ("tcp_listen", tcb, 1) < 0) { return -1; } if (tcb->tcp_state == TCP_ESTABLISHED) { #ifdef DEBUG printf ("tcp_listen: connection %d established\n", connection); #endif DEBUG return connection; } else { printf ("tcp_listen: in state %d\n", tcb->tcp_state); return -1; } } void tcp_close(int connection) { char buf [MAX_BUF]; struct tcp_header * header = (struct tcp_header *) buf; int from; struct tcb * tcb; char * state_str; if ((connection < 1) || (connection >= MAX_CONNECTIONS)) { printf ("error closing connection %d, out of range\n", connection); return; } tcb = &(tcbs [connection]); state_str = print_state (tcb->tcp_state); #ifdef DEBUG printf ("tcp_close: waiting for initial packet in state %s\n", print_state (tcb->tcp_state)); #endif DEBUG if (tcp_receive (state_str, buf, MAX_BUF, &from, 10000) >= 0) { process_packet (tcb, header, 0); } /* do CLOSE processing */ state_str = print_state (tcb->tcp_state); #ifdef DEBUG printf ("tcp_close: starting CLOSE processing in state %s\n", state_str); #endif DEBUG switch (tcb->tcp_state) { case TCP_CLOSED: printf ("error: connection %d does not exist\n", connection); return; case TCP_LISTEN: tcb->tcp_state = TCP_CLOSED; return; case TCP_SYN_SENT: tcb->tcp_state = TCP_CLOSED; return; case TCP_SYN_RCVD: tcb->tcp_state = TCP_ESTABLISHED; /* fall through to TCP_ESTABLISHED */ case TCP_ESTABLISHED: tcb->tcp_state = TCP_FIN_WAIT1; if (tcp_send (htonl (tcb->remote_ip), htons (tcb->local_port), htons (tcb->remote_port), htonl (tcb->snd_nxt), htonl (tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 1) < 0) { printf ("tcp_close: error sending FIN packet\n"); return; } tcb->snd_nxt++; break; case TCP_FIN_WAIT1: case TCP_FIN_WAIT2: case TCP_CLOSING: case TCP_TIME_WAIT: case TCP_LAST_ACK: printf ("tcp_close: warning, connection already closing\n"); break; case TCP_CLOSE_WAIT: tcb->tcp_state = TCP_LAST_ACK; if (tcp_send (htonl (tcb->remote_ip), htons (tcb->local_port), htons (tcb->remote_port), htonl (tcb->snd_nxt), htonl (tcb->rcv_nxt), /* A */ 1, /* R */ 0, /* S */ 0, /* F */ 1) < 0) { printf ("tcp_close: error sending FIN packet\n"); return; } tcb->snd_nxt++; break; default: printf ("tcp_close: error, TCP in state %d\n", tcb->tcp_state); return; } while (tcb->tcp_state != TCP_CLOSED) { /* close connection */ state_str = print_state (tcb->tcp_state); #ifdef DEBUG printf ("tcp_close: calling tcp_receive in state %s\n", state_str); #endif DEBUG if (tcp_receive (state_str, buf, MAX_BUF, &from, 4000) < 0) { printf ("tcp_close: receive timed out in state %s\n", state_str); tcb->tcp_state = TCP_CLOSED; } else { process_packet (tcb, header, 0); } } return; } void tcp_init () { int i; printf ("entering tcp_init\n"); for (i = 0; i < MAX_CONNECTIONS; i++) { tcbs [i].tcp_state = TCP_CLOSED; } return; }