masscan-mark-ii/src/stack-tcp-core.c

2315 lines
78 KiB
C

/*
* 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 <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stddef.h>
#include <stdarg.h>
#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; i<length; i++) {
result = result * 10 + (str[i] - '0');
}
return result;
}
/***************************************************************************
* Called at startup, when processing command-line options, to set
* an HTTP field.
***************************************************************************/
void
tcpcon_set_http_header(struct TCP_ConnectionTable *tcpcon,
const char *name,
size_t value_length,
const void *value,
enum http_field_t what)
{
UNUSEDPARM(tcpcon);
banner_http.hello_length = http_change_field(
(unsigned char**)&banner_http.hello,
banner_http.hello_length,
name,
(const unsigned char *)value,
value_length,
what);
}
/***************************************************************************
* Called at startup, when processing command-line options, to set
* parameters specific to TCP processing.
***************************************************************************/
void
tcpcon_set_parameter(struct TCP_ConnectionTable *tcpcon,
const char *name,
size_t value_length,
const void *value)
{
struct Banner1 *banner1 = tcpcon->banner1;
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;
}