X-Git-Url: http://git.meshlink.io/?a=blobdiff_plain;f=utcp.c;h=d9ba952e2290466331d2346b556560577ac5f18d;hb=93d2e72d3e72e4d91575fc00545dbba96dcdea69;hp=5db93eac623eb592d40213db18196ce98ffd716c;hpb=95d7d348316f1ed8d2ff3e37cb7f962abf4a7f84;p=utcp diff --git a/utcp.c b/utcp.c index 5db93ea..d9ba952 100644 --- a/utcp.c +++ b/utcp.c @@ -109,6 +109,9 @@ struct utcp_connection { struct timeval conn_timeout; struct timeval rtrx_timeout; + + char *sndbuf; + uint32_t sndbufsize; }; struct utcp { @@ -131,7 +134,7 @@ static void set_state(struct utcp_connection *c, enum state state) { fprintf(stderr, "%p new state: %s\n", c->utcp, strstate[state]); } -static void print_packet(void *pkt, size_t len) { +static void print_packet(const void *pkt, size_t len) { struct hdr hdr; if(len < sizeof hdr) { fprintf(stderr, "short packet (%zu bytes)\n", len); @@ -152,7 +155,7 @@ static void print_packet(void *pkt, size_t len) { if(len > sizeof hdr) { fprintf(stderr, " data="); for(int i = sizeof hdr; i < len; i++) { - char *data = pkt; + const char *data = pkt; fprintf(stderr, "%c", data[i] >= 32 ? data[i] : '.'); } } @@ -160,96 +163,131 @@ static void print_packet(void *pkt, size_t len) { fprintf(stderr, "\n"); } -static struct utcp_connection *allocate_connection(struct utcp *utcp) { - struct utcp_connection *c; +static void list_connections(struct utcp *utcp) { + fprintf(stderr, "%p has %d connections:\n", utcp, utcp->nconnections); + for(int i = 0; i < utcp->nconnections; i++) + fprintf(stderr, " %u -> %u state %s\n", utcp->connections[i]->src, utcp->connections[i]->dst, strstate[utcp->connections[i]->state]); +} - // Initial allocation? +// Connections are stored in a sorted list. +// This gives O(log(N)) lookup time, O(N log(N)) insertion time and O(N) deletion time. - if(!utcp->nconnections) { - utcp->nallocated = 4; - utcp->nconnections = 1; // Skip 0 - utcp->connections = calloc(utcp->nallocated, sizeof *utcp->connections); - } +static int compare(const void *va, const void *vb) { + const struct utcp_connection *a = *(struct utcp_connection **)va; + const struct utcp_connection *b = *(struct utcp_connection **)vb; + if(!a->src || !b->src) + abort(); + int c = (int)a->src - (int)b->src; + if(c) + return c; + c = (int)a->dst - (int)b->dst; + return c; +} - // If there is a hole in the list of connections, use it. - // Otherwise, add a new connection to the end. +static struct utcp_connection *find_connection(const struct utcp *utcp, uint16_t src, uint16_t dst) { + if(!utcp->nconnections) + return NULL; + struct utcp_connection key = { + .src = src, + .dst = dst, + }, *keyp = &key; + struct utcp_connection **match = bsearch(&keyp, utcp->connections, utcp->nconnections, sizeof *utcp->connections, compare); + return match ? *match : NULL; +} + +static void free_connection(struct utcp_connection *c) { + struct utcp *utcp = c->utcp; + struct utcp_connection **cp = bsearch(&c, utcp->connections, utcp->nconnections, sizeof *utcp->connections, compare); + if(!cp) + abort(); - if(utcp->gap >= 0) { - c = utcp->connections[utcp->gap] = calloc(1, sizeof *c); - c->src = utcp->gap; - while(++utcp->gap < utcp->nconnections) - if(!utcp->connections[utcp->gap]) - break; + int i = cp - utcp->connections; + memmove(cp + i, cp + i + 1, (utcp->nconnections - i - 1) * sizeof *cp); + utcp->nconnections--; - if(utcp->gap >= utcp->nconnections) - utcp->gap = -1; - } else { - // Too many connections? + free(c); +} - if(utcp->nconnections >= 65536) { +static struct utcp_connection *allocate_connection(struct utcp *utcp, uint16_t src, uint16_t dst) { + // Check whether this combination of src and dst is free + + if(src) { + if(find_connection(utcp, src, dst)) { + errno = EADDRINUSE; + return NULL; + } + } else { // If src == 0, generate a random port number with the high bit set + if(utcp->nconnections >= 32767) { errno = ENOMEM; return NULL; } + src = rand() | 0x8000; + while(find_connection(utcp, src, dst)) + src++; + } - // Need to reserve more memory? + // Allocate memory for the new connection - if(utcp->nconnections >= utcp->nallocated) { + if(utcp->nconnections >= utcp->nallocated) { + if(!utcp->nallocated) + utcp->nallocated = 4; + else utcp->nallocated *= 2; - utcp->connections = realloc(utcp->connections, utcp->nallocated * sizeof *utcp->connections); + struct utcp_connection **new_array = realloc(utcp->connections, utcp->nallocated * sizeof *utcp->connections); + if(!new_array) { + errno = ENOMEM; + return NULL; } + utcp->connections = new_array; + } - c = utcp->connections[utcp->nconnections] = calloc(1, sizeof *c); - c->src = utcp->nconnections++; + struct utcp_connection *c = calloc(1, sizeof *c); + if(!c) { + errno = ENOMEM; + return NULL; } + // Fill in the details + + c->src = src; + c->dst = dst; c->snd.iss = rand(); c->snd.una = c->snd.iss; c->snd.nxt = c->snd.iss + 1; c->rcv.wnd = utcp->mtu; c->utcp = utcp; - return c; -} + c->sndbufsize = 65536; + c->sndbuf = malloc(c->sndbufsize); + if(!c->sndbuf) + c->sndbufsize = 0; -static struct utcp_connection *find_connection(struct utcp *utcp, uint16_t src) { - if(src < utcp->nconnections && utcp->connections[src]) - return utcp->connections[src]; + // Add it to the sorted list of connections - errno = EINVAL; - return NULL; -} + utcp->connections[utcp->nconnections++] = c; + qsort(utcp->connections, utcp->nconnections, sizeof *utcp->connections, compare); -static void free_connection(struct utcp_connection *c) { - if(!c) - return; - if(c->utcp->gap < 0 || c->src < c->utcp->gap) - c->utcp->gap = c->src; - c->utcp->connections[c->src] = NULL; - free(c); + return c; } -struct utcp_connection *utcp_connect(struct utcp *utcp, void *data, size_t len, utcp_recv_t recv, void *priv) { - struct utcp_connection *c = allocate_connection(utcp); +struct utcp_connection *utcp_connect(struct utcp *utcp, uint16_t dst, utcp_recv_t recv, void *priv) { + struct utcp_connection *c = allocate_connection(utcp, 0, dst); if(!c) return NULL; c->recv = recv; - struct { - struct hdr hdr; - char data[len]; - } pkt; + struct hdr hdr; - pkt.hdr.src = c->src; - pkt.hdr.dst = 0; - pkt.hdr.seq = c->snd.iss; - pkt.hdr.ack = 0; - pkt.hdr.ctl = SYN; - pkt.hdr.wnd = c->rcv.wnd; - memcpy(pkt.data, data, len); + hdr.src = c->src; + hdr.dst = c->dst; + hdr.seq = c->snd.iss; + hdr.ack = 0; + hdr.ctl = SYN; + hdr.wnd = c->rcv.wnd; set_state(c, SYN_SENT); - utcp->send(utcp, &pkt, sizeof pkt.hdr + len); + utcp->send(utcp, &hdr, sizeof hdr); // Set timeout? @@ -268,7 +306,7 @@ void utcp_accept(struct utcp_connection *c, utcp_recv_t recv, void *priv) { set_state(c, ESTABLISHED); } -int utcp_send(struct utcp_connection *c, void *data, size_t len) { +ssize_t utcp_send(struct utcp_connection *c, const void *data, size_t len) { if(c->reapable) { fprintf(stderr, "Error: send() called on closed connection %p\n", c); errno = EBADF; @@ -295,30 +333,54 @@ int utcp_send(struct utcp_connection *c, void *data, size_t len) { errno = EPIPE; return -1; } - + + // Add data to send buffer + + if(!len) + return 0; + + if(!data) { + errno = EFAULT; + return -1; + } + + uint32_t bufused = c->snd.nxt - c->snd.una; + + if(len > c->sndbufsize - bufused) + len = c->sndbufsize - bufused; + + memcpy(c->sndbuf + (c->snd.nxt - c->snd.una), data, len); + + // Send segments + struct { struct hdr hdr; - char data[len]; + char data[c->utcp->mtu]; } pkt; pkt.hdr.src = c->src; pkt.hdr.dst = c->dst; - pkt.hdr.seq = c->snd.nxt; pkt.hdr.ack = c->rcv.nxt; pkt.hdr.wnd = c->snd.wnd; pkt.hdr.ctl = ACK; - memcpy(pkt.data, data, len); + uint32_t left = len; - c->snd.nxt += len; + while(left) { + uint32_t seglen = left > c->utcp->mtu ? c->utcp->mtu : left; + pkt.hdr.seq = c->snd.nxt; - c->utcp->send(c->utcp, &pkt, sizeof pkt.hdr + len); - // - // Can we add it to the send window? - - // Do we need to kick some timers? - - return 0; + memcpy(pkt.data, data, seglen); + + c->snd.nxt += seglen; + data += seglen; + left -= seglen; + + c->utcp->send(c->utcp, &pkt, sizeof pkt.hdr + seglen); + } + + fprintf(stderr, "len=%u\n", len); + return len; } static void swap_ports(struct hdr *hdr) { @@ -327,7 +389,24 @@ static void swap_ports(struct hdr *hdr) { hdr->dst = tmp; } -int utcp_recv(struct utcp *utcp, void *data, size_t len) { +static int16_t seqdiff(uint16_t a, uint16_t b) { + return a -b; +} + +int utcp_recv(struct utcp *utcp, const void *data, size_t len) { + if(!utcp) { + errno = EFAULT; + return -1; + } + + if(!len) + return 0; + + if(!data) { + errno = EFAULT; + return -1; + } + fprintf(stderr, "%p got: ", utcp); print_packet(data, len); @@ -346,7 +425,9 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { return -1; } - struct utcp_connection *c = find_connection(utcp, hdr.dst); + //list_connections(utcp); + + struct utcp_connection *c = find_connection(utcp, hdr.dst, hdr.src); // Is it for a new connection? @@ -354,17 +435,14 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { if(hdr.ctl & RST) return 0; - if(hdr.ctl & SYN && !(hdr.ctl & ACK) && utcp->accept && (!utcp->pre_accept || utcp->pre_accept(utcp, data, len)) && (c = allocate_connection(utcp))) { // LISTEN + if(hdr.ctl & SYN && !(hdr.ctl & ACK) && utcp->accept && (!utcp->pre_accept || utcp->pre_accept(utcp, hdr.dst)) && (c = allocate_connection(utcp, hdr.dst, hdr.src))) { // LISTEN // Return SYN+ACK c->snd.wnd = hdr.wnd; c->rcv.irs = hdr.seq; - c->snd.iss = rand(); - c->snd.una = c->snd.iss; - c->snd.nxt = c->snd.iss + 1; c->rcv.nxt = c->rcv.irs + 1; set_state(c, SYN_RECEIVED); - hdr.dst = c->dst = hdr.src; + hdr.dst = c->dst; hdr.src = c->src; hdr.ack = c->rcv.irs + 1; hdr.seq = c->snd.iss; @@ -386,10 +464,10 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { } // It is for an existing connection. - + if(c->state == SYN_SENT) { if(hdr.ctl & ACK) { - if(hdr.ack <= c->snd.iss || hdr.ack > c->snd.nxt) { + if(seqdiff(hdr.ack, c->snd.iss) <= 0 || seqdiff(hdr.ack, c->snd.nxt) > 0) { fprintf(stderr, "Invalid ACK, %u %u %u\n", hdr.ack, c->snd.iss, c->snd.nxt); goto reset; } @@ -410,7 +488,7 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { if(hdr.ctl & ACK) c->snd.una = hdr.ack; - if(c->snd.una > c->snd.iss) { + if(seqdiff(c->snd.una, c->snd.iss) > 0) { set_state(c, ESTABLISHED); // TODO: signal app? swap_ports(&hdr); @@ -455,7 +533,7 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { c->snd.wnd = hdr.wnd; // TODO: check whether segment really starts at rcv.nxt, otherwise trim it. - + if(hdr.ctl & RST) { switch(c->state) { case SYN_RECEIVED: @@ -508,21 +586,21 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { switch(c->state) { case SYN_RECEIVED: - if(hdr.ack >= c->snd.una && hdr.ack <= c->snd.nxt) - c->utcp->accept(c, NULL, 0); + if(seqdiff(hdr.ack, c->snd.una) >= 0 && seqdiff(hdr.ack, c->snd.nxt) <= 0) + c->utcp->accept(c, hdr.dst); if(c->state != ESTABLISHED) goto reset; break; case ESTABLISHED: case CLOSE_WAIT: - if(hdr.ack < c->snd.una) + if(seqdiff(hdr.ack, c->snd.una) < 0) return 0; - if(hdr.ack > c->snd.nxt) + if(seqdiff(hdr.ack, c->snd.nxt) > 0) goto ack_and_drop; - if(hdr.ack > c->snd.una && hdr.ack <= c->snd.nxt) { + if(seqdiff(hdr.ack, c->snd.una) > 0 && seqdiff(hdr.ack, c->snd.nxt) <= 0) { c->snd.una = hdr.ack; - if(c->snd.wl1 < hdr.seq || (c->snd.wl1 == hdr.seq && c->snd.wl2 <= hdr.ack)) { + if(seqdiff(c->snd.wl1, hdr.seq) < 0 || (c->snd.wl1 == hdr.seq && seqdiff(c->snd.wl2, hdr.ack) <= 0)) { c->snd.wnd = hdr.wnd; c->snd.wl1 = hdr.seq; c->snd.wl2 = hdr.ack; @@ -600,7 +678,7 @@ int utcp_recv(struct utcp *utcp, void *data, size_t len) { } // Process the data - + if(len && c->recv) { c->recv(c, data, len); c->rcv.nxt += len; @@ -636,21 +714,27 @@ ack_and_drop: return 0; } -void utcp_shutdown(struct utcp_connection *c, int dir) { +int utcp_shutdown(struct utcp_connection *c, int dir) { + if(!c) { + errno = EFAULT; + return -1; + } + if(c->reapable) { fprintf(stderr, "Error: shutdown() called on closed connection %p\n", c); - return; + errno = EBADF; + return -1; } // TODO: handle dir switch(c->state) { case CLOSED: - return; + return 0; case LISTEN: case SYN_SENT: set_state(c, CLOSED); - return; + return 0; case SYN_RECEIVED: case ESTABLISHED: @@ -658,7 +742,7 @@ void utcp_shutdown(struct utcp_connection *c, int dir) { break; case FIN_WAIT_1: case FIN_WAIT_2: - return; + return 0; case CLOSE_WAIT: set_state(c, LAST_ACK); break; @@ -666,7 +750,7 @@ void utcp_shutdown(struct utcp_connection *c, int dir) { case CLOSING: case LAST_ACK: case TIME_WAIT: - return; + return 0; } // Send FIN @@ -683,31 +767,40 @@ void utcp_shutdown(struct utcp_connection *c, int dir) { c->snd.nxt += 1; c->utcp->send(c->utcp, &hdr, sizeof hdr); + return 0; } -void utcp_close(struct utcp_connection *c) { - utcp_shutdown(c, SHUT_RDWR); +int utcp_close(struct utcp_connection *c) { + if(utcp_shutdown(c, SHUT_RDWR)) + return -1; c->reapable = true; + return 0; } -void utcp_abort(struct utcp_connection *c) { +int utcp_abort(struct utcp_connection *c) { + if(!c) { + errno = EFAULT; + return -1; + } + if(c->reapable) { fprintf(stderr, "Error: abort() called on closed connection %p\n", c); - return; + errno = EBADF; + return -1; } c->reapable = true; switch(c->state) { case CLOSED: - return; + return 0; case LISTEN: case SYN_SENT: case CLOSING: case LAST_ACK: case TIME_WAIT: set_state(c, CLOSED); - return; + return 0; case SYN_RECEIVED: case ESTABLISHED: @@ -730,6 +823,7 @@ void utcp_abort(struct utcp_connection *c) { hdr.ctl = RST; c->utcp->send(c->utcp, &hdr, sizeof hdr); + return 0; } void utcp_timeout(struct utcp *utcp) {