diff --git a/etc/make/conf.mk b/etc/make/conf.mk index 11772d3..0a7c85c 100644 --- a/etc/make/conf.mk +++ b/etc/make/conf.mk @@ -103,6 +103,7 @@ OBJS+=$(O)/sys/sys_net.o \ $(O)/net/icmp.o \ $(O)/net/udp.o \ $(O)/net/raw.o \ + $(O)/net/tcp.o \ $(O)/net/if.o \ $(O)/net/util.o DIRS+=$(O)/net diff --git a/include/net/inet.h b/include/net/inet.h index 61028db..2db9c7c 100644 --- a/include/net/inet.h +++ b/include/net/inet.h @@ -2,6 +2,7 @@ #include "sys/types.h" #define INET_P_ICMP 1 +#define INET_P_TCP 6 #define INET_P_UDP 17 struct inet_frame { diff --git a/include/net/tcp.h b/include/net/tcp.h new file mode 100644 index 0000000..ed97b70 --- /dev/null +++ b/include/net/tcp.h @@ -0,0 +1,32 @@ +#pragma once +#include "sys/types.h" + +struct tcp_frame { + uint16_t src_port; + uint16_t dst_port; + uint32_t seq; + uint32_t ack_seq; + uint8_t ns:1; + uint8_t zero:3; + uint8_t doff:4; + uint8_t fin:1; + uint8_t syn:1; + uint8_t rst:1; + uint8_t psh:1; + uint8_t ack:1; + uint8_t urg:1; + uint8_t ece:1; + uint8_t cwr:1; + uint16_t window; + uint16_t checksum; + uint16_t urg_ptr; +} __attribute__((packed)); + +struct eth_frame; +struct inet_frame; +struct packet; +struct vfs_ioctx; +struct ofile; +struct sockaddr; + +void tcp_handle_frame(struct packet *p, struct eth_frame *eth, struct inet_frame *ip, void *data, size_t len); diff --git a/net/inet.c b/net/inet.c index 2a67a2a..1a48053 100644 --- a/net/inet.c +++ b/net/inet.c @@ -10,6 +10,7 @@ #include "net/arp.h" #include "net/eth.h" #include "net/udp.h" +#include "net/tcp.h" #include "net/if.h" uint16_t inet_checksum(const void *data, size_t len) { @@ -106,6 +107,9 @@ void inet_handle_frame(struct packet *p, struct eth_frame *eth, void *data, size case INET_P_UDP: udp_handle_frame(p, eth, ip, data, len); break; + case INET_P_TCP: + tcp_handle_frame(p, eth, ip, data, len); + break; default: kwarn("%s: dropping unknown protocol %02x\n", p->dev->name, ip->proto); break; diff --git a/net/tcp.c b/net/tcp.c new file mode 100644 index 0000000..fa4af7f --- /dev/null +++ b/net/tcp.c @@ -0,0 +1,78 @@ +#include "net/packet.h" +#include "sys/string.h" +#include "sys/debug.h" +#include "net/inet.h" +#include "net/util.h" +#include "net/eth.h" +#include "net/tcp.h" +#include "net/if.h" + +static uint16_t tcp_checksum(uint32_t daddr, uint32_t saddr, uint16_t tcp_length, void *tcp_data) { + uint32_t sum = 0; + uint32_t tmp = saddr; + sum += tmp & 0xFFFF; + sum += tmp >> 16; + tmp = daddr; + sum += tmp & 0xFFFF; + sum += tmp >> 16; + sum += 6; + sum += tcp_length; + + for (int i = 0; i < tcp_length / 2; ++i) { + sum += ntohs(((uint16_t *) tcp_data)[i]); + } + sum = (sum >> 16) + (sum & 0xFFFF); + return ~(sum & 0xFFFF) & 0xFFFF; +} + +// Reset a connection attempt given its SYN packet +static void tcp_syn_reset(struct netdev *dev, uint32_t inaddr, struct tcp_frame *syn_frame) { + if (!(dev->flags & IF_F_HASIP)) { + return; + } + + char packet[sizeof(struct eth_frame) + sizeof(struct inet_frame) + sizeof(struct tcp_frame)]; + struct tcp_frame *tcp = (struct tcp_frame *) &packet[sizeof(struct eth_frame) + sizeof(struct inet_frame)]; + memset(tcp, 0, sizeof(struct tcp_frame)); + + tcp->src_port = syn_frame->dst_port; + tcp->dst_port = syn_frame->src_port; + tcp->ack_seq = htonl(ntohl(syn_frame->seq) + 1); + tcp->doff = sizeof(struct tcp_frame) / 4; + tcp->rst = 1; + tcp->ack = 1; + tcp->window = syn_frame->window; + + uint16_t checksum = tcp_checksum(inaddr, dev->inaddr, sizeof(struct tcp_frame), tcp); + tcp->checksum = htons(checksum); + + debug_dump(DEBUG_DEFAULT, tcp, sizeof(struct tcp_frame)); + + inet_send_wrapped(dev, inaddr, INET_P_TCP, packet, sizeof(packet)); +} + +void tcp_handle_frame(struct packet *p, struct eth_frame *eth, struct inet_frame *ip, void *data, size_t len) { + if (len < sizeof(struct tcp_frame)) { + kwarn("%s: dropping undersized frame\n", p->dev->name); + return; + } + + struct tcp_frame *tcp = data; + size_t frame_size = 4 * tcp->doff; + + if (len < frame_size) { + kwarn("%s: dropping undersized frame\n", p->dev->name); + return; + } + + data += frame_size; + len -= frame_size; + + if (tcp->syn && !tcp->ack) { + // TODO: Check if it's an initial SYN + // Just reset the connection for now + tcp_syn_reset(p->dev, ntohl(ip->src_inaddr), tcp); + } else { + kwarn("%s: unhandled packet: syn=%d, ack=%d, psh=%d, rst=%d\n", tcp->syn, tcp->ack, tcp->psh, tcp->rst); + } +}