From 4ded684f92cf37eecb5394c8980a96058bb33e9b Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 3 Nov 2019 14:50:15 +0200 Subject: [PATCH] Add in_send and ICMP echo request/reply --- etc/make/conf.mk | 3 +- include/sys/net/in.h | 10 +++ include/sys/net/netdev.h | 10 +++ sys/amd64/hw/pci/rtl8139.c | 3 + sys/net/in.c | 141 ++++++++++++++++++++++++++++++++++--- sys/net/netdev.c | 29 ++++++++ 6 files changed, 185 insertions(+), 11 deletions(-) create mode 100644 sys/net/netdev.c diff --git a/etc/make/conf.mk b/etc/make/conf.mk index 460362f..88c6535 100644 --- a/etc/make/conf.mk +++ b/etc/make/conf.mk @@ -63,7 +63,8 @@ OBJS+=$(O)/sys/debug.o \ $(O)/sys/ring.o \ $(O)/sys/net/eth.o \ $(O)/sys/net/arp.o \ - $(O)/sys/net/in.o + $(O)/sys/net/in.o \ + $(O)/sys/net/netdev.o DIRS+=$(O)/sys/vfs/ext2 \ $(O)/sys/net \ $(O)/sys/blk diff --git a/include/sys/net/in.h b/include/sys/net/in.h index 6427d43..48f12c0 100644 --- a/include/sys/net/in.h +++ b/include/sys/net/in.h @@ -21,4 +21,14 @@ struct in_frame { char payload[]; } __attribute__((packed)); +struct in_route { + struct netdev *dev; + uint32_t mask; + uint32_t network_inaddr; + uint32_t gateway; + struct in_route *next; +}; + +// frame is required to be a fully-wrapped L2/L3/payload frame for now +void in_send(struct netdev *dev, char *frame, size_t length); void in_handle_frame(struct netdev *dev, struct in_frame *frame); diff --git a/include/sys/net/netdev.h b/include/sys/net/netdev.h index 5c4f952..76586ce 100644 --- a/include/sys/net/netdev.h +++ b/include/sys/net/netdev.h @@ -5,8 +5,18 @@ struct netdev; typedef int (*netdev_tx_func_t) (struct netdev *, const void *, size_t); +struct link_info { + struct in_route *in_routes_head; + struct in_route *in_routes_tail; +}; + struct netdev { uint8_t hwaddr[6]; char name[32]; netdev_tx_func_t tx; + + struct link_info link; }; + +void link_add_route_in(struct netdev *dev, uint32_t net, uint32_t mask, uint32_t gw); +struct in_route *link_find_route_in(struct netdev *dev, uint32_t dst_inaddr); diff --git a/sys/amd64/hw/pci/rtl8139.c b/sys/amd64/hw/pci/rtl8139.c index a92ab87..95598df 100644 --- a/sys/amd64/hw/pci/rtl8139.c +++ b/sys/amd64/hw/pci/rtl8139.c @@ -267,6 +267,9 @@ void pci_rtl8139_init(pci_addr_t addr) { kdebug("RTL8139 MAC: " MAC_FMT "\n", MAC_VA(rtl->net.hwaddr)); rtl->net.tx = rtl8139_tx; // TODO: call some kind of netdev_add to bind a name to the device + + // XXX: for testing + link_add_route_in(&rtl->net, 0x0002000A, 0x00FFFFFF, 0x0102000A); } static __init void pci_rtl8139_register(void) { diff --git a/sys/net/in.c b/sys/net/in.c index fd60ea5..9bdf791 100644 --- a/sys/net/in.c +++ b/sys/net/in.c @@ -1,6 +1,8 @@ #include "sys/net/in.h" #include "sys/net/net.h" +#include "sys/net/eth.h" #include "sys/net/arp.h" +#include "sys/string.h" #include "sys/assert.h" #include "sys/debug.h" @@ -12,6 +14,14 @@ struct udp_frame { char payload[]; }; +struct icmp_frame { + uint8_t type; + uint8_t code; + uint16_t checksum; + uint32_t rest; + char payload[]; +}; + static void udp_handle_frame(struct netdev *dev, int in6, void *frame, struct udp_frame *udp) { // in6 is required to be 0 now, I haven't yet implemented IPv6 support _assert(!in6); @@ -26,26 +36,137 @@ static void udp_handle_frame(struct netdev *dev, int in6, void *frame, struct ud debug_dump(DEBUG_DEFAULT, udp->payload, htons(udp->length) - 8); } +uint16_t in_checksum_compute(uint8_t *data, size_t length) { + // Initialise the accumulator. + uint32_t acc = 0xffff; + + // Handle complete 16-bit blocks. + for (size_t i = 0; i + 1 < length; i += 2) { + uint16_t word; + + memcpy(&word, data + i, 2); + acc += htons(word); + if (acc > 0xffff) { + acc -= 0xffff; + } + } + + // Handle any partial block at the end of the data. + if (length & 1) { + uint16_t word = 0; + memcpy(&word, data + length - 1, 1); + acc += htons(word); + if (acc > 0xffff) { + acc -= 0xffff; + } + } + + // Return the checksum in network byte order. + return htons(~acc); +} + +int in_send_frame(struct netdev *dev, char *frame, size_t size) { + /* + * Caller is required to fill the following: + * - in_frame: everything except TTL (optional) and src_inaddr + */ + struct eth_frame *eth_frame = (struct eth_frame *) frame; + struct in_frame *in_frame = (struct in_frame *) eth_frame->payload; + char *payload = in_frame->payload; + + kdebug("Send in_frame to " INADDR_FMT "\n", INADDR_VA(in_frame->dst_inaddr)); + + struct in_route *route = link_find_route_in(dev, in_frame->dst_inaddr); + if (!route) { + kwarn(INADDR_FMT ": no route to host\n", INADDR_VA(in_frame->dst_inaddr)); + return -1; + } + kdebug("Found a route to " INADDR_FMT " via " INADDR_FMT "\n", + INADDR_VA(in_frame->dst_inaddr), + INADDR_VA(route->gateway)); + + + // Find MAC route to next hop + struct arp_route *arp_route = arp_find_route_in(dev, route->gateway); + if (!arp_route) { + kwarn(INADDR_FMT ": no route to gateway\n", INADDR_VA(route->gateway)); + return -1; + } + + // L3: + // TODO: get actual address from netdev->link_info + in_frame->src_inaddr = 0x0F02000A; + in_frame->version = 0x45; + in_frame->dscp = 0; + in_frame->length = htons(size - sizeof(struct eth_frame)); + in_frame->ttl = 0x40; + in_frame->header_chksum = 0; + in_frame->header_chksum = in_checksum_compute((uint8_t *) in_frame, htons(in_frame->length)); + + // L2: + // Fill netdev->hwaddr -> eth_frame->src_mac + memcpy(eth_frame->src_mac, dev->hwaddr, 6); + memcpy(eth_frame->dst_mac, arp_route->hwaddr, 6); + eth_frame->ethertype = htons(ETHERTYPE_IPV4); + + debug_dump(DEBUG_DEFAULT, frame, size); + + dev->tx(dev, frame, size); + + return 0; +} + +static void icmp_handle_frame(struct netdev *dev, int in6, void *frame, struct icmp_frame *icmp) { + // in6 is required to be 0 now, I haven't yet implemented IPv6 support + _assert(!in6); + struct in_frame *in_frame = frame; + + if (icmp->type == 0x08) { + size_t payload_length = htons(in_frame->length) - sizeof(struct in_frame) - 8; + size_t reply_size = htons(in_frame->length) + sizeof(struct eth_frame); + + char reply[reply_size]; + struct eth_frame *eth_reply_frame = (struct eth_frame *) reply; + struct in_frame *in_reply_frame = (struct in_frame *) eth_reply_frame->payload; + struct icmp_frame *icmp_reply_frame = (struct icmp_frame *) in_reply_frame->payload; + + // L4 + memcpy(icmp_reply_frame, icmp, payload_length + 8); + icmp_reply_frame->type = 0; + icmp_reply_frame->checksum = 0; + icmp_reply_frame->checksum = in_checksum_compute((uint8_t *) icmp_reply_frame, payload_length + 8); + + // L3 + in_reply_frame->dst_inaddr = in_frame->src_inaddr; + in_reply_frame->id = in_frame->id; + in_reply_frame->flag_frag_offset = in_frame->flag_frag_offset; + in_reply_frame->protocol = in_frame->protocol; + + kdebug("PING REPLY\n"); + + in_send_frame(dev, reply, reply_size); + } +} + void in_handle_frame(struct netdev *dev, struct in_frame *frame) { kdebug("INET frame\n"); kdebug("\tTotal length: %u\n", htons(frame->length)); kdebug("\tsrcaddr: " INADDR_FMT "\n", INADDR_VA(frame->src_inaddr)); kdebug("\tdstaddr: " INADDR_FMT "\n", INADDR_VA(frame->dst_inaddr)); - uint16_t checksum = 0; - - for (size_t i = 0; i < sizeof(struct in_frame) / 2; ++i) { - checksum += ((uint16_t *) frame)[i]; - } + uint16_t checksum = in_checksum_compute((uint8_t *) frame, htons(frame->length)); kdebug("IP checksum = %04x\n", checksum); - //if (checksum != 0xFFFF) { - // kwarn("Packet IP checksum mismatch\n"); - // // Drop packet - // return; - //} + if (checksum != 0) { + kwarn("Packet IP checksum mismatch\n"); + // Drop packet + return; + } switch (frame->protocol) { + case 0x01: + icmp_handle_frame(dev, 0, frame, (struct icmp_frame *) frame->payload); + break; case IN_PROTO_UDP: udp_handle_frame(dev, 0, frame, (struct udp_frame *) frame->payload); break; diff --git a/sys/net/netdev.c b/sys/net/netdev.c new file mode 100644 index 0000000..f23e6b0 --- /dev/null +++ b/sys/net/netdev.c @@ -0,0 +1,29 @@ +#include "sys/net/netdev.h" +#include "sys/net/in.h" +#include "sys/heap.h" + +void link_add_route_in(struct netdev *dev, uint32_t net, uint32_t mask, uint32_t gw) { + struct in_route *route = (struct in_route *) kmalloc(sizeof(struct in_route)); + route->dev = dev; + route->network_inaddr = net; + route->mask = mask; + route->gateway = gw; + route->next = NULL; + + if (dev->link.in_routes_tail) { + dev->link.in_routes_tail->next = route; + } else { + dev->link.in_routes_head = route; + } + dev->link.in_routes_tail = route; +} + +struct in_route *link_find_route_in(struct netdev *dev, uint32_t dst_inaddr) { + for (struct in_route *route = dev->link.in_routes_head; route; route = route->next) { + if (route->network_inaddr == (dst_inaddr & route->mask)) { + return route; + } + } + + return NULL; +}