Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netpoll: add IPv6 support

Currently, netpoll only supports IPv4. This patch adds IPv6
support to netpoll so that we can run netconsole over IPv6 network.

Cc: David S. Miller <davem@davemloft.net>
Signed-off-by: Cong Wang <amwang@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Cong Wang and committed by
David S. Miller
b3d936f3 acb3e041

+274 -16
+38 -6
drivers/net/netconsole.c
··· 269 269 270 270 static ssize_t show_local_ip(struct netconsole_target *nt, char *buf) 271 271 { 272 - if (!nt->np.ipv6) 272 + if (nt->np.ipv6) 273 + return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.local_ip.in6); 274 + else 273 275 return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.local_ip); 274 276 } 275 277 276 278 static ssize_t show_remote_ip(struct netconsole_target *nt, char *buf) 277 279 { 278 - if (!nt->np.ipv6) 280 + if (nt->np.ipv6) 281 + return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.remote_ip.in6); 282 + else 279 283 return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.remote_ip); 280 284 } 281 285 ··· 416 412 return -EINVAL; 417 413 } 418 414 419 - if (!strnchr(buf, count, ':')) 420 - nt->np.local_ip.ip = in_aton(buf); 415 + if (strnchr(buf, count, ':')) { 416 + const char *end; 417 + if (in6_pton(buf, count, nt->np.local_ip.in6.s6_addr, -1, &end) > 0) { 418 + if (*end && *end != '\n') { 419 + printk(KERN_ERR "netconsole: invalid IPv6 address at: <%c>\n", *end); 420 + return -EINVAL; 421 + } 422 + nt->np.ipv6 = true; 423 + } else 424 + return -EINVAL; 425 + } else { 426 + if (!nt->np.ipv6) { 427 + nt->np.local_ip.ip = in_aton(buf); 428 + } else 429 + return -EINVAL; 430 + } 421 431 422 432 return strnlen(buf, count); 423 433 } ··· 447 429 return -EINVAL; 448 430 } 449 431 450 - if (!strnchr(buf, count, ':')) 451 - nt->np.remote_ip.ip = in_aton(buf); 432 + if (strnchr(buf, count, ':')) { 433 + const char *end; 434 + if (in6_pton(buf, count, nt->np.remote_ip.in6.s6_addr, -1, &end) > 0) { 435 + if (*end && *end != '\n') { 436 + printk(KERN_ERR "netconsole: invalid IPv6 address at: <%c>\n", *end); 437 + return -EINVAL; 438 + } 439 + nt->np.ipv6 = true; 440 + } else 441 + return -EINVAL; 442 + } else { 443 + if (!nt->np.ipv6) { 444 + nt->np.remote_ip.ip = in_aton(buf); 445 + } else 446 + return -EINVAL; 447 + } 452 448 453 449 return strnlen(buf, count); 454 450 }
+236 -10
net/core/netpoll.c
··· 29 29 #include <linux/if_vlan.h> 30 30 #include <net/tcp.h> 31 31 #include <net/udp.h> 32 + #include <net/addrconf.h> 33 + #include <net/ndisc.h> 34 + #include <net/ip6_checksum.h> 32 35 #include <asm/unaligned.h> 33 36 #include <trace/events/napi.h> 34 37 ··· 387 384 struct iphdr *iph; 388 385 struct ethhdr *eth; 389 386 static atomic_t ip_ident; 387 + struct ipv6hdr *ip6h; 390 388 391 389 udp_len = len + sizeof(*udph); 392 - if (!np->ipv6) 390 + if (np->ipv6) 391 + ip_len = udp_len + sizeof(*ip6h); 392 + else 393 393 ip_len = udp_len + sizeof(*iph); 394 394 395 395 total_len = ip_len + LL_RESERVED_SPACE(np->dev); ··· 412 406 udph->dest = htons(np->remote_port); 413 407 udph->len = htons(udp_len); 414 408 415 - if (!np->ipv6) { 409 + if (np->ipv6) { 410 + udph->check = 0; 411 + udph->check = csum_ipv6_magic(&np->local_ip.in6, 412 + &np->remote_ip.in6, 413 + udp_len, IPPROTO_UDP, 414 + csum_partial(udph, udp_len, 0)); 415 + if (udph->check == 0) 416 + udph->check = CSUM_MANGLED_0; 417 + 418 + skb_push(skb, sizeof(*ip6h)); 419 + skb_reset_network_header(skb); 420 + ip6h = ipv6_hdr(skb); 421 + 422 + /* ip6h->version = 6; ip6h->priority = 0; */ 423 + put_unaligned(0x60, (unsigned char *)ip6h); 424 + ip6h->flow_lbl[0] = 0; 425 + ip6h->flow_lbl[1] = 0; 426 + ip6h->flow_lbl[2] = 0; 427 + 428 + ip6h->payload_len = htons(sizeof(struct udphdr) + len); 429 + ip6h->nexthdr = IPPROTO_UDP; 430 + ip6h->hop_limit = 32; 431 + ip6h->saddr = np->local_ip.in6; 432 + ip6h->daddr = np->remote_ip.in6; 433 + 434 + eth = (struct ethhdr *) skb_push(skb, ETH_HLEN); 435 + skb_reset_mac_header(skb); 436 + skb->protocol = eth->h_proto = htons(ETH_P_IPV6); 437 + } else { 416 438 udph->check = 0; 417 439 udph->check = csum_tcpudp_magic(np->local_ip.ip, 418 440 np->remote_ip.ip, ··· 482 448 483 449 static void netpoll_neigh_reply(struct sk_buff *skb, struct netpoll_info *npinfo) 484 450 { 485 - struct arphdr *arp; 486 - unsigned char *arp_ptr; 487 - int size, type = ARPOP_REPLY, ptype = ETH_P_ARP; 451 + int size, type = ARPOP_REPLY; 488 452 __be32 sip, tip; 489 453 unsigned char *sha; 490 454 struct sk_buff *send_skb; ··· 509 477 510 478 proto = ntohs(eth_hdr(skb)->h_proto); 511 479 if (proto == ETH_P_IP) { 480 + struct arphdr *arp; 481 + unsigned char *arp_ptr; 512 482 /* No arp on this interface */ 513 483 if (skb->dev->flags & IFF_NOARP) 514 484 return; ··· 562 528 send_skb->protocol = htons(ETH_P_ARP); 563 529 564 530 /* Fill the device header for the ARP frame */ 565 - if (dev_hard_header(send_skb, skb->dev, ptype, 531 + if (dev_hard_header(send_skb, skb->dev, ETH_P_ARP, 566 532 sha, np->dev->dev_addr, 567 533 send_skb->len) < 0) { 568 534 kfree_skb(send_skb); ··· 599 565 break; 600 566 } 601 567 spin_unlock_irqrestore(&npinfo->rx_lock, flags); 568 + } else if( proto == ETH_P_IPV6) { 569 + #if IS_ENABLED(CONFIG_IPV6) 570 + struct nd_msg *msg; 571 + u8 *lladdr = NULL; 572 + struct ipv6hdr *hdr; 573 + struct icmp6hdr *icmp6h; 574 + const struct in6_addr *saddr; 575 + const struct in6_addr *daddr; 576 + struct inet6_dev *in6_dev = NULL; 577 + struct in6_addr *target; 578 + 579 + in6_dev = in6_dev_get(skb->dev); 580 + if (!in6_dev || !in6_dev->cnf.accept_ra) 581 + return; 582 + 583 + if (!pskb_may_pull(skb, skb->len)) 584 + return; 585 + 586 + msg = (struct nd_msg *)skb_transport_header(skb); 587 + 588 + __skb_push(skb, skb->data - skb_transport_header(skb)); 589 + 590 + if (ipv6_hdr(skb)->hop_limit != 255) 591 + return; 592 + if (msg->icmph.icmp6_code != 0) 593 + return; 594 + if (msg->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION) 595 + return; 596 + 597 + saddr = &ipv6_hdr(skb)->saddr; 598 + daddr = &ipv6_hdr(skb)->daddr; 599 + 600 + size = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); 601 + 602 + spin_lock_irqsave(&npinfo->rx_lock, flags); 603 + list_for_each_entry_safe(np, tmp, &npinfo->rx_np, rx) { 604 + if (memcmp(daddr, &np->local_ip, sizeof(*daddr))) 605 + continue; 606 + 607 + hlen = LL_RESERVED_SPACE(np->dev); 608 + tlen = np->dev->needed_tailroom; 609 + send_skb = find_skb(np, size + hlen + tlen, hlen); 610 + if (!send_skb) 611 + continue; 612 + 613 + send_skb->protocol = htons(ETH_P_IPV6); 614 + send_skb->dev = skb->dev; 615 + 616 + skb_reset_network_header(send_skb); 617 + skb_put(send_skb, sizeof(struct ipv6hdr)); 618 + hdr = ipv6_hdr(send_skb); 619 + 620 + *(__be32*)hdr = htonl(0x60000000); 621 + 622 + hdr->payload_len = htons(size); 623 + hdr->nexthdr = IPPROTO_ICMPV6; 624 + hdr->hop_limit = 255; 625 + hdr->saddr = *saddr; 626 + hdr->daddr = *daddr; 627 + 628 + send_skb->transport_header = send_skb->tail; 629 + skb_put(send_skb, size); 630 + 631 + icmp6h = (struct icmp6hdr *)skb_transport_header(skb); 632 + icmp6h->icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT; 633 + icmp6h->icmp6_router = 0; 634 + icmp6h->icmp6_solicited = 1; 635 + target = (struct in6_addr *)skb_transport_header(send_skb) + sizeof(struct icmp6hdr); 636 + *target = msg->target; 637 + icmp6h->icmp6_cksum = csum_ipv6_magic(saddr, daddr, size, 638 + IPPROTO_ICMPV6, 639 + csum_partial(icmp6h, 640 + size, 0)); 641 + 642 + if (dev_hard_header(send_skb, skb->dev, ETH_P_IPV6, 643 + lladdr, np->dev->dev_addr, 644 + send_skb->len) < 0) { 645 + kfree_skb(send_skb); 646 + continue; 647 + } 648 + 649 + netpoll_send_skb(np, send_skb); 650 + 651 + /* If there are several rx_hooks for the same address, 652 + we're fine by sending a single reply */ 653 + break; 654 + } 655 + spin_unlock_irqrestore(&npinfo->rx_lock, flags); 656 + #endif 602 657 } 658 + } 659 + 660 + static bool pkt_is_ns(struct sk_buff *skb) 661 + { 662 + struct nd_msg *msg; 663 + struct ipv6hdr *hdr; 664 + 665 + if (skb->protocol != htons(ETH_P_ARP)) 666 + return false; 667 + if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + sizeof(struct nd_msg))) 668 + return false; 669 + 670 + msg = (struct nd_msg *)skb_transport_header(skb); 671 + __skb_push(skb, skb->data - skb_transport_header(skb)); 672 + hdr = ipv6_hdr(skb); 673 + 674 + if (hdr->nexthdr != IPPROTO_ICMPV6) 675 + return false; 676 + if (hdr->hop_limit != 255) 677 + return false; 678 + if (msg->icmph.icmp6_code != 0) 679 + return false; 680 + if (msg->icmph.icmp6_type != NDISC_NEIGHBOUR_SOLICITATION) 681 + return false; 682 + 683 + return true; 603 684 } 604 685 605 686 int __netpoll_rx(struct sk_buff *skb, struct netpoll_info *npinfo) ··· 732 583 goto out; 733 584 734 585 /* check if netpoll clients need ARP */ 735 - if (skb->protocol == htons(ETH_P_ARP) && 736 - atomic_read(&trapped)) { 586 + if (skb->protocol == htons(ETH_P_ARP) && atomic_read(&trapped)) { 587 + skb_queue_tail(&npinfo->neigh_tx, skb); 588 + return 1; 589 + } else if (pkt_is_ns(skb) && atomic_read(&trapped)) { 737 590 skb_queue_tail(&npinfo->neigh_tx, skb); 738 591 return 1; 739 592 } ··· 802 651 ulen - sizeof(struct udphdr)); 803 652 hits++; 804 653 } 654 + } else { 655 + #if IS_ENABLED(CONFIG_IPV6) 656 + const struct ipv6hdr *ip6h; 657 + 658 + if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) 659 + goto out; 660 + ip6h = (struct ipv6hdr *)skb->data; 661 + if (ip6h->version != 6) 662 + goto out; 663 + len = ntohs(ip6h->payload_len); 664 + if (!len) 665 + goto out; 666 + if (len + sizeof(struct ipv6hdr) > skb->len) 667 + goto out; 668 + if (pskb_trim_rcsum(skb, len + sizeof(struct ipv6hdr))) 669 + goto out; 670 + ip6h = ipv6_hdr(skb); 671 + if (!pskb_may_pull(skb, sizeof(struct udphdr))) 672 + goto out; 673 + uh = udp_hdr(skb); 674 + ulen = ntohs(uh->len); 675 + if (ulen != skb->len) 676 + goto out; 677 + if (udp6_csum_init(skb, uh, IPPROTO_UDP)) 678 + goto out; 679 + list_for_each_entry_safe(np, tmp, &npinfo->rx_np, rx) { 680 + if (memcmp(&np->local_ip.in6, &ip6h->daddr, sizeof(struct in6_addr)) != 0) 681 + continue; 682 + if (memcmp(&np->remote_ip.in6, &ip6h->saddr, sizeof(struct in6_addr)) != 0) 683 + continue; 684 + if (np->local_port && np->local_port != ntohs(uh->dest)) 685 + continue; 686 + 687 + np->rx_hook(np, ntohs(uh->source), 688 + (char *)(uh+1), 689 + ulen - sizeof(struct udphdr)); 690 + hits++; 691 + } 692 + #endif 805 693 } 806 694 807 695 if (!hits) ··· 861 671 void netpoll_print_options(struct netpoll *np) 862 672 { 863 673 np_info(np, "local port %d\n", np->local_port); 864 - if (!np->ipv6) 674 + if (np->ipv6) 675 + np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); 676 + else 865 677 np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); 866 678 np_info(np, "interface '%s'\n", np->dev_name); 867 679 np_info(np, "remote port %d\n", np->remote_port); 868 - if (!np->ipv6) 680 + if (np->ipv6) 681 + np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); 682 + else 869 683 np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); 870 684 np_info(np, "remote ethernet address %pM\n", np->remote_mac); 871 685 } ··· 1113 919 np->local_ip.ip = in_dev->ifa_list->ifa_local; 1114 920 rcu_read_unlock(); 1115 921 np_info(np, "local IP %pI4\n", &np->local_ip.ip); 922 + } else { 923 + #if IS_ENABLED(CONFIG_IPV6) 924 + struct inet6_dev *idev; 925 + 926 + err = -EDESTADDRREQ; 927 + rcu_read_lock(); 928 + idev = __in6_dev_get(ndev); 929 + if (idev) { 930 + struct inet6_ifaddr *ifp; 931 + 932 + read_lock_bh(&idev->lock); 933 + list_for_each_entry(ifp, &idev->addr_list, if_list) { 934 + if (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL) 935 + continue; 936 + np->local_ip.in6 = ifp->addr; 937 + err = 0; 938 + break; 939 + } 940 + read_unlock_bh(&idev->lock); 941 + } 942 + rcu_read_unlock(); 943 + if (err) { 944 + np_err(np, "no IPv6 address for %s, aborting\n", 945 + np->dev_name); 946 + goto put; 947 + } else 948 + np_info(np, "local IPv6 %pI6c\n", &np->local_ip.in6); 949 + #else 950 + np_err(np, "IPv6 is not supported %s, aborting\n", 951 + np->dev_name); 952 + goto put; 953 + #endif 1116 954 } 1117 955 } 1118 956