|  | // Copyright 2016 The Fuchsia Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <stdint.h> | 
|  | #include <stdio.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <inet6.h> | 
|  | #include <zircon/boot/netboot.h> | 
|  |  | 
|  | // Enable at your own risk. Some of these packet errors can be fairly | 
|  | // common when the buffers start to overflow. | 
|  | #if 0 | 
|  | #define BAD(n, ...)                 \ | 
|  | do {                            \ | 
|  | printf("error: ");          \ | 
|  | printf(n, ##__VA_ARGS__);   \ | 
|  | printf("\n");               \ | 
|  | return;                     \ | 
|  | } while (0) | 
|  | #else | 
|  | #define BAD(n, ...)  \ | 
|  | do {             \ | 
|  | return;      \ | 
|  | } while (0) | 
|  | #endif | 
|  |  | 
|  | // useful addresses | 
|  | const ip6_addr ip6_ll_all_nodes = { | 
|  | .x = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, | 
|  | }; | 
|  | const ip6_addr ip6_ll_all_routers = { | 
|  | .x = {0xFF, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2}, | 
|  | }; | 
|  |  | 
|  | // Convert MAC Address to IPv6 Link Local Address | 
|  | // aa:bb:cc:dd:ee:ff => FF80::aabb:ccFF:FEdd:eeff | 
|  | // bit 2 (U/L) of the mac is inverted | 
|  | void ll6addr_from_mac(ip6_addr* _ip, const mac_addr* _mac) { | 
|  | uint8_t* ip = _ip->x; | 
|  | const uint8_t* mac = _mac->x; | 
|  | memset(ip, 0, IP6_ADDR_LEN); | 
|  | ip[0] = 0xFE; | 
|  | ip[1] = 0x80; | 
|  | memset(ip + 2, 0, 6); | 
|  | ip[8] = mac[0] ^ 2; | 
|  | ip[9] = mac[1]; | 
|  | ip[10] = mac[2]; | 
|  | ip[11] = 0xFF; | 
|  | ip[12] = 0xFE; | 
|  | ip[13] = mac[3]; | 
|  | ip[14] = mac[4]; | 
|  | ip[15] = mac[5]; | 
|  | } | 
|  |  | 
|  | // Convert MAC Address to IPv6 Solicit Neighbor Multicast Address | 
|  | // aa:bb:cc:dd:ee:ff -> FF02::1:FFdd:eeff | 
|  | void snmaddr_from_mac(ip6_addr* _ip, const mac_addr* _mac) { | 
|  | uint8_t* ip = _ip->x; | 
|  | const uint8_t* mac = _mac->x; | 
|  | ip[0] = 0xFF; | 
|  | ip[1] = 0x02; | 
|  | memset(ip + 2, 0, 9); | 
|  | ip[11] = 0x01; | 
|  | ip[12] = 0xFF; | 
|  | ip[13] = mac[3]; | 
|  | ip[14] = mac[4]; | 
|  | ip[15] = mac[5]; | 
|  | } | 
|  |  | 
|  | // Convert IPv6 Multicast Address to Ethernet Multicast Address | 
|  | void multicast_from_ip6(mac_addr* _mac, const ip6_addr* _ip6) { | 
|  | const uint8_t* ip = _ip6->x; | 
|  | uint8_t* mac = _mac->x; | 
|  | mac[0] = 0x33; | 
|  | mac[1] = 0x33; | 
|  | mac[2] = ip[12]; | 
|  | mac[3] = ip[13]; | 
|  | mac[4] = ip[14]; | 
|  | mac[5] = ip[15]; | 
|  | } | 
|  |  | 
|  | // ip6 stack configuration | 
|  | mac_addr ll_mac_addr; | 
|  | ip6_addr ll_ip6_addr; | 
|  | mac_addr snm_mac_addr; | 
|  | ip6_addr snm_ip6_addr; | 
|  |  | 
|  | // cache for the last source addresses we've seen | 
|  | static mac_addr rx_mac_addr; | 
|  | static ip6_addr rx_ip6_addr; | 
|  |  | 
|  | void ip6_init(void* macaddr) { | 
|  | char tmp[IP6TOAMAX]; | 
|  | mac_addr all; | 
|  |  | 
|  | // save our ethernet MAC and synthesize link layer addresses | 
|  | memcpy(&ll_mac_addr, macaddr, 6); | 
|  | ll6addr_from_mac(&ll_ip6_addr, &ll_mac_addr); | 
|  | snmaddr_from_mac(&snm_ip6_addr, &ll_mac_addr); | 
|  | multicast_from_ip6(&snm_mac_addr, &snm_ip6_addr); | 
|  |  | 
|  | eth_add_mcast_filter(&snm_mac_addr); | 
|  |  | 
|  | multicast_from_ip6(&all, &ip6_ll_all_nodes); | 
|  | eth_add_mcast_filter(&all); | 
|  |  | 
|  | printf("macaddr: %02x:%02x:%02x:%02x:%02x:%02x\n", | 
|  | ll_mac_addr.x[0], ll_mac_addr.x[1], ll_mac_addr.x[2], | 
|  | ll_mac_addr.x[3], ll_mac_addr.x[4], ll_mac_addr.x[5]); | 
|  | printf("ip6addr: %s\n", ip6toa(tmp, &ll_ip6_addr)); | 
|  | printf("snmaddr: %s\n", ip6toa(tmp, &snm_ip6_addr)); | 
|  | } | 
|  |  | 
|  | mac_addr eth_addr(void) { | 
|  | return ll_mac_addr; | 
|  | } | 
|  |  | 
|  | static int resolve_ip6(mac_addr* _mac, const ip6_addr* _ip) { | 
|  | const uint8_t* ip = _ip->x; | 
|  |  | 
|  | // Multicast addresses are a simple transform | 
|  | if (ip[0] == 0xFF) { | 
|  | multicast_from_ip6(_mac, _ip); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // Trying to send to the IP that we last received a packet from? | 
|  | // Assume their mac address has not changed | 
|  | if (memcmp(_ip, &rx_ip6_addr, sizeof(rx_ip6_addr)) == 0) { | 
|  | memcpy(_mac, &rx_mac_addr, sizeof(rx_mac_addr)); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // We don't know how to find peers or routers yet, so give up... | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static uint16_t checksum(const void* _data, size_t len, uint16_t _sum) { | 
|  | uint32_t sum = _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; | 
|  | } | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t eth[16]; | 
|  | ip6_hdr ip6; | 
|  | uint8_t data[0]; | 
|  | } ip6_pkt; | 
|  |  | 
|  | typedef struct { | 
|  | uint8_t eth[16]; | 
|  | ip6_hdr ip6; | 
|  | udp_hdr udp; | 
|  | uint8_t data[0]; | 
|  | } udp_pkt; | 
|  |  | 
|  | static unsigned ip6_checksum(ip6_hdr* ip, unsigned type, size_t length) { | 
|  | uint16_t sum; | 
|  |  | 
|  | // length and protocol field for pseudo-header | 
|  | sum = checksum(&ip->length, 2, htons(type)); | 
|  | // src/dst for pseudo-header + payload | 
|  | sum = checksum(ip->src, 32 + length, sum); | 
|  |  | 
|  | // 0 is illegal, so 0xffff remains 0xffff | 
|  | if (sum != 0xffff) { | 
|  | return ~sum; | 
|  | } else { | 
|  | return sum; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int ip6_setup(ip6_pkt* p, const ip6_addr* daddr, size_t length, uint8_t type) { | 
|  | mac_addr dmac; | 
|  |  | 
|  | if (resolve_ip6(&dmac, daddr)) | 
|  | return -1; | 
|  |  | 
|  | // ethernet header | 
|  | memcpy(p->eth + 2, &dmac, ETH_ADDR_LEN); | 
|  | memcpy(p->eth + 8, &ll_mac_addr, ETH_ADDR_LEN); | 
|  | p->eth[14] = (ETH_IP6 >> 8) & 0xFF; | 
|  | p->eth[15] = ETH_IP6 & 0xFF; | 
|  |  | 
|  | // ip6 header | 
|  | p->ip6.ver_tc_flow = 0x60; // v=6, tc=0, flow=0 | 
|  | p->ip6.length = htons(length); | 
|  | p->ip6.next_header = type; | 
|  | p->ip6.hop_limit = 255; | 
|  | memcpy(p->ip6.src, &ll_ip6_addr, sizeof(ip6_addr)); | 
|  | memcpy(p->ip6.dst, daddr, sizeof(ip6_addr)); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | #define UDP6_MAX_PAYLOAD (ETH_MTU - ETH_HDR_LEN - IP6_HDR_LEN - UDP_HDR_LEN) | 
|  |  | 
|  | int udp6_send(const void* data, size_t dlen, const ip6_addr* daddr, uint16_t dport, uint16_t sport) { | 
|  | size_t length = dlen + UDP_HDR_LEN; | 
|  | udp_pkt* p = eth_get_buffer(ETH_MTU + 2); | 
|  |  | 
|  | if (p == NULL) | 
|  | return -1; | 
|  | if (dlen > UDP6_MAX_PAYLOAD) { | 
|  | printf("Internal error: UDP write request is too long\n"); | 
|  | goto fail; | 
|  | } | 
|  | if (ip6_setup((void*)p, daddr, length, HDR_UDP)) { | 
|  | printf("Error: ip6_setup failed!\n"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | // udp header | 
|  | p->udp.src_port = htons(sport); | 
|  | p->udp.dst_port = htons(dport); | 
|  | p->udp.length = htons(length); | 
|  | p->udp.checksum = 0; | 
|  |  | 
|  | memcpy(p->data, data, dlen); | 
|  | p->udp.checksum = ip6_checksum(&p->ip6, HDR_UDP, length); | 
|  | return eth_send(p->eth + 2, ETH_HDR_LEN + IP6_HDR_LEN + length); | 
|  |  | 
|  | fail: | 
|  | eth_put_buffer(p); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | #define ICMP6_MAX_PAYLOAD (ETH_MTU - ETH_HDR_LEN - IP6_HDR_LEN) | 
|  |  | 
|  | static int icmp6_send(const void* data, size_t length, const ip6_addr* daddr) { | 
|  | ip6_pkt* p; | 
|  | icmp6_hdr* icmp; | 
|  |  | 
|  | p = eth_get_buffer(ETH_MTU + 2); | 
|  | if (p == NULL) | 
|  | return -1; | 
|  | if (length > ICMP6_MAX_PAYLOAD) { | 
|  | printf("Internal error: ICMP write request is too long\n"); | 
|  | goto fail; | 
|  | } | 
|  | if (ip6_setup(p, daddr, length, HDR_ICMP6)) { | 
|  | printf("Error: ip6_setup failed!\n"); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | icmp = (void*)p->data; | 
|  | memcpy(icmp, data, length); | 
|  | icmp->checksum = ip6_checksum(&p->ip6, HDR_ICMP6, length); | 
|  | return eth_send(p->eth + 2, ETH_HDR_LEN + IP6_HDR_LEN + length); | 
|  |  | 
|  | fail: | 
|  | eth_put_buffer(p); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | void udp6_recv(ip6_hdr* ip, void* _data, size_t len) { | 
|  | udp_hdr* udp = _data; | 
|  | uint16_t sum, n; | 
|  |  | 
|  | if (len < UDP_HDR_LEN) | 
|  | BAD("Bogus Header Len"); | 
|  |  | 
|  | if (udp->checksum == 0) | 
|  | BAD("Missing checksum"); | 
|  |  | 
|  | if (udp->checksum == 0xFFFF) | 
|  | udp->checksum = 0; | 
|  |  | 
|  | sum = checksum(&ip->length, 2, htons(HDR_UDP)); | 
|  | sum = checksum(ip->src, 32 + len, sum); | 
|  | if (sum != 0xFFFF) | 
|  | BAD("Checksum Incorrect"); | 
|  |  | 
|  | n = ntohs(udp->length); | 
|  | if (n < UDP_HDR_LEN) | 
|  | BAD("Bogus Header Len"); | 
|  | if (n > len) | 
|  | BAD("Packet Too Short"); | 
|  | len = n - UDP_HDR_LEN; | 
|  |  | 
|  | uint16_t dport = ntohs(udp->dst_port); | 
|  | uint16_t sport = ntohs(udp->src_port); | 
|  |  | 
|  | switch (dport) { | 
|  | case NB_SERVER_PORT: | 
|  | netboot_recv((uint8_t*)_data + UDP_HDR_LEN, len, (void*)ip->src, sport); | 
|  | break; | 
|  | case NB_TFTP_INCOMING_PORT: | 
|  | case NB_TFTP_OUTGOING_PORT: | 
|  | tftp_recv((uint8_t*)_data + UDP_HDR_LEN, len, (void*)ip->dst, dport, (void*)ip->src, sport); | 
|  | break; | 
|  | default: | 
|  | // Ignore | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | void icmp6_recv(ip6_hdr* ip, void* _data, size_t len) { | 
|  | icmp6_hdr* icmp = _data; | 
|  | uint16_t sum; | 
|  |  | 
|  | if (icmp->checksum == 0) | 
|  | BAD("Checksum Invalid"); | 
|  | if (icmp->checksum == 0xFFFF) | 
|  | icmp->checksum = 0; | 
|  |  | 
|  | sum = checksum(&ip->length, 2, htons(HDR_ICMP6)); | 
|  | sum = checksum(ip->src, 32 + len, sum); | 
|  | if (sum != 0xFFFF) | 
|  | BAD("Checksum Incorrect"); | 
|  |  | 
|  | if (icmp->type == ICMP6_NDP_N_SOLICIT) { | 
|  | ndp_n_hdr* ndp = _data; | 
|  | struct { | 
|  | ndp_n_hdr hdr; | 
|  | uint8_t opt[8]; | 
|  | } msg; | 
|  |  | 
|  | if (len < sizeof(ndp_n_hdr)) | 
|  | BAD("Bogus NDP Message"); | 
|  | if (ndp->code != 0) | 
|  | BAD("Bogus NDP Code"); | 
|  | if (memcmp(ndp->target, &ll_ip6_addr, IP6_ADDR_LEN)) | 
|  | BAD("NDP Not For Me"); | 
|  |  | 
|  | msg.hdr.type = ICMP6_NDP_N_ADVERTISE; | 
|  | msg.hdr.code = 0; | 
|  | msg.hdr.checksum = 0; | 
|  | msg.hdr.flags = 0x60; // (S)olicited and (O)verride flags | 
|  | memcpy(msg.hdr.target, &ll_ip6_addr, IP6_ADDR_LEN); | 
|  | msg.opt[0] = NDP_N_TGT_LL_ADDR; | 
|  | msg.opt[1] = 1; | 
|  | memcpy(msg.opt + 2, &ll_mac_addr, ETH_ADDR_LEN); | 
|  |  | 
|  | icmp6_send(&msg, sizeof(msg), (void*)ip->src); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (icmp->type == ICMP6_ECHO_REQUEST) { | 
|  | icmp->checksum = 0; | 
|  | icmp->type = ICMP6_ECHO_REPLY; | 
|  | icmp6_send(_data, len, (void*)ip->src); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BAD("ICMP6 Unhandled %d", icmp->type); | 
|  | } | 
|  |  | 
|  | void eth_recv(void* _data, size_t len) { | 
|  | uint8_t* data = _data; | 
|  | ip6_hdr* ip; | 
|  | uint32_t n; | 
|  |  | 
|  | if (len < (ETH_HDR_LEN + IP6_HDR_LEN)) | 
|  | BAD("Bogus Header Len"); | 
|  | if (data[12] != (ETH_IP6 >> 8) || data[13] != (ETH_IP6 & 0xFF)) | 
|  | BAD("Not IP6"); | 
|  |  | 
|  | ip = (void*)(data + ETH_HDR_LEN); | 
|  | data += (ETH_HDR_LEN + IP6_HDR_LEN); | 
|  | len -= (ETH_HDR_LEN + IP6_HDR_LEN); | 
|  |  | 
|  | // require v6 | 
|  | if ((ip->ver_tc_flow & 0xF0) != 0x60) | 
|  | BAD("Unknown IP6 Version"); | 
|  |  | 
|  | // ensure length is sane | 
|  | n = ntohs(ip->length); | 
|  | if (n > len) | 
|  | BAD("IP6 Length Mismatch %d %zu", n, len); | 
|  |  | 
|  | // ignore any trailing data in the ethernet frame | 
|  | len = n; | 
|  |  | 
|  | // require that we are the destination | 
|  | if (memcmp(&ll_ip6_addr, ip->dst, IP6_ADDR_LEN) && | 
|  | memcmp(&snm_ip6_addr, ip->dst, IP6_ADDR_LEN) && | 
|  | memcmp(&ip6_ll_all_nodes, ip->dst, IP6_ADDR_LEN)) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // stash the sender's info to simplify replies | 
|  | memcpy(&rx_mac_addr, (uint8_t*)_data + 6, ETH_ADDR_LEN); | 
|  | memcpy(&rx_ip6_addr, ip->src, IP6_ADDR_LEN); | 
|  |  | 
|  | if (ip->next_header == HDR_ICMP6) { | 
|  | icmp6_recv(ip, data, len); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (ip->next_header == HDR_UDP) { | 
|  | udp6_recv(ip, data, len); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BAD("Unhandled IP6 %d", ip->next_header); | 
|  | } | 
|  |  | 
|  | char* ip6toa(char* _out, void* ip6addr) { | 
|  | const uint8_t* x = ip6addr; | 
|  | const uint8_t* end = x + 16; | 
|  | char* out = _out; | 
|  | uint16_t n; | 
|  |  | 
|  | n = (x[0] << 8) | x[1]; | 
|  | while ((n == 0) && (x < end)) { | 
|  | x += 2; | 
|  | n = (x[0] << 8) | x[1]; | 
|  | } | 
|  |  | 
|  | if ((end - x) < 16) { | 
|  | if (end == x) { | 
|  | // all 0s - special case | 
|  | sprintf(out, "::"); | 
|  | return _out; | 
|  | } | 
|  | // we consumed some number of leading 0s | 
|  | out += sprintf(out, ":"); | 
|  | while (x < end) { | 
|  | out += sprintf(out, ":%x", n); | 
|  | x += 2; | 
|  | n = (x[0] << 8) | x[1]; | 
|  | } | 
|  | return _out; | 
|  | } | 
|  |  | 
|  | while (x < (end - 2)) { | 
|  | out += sprintf(out, "%x:", n); | 
|  | x += 2; | 
|  | n = (x[0] << 8) | x[1]; | 
|  | if (n == 0) | 
|  | goto middle_zeros; | 
|  | } | 
|  | out += sprintf(out, "%x", n); | 
|  | return _out; | 
|  |  | 
|  | middle_zeros: | 
|  | while ((n == 0) && (x < end)) { | 
|  | x += 2; | 
|  | n = (x[0] << 8) | x[1]; | 
|  | } | 
|  | if (x == end) { | 
|  | out += sprintf(out, ":"); | 
|  | return _out; | 
|  | } | 
|  | out += sprintf(out, ":%x", n); | 
|  | while (x < (end - 2)) { | 
|  | x += 2; | 
|  | n = (x[0] << 8) | x[1]; | 
|  | out += sprintf(out, ":%x", n); | 
|  | } | 
|  | return _out; | 
|  | } |