commit 18abedd9d12115dea8ee019ab61d270b0911608a
parent b8e8a89224f5f477ef09963325aadbeb974cff35
Author: Brian Swetland <swetland@frotz.net>
Date: Sat, 21 May 2022 16:27:39 -0700
net: start of a very simple ip6/udp6/icmp "netstack"
Diffstat:
4 files changed, 529 insertions(+), 0 deletions(-)
diff --git a/example/ex05-ethernet.c b/example/ex05-ethernet.c
@@ -0,0 +1,99 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0
+
+#include <hw/riscv.h>
+#include <hw/context.h>
+#include <hw/debug.h>
+#include <hw/intrinsics.h>
+
+#include <hw/platform.h>
+#include <hw/litex.h>
+
+#include <net/ipv6.h>
+
+#include <string.h>
+
+#define eth_rd(a) io_rd32(ETHMAC_BASE + LX_ETHMAC_ ## a)
+#define eth_wr(a,v) io_wr32(ETHMAC_BASE + LX_ETHMAC_ ## a, v)
+
+void interrupt_handler(void) { xputc('@'); }
+
+void pkt_rx_udp(void *data, unsigned len, unsigned port, int mcast) {
+ xprintf("UDP %u: %p(%u) %s\n", port, data, len, mcast ? "M" : "");
+ net_tx_udp_reply(data, "multipass", 10, port);
+}
+
+uint8_t rxbuf[1536] = { 0, };
+
+void eth_rx(uint8_t *rxb, unsigned rxlen) {
+ if (rxlen > 1534) return;
+ memcpy(rxbuf + 2, rxb, rxlen);
+ net_rx_eth(rxbuf, rxlen + 2);
+}
+
+static unsigned txslot;
+
+void eth_tx(void *txb, unsigned txlen) {
+ if (txlen > 1534) return;
+
+ while (eth_rd(RD_READY) != 1) ;
+
+ memcpy((void*)(ETHMAC_SRAM_BASE + ETHMAC_SLOT_SIZE * (ETHMAC_RX_SLOTS + txslot)), txb, txlen);
+
+ eth_wr(RD_SLOT, txslot);
+ eth_wr(RD_LEN, txlen);
+ eth_wr(RD_START, 1);
+
+ txslot ^= 1;
+}
+
+void eth_init(void) {
+ eth_wr(WR_EV_ENABLE, 0);
+ eth_wr(RD_EV_ENABLE, 0);
+
+ eth_wr(WR_EV_PENDING, 1);
+ eth_wr(RD_EV_PENDING, 1);
+
+ txslot = 0;
+ eth_wr(RD_SLOT, txslot);
+
+ eth_wr(WR_EV_ENABLE, 1);
+ eth_wr(RD_EV_ENABLE, 1);
+}
+
+void start(void) {
+ xprintf("Example 05 - Ethernet\n\n");
+
+ // set trap vector to trap_entry() in trap-entry-single.S
+ // it will call exception_handler() or interrupt_handler()
+ csr_write(CSR_STVEC, (uintptr_t) trap_entry);
+
+ // enable timer0 irq
+ //csr_set(CSR_S_INTC_ENABLE, TIMER0_IRQb);
+
+ // enable external interrupts
+ //csr_set(CSR_SIE, INTb_SVC_EXTERN);
+
+ // enable interrupts
+ //irq_enable();
+
+ eth_init();
+
+ while (1) {
+ if (eth_rd(WR_EV_PENDING) & 1) {
+ uint32_t slot = eth_rd(WR_SLOT);
+ uint8_t *rxb = (void*) (ETHMAC_SRAM_BASE + ETHMAC_SLOT_SIZE * slot);
+ eth_rx(rxb, eth_rd(WR_LEN));
+ eth_wr(WR_EV_PENDING, 1);
+ }
+ }
+}
+
+// if an exception occurs, dump register state and halt
+void exception_handler(eframe_t *ef) {
+ xprintf("\n** SUPERVISOR EXCEPTION **\n");
+ xprint_s_exception(ef);
+ xprintf("\nHALT\n");
+ for (;;) ;
+}
+
diff --git a/net/inc/net/ipv6.h b/net/inc/net/ipv6.h
@@ -0,0 +1,128 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#pragma once
+
+#include <stdint.h>
+#include <assert.h>
+
+#define ETH_ADDR_LEN 6
+#define IP6_ADDR_LEN 16
+
+#define ETH_HDR_LEN 14
+#define IP6_HDR_LEN 40
+#define UDP_HDR_LEN 8
+#define ICMP_HDR_LEN 4
+#define NDP_HDR_LEN 24
+
+#define IP6_MTU 1280
+
+#define ETH_TYPE_IP4 0x0800
+#define ETH_TYPE_ARP 0x0806
+#define ETH_TYPE_IP6 0x86DD
+
+#define IP_HDR_TCP 6
+#define IP_HDR_UDP 17
+#define IP_HDR_ICMP 58
+
+#define ICMP_ECHO_REQUEST 128
+#define ICMP_ECHO_REPLY 129
+#define ICMP_NDP_N_SOLICIT 135
+#define ICMP_NDP_N_ADVERTISE 136
+
+#define NDP_N_SRC_LL_ADDR 1
+#define NDP_N_TGT_LL_ADDR 2
+#define NDP_N_PREFIX_INFO 3
+#define NDP_N_REDIRECTED_HDR 4
+#define NDP_N_MTU 5
+
+typedef union {
+ uint8_t b[IP6_ADDR_LEN];
+ uint32_t w[IP6_ADDR_LEN / 4];
+} ip6_addr_t;
+
+typedef struct {
+ uint8_t pad[2];
+ uint8_t dst[ETH_ADDR_LEN];
+ uint8_t src[ETH_ADDR_LEN];
+ uint16_t type;
+} eth_hdr_t;
+
+typedef struct {
+ uint32_t bits;
+ uint16_t length;
+ uint8_t next_header;
+ uint8_t hop_limit;
+ ip6_addr_t src;
+ ip6_addr_t dst;
+} ip6_hdr_t;
+
+typedef struct {
+ uint16_t src_port;
+ uint16_t dst_port;
+ uint16_t length;
+ uint16_t checksum;
+} udp_hdr_t;
+
+typedef struct {
+ uint8_t type;
+ uint8_t code;
+ uint16_t checksum;
+} icmp_hdr_t;
+
+typedef struct {
+ uint8_t type;
+ uint8_t code;
+ uint16_t checksum;
+ uint32_t flags;
+ ip6_addr_t target;
+ uint8_t options[];
+} ndp_hdr_t;
+
+// ensure structures are right size
+static_assert(sizeof(ip6_addr_t) == IP6_ADDR_LEN, "");
+static_assert(sizeof(eth_hdr_t) == (ETH_HDR_LEN + 2), "");
+static_assert(sizeof(ip6_hdr_t) == IP6_HDR_LEN, "");
+static_assert(sizeof(udp_hdr_t) == UDP_HDR_LEN, "");
+static_assert(sizeof(icmp_hdr_t) == ICMP_HDR_LEN, "");
+static_assert(sizeof(ndp_hdr_t) == NDP_HDR_LEN, "");
+
+
+#define ntohs(val) __builtin_bswap16(val)
+#define ntohl(val) __builtin_bswap32(val)
+
+#define htons(val) __builtin_bswap16(val)
+#define htonl(val) __builtin_bswap32(val)
+
+
+static inline void ip6_addr_copy(ip6_addr_t *dst, ip6_addr_t *src) {
+ dst->w[0] = src->w[0];
+ dst->w[1] = src->w[1];
+ dst->w[2] = src->w[2];
+ dst->w[3] = src->w[3];
+}
+
+static inline void eth_addr_copy(uint8_t *_dst, uint8_t *_src) {
+ uint16_t *dst = (void*) _dst;
+ uint16_t *src = (void*) _src;
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+}
+
+// push rx packets into the network stack
+void net_rx_eth(void *data, unsigned len);
+
+// send a reply from pkt_rx_udp()
+// replyto must be the data buffer passed in to pkt_rx_udp()
+void net_tx_udp_reply(void *replyto, void *data, unsigned len, unsigned port);
+
+void net_tx_ip6_reply(void *replyto, void *data, unsigned len,
+ unsigned type, unsigned chk_off);
+
+// callback on udp packet receipt
+void pkt_rx_udp(void *data, unsigned len, unsigned port, int mcast);
+
+// callback to transmit a packet
+void eth_tx(void *data, unsigned len);
+
diff --git a/net/ipv6.c b/net/ipv6.c
@@ -0,0 +1,294 @@
+// Copyright 2022, Brian Swetland <swetland@frotz.net>
+// Licensed under the Apache License, Version 2.0.
+
+#include <string.h>
+#include <net/ipv6.h>
+#include <hw/debug.h>
+
+void hexdump(void *data, int len) {
+ unsigned off = 0;
+ while (len > 0) {
+ xprintf("%04x:", off);
+ for (int n = 0; n < 16; n++) {
+ if (n == len) break;
+ xprintf(" %02x", ((uint8_t*)data)[n]);
+ if (n == 7) xputc(' ');
+ }
+ xputc('\n');
+ len -= 16;
+ data += 16;
+ off += 16;
+ }
+}
+
+#define NETBUF_MAX 1536 // 1.5KB
+
+#define TRACE_DISCARD 1
+
+typedef struct netbuf {
+ struct netbuf* next;
+ uint32_t rsvd0;
+ uint32_t rsvd1;
+ uint32_t rsvd2;
+ uint8_t data[NETBUF_MAX];
+} netbuf_t;
+
+// fast mac matching via word comparison
+typedef union {
+ uint32_t w[2];
+ uint8_t b[8];
+} match_t;
+
+match_t m_dev_mac = { .b = { 0, 0, 0x72, 0x72, 0xaa, 0xbb, 0xcc, 0xdd } };
+match_t m_mcast_all = { .b = { 0, 0, 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 } };
+match_t m_mcast_ns = { .b = { 0, 0, 0x33, 0x33, 0xff, 0xbb, 0xcc, 0xdd } };
+
+uint8_t device_mac[ETH_ADDR_LEN] = { 0x72, 0x72, 0xaa, 0xbb, 0xcc, 0xdd };
+
+ip6_addr_t device_ip6 = { .b = {
+ 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x70, 0x72, 0xaa, 0xff, 0xfe, 0xbb, 0xcc, 0xdd, } };
+
+ip6_addr_t mcast_all_ip6 = { .b = {
+ 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } };
+
+ip6_addr_t mcast_ns_ip6 = { .b = {
+ 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xff, 0xbb, 0xcc, 0xdd } };
+
+// returns 16bit checksum of provided byte data buffer
+static uint32_t checksum(const void* _data, unsigned len, uint32_t sum) {
+ const uint16_t* data = _data;
+ while (len > 1) {
+ sum += *data++;
+ len -= 2;
+ }
+ if (len) {
+ sum += (*data & 0xFF);
+ }
+ while (sum > 0xFFFF) {
+ sum = (sum & 0xFFFF) + (sum >> 16);
+ }
+ return sum;
+}
+
+// Takes pointer to ip6 packet starting with ip6 header (filled out)
+// and length of payload (exclusive of the ip6 header length)
+//
+// Returns 16bit ip6 checksum
+static uint32_t checksum_ip6(ip6_hdr_t* hdr, unsigned len) {
+ // sum of length and protocol fields from pseudo-header
+ uint32_t sum = checksum(&hdr->length, 2, htons(hdr->next_header));
+ // sum of src and dst fields from pseudo-header + payload
+ sum = checksum(&hdr->src, len + 32, sum);
+ // avoid return sum 0
+ return (sum == 0xffff) ? sum : ~sum;
+}
+
+void net_rx_udp6(void *data, unsigned dlen, int mcast) {
+ if (dlen < sizeof(udp_hdr_t)) {
+ xprintf("UPD6: runt\n");
+ return;
+ }
+ udp_hdr_t *udp = data;
+ uint16_t port = ntohs(udp->dst_port);
+ uint16_t len = ntohs(udp->length);
+ if ((len < sizeof(udp_hdr_t)) || (len > dlen)) {
+ xprintf("UDP6: badlen %u (dlen %u)\n", len, dlen);
+ // bogus length or short packet
+ return;
+ }
+ pkt_rx_udp(data + sizeof(udp_hdr_t), len - sizeof(udp_hdr_t), port, mcast);
+}
+
+void net_rx_icmp6(void *data, unsigned dlen, int mcast) {
+ if (dlen < sizeof(icmp_hdr_t)) {
+ xprintf("ICMP6 runt\n");
+ return;
+ }
+ icmp_hdr_t *icmp = data;
+
+ switch (icmp->type) {
+ case ICMP_NDP_N_SOLICIT: {
+ ndp_hdr_t *ndp = data;
+ if ((dlen < sizeof(ndp_hdr_t)) || (ndp->code != 0)) {
+ return;
+ }
+ if (memcmp(&ndp->target, &device_ip6, sizeof(ip6_addr_t))) {
+ return; // not for us
+ }
+
+ struct {
+ ndp_hdr_t hdr;
+ uint8_t opt[8];
+ } reply = {
+ .hdr.type = ICMP_NDP_N_ADVERTISE,
+ .hdr.code = 0,
+ .hdr.checksum = 0,
+ .hdr.flags = 0x60, // (S)olicited and (O)verride
+ .opt[0] = NDP_N_TGT_LL_ADDR,
+ .opt[1] = 1,
+ };
+ memcpy(&reply.hdr.target, &device_ip6, sizeof(ip6_addr_t));
+ memcpy(&reply.opt[2], device_mac, ETH_ADDR_LEN);
+
+ net_tx_ip6_reply(data, &reply, sizeof(reply), IP_HDR_ICMP, offsetof(ndp_hdr_t, checksum));
+ return;
+ }
+ case ICMP_ECHO_REQUEST: {
+ icmp_hdr_t *icmp = data;
+ if (dlen < sizeof(icmp_hdr_t)) {
+ return;
+ }
+ icmp->type = ICMP_ECHO_REPLY;
+ icmp->checksum = 0;
+ net_tx_ip6_reply(data, icmp, dlen, IP_HDR_ICMP, offsetof(icmp_hdr_t, checksum));
+ return;
+ }
+ default:
+ xprintf("ICMP ? %u %u %s\n", icmp->type, icmp->code, mcast ? "M" : "");
+ }
+}
+
+// len >= 44
+void net_rx_ip6(void *data, unsigned len, int mcast) {
+ ip6_hdr_t *ip6 = data;
+
+ if (mcast) {
+ if (memcmp(&ip6->dst, &mcast_all_ip6, sizeof(ip6_addr_t)) &&
+ memcmp(&ip6->dst, &mcast_ns_ip6, sizeof(ip6_addr_t))) {
+ xprintf("IP6: MA ?\n");
+ return;
+ }
+ } else {
+ if (memcmp(&ip6->dst, &device_ip6, sizeof(ip6_addr_t))) {
+ xprintf("IP6: UA ?\n");
+ return;
+ }
+ }
+
+ switch (ip6->next_header) {
+ case IP_HDR_ICMP:
+ net_rx_icmp6(data + sizeof(ip6_hdr_t), len - sizeof(ip6_hdr_t), mcast);
+ return;
+ case IP_HDR_UDP:
+ net_rx_udp6(data + sizeof(ip6_hdr_t), len - sizeof(ip6_hdr_t), mcast);
+ return;
+ default:
+ xprintf("IP6: HDR %u ?\n", ip6->next_header);
+ return;
+ }
+}
+
+void net_rx_eth(void *data, unsigned len) {
+ if (len < 60) {
+ xprintf("ETH runt\n");
+ return; // runt packet
+ }
+
+ // discard non-ipv6 packets
+ eth_hdr_t *eth = data;
+ if (eth->type != htons(ETH_TYPE_IP6)) {
+ return;
+ }
+
+ //hexdump(data + 2, len - 2);
+
+ // accept packets which match our ethernet MAC or multicast address
+ uint32_t *w = data;
+ if ((w[0] == m_dev_mac.w[0]) && (w[1] == m_dev_mac.w[1])) {
+ net_rx_ip6(data + sizeof(eth_hdr_t), len - sizeof(eth_hdr_t), 0);
+ } else if ((w[0] == m_mcast_ns.w[0]) && (w[1] == m_mcast_ns.w[1])) {
+ net_rx_ip6(data + sizeof(eth_hdr_t), len - sizeof(eth_hdr_t), 1);
+ } else if ((w[0] == m_mcast_all.w[0]) && (w[1] == m_mcast_all.w[1])) {
+ net_rx_ip6(data + sizeof(eth_hdr_t), len - sizeof(eth_hdr_t), 1);
+ } else {
+ xprintf("ETH ? %02x %02x %02x %02x %02x %02x\n",
+ eth->dst[0], eth->dst[1], eth->dst[2],
+ eth->dst[3], eth->dst[4], eth->dst[5]);
+ }
+
+}
+
+typedef struct {
+ eth_hdr_t eth;
+ ip6_hdr_t ip6;
+ udp_hdr_t udp;
+ uint8_t data[];
+} udp6_pkt_t;
+
+static_assert(sizeof(udp6_pkt_t) == (sizeof(eth_hdr_t) + sizeof(ip6_hdr_t) + sizeof(udp_hdr_t)), "");
+#define MAX_UDP_PAYLOAD (NETBUF_MAX - sizeof(udp6_pkt_t))
+
+void net_tx_udp_reply(void *replyto, void *data, unsigned len, unsigned port) {
+ uint8_t packet[NETBUF_MAX];
+ udp6_pkt_t *tx = (void*) packet;
+ udp6_pkt_t *rx = replyto - sizeof(udp6_pkt_t);
+
+ if (len > MAX_UDP_PAYLOAD) {
+ return;
+ }
+
+ eth_addr_copy(tx->eth.dst, rx->eth.src);
+ eth_addr_copy(tx->eth.src, device_mac);
+ tx->eth.type = htons(ETH_TYPE_IP6);
+
+ tx->ip6.bits = 0x00000060; // ver=0, tc=0, flow=0
+ tx->ip6.length = htons(UDP_HDR_LEN + len);
+ tx->ip6.next_header = IP_HDR_UDP;
+ tx->ip6.hop_limit = 255;
+ ip6_addr_copy(&tx->ip6.src, &device_ip6);
+ ip6_addr_copy(&tx->ip6.dst, &rx->ip6.src);
+
+ tx->udp.src_port = htons(port);
+ tx->udp.dst_port = rx->udp.src_port;
+ tx->udp.length = htons(len + UDP_HDR_LEN);
+ tx->udp.checksum = 0;
+
+ memcpy(tx->data, data, len);
+
+ tx->udp.checksum = checksum_ip6(&tx->ip6, len + UDP_HDR_LEN);
+
+ eth_tx(packet + 2, ETH_HDR_LEN + IP6_HDR_LEN + UDP_HDR_LEN + len);
+}
+
+typedef struct {
+ eth_hdr_t eth;
+ ip6_hdr_t ip6;
+ uint8_t data[];
+} ip6_pkt_t;
+
+static_assert(sizeof(ip6_pkt_t) == (sizeof(eth_hdr_t) + sizeof(ip6_hdr_t)), "");
+#define MAX_IP6_PAYLOAD (NETBUF_MAX - sizeof(ip6_pkt_t))
+
+void net_tx_ip6_reply(void *replyto, void *data, unsigned len,
+ unsigned type, unsigned chk_off) {
+ uint8_t packet[NETBUF_MAX];
+ ip6_pkt_t *tx = (void*) packet;
+ ip6_pkt_t *rx = replyto - sizeof(ip6_pkt_t);
+
+ if (len > MAX_IP6_PAYLOAD) {
+ return;
+ }
+
+ eth_addr_copy(tx->eth.dst, rx->eth.src);
+ eth_addr_copy(tx->eth.src, device_mac);
+ tx->eth.type = htons(ETH_TYPE_IP6);
+
+ tx->ip6.bits = 0x00000060; // ver=0, tc=0, flow=0
+ tx->ip6.length = htons(len);
+ tx->ip6.next_header = type;
+ tx->ip6.hop_limit = 255;
+ ip6_addr_copy(&tx->ip6.src, &device_ip6);
+ ip6_addr_copy(&tx->ip6.dst, &rx->ip6.src);
+
+ memcpy(tx->data, data, len);
+
+ *((uint16_t*) (tx->data + chk_off)) = checksum_ip6(&tx->ip6, len);
+
+ //hexdump(packet + 2, ETH_HDR_LEN + IP6_HDR_LEN + len);
+
+ eth_tx(packet + 2, ETH_HDR_LEN + IP6_HDR_LEN + len);
+}
+
diff --git a/project/ex05-ethernet.app.mk b/project/ex05-ethernet.app.mk
@@ -0,0 +1,8 @@
+MOD_NAME := ex05-ethernet
+MOD_SRC := hw/src/start.S hw/src/trap-entry-single-stack.S
+MOD_SRC += example/ex05-ethernet.c
+MOD_SRC += hw/src/print-exception.c hw/src/debug-printf.c hw/src/debug-io.c
+MOD_SRC += net/ipv6.c
+MOD_INC := net/inc
+MOD_LIB := c
+include make/app.mk