tcp: implement shutdown, getopt and setopt

This commit is contained in:
Matheus
2026-04-01 17:30:26 -03:00
parent ea8da40128
commit 66a6d5ffcc
2 changed files with 131 additions and 29 deletions
+1
View File
@@ -123,6 +123,7 @@ typedef struct {
#define SO_DOMAIN 39
#define SOL_SOCKET 1
#define SOL_TCP 6
typedef unsigned socklen_t;
+130 -29
View File
@@ -125,6 +125,7 @@ typedef struct tcb_t {
size_t backlogfree;
ringbuffer_t backlog;
bool reset;
int shutdown;
itimer_t itimer;
int currentrto;
@@ -555,7 +556,10 @@ static bool tcp_queuereceivepacket(tcb_t *tcb, tcpheader_t *tcpheader, size_t pa
if (tcpheader->seq == tcb->rcvnext) {
// it is, flush into the user readable buffer
size_t written = 0;
__assert(ringbuffer_write(&tcb->receivebuffer, (void *)((uintptr_t)tcpheader + headerlen), datalen) == datalen);
if ((tcb->shutdown & SOCKET_SHUTDOWN_READ) == 0) {
__assert(ringbuffer_write(&tcb->receivebuffer, (void *)((uintptr_t)tcpheader + headerlen), datalen) == datalen);
}
tcb->rcvnext += datalen;
written += datalen;
@@ -566,7 +570,9 @@ static bool tcp_queuereceivepacket(tcb_t *tcb, tcpheader_t *tcpheader, size_t pa
if (iterator->tcpheader.seq == tcb->rcvnext) {
size_t iteratorheaderlen = (iterator->tcpheader.dataoffset >> 4) * 4;
// we can flush this segment
__assert(ringbuffer_write(&tcb->receivebuffer, (void *)((uintptr_t)&iterator->tcpheader + iteratorheaderlen), iterator->datalen) == iterator->datalen);
if ((tcb->shutdown & SOCKET_SHUTDOWN_READ) == 0) {
__assert(ringbuffer_write(&tcb->receivebuffer, (void *)((uintptr_t)&iterator->tcpheader + iteratorheaderlen), iterator->datalen) == iterator->datalen);
}
tcb->rcvnext += iterator->datalen;
written += iterator->datalen;
@@ -720,7 +726,7 @@ static void tcp_handledatareceive(tcb_t *tcb, tcpheader_t *tcpheader, ipv4pseudo
tcb->state = finstate;
tcb->rcvnext += 1;
shouldack = true;
poll_event(&tcb->pollheader, POLLHUP);
poll_event(&tcb->pollheader, POLLIN);
}
if (shouldack)
@@ -1145,8 +1151,6 @@ __attribute__((noreturn)) static void tcp_worker() {
tcp_handledatareceive(tcb, tcpheader, &ipv4, tcb->state == TCB_STATE_FINWAIT2 ? TCB_STATE_TIMEWAIT : TCB_STATE_CLOSING);
if (tcb->state == TCB_STATE_TIMEWAIT) {
itimer_pause(&tcb->itimer, NULL, NULL);
itimer_set(&tcb->itimer, MSL_SEC * 1000000, 0);
itimer_resume(&tcb->itimer);
}
break;
}
@@ -1160,8 +1164,6 @@ __attribute__((noreturn)) static void tcp_worker() {
tcp_handledatareceive(tcb, tcpheader, &ipv4, TCB_STATE_TIMEWAIT);
if (tcb->state == TCB_STATE_TIMEWAIT) {
itimer_pause(&tcb->itimer, NULL, NULL);
itimer_set(&tcb->itimer, MSL_SEC * 1000000, 0);
itimer_resume(&tcb->itimer);
}
break;
}
@@ -1216,23 +1218,24 @@ static int internalpoll(tcb_t *tcb, polldata_t* data, int events) {
break;
// both ends are open
case TCB_STATE_ESTABILISHED:
if (RINGBUFFER_DATACOUNT(&tcb->transmitbuffer) != TCB_RINGBUFFER_SIZE)
revents |= POLLOUT & events;
// here only our end is closed
case TCB_STATE_FINWAIT1:
case TCB_STATE_FINWAIT2:
if (RINGBUFFER_DATACOUNT(&tcb->receivebuffer))
if (RINGBUFFER_DATACOUNT(&tcb->receivebuffer) || (tcb->shutdown & SOCKET_SHUTDOWN_READ))
revents |= POLLIN & events;
if (RINGBUFFER_DATACOUNT(&tcb->transmitbuffer) != TCB_RINGBUFFER_SIZE)
revents |= POLLOUT & events;
// TODO POLLPRI
break;
// here the other end is closed
case TCB_STATE_CLOSEWAIT:
revents |= POLLOUT & events;
case TCB_STATE_CLOSING:
case TCB_STATE_LASTACK:
case TCB_STATE_TIMEWAIT:
case TCB_STATE_CLOSED:
// waiting for local side to close socket
if (RINGBUFFER_DATACOUNT(&tcb->receivebuffer))
if (RINGBUFFER_DATACOUNT(&tcb->receivebuffer) || (tcb->shutdown & SOCKET_SHUTDOWN_READ))
revents |= POLLIN & events;
revents |= POLLHUP;
break;
@@ -1274,7 +1277,7 @@ static int tcp_send(socket_t *socket, sockdesc_t *sockdesc) {
goto leave;
int revents = internalpoll(tcb, &polldesc.data[0], POLLOUT);
if (revents & POLLHUP) {
if ((revents & POLLHUP) || (tcb->shutdown & SOCKET_SHUTDOWN_WRITE)) {
poll_leave(&polldesc);
poll_destroydesc(&polldesc);
@@ -1439,9 +1442,6 @@ static int tcp_poll(socket_t *socket, polldata_t *data, int events) {
static int tcp_bind(socket_t *socket, sockaddr_t *addr, cred_t *cred) {
tcpsocket_t *tcpsocket = (tcpsocket_t *)socket;
if (addr->ipv4addr.addr != 0) {
printf("tcp: bind tried to bind to non zero address %x\n", addr->ipv4addr.addr);
}
if (addr->ipv4addr.port != 0 && addr->ipv4addr.port < 1024) {
// this port is reserved for privileged users
@@ -1453,7 +1453,8 @@ static int tcp_bind(socket_t *socket, sockaddr_t *addr, cred_t *cred) {
MUTEX_ACQUIRE(&socket->mutex);
tcb_t *tcb = tcpsocket->tcb;
if (tcb == NULL) {
tcb = allocatetcb(1); // smallest possible buffers TODO not even allocate those
int mtu = addr->ipv4addr.addr ? ipv4_getmtu(addr->ipv4addr.addr) : 1500;
tcb = allocatetcb(mtu);
if (tcb == NULL) {
MUTEX_RELEASE(&socket->mutex);
return ENOMEM;
@@ -1767,7 +1768,13 @@ static void tcp_destroy(socket_t *socket) {
if (tcb) {
MUTEX_ACQUIRE(&tcb->mutex);
if (tcb->state != TCB_STATE_CLOSED && tcb->state != TCB_STATE_ABORT)
if ( tcb->state != TCB_STATE_CLOSED &&
tcb->state != TCB_STATE_ABORT &&
tcb->state != TCB_STATE_FINWAIT1 &&
tcb->state != TCB_STATE_FINWAIT2 &&
tcb->state != TCB_STATE_LASTACK &&
tcb->state != TCB_STATE_TIMEWAIT &&
tcb->state != TCB_STATE_CLOSED)
tcp_handleclose(tcb);
MUTEX_RELEASE(&tcb->mutex);
@@ -1820,27 +1827,120 @@ static int tcp_getpeername(socket_t *socket, sockaddr_t *addr) {
}
// called with socket locked
static int tcp_setopt(socket_t *socket, int optname, void *buffer, size_t len, cred_t *cred) {
static int tcp_setopt(socket_t *socket, int layer, int optname, void *buffer, socklen_t len, cred_t *cred) {
tcpsocket_t *tcpsocket = (tcpsocket_t *)socket;
tcb_t *tcb = tcpsocket->tcb;
if (tcb) {
MUTEX_ACQUIRE(&tcb->mutex);
int error = 0;
if (layer == SOL_SOCKET) {
switch (optname) {
case SO_SNDLOWAT:
case SO_OOBINLINE:
case SO_KEEPALIVE:
// TODO this one
case SO_SNDBUF:
case SO_RCVBUF:
case SO_REUSEADDR:
break;
default:
error = ENOPROTOOPT;
}
} else if (layer == SOL_TCP) {
switch (optname) {
case TCP_NODELAY:
case TCP_KEEPINTVL:
case TCP_KEEPIDLE:
break;
default:
error = ENOPROTOOPT;
}
MUTEX_RELEASE(&tcb->mutex);
}
return 0;
if (error == ENOPROTOOPT) {
printf("tcp_setopt: unknown %lu %lu called\n", layer, optname);
}
return error;
}
static int tcp_getopt(socket_t *socket, int layer, int optname, void *unsafe_buffer, socklen_t *unsafe_len, cred_t *cred) {
socklen_t len;
int error = USERCOPY_POSSIBLY_FROM_USER(&len, unsafe_len, sizeof(len));
if (error)
return error;
if (layer == SOL_SOCKET) {
switch (optname) {
case SO_SNDLOWAT: {
int value = 1;
return USERCOPY_POSSIBLY_TO_USER(unsafe_buffer, &value, len);
}
case SO_TYPE: {
if (len != 4)
return EINVAL;
int type = SOCK_STREAM;
return USERCOPY_POSSIBLY_TO_USER(unsafe_buffer, &type, len);
}
case SO_OOBINLINE:
case SO_KEEPALIVE:
case SO_REUSEADDR:
return USERCOPY_POSSIBLY_MEMSET_TO_USER(unsafe_buffer, 0, len);
case SO_SNDBUF:
case SO_RCVBUF: {
if (len != 4)
return EINVAL;
int value = TCB_RINGBUFFER_SIZE;
return USERCOPY_POSSIBLY_TO_USER(unsafe_buffer, &value, 4);
}
}
} else if (layer == SOL_TCP) {
switch (optname) {
case TCP_NODELAY:
case TCP_KEEPIDLE:
case TCP_KEEPINTVL:
return USERCOPY_POSSIBLY_MEMSET_TO_USER(unsafe_buffer, 0, len);
}
}
printf("tcp_getopt unknown %lu %lu\n", layer, optname);
return ENOPROTOOPT;
}
static int tcp_shutdown(socket_t *socket, int how) {
printf("tcp: shutdown is not implemented\n");
return ENOSYS;
tcpsocket_t *tcpsocket = (tcpsocket_t *)socket;
MUTEX_ACQUIRE(&socket->mutex);
int new_how = socket->shutdown ^ how;
socket->shutdown |= new_how;
int error = 0;
tcb_t *tcb = tcpsocket->tcb;
if (tcb) {
MUTEX_ACQUIRE(&tcb->mutex);
tcb->shutdown |= new_how;
if ((new_how & SOCKET_SHUTDOWN_WRITE) &&
tcb->state != TCB_STATE_CLOSED &&
tcb->state != TCB_STATE_ABORT &&
tcb->state != TCB_STATE_FINWAIT1 &&
tcb->state != TCB_STATE_FINWAIT2 &&
tcb->state != TCB_STATE_LASTACK &&
tcb->state != TCB_STATE_TIMEWAIT &&
tcb->state != TCB_STATE_CLOSED)
tcp_handleclose(tcb);
if (new_how & SOCKET_SHUTDOWN_READ)
poll_event(&tcb->pollheader, POLLIN);
MUTEX_RELEASE(&tcb->mutex);
} else {
error = ENOTCONN;
}
MUTEX_RELEASE(&socket->mutex);
return error;
}
static socketops_t socketops = {
@@ -1856,7 +1956,8 @@ static socketops_t socketops = {
.getname = tcp_getname,
.getpeername = tcp_getpeername,
.setopt = tcp_setopt,
.shutdown = tcp_shutdown
.shutdown = tcp_shutdown,
.getopt = tcp_getopt
};
socket_t *tcp_createsocket() {