/* * This is the core TCP layer in the stack. It's notified of incoming * IP datagrams containing TCP protocols. This is where the TCP state * diagram is handled. * * * +---------+ ---------\ active OPEN * | CLOSED | \ ----------- * +---------+<---------\ \ create TCB * | ^ \ \ snd SYN * passive OPEN | | CLOSE \ \ * ------------ | | ---------- \ \ * create TCB | | delete TCB \ \ * V | \ \ * +---------+ CLOSE | \ * | LISTEN | ---------- | | * +---------+ delete TCB | | * rcv SYN | | SEND | | * ----------- | | ------- | V * +---------+ snd SYN,ACK / \ snd SYN +---------+ * | |<----------------- ------------------>| | * | SYN | rcv SYN | SYN | * | RCVD |<-----------------------------------------------| SENT | * | | snd ACK | | * | |------------------ -------------------| | * +---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+ * | -------------- | | ----------- * | x | | snd ACK * | V V * | CLOSE +---------+ * | ------- | ESTAB | * | snd FIN +---------+ * | CLOSE | | rcv FIN * V ------- | | ------- * +---------+ snd FIN / \ snd ACK +---------+ * | FIN |<----------------- ------------------>| CLOSE | * | WAIT-1 |------------------ | WAIT | * +---------+ rcv FIN \ +---------+ * | rcv ACK of FIN ------- | CLOSE | * | -------------- snd ACK | ------- | * V x V snd FIN V * +---------+ +---------+ +---------+ * |FINWAIT-2| | CLOSING | | LAST-ACK| * +---------+ +---------+ +---------+ * | rcv ACK of FIN | rcv ACK of FIN | * | rcv FIN -------------- | Timeout=2MSL -------------- | * | ------- x V ------------ x V * \ snd ACK +---------+delete TCB +---------+ * ------------------------>|TIME WAIT|------------------>| CLOSED | * +---------+ +---------+ * */ #include "stack-tcp-core.h" #include "stack-tcp-api.h" #include "stack-tcp-app.h" #include #include #include #include #include #include #include #include #include #include "syn-cookie.h" #include "event-timeout.h" /* for tracking future events */ #include "rawsock.h" #include "util-logger.h" #include "templ-pkt.h" #include "pixie-timer.h" #include "stack-queue.h" #include "proto-banner1.h" #include "proto-ssl.h" #include "proto-http.h" #include "proto-smb.h" #include "proto-versioning.h" #include "output.h" #include "util-safefunc.h" #include "main-globals.h" #include "crypto-base64.h" #include "util-malloc.h" #include "util-errormsg.h" #include "scripting.h" #ifdef _MSC_VER #pragma warning(disable:4204) #define snprintf _snprintf #pragma warning(disable:4996) #endif struct TCP_Segment { unsigned seqno; unsigned char *buf; size_t length; enum TCP__flags flags; bool is_fin; /* was fin sent */ struct TCP_Segment *next; }; /*************************************************************************** * A "TCP control block" is what most operating-systems/network-stack * calls the structure that corresponds to a TCP connection. It contains * things like the IP addresses, port numbers, sequence numbers, timers, * and other things. ***************************************************************************/ struct TCP_Control_Block { ipaddress ip_me; ipaddress ip_them; unsigned short port_me; unsigned short port_them; uint32_t seqno_me; /* next seqno I will use for transmit */ uint32_t seqno_them; /* the next seqno I expect to receive */ uint32_t ackno_me; uint32_t ackno_them; uint32_t seqno_me_first; uint32_t seqno_them_first; struct TCP_Control_Block *next; struct TimeoutEntry timeout[1]; unsigned char ttl; unsigned char syns_sent; /* reconnect */ unsigned short mss; /* maximum segment size 1460 */ unsigned tcpstate:4; unsigned is_ipv6:1; unsigned is_small_window:1; /* send with smaller window */ unsigned is_their_fin:1; /** Set to true when the TCB is in-use/allocated, set to zero * when it's about to be deleted soon */ unsigned is_active:1; /* If the payload we've sent was dynamically allocated with * malloc() from the heap, in which case we'll have to free() * it. (Most payloads are static memory) */ unsigned is_payload_dynamic:1; unsigned app_state; struct TCP_Segment *segments; /* unsigned short payload_length; const unsigned char *payload; */ time_t when_created; /* * If Running a script, the thread object */ struct ScriptingThread *scripting_thread; const struct ProtocolParserStream *stream; struct BannerOutput banout; struct StreamState banner1_state; unsigned packet_number; }; struct TCP_ConnectionTable { struct TCP_Control_Block **entries; struct TCP_Control_Block *freed_list; unsigned count; unsigned mask; unsigned timeout_connection; unsigned timeout_hello; uint64_t active_count; uint64_t entropy; struct Timeouts *timeouts; struct TemplatePacket *pkt_template; struct stack_t *stack; struct Banner1 *banner1; OUTPUT_REPORT_BANNER report_banner; struct Output *out; struct ScriptingVM *scripting_vm; /** This is for creating follow-up connections based on the first * connection. Given an existing IP/port, it returns a different * one for the new conenction. */ struct { const void *data; void *(*cb)(const void *in_src, const ipaddress ip, unsigned port, ipaddress *next_ip, unsigned *next_port); } next_ip_port; }; enum { STATE_SYN_SENT=0, /* must be zero */ //STATE_SYN_RECEIVED, STATE_ESTABLISHED_SEND, /* our own special state, can only send */ STATE_ESTABLISHED_RECV, /* our own special state, can only receive */ STATE_CLOSE_WAIT, STATE_LAST_ACK, STATE_FIN_WAIT1_SEND, STATE_FIN_WAIT1_RECV, STATE_FIN_WAIT2, STATE_CLOSING, STATE_TIME_WAIT, }; /*************************************************************************** * DEBUG: when printing debug messages (-d option), this prints a string * for the given state. ***************************************************************************/ static const char * state_to_string(int state) { static char buf[64]; switch (state) { //STATE_SYN_RECEIVED, case STATE_CLOSE_WAIT: return "CLOSE-WAIT"; case STATE_LAST_ACK: return "LAST-ACK"; case STATE_FIN_WAIT1_SEND: return "FIN-WAIT-1-SEND"; case STATE_FIN_WAIT1_RECV: return "FIN-WAIT-1-RECV"; case STATE_FIN_WAIT2: return "FIN-WAIT-2"; case STATE_CLOSING: return "CLOSING"; case STATE_TIME_WAIT: return "TIME-WAIT"; case STATE_SYN_SENT: return "SYN_SENT"; case STATE_ESTABLISHED_SEND:return "ESTABLISHED_SEND"; case STATE_ESTABLISHED_RECV:return "ESTABLISHED_RECV"; default: snprintf(buf, sizeof(buf), "%d", state); return buf; } } static void vLOGtcb(const struct TCP_Control_Block *tcb, int dir, const char *fmt, va_list marker) { char sz[256]; ipaddress_formatted_t fmt1 = ipaddress_fmt(tcb->ip_them); snprintf(sz, sizeof(sz), "[%s:%u %4u,%4u] %s:%5u [%4u,%4u] {%s} ", fmt1.string, tcb->port_them, tcb->seqno_them - tcb->seqno_them_first, tcb->ackno_me - tcb->seqno_them_first, (dir > 0) ? "-->" : "<--", tcb->port_me, tcb->seqno_me - tcb->seqno_me_first, tcb->ackno_them - tcb->seqno_me_first, state_to_string(tcb->tcpstate) ); if (dir == 2) { char *brace = strchr(sz, '{'); memset(sz, ' ', brace-sz); } fprintf(stderr, "%s", sz); vfprintf(stderr, fmt, marker); fflush(stderr); } int is_tcp_debug = 0; static void LOGtcb(const struct TCP_Control_Block *tcb, int dir, const char *fmt, ...) { va_list marker; if (!is_tcp_debug) return; va_start(marker, fmt); vLOGtcb(tcb, dir, fmt, marker); va_end(marker); } /*************************************************************************** * Process all events, up to the current time, that need timing out. ***************************************************************************/ void tcpcon_timeouts(struct TCP_ConnectionTable *tcpcon, unsigned secs, unsigned usecs) { uint64_t timestamp = TICKS_FROM_TV(secs, usecs); for (;;) { struct TCP_Control_Block *tcb; enum TCB_result x; /* * Get the next event that is older than the current time */ tcb = (struct TCP_Control_Block *)timeouts_remove(tcpcon->timeouts, timestamp); /* * If everything up to the current time has already been processed, * then exit this loop */ if (tcb == NULL) break; /* * Process this timeout */ x = stack_incoming_tcp(tcpcon, tcb, TCP_WHAT_TIMEOUT, 0, 0, secs, usecs, tcb->seqno_them, tcb->ackno_them); /* If the TCB hasn't been destroyed, then we need to make sure * there is a timeout associated with it. KLUDGE: here is the problem: * there must ALWAYS be a 'timeout' associated with a TCB, otherwise, * we'll lose track of it and leak memory. In theory, this should be * automatically handled elsewhere, but I have bugs, and it's not, * so I put some code here as a catch-all: if the TCB hasn't been * deleted, but hasn't been inserted back into the timeout system, * then insert it here. */ if (x != TCB__destroyed && timeout_is_unlinked(tcb->timeout)) { timeouts_add( tcpcon->timeouts, tcb->timeout, offsetof(struct TCP_Control_Block, timeout), TICKS_FROM_TV(secs+2, usecs)); } } } /*************************************************************************** ***************************************************************************/ static int name_equals(const char *lhs, const char *rhs) { for (;;) { while (*lhs == '-' || *lhs == '.' || *lhs == '_') lhs++; while (*rhs == '-' || *rhs == '.' || *rhs == '_') rhs++; if (*lhs == '\0' && *rhs == '[') return 1; /*arrays*/ if (*rhs == '\0' && *lhs == '[') return 1; /*arrays*/ if (tolower(*lhs & 0xFF) != tolower(*rhs & 0xFF)) return 0; if (*lhs == '\0') return 1; lhs++; rhs++; } } /*************************************************************************** * When setting parameters, this will parse integers from the config * parameter strings. ***************************************************************************/ static uint64_t parseInt(const void *vstr, size_t length) { const char *str = (const char *)vstr; uint64_t result = 0; size_t i; for (i=0; ibanner1; if (name_equals(name, "http-payload")) { char lenstr[64]; snprintf(lenstr, sizeof(lenstr), "%u", (unsigned)value_length); banner_http.hello_length = http_change_requestline( (unsigned char**)&banner_http.hello, banner_http.hello_length, (const unsigned char *)value, value_length, 3); /* payload*/ banner_http.hello_length = http_change_field( (unsigned char**)&banner_http.hello, banner_http.hello_length, "Content-Length:", (const unsigned char *)lenstr, strlen(lenstr), http_field_replace); return; } /* * You can reset your user-agent here. Whenever I do a scan, I always * reset my user-agent. That's now you know it's not me scanning * you on the open Internet -- I would never use the default user-agent * string built into masscan */ if (name_equals(name, "http-user-agent")) { banner_http.hello_length = http_change_field( (unsigned char**)&banner_http.hello, banner_http.hello_length, "User-Agent:", (const unsigned char *)value, value_length, http_field_replace); return; } if (name_equals(name, "http-host")) { banner_http.hello_length = http_change_field( (unsigned char**)&banner_http.hello, banner_http.hello_length, "Host:", (const unsigned char *)value, value_length, http_field_replace); return; } /** * Changes the URL */ if (name_equals(name, "http-method")) { banner_http.hello_length = http_change_requestline( (unsigned char**)&banner_http.hello, banner_http.hello_length, (const unsigned char *)value, value_length, 0); /* method*/ return; } if (name_equals(name, "http-url")) { banner_http.hello_length = http_change_requestline( (unsigned char**)&banner_http.hello, banner_http.hello_length, (const unsigned char *)value, value_length, 1); /* url */ return; } if (name_equals(name, "http-version")) { banner_http.hello_length = http_change_requestline( (unsigned char**)&banner_http.hello, banner_http.hello_length, (const unsigned char *)value, value_length, 2); /* version */ return; } if (name_equals(name, "timeout") || name_equals(name, "connection-timeout")) { uint64_t n = parseInt(value, value_length); tcpcon->timeout_connection = (unsigned)n; LOG(1, "TCP connection-timeout = %u\n", tcpcon->timeout_connection); return; } if (name_equals(name, "hello-timeout")) { uint64_t n = parseInt(value, value_length); tcpcon->timeout_hello = (unsigned)n; LOG(1, "TCP hello-timeout = \"%.*s\"\n", (int)value_length, (const char *)value); LOG(1, "TCP hello-timeout = %u\n", (unsigned)tcpcon->timeout_hello); return; } /* * Force SSL processing on all ports */ if (name_equals(name, "hello") && name_equals(value, "ssl")) { unsigned i; LOG(2, "HELLO: setting SSL hello message\n"); for (i=0; i<65535; i++) { banner1->payloads.tcp[i] = &banner_ssl; } return; } /* * Force HTTP processing on all ports */ if (name_equals(name, "hello") && name_equals(value, "http")) { unsigned i; LOG(2, "HELLO: setting HTTP hello message\n"); for (i=0; i<65535; i++) { banner1->payloads.tcp[i] = &banner_http; } return; } /* * Downgrade SMB hello from v1/v2 to use only v1 */ if (name_equals(name, "hello") && name_equals(value, "smbv1")) { smb_set_hello_v1(&banner_smb1); return; } /* * 2014-04-08: scan for Neel Mehta's "heartbleed" bug */ if (name_equals(name, "heartbleed")) { unsigned i; /* Change the hello message to including negotiating the use of * the "heartbeat" extension */ banner_ssl.hello = ssl_hello(ssl_hello_heartbeat_template); banner_ssl.hello_length = ssl_hello_size(banner_ssl.hello); tcpcon->banner1->is_heartbleed = 1; for (i=0; i<65535; i++) { banner1->payloads.tcp[i] = &banner_ssl; } return; } if (name_equals(name, "ticketbleed")) { unsigned i; /* Change the hello message to including negotiating the use of * the "heartbeat" extension */ banner_ssl.hello = ssl_hello(ssl_hello_ticketbleed_template); banner_ssl.hello_length = ssl_hello_size(banner_ssl.hello); tcpcon->banner1->is_ticketbleed = 1; for (i=0; i<65535; i++) { banner1->payloads.tcp[i] = &banner_ssl; } return; } /* * 2014-10-16: scan for SSLv3 servers (POODLE) */ if (name_equals(name, "poodle") || name_equals(name, "sslv3")) { unsigned i; void *px; /* Change the hello message to including negotiating the use of * the "heartbeat" extension */ px = ssl_hello(ssl_hello_sslv3_template); banner_ssl.hello = ssl_add_cipherspec(px, 0x5600, 1); banner_ssl.hello_length = ssl_hello_size(banner_ssl.hello); tcpcon->banner1->is_poodle_sslv3 = 1; for (i=0; i<65535; i++) { banner1->payloads.tcp[i] = &banner_ssl; } return; } /* * You can reconfigure the "hello" message to be anything * you want. */ if (name_equals(name, "hello-string")) { struct ProtocolParserStream *x; const char *p = strchr(name, '['); unsigned port; if (p == NULL) { ERRMSG("tcpcon: parameter: expected array []: %s\n", name); return; } port = (unsigned)strtoul(p+1, 0, 0); x = CALLOC(1, sizeof(*x)); if (banner1->payloads.tcp[port]) memcpy(x, banner1->payloads.tcp[port], sizeof (*x)); x->name = "(allocated)"; x->hello = MALLOC(value_length); x->hello_length = base64_decode((char*)x->hello, value_length, value, value_length); banner1->payloads.tcp[port] = x; } } /*************************************************************************** ***************************************************************************/ void tcpcon_set_banner_flags(struct TCP_ConnectionTable *tcpcon, unsigned is_capture_cert, unsigned is_capture_servername, unsigned is_capture_html, unsigned is_capture_heartbleed, unsigned is_capture_ticketbleed) { tcpcon->banner1->is_capture_cert = is_capture_cert; tcpcon->banner1->is_capture_servername = is_capture_servername; tcpcon->banner1->is_capture_html = is_capture_html; tcpcon->banner1->is_capture_heartbleed = is_capture_heartbleed; tcpcon->banner1->is_capture_ticketbleed = is_capture_ticketbleed; } /*************************************************************************** ***************************************************************************/ void scripting_init_tcp(struct TCP_ConnectionTable *tcpcon, struct lua_State *L) { tcpcon->banner1->L = L; banner_scripting.init(tcpcon->banner1); } /*************************************************************************** * Called at startup, by a receive thread, to create a TCP connection * table. ***************************************************************************/ struct TCP_ConnectionTable * tcpcon_create_table( size_t entry_count, struct stack_t *stack, struct TemplatePacket *pkt_template, OUTPUT_REPORT_BANNER report_banner, struct Output *out, unsigned connection_timeout, uint64_t entropy ) { struct TCP_ConnectionTable *tcpcon; tcpcon = CALLOC(1, sizeof(*tcpcon)); tcpcon->timeout_connection = connection_timeout; if (tcpcon->timeout_connection == 0) tcpcon->timeout_connection = 30; /* half a minute before destroying tcb */ tcpcon->timeout_hello = 2; tcpcon->entropy = entropy; /* Find nearest power of 2 to the tcb count, but don't go * over the number 16-million */ { size_t new_entry_count; new_entry_count = 1; while (new_entry_count < entry_count) { new_entry_count *= 2; if (new_entry_count == 0) { new_entry_count = (1<<24); break; } } if (new_entry_count > (1<<24)) new_entry_count = (1<<24); if (new_entry_count < (1<<10)) new_entry_count = (1<<10); entry_count = new_entry_count; } /* Create the table. If we can't allocate enough memory, then shrink * the desired size of the table */ while (tcpcon->entries == 0) { tcpcon->entries = malloc(entry_count * sizeof(*tcpcon->entries)); if (tcpcon->entries == NULL) { entry_count >>= 1; } } memset(tcpcon->entries, 0, entry_count * sizeof(*tcpcon->entries)); /* fill in the table structure */ tcpcon->count = (unsigned)entry_count; tcpcon->mask = (unsigned)(entry_count-1); /* create an event/timeouts structure */ tcpcon->timeouts = timeouts_create(TICKS_FROM_SECS(time(0))); tcpcon->pkt_template = pkt_template; tcpcon->stack = stack; tcpcon->banner1 = banner1_create(); tcpcon->report_banner = report_banner; tcpcon->out = out; return tcpcon; } static int TCB_EQUALS(const struct TCP_Control_Block *lhs, const struct TCP_Control_Block *rhs) { if (lhs->port_me != rhs->port_me || lhs->port_them != rhs->port_them) return 0; if (lhs->ip_me.version != rhs->ip_me.version) return 0; if (lhs->ip_me.version == 6) { if (memcmp(&lhs->ip_me.ipv6, &rhs->ip_me.ipv6, sizeof(rhs->ip_me.ipv6)) != 0) return 0; if (memcmp(&lhs->ip_them.ipv6, &rhs->ip_them.ipv6, sizeof(rhs->ip_them.ipv6)) != 0) return 0; } else { if (lhs->ip_me.ipv4 != rhs->ip_me.ipv4) return 0; if (lhs->ip_them.ipv4 != rhs->ip_them.ipv4) return 0; } return 1; } /*************************************************************************** ***************************************************************************/ static void _tcb_change_state_to(struct TCP_Control_Block *tcb, unsigned new_state) { LOGtcb(tcb, 2, "to {%s}\n", state_to_string(new_state)); tcb->tcpstate = new_state; } /*************************************************************************** ***************************************************************************/ static unsigned tcb_hash( ipaddress ip_me, unsigned port_me, ipaddress ip_them, unsigned port_them, uint64_t entropy) { unsigned index; /* TCB hash table uses symmetric hash, so incoming/outgoing packets * get the same hash. */ if (ip_me.version == 6) { ipv6address ipv6 = ip_me.ipv6; ipv6.hi ^= ip_them.ipv6.hi; ipv6.lo ^= ip_them.ipv6.lo; index = (unsigned)syn_cookie_ipv6( ipv6, port_me ^ port_them, ipv6, port_me ^ port_them, entropy); } else { index = (unsigned)syn_cookie_ipv4( ip_me.ipv4 ^ ip_them.ipv4, port_me ^ port_them, ip_me.ipv4 ^ ip_them.ipv4, port_me ^ port_them, entropy ); } return index; } enum DestroyReason { Reason_Timeout = 1, Reason_FIN = 2, Reason_RST = 3, Reason_Foo = 4, Reason_Shutdown = 5, Reason_StateDone = 6, }; /*************************************************************************** * Flush all the banners associated with this TCP connection. This always * called when TCB is destroyed. This may also be called earlier, such * as when a FIN is received. ***************************************************************************/ void banner_flush(struct stack_handle_t *socket) { struct TCP_ConnectionTable *tcpcon = socket->tcpcon; struct TCP_Control_Block *tcb = socket->tcb; struct BannerOutput *banout; /* Go through and print all the banners. Some protocols have * multiple banners. For example, web servers have both * HTTP and HTML banners, and SSL also has several * X.509 certificate banners */ for (banout = &tcb->banout; banout != NULL; banout = banout->next) { if (banout->length && banout->protocol) { tcpcon->report_banner( tcpcon->out, global_now, tcb->ip_them, 6, /*TCP protocol*/ tcb->port_them, banout->protocol & 0x0FFFFFFF, tcb->ttl, banout->banner, banout->length); } } /* * Free up all the banners. */ banout_release(&tcb->banout); } /*************************************************************************** * Destroy a TCP connection entry. We have to unlink both from the * TCB-table as well as the timeout-table. * Called from ***************************************************************************/ static void tcpcon_destroy_tcb( struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb, enum DestroyReason reason) { unsigned index; struct TCP_Control_Block **r_entry; UNUSEDPARM(reason); /* * The TCB doesn't point to it's location in the table. Therefore, we * have to do a lookup to find the head pointer in the table. */ index = tcb_hash( tcb->ip_me, tcb->port_me, tcb->ip_them, tcb->port_them, tcpcon->entropy); /* * At this point, we have the head of a linked list of TCBs. Now, * traverse that linked list until we find our TCB */ r_entry = &tcpcon->entries[index & tcpcon->mask]; while (*r_entry && *r_entry != tcb) r_entry = &(*r_entry)->next; if (*r_entry == NULL) { LOG(1, "tcb: double free\n"); return; } /* * Print out any banners associated with this TCP session. Most of the * time, there'll only be one. After printing them out, delete the * banners. */ { struct stack_handle_t socket = {tcpcon, tcb, 0, 0}; banner_flush(&socket); } LOGtcb(tcb, 2, "--DESTROYED--\n"); /* * If there are any queued segments to transmit, then free them */ while (tcb->segments) { struct TCP_Segment *seg; seg = tcb->segments; tcb->segments = seg->next; if (seg->flags == TCP__copy || seg->flags == TCP__adopt) { free(seg->buf); seg->buf = 0; } free(seg); } if (tcb->scripting_thread) ; //scripting_thread_close(tcb->scripting_thread); tcb->scripting_thread = 0; /* KLUDGE: this needs to be made elegant */ switch (tcb->banner1_state.app_proto) { case PROTO_SMB: banner_smb1.cleanup(&tcb->banner1_state); break; } /* * Unlink this from the timeout system. */ timeout_unlink(tcb->timeout); tcb->ip_them.ipv4 = (unsigned)~0; tcb->port_them = (unsigned short)~0; tcb->ip_me.ipv4 = (unsigned)~0; tcb->port_me = (unsigned short)~0; tcb->is_active = 0; (*r_entry) = tcb->next; tcb->next = tcpcon->freed_list; tcpcon->freed_list = tcb; tcpcon->active_count--; } /*************************************************************************** * Called at shutdown to free up all the memory used by the TCP * connection table. ***************************************************************************/ void tcpcon_destroy_table(struct TCP_ConnectionTable *tcpcon) { unsigned i; if (tcpcon == NULL) return; /* * Do a graceful destruction of all the entires. If they have banners, * they will be sent to the output */ for (i=0; i<=tcpcon->mask; i++) { while (tcpcon->entries[i]) tcpcon_destroy_tcb(tcpcon, tcpcon->entries[i], Reason_Shutdown); } /* * Now free the memory */ while (tcpcon->freed_list) { struct TCP_Control_Block *tcb = tcpcon->freed_list; tcpcon->freed_list = tcb->next; free(tcb); } banner1_destroy(tcpcon->banner1); free(tcpcon->entries); free(tcpcon); } /*************************************************************************** * * Called when we receive a "SYN-ACK" packet with the correct SYN-cookie. * ***************************************************************************/ struct TCP_Control_Block * tcpcon_create_tcb( struct TCP_ConnectionTable *tcpcon, ipaddress ip_me, ipaddress ip_them, unsigned port_me, unsigned port_them, unsigned seqno_me, unsigned seqno_them, unsigned ttl, const struct ProtocolParserStream *stream, unsigned secs, unsigned usecs) { unsigned index; struct TCP_Control_Block tmp; struct TCP_Control_Block *tcb; assert(ip_me.version != 0 && ip_them.version != 0); tmp.ip_me = ip_me; tmp.ip_them = ip_them; tmp.port_me = (unsigned short)port_me; tmp.port_them = (unsigned short)port_them; /* Lookup the location in the hash table where this tcb should be * placed */ index = tcb_hash(ip_me, port_me, ip_them, port_them, tcpcon->entropy); tcb = tcpcon->entries[index & tcpcon->mask]; while (tcb && !TCB_EQUALS(tcb, &tmp)) { tcb = tcb->next; } if (tcb != NULL) { /* If it already exists, just return the existing one */ return tcb; } /* Allocate a new TCB, using a pool */ if (tcpcon->freed_list) { tcb = tcpcon->freed_list; tcpcon->freed_list = tcb->next; } else { tcb = MALLOC(sizeof(*tcb)); } memset(tcb, 0, sizeof(*tcb)); /* Add it to this spot in the hash table */ tcb->next = tcpcon->entries[index & tcpcon->mask]; tcpcon->entries[index & tcpcon->mask] = tcb; /* * Initialize the entry */ tcb->ip_me = ip_me; tcb->ip_them = ip_them; tcb->port_me = (unsigned short)port_me; tcb->port_them = (unsigned short)port_them; tcb->seqno_them_first = seqno_them; tcb->seqno_me_first = seqno_me; tcb->seqno_me = seqno_me; tcb->seqno_them = seqno_them; tcb->ackno_me = seqno_them; tcb->ackno_them = seqno_me; tcb->when_created = global_now; tcb->ttl = (unsigned char)ttl; tcb->mss = 1400; /* Insert the TCB into the timeout. A TCB must always have a timeout * active. */ timeout_init(tcb->timeout); timeouts_add(tcpcon->timeouts, tcb->timeout, offsetof(struct TCP_Control_Block, timeout), TICKS_FROM_TV(secs+1,usecs) ); /* Get the protocol handler assigned to this port */ tcb->banner1_state.port = (unsigned short)port_them; if (stream == NULL) { struct Banner1 *banner1 = tcpcon->banner1; stream = banner1->payloads.tcp[port_them]; } tcb->stream = stream; banout_init(&tcb->banout); /* The TCB is now allocated/in-use */ assert(tcb->ip_me.version != 0 && tcb->ip_them.version != 0); tcb->is_active = 1; tcpcon->active_count++; tcpcon_lookup_tcb(tcpcon, ip_me, ip_them, port_me, port_them); return tcb; } /*************************************************************************** ***************************************************************************/ struct TCP_Control_Block * tcpcon_lookup_tcb( struct TCP_ConnectionTable *tcpcon, ipaddress ip_me, ipaddress ip_them, unsigned port_me, unsigned port_them) { unsigned index; struct TCP_Control_Block tmp; struct TCP_Control_Block *tcb; ipaddress_formatted_t fmt1; ipaddress_formatted_t fmt2; tmp.ip_me = ip_me; tmp.ip_them = ip_them; tmp.port_me = (unsigned short)port_me; tmp.port_them = (unsigned short)port_them; index = tcb_hash(ip_me, port_me, ip_them, port_them, tcpcon->entropy); fmt1 = ipaddress_fmt(ip_me); fmt2 = ipaddress_fmt(ip_them); LOG(1, "tcb_hash(0x%08x) = %s %u %s %u\n", (unsigned)index, fmt1.string, port_me, fmt2.string, port_them); /* Hash to an entry in the table, then follow a linked list from * that point forward. */ tcb = tcpcon->entries[index & tcpcon->mask]; while (tcb && !TCB_EQUALS(tcb, &tmp)) { tcb = tcb->next; } return tcb; } /*************************************************************************** ***************************************************************************/ static void tcpcon_send_packet( struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb, unsigned tcp_flags, const unsigned char *payload, size_t payload_length) { struct PacketBuffer *response = 0; unsigned is_syn = (tcp_flags == 0x02); assert(tcb->ip_me.version != 0 && tcb->ip_them.version != 0); /* If sending an ACK, print a message */ if ((tcp_flags & 0x10) == 0x10) { LOGtcb(tcb, 0, "xmit ACK ackingthem=%u\n", tcb->seqno_them-tcb->seqno_them_first); } /* Get a buffer for sending the response packet. This thread doesn't * send the packet itself. Instead, it formats a packet, then hands * that packet off to a transmit thread for later transmission. */ response = stack_get_packetbuffer(tcpcon->stack); if (response == NULL) { static int is_warning_printed = 0; if (!is_warning_printed) { LOG(0, "packet buffers empty (should be impossible)\n"); is_warning_printed = 1; } fflush(stdout); /* FIXME: I'm no sure the best way to handle this. * This would result from a bug in the code, * but I'm not sure what should be done in response */ pixie_usleep(100); /* no packet available */ } if (response == NULL) return; /* Format the packet as requested. Note that there are really only * four types of packets: * 1. a SYN-ACK packet with no payload * 2. an ACK packet with no payload * 3. a RST packet with no payload * 4. a PSH-ACK packet WITH PAYLOAD */ response->length = tcp_create_packet( tcpcon->pkt_template, tcb->ip_them, tcb->port_them, tcb->ip_me, tcb->port_me, tcb->seqno_me - is_syn, tcb->seqno_them, tcp_flags, payload, payload_length, response->px, sizeof(response->px) ); /* * KLUDGE: */ if (tcb->is_small_window) tcp_set_window(response->px, response->length, 600); /* Put this buffer on the transmit queue. Remember: transmits happen * from a transmit-thread only, and this function is being called * from a receive-thread. Therefore, instead of transmiting ourselves, * we hae to queue it up for later transmission. */ stack_transmit_packetbuffer(tcpcon->stack, response); } /*************************************************************************** ***************************************************************************/ void tcp_send_RST( struct TemplatePacket *templ, struct stack_t *stack, ipaddress ip_them, ipaddress ip_me, unsigned port_them, unsigned port_me, unsigned seqno_them, unsigned seqno_me ) { struct PacketBuffer *response = 0; /* Get a buffer for sending the response packet. This thread doesn't * send the packet itself. Instead, it formats a packet, then hands * that packet off to a transmit thread for later transmission. */ response = stack_get_packetbuffer(stack); if (response == NULL) { static int is_warning_printed = 0; if (!is_warning_printed) { LOG(0, "packet buffers empty (should be impossible)\n"); is_warning_printed = 1; } fflush(stdout); pixie_usleep(100); /* no packet available */ } if (response == NULL) return; response->length = tcp_create_packet( templ, ip_them, port_them, ip_me, port_me, seqno_me, seqno_them, 0x04, /*RST*/ 0, 0, response->px, sizeof(response->px) ); /* Put this buffer on the transmit queue. Remember: transmits happen * from a transmit-thread only, and this function is being called * from a receive-thread. Therefore, instead of transmitting ourselves, * we have to queue it up for later transmission. */ stack_transmit_packetbuffer(stack, response); } /*************************************************************************** * DEBUG: when printing debug messages (-d option), this prints a string * for the given state. ***************************************************************************/ static const char * what_to_string(enum TCP_What state) { static char buf[64]; switch (state) { case TCP_WHAT_TIMEOUT: return "TIMEOUT"; case TCP_WHAT_SYNACK: return "SYNACK"; case TCP_WHAT_RST: return "RST"; case TCP_WHAT_FIN: return "FIN"; case TCP_WHAT_ACK: return "ACK"; case TCP_WHAT_DATA: return "DATA"; case TCP_WHAT_CLOSE: return "CLOSE"; default: snprintf(buf, sizeof(buf), "%d", state); return buf; } } /*************************************************************************** ***************************************************************************/ static void LOGSEND(struct TCP_Control_Block *tcb, const char *what) { if (tcb == NULL) return; LOGip(5, tcb->ip_them, tcb->port_them, "=%s : --->> %s \n", state_to_string(tcb->tcpstate), what); } void tcpcon_send_RST( struct TCP_ConnectionTable *tcpcon, ipaddress ip_me, ipaddress ip_them, unsigned port_me, unsigned port_them, uint32_t seqno_them, uint32_t ackno_them) { struct TCP_Control_Block tcb; memset(&tcb, 0, sizeof(tcb)); tcb.ip_me = ip_me; tcb.ip_them = ip_them; tcb.port_me = (unsigned short)port_me; tcb.port_them = (unsigned short)port_them; tcb.seqno_me = ackno_them; tcb.ackno_me = seqno_them + 1; tcb.seqno_them = seqno_them + 1; tcb.ackno_them = ackno_them; LOGSEND(&tcb, "send RST"); tcpcon_send_packet(tcpcon, &tcb, 0x04 /*RST*/, 0, 0); } /*************************************************************************** * Called upon timeouts when an acknowledgement hasn't been received in * time. Will resend the segments. ***************************************************************************/ static void _tcb_seg_resend(struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb) { struct TCP_Segment *seg = tcb->segments; if (seg) { if (tcb->seqno_me != seg->seqno) { ERRMSG("SEQNO FAILURE diff=%d %s\n", tcb->seqno_me - seg->seqno, seg->is_fin?"FIN":""); return; } if (seg->is_fin && seg->length == 0) { tcpcon_send_packet(tcpcon, tcb, 0x11, /*FIN-ACK*/ 0, /*FIN has no data */ 0 /*logically is 1 byte, but not payload byte */); } else { tcpcon_send_packet(tcpcon, tcb, 0x18 | (seg->is_fin?0x01:0x00), seg->buf, seg->length); } } } /*************************************************************************** ***************************************************************************/ static unsigned application_notify(struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb, enum App_Event event, const void *payload, size_t payload_length, unsigned secs, unsigned usecs) { struct Banner1 *banner1 = tcpcon->banner1; const struct ProtocolParserStream *stream = tcb->stream; struct stack_handle_t socket = { tcpcon, tcb, secs, usecs}; return application_event(&socket, tcb->app_state, event, stream, banner1, payload, payload_length ); } /*************************************************************************** ***************************************************************************/ static void _tcb_seg_send(void *in_tcpcon, void *in_tcb, const void *buf, size_t length, enum TCP__flags flags) { struct TCP_ConnectionTable *tcpcon = (struct TCP_ConnectionTable *)in_tcpcon; struct TCP_Control_Block *tcb = (struct TCP_Control_Block *)in_tcb; struct TCP_Segment *seg; struct TCP_Segment **next; unsigned seqno = tcb->seqno_me; size_t length_more = 0; bool is_fin = (flags == TCP__close_fin); if (length > tcb->mss) { length_more = length - tcb->mss; length = tcb->mss; } if (length == 0 && !is_fin) return; /* Go to the end of the segment list */ for (next = &tcb->segments; *next; next = &(*next)->next) { seqno = (unsigned)((*next)->seqno + (*next)->length); if ((*next)->is_fin) { /* can't send past a FIN */ LOGip(0, tcb->ip_them, tcb->port_them, "can't send past a FIN\n"); if (flags == TCP__adopt) { free((void*)buf); /* discard const */ buf = NULL; } return; } } /* Append this segment to the list */ seg = calloc(1, sizeof(*seg)); *next = seg; /* Fill in this segment's members */ seg->seqno = seqno; seg->length = length; seg->flags = flags; switch (flags) { case TCP__static: case TCP__adopt: seg->buf = (void *)buf; break; case TCP__copy: seg->buf = malloc(length); memcpy(seg->buf, buf, length); break; case TCP__close_fin: seg->buf = 0; break; } if (length_more == 0) seg->is_fin = is_fin; if (!seg->is_fin && seg->length && tcb->tcpstate != STATE_ESTABLISHED_SEND) application_notify(tcpcon, tcb, APP_SENDING, seg->buf, seg->length, 0, 0); LOGtcb(tcb, 0, "send = %u-bytes %s @ %u\n", length, is_fin?"FIN":"", seg->seqno-tcb->seqno_me_first); /* If this is the head of the segment list, then transmit right away */ if (tcb->segments == seg) { LOGtcb(tcb, 0, "xmit = %u-bytes %s @ %u\n", length, is_fin?"FIN":"", seg->seqno-tcb->seqno_me_first); tcpcon_send_packet(tcpcon, tcb, 0x18 | (is_fin?1:0), seg->buf, seg->length); if (!is_fin) _tcb_change_state_to(tcb, STATE_ESTABLISHED_SEND); } /* If the input buffer was too large to fit a single segment, then * split it up into multiple segments */ if (length_more) { if (flags == TCP__adopt) flags = TCP__copy; _tcb_seg_send(tcpcon, tcb, (unsigned char*)buf + length, length_more, flags); } //tcb->established = App_SendNext; } /*************************************************************************** ***************************************************************************/ static int _tcp_seg_acknowledge( struct TCP_Control_Block *tcb, uint32_t ackno) { /*LOG(4, "%s - %u-sending, %u-reciving\n", fmt.string, tcb->seqno_me - ackno, ackno - tcb->ackno_them );*/ /* Normal: just discard repeats */ if (ackno == tcb->seqno_me) { return 0; } /* Make sure this isn't a duplicate ACK from past * WRAPPING of 32-bit arithmetic happens here */ if (ackno - tcb->seqno_me > 100000) { ipaddress_formatted_t fmt = ipaddress_fmt(tcb->ip_them); LOG(4, "%s - " "tcb: ackno from past: " "old ackno = 0x%08x, this ackno = 0x%08x\n", fmt.string, tcb->ackno_me, ackno); return 0; } /* Make sure this isn't invalid ACK from the future * WRAPPING of 32-bit arithmetic happens here */ if (tcb->seqno_me - ackno < 100000) { ipaddress_formatted_t fmt = ipaddress_fmt(tcb->ip_them); LOG(0, "%s - " "tcb: ackno from future: " "my seqno = 0x%08x, their ackno = 0x%08x\n", fmt.string, tcb->seqno_me, ackno); return 0; } /* Handle FIN specially */ handle_fin: if (tcb->segments && tcb->segments->is_fin) { struct TCP_Segment *seg = tcb->segments; if (seg->seqno+1 == ackno) { LOGtcb(tcb, 1, "ACKed FIN\n"); tcb->seqno_me += 1; tcb->ackno_them += 1; return 1; } else if (seg->seqno == ackno) { return 0; } else { LOGtcb(tcb, 1, "@@@@@BAD ACK of FIN@@@@\n", seg->length); return 0; } } /* Retire outstanding segments */ { unsigned length = ackno - tcb->seqno_me; while (tcb->segments && length >= tcb->segments->length) { struct TCP_Segment *seg = tcb->segments; if (seg->is_fin) goto handle_fin; tcb->segments = seg->next; length -= seg->length; tcb->seqno_me += seg->length; tcb->ackno_them += seg->length; LOGtcb(tcb, 1, "ACKed %u-bytes\n", seg->length); /* free the old segment */ switch (seg->flags) { case TCP__static: break; case TCP__adopt: case TCP__copy: if (seg->buf) { free(seg->buf); seg->buf = NULL; } break; default: ; } free(seg); if (ackno == tcb->ackno_them) return 1; /* good ACK */ } if (tcb->segments && length < tcb->segments->length) { struct TCP_Segment *seg = tcb->segments; tcb->seqno_me += length + seg->is_fin; tcb->ackno_them += length + seg->is_fin; LOGtcb(tcb, 1, "ACKed %u-bytes %s\n", length, seg->is_fin?"FIN":""); /* This segment needs to be reduced */ if (seg->flags == TCP__adopt || seg->flags == TCP__copy) { size_t new_length = seg->length - length; unsigned char *buf = malloc(new_length); memcpy(buf, seg->buf + length, new_length); free(seg->buf); seg->buf = buf; seg->length -= length; seg->flags = TCP__copy; } else { seg->buf += length; } } } /* Mark that this was a good ack */ return 1; } void banner_set_sslhello(struct stack_handle_t *socket, bool is_true) { struct TCP_Control_Block *tcb = socket->tcb; tcb->banner1_state.is_sent_sslhello = is_true; } void banner_set_small_window(struct stack_handle_t *socket, bool is_true) { struct TCP_Control_Block *tcb = socket->tcb; tcb->is_small_window = is_true; } bool banner_is_heartbleed(const struct stack_handle_t *socket) { struct TCP_ConnectionTable *tcpcon = socket->tcpcon; return tcpcon->banner1->is_heartbleed != 0; } /*************************************************************************** * Parse the information we get from the server we are scanning. Typical * examples are SSH banners, FTP banners, or the response from HTTP * requests ***************************************************************************/ size_t banner_parse( struct stack_handle_t *socket, const unsigned char *payload, size_t payload_length ) { struct TCP_ConnectionTable *tcpcon = socket->tcpcon; struct TCP_Control_Block *tcb = socket->tcb; assert(tcb->banout.max_length); banner1_parse( tcpcon->banner1, &tcb->banner1_state, payload, payload_length, &tcb->banout, socket); return payload_length; } /*************************************************************************** ***************************************************************************/ static void _next_IP_port(struct TCP_ConnectionTable *tcpcon, ipaddress *ip_me, unsigned *port_me) { const struct stack_src_t *src = tcpcon->stack->src; unsigned index; /* Get another source port, because we can't use the existing * one for new connection */ index = *port_me - src->port.first + 1; *port_me = src->port.first + index; if (*port_me >= src->port.last) { *port_me = src->port.first; /* We've wrapped the ports, so therefore choose another source * IP address as well. */ switch (ip_me->version) { case 4: index = ip_me->ipv4 - src->ipv4.first + 1; ip_me->ipv4 = src->ipv4.first + index; if (ip_me->ipv4 >= src->ipv4.last) ip_me->ipv4 = src->ipv4.first; break; case 6: { /* TODO: this code is untested, yolo */ ipv6address_t diff; diff = ipv6address_subtract(ip_me->ipv6, src->ipv6.first); diff = ipv6address_add_uint64(diff, 1); ip_me->ipv6 = ipv6address_add(src->ipv6.first, diff); if (ipv6address_is_lessthan(src->ipv6.last, ip_me->ipv6)) ip_me->ipv6 = src->ipv6.first; break; } default: break; } } } /*************************************************************************** ***************************************************************************/ static void _do_reconnect(struct TCP_ConnectionTable *tcpcon, const struct TCP_Control_Block *old_tcb, const struct ProtocolParserStream *stream, unsigned secs, unsigned usecs, unsigned established) { struct TCP_Control_Block *new_tcb; ipaddress ip_them = old_tcb->ip_them; unsigned port_them = old_tcb->port_them; ipaddress ip_me = old_tcb->ip_me; unsigned port_me = old_tcb->port_me; unsigned seqno; /* * First, get another port number and potentially ip address */ { ipaddress prev_ip = ip_me; unsigned prev_port = port_me; _next_IP_port(tcpcon, &ip_me, &port_me); if (ipaddress_is_equal(ip_me, prev_ip) && port_me == prev_port) ERRMSG("There must be multiple source ports/addresses for reconnection\n"); } /* * Calculate the SYN cookie, the same algorithm as for when spewing * SYN packets. However, since we'll probably be using a different * port or IP address, it'll be different in practice. */ seqno = (unsigned)syn_cookie(ip_them, port_them, ip_me, port_me, tcpcon->entropy); /* * Now create a new TCB for this new connection */ new_tcb = tcpcon_create_tcb( tcpcon, ip_me, ip_them, port_me, port_them, seqno+1, 0, 255, stream, secs, usecs); new_tcb->app_state = established; } static void _tcb_seg_close(void *in_tcpcon, void *in_tcb, unsigned secs, unsigned usecs) { struct TCP_ConnectionTable *tcpcon = (struct TCP_ConnectionTable *)in_tcpcon; struct TCP_Control_Block *tcb = (struct TCP_Control_Block *)in_tcb; stack_incoming_tcp(tcpcon, tcb, TCP_WHAT_CLOSE, 0, 0, secs, usecs, tcb->seqno_them, tcb->ackno_them); } /*************************************************************************** ***************************************************************************/ int tcpapi_set_timeout(struct stack_handle_t *socket, unsigned secs, unsigned usecs ) { struct TCP_ConnectionTable *tcpcon = socket->tcpcon; struct TCP_Control_Block *tcb = socket->tcb; if (socket == NULL) return SOCKERR_EBADF; timeouts_add(tcpcon->timeouts, tcb->timeout, offsetof(struct TCP_Control_Block, timeout), TICKS_FROM_TV(socket->secs+secs, socket->usecs + usecs) ); return 0; } /*************************************************************************** ***************************************************************************/ int tcpapi_recv(struct stack_handle_t *socket) { struct TCP_Control_Block *tcb; if (socket == 0 || socket->tcb == 0) return SOCKERR_EBADF; tcb = socket->tcb; switch (tcb->tcpstate) { default: case STATE_ESTABLISHED_SEND: _tcb_change_state_to(socket->tcb, STATE_ESTABLISHED_RECV); break; case STATE_FIN_WAIT1_RECV: _tcb_change_state_to(socket->tcb, STATE_FIN_WAIT1_RECV); break; case STATE_FIN_WAIT1_SEND: _tcb_change_state_to(socket->tcb, STATE_FIN_WAIT1_RECV); break; } return 0; } int tcpapi_send(struct stack_handle_t *socket, const void *buf, size_t length, enum TCP__flags flags) { struct TCP_Control_Block *tcb; if (socket == 0 || socket->tcb == 0) return SOCKERR_EBADF; tcb = socket->tcb; switch (tcb->tcpstate) { case STATE_ESTABLISHED_RECV: _tcb_change_state_to(tcb, STATE_ESTABLISHED_SEND); /*follow through*/ case STATE_ESTABLISHED_SEND: _tcb_seg_send(socket->tcpcon, tcb, buf, length, flags); return 0; default: LOG(1, "TCP app attempted SEND in wrong state\n"); return 1; } } int tcpapi_reconnect(struct stack_handle_t *old_socket, struct ProtocolParserStream *new_stream, unsigned new_app_state) { if (old_socket == 0 || old_socket->tcb == 0) return SOCKERR_EBADF; _do_reconnect(old_socket->tcpcon, old_socket->tcb, new_stream, old_socket->secs, old_socket->usecs, new_app_state); return 0; } unsigned tcpapi_change_app_state(struct stack_handle_t *socket, unsigned new_app_state) { struct TCP_Control_Block *tcb; if (socket == 0 || socket->tcb == 0) return SOCKERR_EBADF; tcb = socket->tcb; //printf("%u --> %u\n", tcb->app_state, new_app_state); tcb->app_state = new_app_state; return new_app_state; } int tcpapi_close(struct stack_handle_t *socket) { if (socket == NULL || socket->tcb == NULL) return SOCKERR_EBADF; _tcb_seg_close(socket->tcpcon, socket->tcb, socket->secs, socket->usecs); return 0; } static bool _tcb_they_have_acked_my_fin(struct TCP_Control_Block *tcb) { if (tcb->segments && tcb->segments->is_fin && tcb->segments->length == 0) { if (tcb->ackno_them >= tcb->segments->seqno + 1) return true; return false; } else return false; } static void _tcb_send_ack(struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb) { tcpcon_send_packet(tcpcon, tcb, 0x10, 0, 0); } static int _tcb_seg_recv(struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb, const unsigned char *payload, size_t payload_length, unsigned seqno_them, unsigned secs, unsigned usecs, bool is_fin) { /* Special case when packet contains only a FIN */ if (payload_length == 0 && is_fin && (tcb->seqno_them - seqno_them) == 0) { tcb->is_their_fin = 1; tcb->seqno_them += 1; tcb->ackno_me += 1; tcpcon_send_packet(tcpcon, tcb, 0x10/*ACK*/, 0, 0); return 1; } if ((tcb->seqno_them - seqno_them) > payload_length) { LOGSEND(tcb, "peer(ACK) [acknowledge payload 1]"); tcpcon_send_packet(tcpcon, tcb, 0x10 /*ACK*/, 0, 0); return 1; } while (seqno_them != tcb->seqno_them && payload_length) { seqno_them++; payload_length--; payload++; } if (tcb->is_their_fin) { /* payload cannot be received after a FIN */ return 1; } if (payload_length == 0) { tcpcon_send_packet(tcpcon, tcb, 0x10/*ACK*/, 0, 0); return 1; } LOGtcb(tcb, 2, "received %u bytes\n", payload_length); tcb->seqno_them += payload_length + is_fin; tcb->ackno_me += payload_length + is_fin; application_notify(tcpcon, tcb, APP_RECV_PAYLOAD, payload, payload_length, secs, usecs); if (is_fin) tcb->is_their_fin = true; /* Send ack for the data */ _tcb_send_ack(tcpcon, tcb); return 0; } /***************************************************************************** * Handles incoming events, like timeouts and packets, that cause a change * in the TCP control block "state". * * This is the part of the code that implements the famous TCP state-machine * you see drawn everywhere, where they have states like "TIME_WAIT". Only * we don't really have those states. *****************************************************************************/ enum TCB_result stack_incoming_tcp(struct TCP_ConnectionTable *tcpcon, struct TCP_Control_Block *tcb, enum TCP_What what, const unsigned char *payload, size_t payload_length, unsigned secs, unsigned usecs, unsigned seqno_them, unsigned ackno_them) { /* FILTER * Reject out-of-order payloads */ if (payload_length) { /* Wrapping technique: If there is a gap between this * packet and the last one, then it means there is a missing * packet somewhere. In that case, this calculation will * wrap and `payload_offset` will be some huge number in the future. * If there is no gap, then this will be zero. * If there's overlap between this packet and the previous, `payload_offset` * will be a small number less than the `length` of this packet. * If it's a retransmission, the numbers will be the same */ int payload_offset = seqno_them - tcb->seqno_them; if (payload_offset < 0) { /* This is a retrnasmission that we've already acknowledged */ if (payload_offset <= 0 - (int)payload_length) { /* Both begin and end are old, so simply discard it */ return TCB__okay; } else { /* Otherwise shorten the payload */ payload_length += payload_offset; payload -= payload_offset; seqno_them -= payload_offset; assert(payload_length < 2000); } } else if (payload_offset > 0) { /* This is an out-of-order fragment in the future. an important design * of this light-weight stack is that we don't support this, and * force the other side to retransmit such packets */ return TCB__okay; } } /* FILTER: * Reject out-of-order FINs. * Handle duplicate FINs here */ if (what == TCP_WHAT_FIN) { if (seqno_them == tcb->seqno_them - 1) { /* Duplicate FIN, respond with ACK */ LOGtcb(tcb, 1, "duplicate FIN\n"); _tcb_send_ack(tcpcon, tcb); return TCB__okay; } else if (seqno_them != tcb->seqno_them) { /* out of order FIN, so drop it */ LOGtcb(tcb, 1, "out-of-order FIN\n"); return TCB__okay; } } LOGtcb(tcb, 1, "##%s##\n", what_to_string(what)); /* Make sure no connection lasts longer than ~30 seconds */ if (what == TCP_WHAT_TIMEOUT) { if (tcb->when_created + tcpcon->timeout_connection < secs) { LOGip(8, tcb->ip_them, tcb->port_them, "%s \n", "CONNECTION TIMEOUT---"); LOGSEND(tcb, "peer(RST)"); tcpcon_send_packet(tcpcon, tcb, 0x04 /*RST*/, 0, 0); tcpcon_destroy_tcb(tcpcon, tcb, Reason_Timeout); return TCB__destroyed; } } if (what == TCP_WHAT_RST) { LOGSEND(tcb, "tcb(destroy)"); tcpcon_destroy_tcb(tcpcon, tcb, Reason_RST); return TCB__destroyed; } /* * * * * * * */ switch (tcb->tcpstate) { /* TODO: validate any SYNACK is real before sending it here * to the state-machine, by validating that it's acking * something */ case STATE_SYN_SENT: switch (what) { case TCP_WHAT_TIMEOUT: /* We've sent a SYN, but didn't get SYN-ACK, so * send another */ tcb->syns_sent++; /* Send a SYN */ tcpcon_send_packet(tcpcon, tcb, 0x02 /*SYN*/, 0, 0); break; case TCP_WHAT_SYNACK: tcb->seqno_them = seqno_them; tcb->seqno_them_first = seqno_them - 1; tcb->seqno_me = ackno_them; tcb->seqno_me_first = ackno_them - 1; LOGtcb(tcb, 1, "%s connection established\n", what_to_string(what)); /* Send "ACK" to acknowlege their "SYN-ACK" */ _tcb_send_ack(tcpcon, tcb); _tcb_change_state_to(tcb, STATE_ESTABLISHED_RECV); application_notify(tcpcon, tcb, APP_CONNECTED, 0, 0, secs, usecs); break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_ESTABLISHED_SEND: switch (what) { case TCP_WHAT_CLOSE: _tcb_seg_send(tcpcon, tcb, 0, 0, TCP__close_fin); _tcb_change_state_to(tcb, STATE_FIN_WAIT1_SEND); break; case TCP_WHAT_FIN: if (seqno_them == tcb->seqno_them) { /* I have ACKed all their data, so therefore process this */ _tcb_seg_recv(tcpcon, tcb, 0, 0, seqno_them, secs, usecs, true); _tcb_change_state_to(tcb, STATE_FIN_WAIT1_SEND); _tcb_send_ack(tcpcon, tcb); } else { /* I haven't received all their data, so ignore it until I do */ _tcb_send_ack(tcpcon, tcb); } break; case TCP_WHAT_ACK: _tcp_seg_acknowledge(tcb, ackno_them); if (tcb->segments == NULL || tcb->segments->length == 0) { /* We've finished sending everything, so switch our application state * back to sending */ _tcb_change_state_to(tcb, STATE_ESTABLISHED_RECV); /* All the payload has been sent. Notify the application of this, so that they * can send more if the want, or switch to listening. */ application_notify(tcpcon, tcb, APP_SEND_SENT, 0, 0, secs, usecs); } break; case TCP_WHAT_TIMEOUT: /* They haven't acknowledged everything yet, so resend the last segment */ _tcb_seg_resend(tcpcon, tcb); break; case TCP_WHAT_DATA: /* We don't receive data while in the sending state. We force them * to keep re-sending it until we are prepared to receive it. This * saves us from having to buffer it in this stack. */ break; case TCP_WHAT_SYNACK: break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_ESTABLISHED_RECV: switch (what) { case TCP_WHAT_CLOSE: _tcb_seg_send(tcpcon, tcb, 0, 0, TCP__close_fin); _tcb_change_state_to(tcb, STATE_FIN_WAIT1_RECV); break; case TCP_WHAT_FIN: if (seqno_them == tcb->seqno_them) { /* I have ACKed all their data, so therefore process this */ _tcb_seg_recv(tcpcon, tcb, 0, 0, seqno_them, secs, usecs, true); _tcb_change_state_to(tcb, STATE_CLOSE_WAIT); //_tcb_send_ack(tcpcon, tcb); application_notify(tcpcon, tcb, APP_CLOSE, 0, payload_length, secs, usecs); } else { /* I haven't received all their data, so ignore it until I do */ _tcb_send_ack(tcpcon, tcb); } break; case TCP_WHAT_ACK: _tcp_seg_acknowledge(tcb, ackno_them); break; case TCP_WHAT_TIMEOUT: application_notify(tcpcon, tcb, APP_RECV_TIMEOUT, 0, 0, secs, usecs); break; case TCP_WHAT_DATA: _tcb_seg_recv(tcpcon, tcb, payload, payload_length, seqno_them, secs, usecs, false); break; case TCP_WHAT_SYNACK: /* This happens when a delayed SYN-ACK arrives from the target. * I see these fairly often from host 178.159.37.125. * We are going to make them silent for now, but eventually, keep * statistics about this sort of thing. */ break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; /* SYN-RCVD + FIN = FIN-WAIT-1 ESTAB + FIN = FIN-WAIT-1 +---------+ | FIN | | WAIT-1 | +---------+ FIN-WAIT-1 + FIN --> CLOSING FIN-WAIT-1 + ACK-of-FIN --> FIN-WAIT-2 */ case STATE_FIN_WAIT1_SEND: switch (what) { case TCP_WHAT_FIN: /* Ignore their FIN while in the SENDing state. */ break; case TCP_WHAT_ACK: /* Apply the ack */ if (_tcp_seg_acknowledge(tcb, ackno_them)) { /* Same a in ESTABLISHED_SEND, once they've acknowledged * all reception BEFORE THE FIN, then change the state */ if (tcb->segments == NULL || tcb->segments->length == 0) { /* All the payload has been sent. Notify the application of this, so that they * can send more if the want, or switch to listening. */ _tcb_change_state_to(tcb, STATE_FIN_WAIT1_RECV); application_notify(tcpcon, tcb, APP_SEND_SENT, 0, 0, secs, usecs); } } break; case TCP_WHAT_TIMEOUT: _tcb_seg_resend(tcpcon, tcb); /* also resends FINs */ break; case TCP_WHAT_DATA: /* Ignore any data received while in the SEND state */ break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_FIN_WAIT1_RECV: switch (what) { case TCP_WHAT_FIN: _tcb_seg_recv(tcpcon, tcb, 0, 0, seqno_them, secs, usecs, true); _tcb_change_state_to(tcb, STATE_CLOSING); _tcb_send_ack(tcpcon, tcb); application_notify(tcpcon, tcb, APP_CLOSE, 0, 0, secs, usecs); break; case TCP_WHAT_ACK: /* Apply the ack */ if (_tcp_seg_acknowledge(tcb, ackno_them)) { if (_tcb_they_have_acked_my_fin(tcb)) { _tcb_change_state_to(tcb, STATE_FIN_WAIT2); application_notify(tcpcon, tcb, APP_CLOSE, 0, 0, secs, usecs); } } break; case TCP_WHAT_TIMEOUT: _tcb_seg_resend(tcpcon, tcb); /* also recv FIN */ break; case TCP_WHAT_DATA: _tcb_seg_recv(tcpcon, tcb, payload, payload_length, seqno_them, secs, usecs, false); break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_CLOSING: switch (what) { case TCP_WHAT_TIMEOUT: tcpcon_destroy_tcb(tcpcon, tcb, Reason_Timeout); return TCB__destroyed; case TCP_WHAT_ACK: _tcp_seg_acknowledge(tcb, ackno_them); if (_tcb_they_have_acked_my_fin(tcb)) { tcpcon_destroy_tcb(tcpcon, tcb, Reason_FIN); return TCB__destroyed; } break; case TCP_WHAT_FIN: /* I've already acknowledged their FIN, but hey, do it again */ _tcb_send_ack(tcpcon, tcb); break; case TCP_WHAT_CLOSE: /* The application this machine has issued a second `tcpapi_close()` request. * This represents a bug in the application process. One place where I see this * when scanning 193.109.9.122:992. * FIXME TODO */ ; /* make this silent for now */ break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_FIN_WAIT2: case STATE_TIME_WAIT: switch (what) { case TCP_WHAT_TIMEOUT: /* giving up */ if (tcb->tcpstate == STATE_TIME_WAIT) { tcpcon_destroy_tcb(tcpcon, tcb, Reason_Timeout); return TCB__destroyed; } break; case TCP_WHAT_ACK: break; case TCP_WHAT_FIN: /* Processing incoming FIN as an empty paylaod */ _tcb_seg_recv(tcpcon, tcb, 0, 0, seqno_them, secs, usecs, true); _tcb_change_state_to(tcb, STATE_TIME_WAIT); timeouts_add( tcpcon->timeouts, tcb->timeout, offsetof(struct TCP_Control_Block, timeout), TICKS_FROM_TV(secs+5,usecs) ); break; case TCP_WHAT_SYNACK: case TCP_WHAT_RST: case TCP_WHAT_DATA: break; case TCP_WHAT_CLOSE: /* FIXME: to reach this state, we've already done a close. * FIXME: but this happens twice, because only have * FIXME: a single close function. */ break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_CLOSE_WAIT: /* Waiting for app to call `close()` */ switch (what) { case TCP_WHAT_CLOSE: _tcb_seg_send(tcpcon, tcb, 0, 0, TCP__close_fin); _tcb_change_state_to(tcb, STATE_LAST_ACK); break; case TCP_WHAT_TIMEOUT: /* Remind the app that it's waiting for it to be closed */ application_notify(tcpcon, tcb, APP_CLOSE, 0, payload_length, secs, usecs); break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; case STATE_LAST_ACK: switch (what) { case TCP_WHAT_TIMEOUT: /* They haven't acknowledged everything yet, so resend the last segment */ _tcb_seg_resend(tcpcon, tcb); break; case TCP_WHAT_ACK: if (_tcp_seg_acknowledge(tcb, ackno_them)) { tcpcon_destroy_tcb(tcpcon, tcb, Reason_Shutdown); return TCB__destroyed; } break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } break; break; default: ERRMSGip(tcb->ip_them, tcb->port_them, "%s:%s **** UNHANDLED EVENT ****\n", state_to_string(tcb->tcpstate), what_to_string(what)); break; } return TCB__okay; }