rjw | 1f88458 | 2022-01-06 17:20:42 +0800 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (c) 2014 Chris Anderson |
| 3 | * Copyright (c) 2014 Brian Swetland |
| 4 | * |
| 5 | * Permission is hereby granted, free of charge, to any person obtaining |
| 6 | * a copy of this software and associated documentation files |
| 7 | * (the "Software"), to deal in the Software without restriction, |
| 8 | * including without limitation the rights to use, copy, modify, merge, |
| 9 | * publish, distribute, sublicense, and/or sell copies of the Software, |
| 10 | * and to permit persons to whom the Software is furnished to do so, |
| 11 | * subject to the following conditions: |
| 12 | * |
| 13 | * The above copyright notice and this permission notice shall be |
| 14 | * included in all copies or substantial portions of the Software. |
| 15 | * |
| 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| 19 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| 20 | * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| 21 | * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 23 | */ |
| 24 | |
| 25 | #include "minip-internal.h" |
| 26 | |
| 27 | #include <err.h> |
| 28 | #include <stdio.h> |
| 29 | #include <debug.h> |
| 30 | #include <endian.h> |
| 31 | #include <errno.h> |
| 32 | #include <iovec.h> |
| 33 | #include <stdlib.h> |
| 34 | #include <string.h> |
| 35 | #include <trace.h> |
| 36 | #include <malloc.h> |
| 37 | #include <list.h> |
| 38 | #include <kernel/thread.h> |
| 39 | |
| 40 | static struct list_node arp_list = LIST_INITIAL_VALUE(arp_list); |
| 41 | |
| 42 | // TODO |
| 43 | // 1. Tear endian code out into something that flips words before/after tx/rx calls |
| 44 | |
| 45 | #define LOCAL_TRACE 0 |
| 46 | static uint32_t minip_ip = IPV4_NONE; |
| 47 | static uint32_t minip_netmask = IPV4_NONE; |
| 48 | static uint32_t minip_broadcast = IPV4_BCAST; |
| 49 | static uint32_t minip_gateway = IPV4_NONE; |
| 50 | |
| 51 | static const uint8_t broadcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
| 52 | static uint8_t minip_mac[6] = {0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC}; |
| 53 | |
| 54 | static char minip_hostname[32] = ""; |
| 55 | |
| 56 | static void dump_mac_address(const uint8_t *mac); |
| 57 | static void dump_ipv4_addr(uint32_t addr); |
| 58 | |
| 59 | void minip_set_hostname(const char *name) { |
| 60 | strlcpy(minip_hostname, name, sizeof(minip_hostname)); |
| 61 | } |
| 62 | |
| 63 | const char *minip_get_hostname(void) { |
| 64 | return minip_hostname; |
| 65 | } |
| 66 | |
| 67 | static void compute_broadcast_address(void) |
| 68 | { |
| 69 | minip_broadcast = (minip_ip & minip_netmask) | (IPV4_BCAST & ~minip_netmask); |
| 70 | } |
| 71 | |
| 72 | void minip_get_macaddr(uint8_t *addr) { |
| 73 | mac_addr_copy(addr, minip_mac); |
| 74 | } |
| 75 | |
| 76 | void minip_set_macaddr(const uint8_t *addr) { |
| 77 | mac_addr_copy(minip_mac, addr); |
| 78 | } |
| 79 | |
| 80 | uint32_t minip_get_ipaddr(void) { |
| 81 | return minip_ip; |
| 82 | } |
| 83 | |
| 84 | void minip_set_ipaddr(const uint32_t addr) { |
| 85 | minip_ip = addr; |
| 86 | compute_broadcast_address(); |
| 87 | } |
| 88 | |
| 89 | void gen_random_mac_address(uint8_t *mac_addr) |
| 90 | { |
| 91 | for (size_t i = 0; i < 6; i++) { |
| 92 | mac_addr[i] = rand() & 0xff; |
| 93 | } |
| 94 | /* unicast and locally administered */ |
| 95 | mac_addr[0] &= ~(1<<0); |
| 96 | mac_addr[0] |= (1<<1); |
| 97 | } |
| 98 | |
| 99 | /* This function is called by minip to send packets */ |
| 100 | tx_func_t minip_tx_handler; |
| 101 | void *minip_tx_arg; |
| 102 | |
| 103 | void minip_init(tx_func_t tx_handler, void *tx_arg, |
| 104 | uint32_t ip, uint32_t mask, uint32_t gateway) |
| 105 | { |
| 106 | minip_tx_handler = tx_handler; |
| 107 | minip_tx_arg = tx_arg; |
| 108 | |
| 109 | minip_ip = ip; |
| 110 | minip_netmask = mask; |
| 111 | minip_gateway = gateway; |
| 112 | compute_broadcast_address(); |
| 113 | |
| 114 | arp_cache_init(); |
| 115 | net_timer_init(); |
| 116 | } |
| 117 | |
| 118 | uint16_t ipv4_payload_len(struct ipv4_hdr *pkt) |
| 119 | { |
| 120 | return (pkt->len - ((pkt->ver_ihl >> 4) * 5)); |
| 121 | } |
| 122 | |
| 123 | void minip_build_mac_hdr(struct eth_hdr *pkt, const uint8_t *dst, uint16_t type) |
| 124 | { |
| 125 | mac_addr_copy(pkt->dst_mac, dst); |
| 126 | mac_addr_copy(pkt->src_mac, minip_mac); |
| 127 | pkt->type = htons(type); |
| 128 | } |
| 129 | |
| 130 | void minip_build_ipv4_hdr(struct ipv4_hdr *ipv4, uint32_t dst, uint8_t proto, uint16_t len) |
| 131 | { |
| 132 | ipv4->ver_ihl = 0x45; |
| 133 | ipv4->dscp_ecn = 0; |
| 134 | ipv4->len = htons(20 + len); // 5 * 4 from ihl, plus payload length |
| 135 | ipv4->id = 0; |
| 136 | ipv4->flags_frags = 0x40; // no offset, no fragments |
| 137 | ipv4->ttl = 64; |
| 138 | ipv4->proto = proto; |
| 139 | ipv4->dst_addr = dst; |
| 140 | ipv4->src_addr = minip_ip; |
| 141 | |
| 142 | /* This may be unnecessary if the controller supports checksum offloading */ |
| 143 | ipv4->chksum = 0; |
| 144 | ipv4->chksum = rfc1701_chksum((uint8_t *) ipv4, sizeof(struct ipv4_hdr)); |
| 145 | } |
| 146 | |
| 147 | int send_arp_request(uint32_t addr) |
| 148 | { |
| 149 | pktbuf_t *p; |
| 150 | struct eth_hdr *eth; |
| 151 | struct arp_pkt *arp; |
| 152 | |
| 153 | if ((p = pktbuf_alloc()) == NULL) { |
| 154 | return -1; |
| 155 | } |
| 156 | |
| 157 | eth = pktbuf_prepend(p, sizeof(struct eth_hdr)); |
| 158 | arp = pktbuf_append(p, sizeof(struct arp_pkt)); |
| 159 | minip_build_mac_hdr(eth, bcast_mac, ETH_TYPE_ARP); |
| 160 | |
| 161 | arp->htype = htons(0x0001); |
| 162 | arp->ptype = htons(0x0800); |
| 163 | arp->hlen = 6; |
| 164 | arp->plen = 4; |
| 165 | arp->oper = htons(ARP_OPER_REQUEST); |
| 166 | arp->spa = minip_ip; |
| 167 | arp->tpa = addr; |
| 168 | mac_addr_copy(arp->sha, minip_mac); |
| 169 | mac_addr_copy(arp->tha, bcast_mac); |
| 170 | |
| 171 | minip_tx_handler(p); |
| 172 | return 0; |
| 173 | } |
| 174 | |
| 175 | static void handle_arp_timeout_cb(void *arg) { |
| 176 | *(bool *)arg = true; |
| 177 | } |
| 178 | |
| 179 | const uint8_t *get_dest_mac(uint32_t host) |
| 180 | { |
| 181 | uint8_t *dst_mac = NULL; |
| 182 | bool arp_timeout = false; |
| 183 | net_timer_t arp_timeout_timer; |
| 184 | |
| 185 | if (host == IPV4_BCAST) { |
| 186 | return bcast_mac; |
| 187 | } |
| 188 | |
| 189 | dst_mac = arp_cache_lookup(host); |
| 190 | if (dst_mac == NULL) { |
| 191 | send_arp_request(host); |
| 192 | memset(&arp_timeout_timer, 0, sizeof(arp_timeout_timer)); |
| 193 | net_timer_set(&arp_timeout_timer, handle_arp_timeout_cb, &arp_timeout, 100); |
| 194 | while (!arp_timeout) { |
| 195 | dst_mac = arp_cache_lookup(host); |
| 196 | if (dst_mac) { |
| 197 | net_timer_cancel(&arp_timeout_timer); |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | return dst_mac; |
| 204 | } |
| 205 | |
| 206 | status_t minip_ipv4_send(pktbuf_t *p, uint32_t dest_addr, uint8_t proto) |
| 207 | { |
| 208 | status_t ret = 0; |
| 209 | size_t data_len = p->dlen; |
| 210 | const uint8_t *dst_mac; |
| 211 | |
| 212 | struct ipv4_hdr *ip = pktbuf_prepend(p, sizeof(struct ipv4_hdr)); |
| 213 | struct eth_hdr *eth = pktbuf_prepend(p, sizeof(struct eth_hdr)); |
| 214 | |
| 215 | |
| 216 | if (dest_addr == IPV4_BCAST || dest_addr == minip_broadcast) { |
| 217 | dst_mac = bcast_mac; |
| 218 | goto ready; |
| 219 | } |
| 220 | |
| 221 | dst_mac = get_dest_mac(dest_addr); |
| 222 | if (!dst_mac) { |
| 223 | pktbuf_free(p, true); |
| 224 | ret = -EHOSTUNREACH; |
| 225 | goto err; |
| 226 | } |
| 227 | |
| 228 | ready: |
| 229 | minip_build_mac_hdr(eth, dst_mac, ETH_TYPE_IPV4); |
| 230 | minip_build_ipv4_hdr(ip, dest_addr, proto, data_len); |
| 231 | |
| 232 | minip_tx_handler(p); |
| 233 | |
| 234 | err: |
| 235 | return ret; |
| 236 | } |
| 237 | |
| 238 | /* Swap the dst/src ip addresses and send an ICMP ECHO REPLY with the same payload. |
| 239 | * According to spec the data portion doesn't matter, but ping itself validates that |
| 240 | * the payload is identical |
| 241 | */ |
| 242 | void send_ping_reply(uint32_t ipaddr, struct icmp_pkt *req, size_t reqdatalen) |
| 243 | { |
| 244 | pktbuf_t *p; |
| 245 | size_t len; |
| 246 | struct eth_hdr *eth; |
| 247 | struct ipv4_hdr *ip; |
| 248 | struct icmp_pkt *icmp; |
| 249 | |
| 250 | if ((p = pktbuf_alloc()) == NULL) { |
| 251 | return; |
| 252 | } |
| 253 | |
| 254 | icmp = pktbuf_prepend(p, sizeof(struct icmp_pkt)); |
| 255 | ip = pktbuf_prepend(p, sizeof(struct ipv4_hdr)); |
| 256 | eth = pktbuf_prepend(p, sizeof(struct eth_hdr)); |
| 257 | pktbuf_append_data(p, req->data, reqdatalen); |
| 258 | |
| 259 | len = sizeof(struct icmp_pkt) + reqdatalen; |
| 260 | |
| 261 | minip_build_mac_hdr(eth, arp_cache_lookup(ipaddr), ETH_TYPE_IPV4); |
| 262 | minip_build_ipv4_hdr(ip, ipaddr, IP_PROTO_ICMP, len); |
| 263 | |
| 264 | icmp->type = ICMP_ECHO_REPLY; |
| 265 | icmp->code = 0; |
| 266 | memcpy(icmp->hdr_data, req->hdr_data, sizeof(icmp->hdr_data)); |
| 267 | icmp->chksum = 0; |
| 268 | icmp->chksum = rfc1701_chksum((uint8_t *) icmp, len); |
| 269 | |
| 270 | minip_tx_handler(p); |
| 271 | } |
| 272 | |
| 273 | static void dump_ipv4_addr(uint32_t addr) |
| 274 | { |
| 275 | const uint8_t *a = (void *)&addr; |
| 276 | |
| 277 | printf("%hhu.%hhu.%hhu.%hhu", a[0], a[1], a[2], a[3]); |
| 278 | } |
| 279 | |
| 280 | static void dump_ipv4_packet(const struct ipv4_hdr *ip) |
| 281 | { |
| 282 | printf("IP "); |
| 283 | dump_ipv4_addr(ip->src_addr); |
| 284 | printf(" -> "); |
| 285 | dump_ipv4_addr(ip->dst_addr); |
| 286 | printf(" hlen 0x%x, prot 0x%x, cksum 0x%x, len 0x%x, ident 0x%x, frag offset 0x%x\n", |
| 287 | (ip->ver_ihl & 0xf) * 4, ip->proto, ntohs(ip->chksum), ntohs(ip->len), ntohs(ip->id), ntohs(ip->flags_frags) & 0x1fff); |
| 288 | } |
| 289 | |
| 290 | __NO_INLINE static void handle_ipv4_packet(pktbuf_t *p, const uint8_t *src_mac) |
| 291 | { |
| 292 | struct ipv4_hdr *ip; |
| 293 | |
| 294 | ip = (struct ipv4_hdr *)p->data; |
| 295 | if (p->dlen < sizeof(struct ipv4_hdr)) |
| 296 | return; |
| 297 | |
| 298 | /* print packets for us */ |
| 299 | if (LOCAL_TRACE) { |
| 300 | dump_ipv4_packet(ip); |
| 301 | } |
| 302 | |
| 303 | /* reject bad packets */ |
| 304 | if (((ip->ver_ihl >> 4) & 0xf) != 4) { |
| 305 | /* not version 4 */ |
| 306 | LTRACEF("REJECT: not version 4\n"); |
| 307 | return; |
| 308 | } |
| 309 | |
| 310 | /* do we have enough buffer to hold the full header + options? */ |
| 311 | size_t header_len = (ip->ver_ihl & 0xf) * 4; |
| 312 | if (p->dlen < header_len) { |
| 313 | LTRACEF("REJECT: not enough buffer to hold header\n"); |
| 314 | return; |
| 315 | } |
| 316 | |
| 317 | /* compute checksum */ |
| 318 | if (rfc1701_chksum((void *)ip, header_len) != 0) { |
| 319 | /* bad checksum */ |
| 320 | LTRACEF("REJECT: bad checksum\n"); |
| 321 | return; |
| 322 | } |
| 323 | |
| 324 | /* is the pkt_buf large enough to hold the length the header says the packet is? */ |
| 325 | if (htons(ip->len) > p->dlen) { |
| 326 | LTRACEF("REJECT: packet exceeds size of buffer (header %d, dlen %d)\n", htons(ip->len), p->dlen); |
| 327 | return; |
| 328 | } |
| 329 | |
| 330 | /* trim any excess bytes at the end of the packet */ |
| 331 | if (p->dlen > htons(ip->len)) { |
| 332 | pktbuf_consume_tail(p, p->dlen - htons(ip->len)); |
| 333 | } |
| 334 | |
| 335 | /* remove the header from the front of the packet_buf */ |
| 336 | if (pktbuf_consume(p, header_len) == NULL) { |
| 337 | return; |
| 338 | } |
| 339 | |
| 340 | /* the packet is good, we can use it to populate our arp cache */ |
| 341 | arp_cache_update(ip->src_addr, src_mac); |
| 342 | |
| 343 | /* see if it's for us */ |
| 344 | if (ip->dst_addr != IPV4_BCAST) { |
| 345 | if (minip_ip != IPV4_NONE && ip->dst_addr != minip_ip && ip->dst_addr != minip_broadcast) { |
| 346 | LTRACEF("REJECT: for another host\n"); |
| 347 | return; |
| 348 | } |
| 349 | } |
| 350 | |
| 351 | /* We only handle UDP and ECHO REQUEST */ |
| 352 | switch (ip->proto) { |
| 353 | case IP_PROTO_ICMP: { |
| 354 | struct icmp_pkt *icmp; |
| 355 | if ((icmp = pktbuf_consume(p, sizeof(struct icmp_pkt))) == NULL) { |
| 356 | break; |
| 357 | } |
| 358 | if (icmp->type == ICMP_ECHO_REQUEST) { |
| 359 | send_ping_reply(ip->src_addr, icmp, p->dlen); |
| 360 | } |
| 361 | } |
| 362 | break; |
| 363 | |
| 364 | case IP_PROTO_UDP: |
| 365 | udp_input(p, ip->src_addr); |
| 366 | break; |
| 367 | |
| 368 | case IP_PROTO_TCP: |
| 369 | tcp_input(p, ip->src_addr, ip->dst_addr); |
| 370 | break; |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | __NO_INLINE static int handle_arp_pkt(pktbuf_t *p) |
| 375 | { |
| 376 | struct eth_hdr *eth; |
| 377 | struct arp_pkt *arp; |
| 378 | |
| 379 | eth = (void*) (p->data - sizeof(struct eth_hdr)); |
| 380 | |
| 381 | if ((arp = pktbuf_consume(p, sizeof(struct arp_pkt))) == NULL) { |
| 382 | return -1; |
| 383 | } |
| 384 | |
| 385 | switch(ntohs(arp->oper)) { |
| 386 | case ARP_OPER_REQUEST: { |
| 387 | pktbuf_t *rp; |
| 388 | struct eth_hdr *reth; |
| 389 | struct arp_pkt *rarp; |
| 390 | |
| 391 | if (memcmp(&arp->tpa, &minip_ip, sizeof(minip_ip)) == 0) { |
| 392 | if ((rp = pktbuf_alloc()) == NULL) { |
| 393 | break; |
| 394 | } |
| 395 | |
| 396 | reth = pktbuf_prepend(rp, sizeof(struct eth_hdr)); |
| 397 | rarp = pktbuf_append(rp, sizeof(struct arp_pkt)); |
| 398 | |
| 399 | // Eth header |
| 400 | minip_build_mac_hdr(reth, eth->src_mac, ETH_TYPE_ARP); |
| 401 | |
| 402 | // ARP packet |
| 403 | rarp->oper = htons(ARP_OPER_REPLY); |
| 404 | rarp->htype = htons(0x0001); |
| 405 | rarp->ptype = htons(0x0800); |
| 406 | rarp->hlen = 6; |
| 407 | rarp->plen = 4; |
| 408 | mac_addr_copy(rarp->sha, minip_mac); |
| 409 | rarp->spa = minip_ip; |
| 410 | mac_addr_copy(rarp->tha, arp->sha); |
| 411 | rarp->tpa = arp->spa; |
| 412 | |
| 413 | minip_tx_handler(rp); |
| 414 | } |
| 415 | } |
| 416 | break; |
| 417 | |
| 418 | case ARP_OPER_REPLY: { |
| 419 | uint32_t addr; |
| 420 | memcpy(&addr, &arp->spa, sizeof(addr)); // unaligned word |
| 421 | arp_cache_update(addr, arp->sha); |
| 422 | } |
| 423 | break; |
| 424 | } |
| 425 | |
| 426 | return 0; |
| 427 | } |
| 428 | |
| 429 | static void dump_mac_address(const uint8_t *mac) |
| 430 | { |
| 431 | printf("%02x:%02x:%02x:%02x:%02x:%02x", |
| 432 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| 433 | } |
| 434 | |
| 435 | static void dump_eth_packet(const struct eth_hdr *eth) |
| 436 | { |
| 437 | printf("ETH src "); |
| 438 | dump_mac_address(eth->src_mac); |
| 439 | printf(" dst "); |
| 440 | dump_mac_address(eth->dst_mac); |
| 441 | printf(" type 0x%hx\n", htons(eth->type)); |
| 442 | } |
| 443 | |
| 444 | void minip_rx_driver_callback(pktbuf_t *p) |
| 445 | { |
| 446 | struct eth_hdr *eth; |
| 447 | |
| 448 | if ((eth = (void*) pktbuf_consume(p, sizeof(struct eth_hdr))) == NULL) { |
| 449 | return; |
| 450 | } |
| 451 | |
| 452 | if (LOCAL_TRACE) { |
| 453 | dump_eth_packet(eth); |
| 454 | } |
| 455 | |
| 456 | if (memcmp(eth->dst_mac, minip_mac, 6) != 0 && |
| 457 | memcmp(eth->dst_mac, broadcast_mac, 6) != 0) { |
| 458 | /* not for us */ |
| 459 | return; |
| 460 | } |
| 461 | |
| 462 | switch(htons(eth->type)) { |
| 463 | case ETH_TYPE_IPV4: |
| 464 | LTRACEF("ipv4 pkt\n"); |
| 465 | handle_ipv4_packet(p, eth->src_mac); |
| 466 | break; |
| 467 | |
| 468 | case ETH_TYPE_ARP: |
| 469 | LTRACEF("arp pkt\n"); |
| 470 | handle_arp_pkt(p); |
| 471 | break; |
| 472 | } |
| 473 | } |
| 474 | |
| 475 | uint32_t minip_parse_ipaddr(const char* ipaddr_str, size_t len) |
| 476 | { |
| 477 | uint8_t ip[4] = { 0, 0, 0, 0 }; |
| 478 | uint8_t pos = 0, i = 0; |
| 479 | |
| 480 | while (pos < len) { |
| 481 | char c = ipaddr_str[pos]; |
| 482 | if (c == '.') { |
| 483 | i++; |
| 484 | } else if (c == '\0') { |
| 485 | break; |
| 486 | } else { |
| 487 | ip[i] *= 10; |
| 488 | ip[i] += c - '0'; |
| 489 | } |
| 490 | pos++; |
| 491 | } |
| 492 | |
| 493 | return IPV4_PACK(ip); |
| 494 | } |
| 495 | |
| 496 | // vim: set ts=4 sw=4 expandtab: |