os-workshop

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

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:
Aexample/ex05-ethernet.c | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anet/inc/net/ipv6.h | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anet/ipv6.c | 294+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aproject/ex05-ethernet.app.mk | 8++++++++
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