To make IPv6 work with learnable l2-bridge, need to process the TX-path: * Replace Source-ll-addr in Solicitation ndisc, * Replace Target-ll-addr in Advertisement ndisc No need to do anything in RX-path Signed-off-by: Dmitry Skorodumov --- drivers/net/ipvlan/ipvlan_core.c | 125 +++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 14 deletions(-) diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index fe8e59066c46..ce06a06d8a28 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c @@ -738,11 +738,117 @@ static int ipvlan_xmit_mode_l3(struct sk_buff *skb, struct net_device *dev) return ipvlan_process_outbound(skb); } +static void ipvlan_snat_patch_tx_arp(struct ipvl_dev *ipvlan, + struct sk_buff *skb) +{ + int addr_type; + struct arphdr *arph; + + arph = (struct arphdr *)ipvlan_get_L3_hdr(ipvlan->port, skb, + &addr_type); + ether_addr_copy((u8 *)(arph + 1), ipvlan->phy_dev->dev_addr); +} + +#if IS_ENABLED(CONFIG_IPV6) + +static u8 *ipvlan_search_icmp6_ll_addr(struct sk_buff *skb, u8 icmp_option) +{ + /* skb is ensured to pullable for all ipv6 payload_len by caller */ + struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct icmp6hdr *icmph = (struct icmp6hdr *)(ip6h + 1); + int curr_off = sizeof(*icmph); + int ndsize = htons(ip6h->payload_len); + + if (icmph->icmp6_type != NDISC_ROUTER_SOLICITATION) + curr_off += sizeof(struct in6_addr); + + while ((curr_off + 2) < ndsize) { + u8 *data = (u8 *)icmph + curr_off; + u32 opt_len = data[1] << 3; + + if (unlikely(opt_len == 0)) + return NULL; + + if (data[0] != icmp_option) { + curr_off += opt_len; + continue; + } + + if (unlikely(opt_len < ETH_ALEN + 2)) + return NULL; + + if (unlikely(curr_off + opt_len > ndsize)) + return NULL; + + return data + 2; + } + + return NULL; +} + +static void ipvlan_snat_patch_tx_ipv6(struct ipvl_dev *ipvlan, + struct sk_buff *skb) +{ + struct ipv6hdr *ip6h; + struct icmp6hdr *icmph; + u8 icmp_option; + u8 *lladdr; + u16 ndsize; + + if (unlikely(!pskb_may_pull(skb, sizeof(*ip6h)))) + return; + + if (ipv6_hdr(skb)->nexthdr != NEXTHDR_ICMP) + return; + + if (unlikely(!pskb_may_pull(skb, sizeof(*ip6h) + sizeof(*icmph)))) + return; + + ip6h = ipv6_hdr(skb); + icmph = (struct icmp6hdr *)(ip6h + 1); + + /* Patch Source-LL for solicitation, Target-LL for advertisement */ + if (icmph->icmp6_type == NDISC_NEIGHBOUR_SOLICITATION || + icmph->icmp6_type == NDISC_ROUTER_SOLICITATION) + icmp_option = ND_OPT_SOURCE_LL_ADDR; + else if (icmph->icmp6_type == NDISC_NEIGHBOUR_ADVERTISEMENT) + icmp_option = ND_OPT_TARGET_LL_ADDR; + else + return; + + ndsize = htons(ip6h->payload_len); + if (unlikely(!pskb_may_pull(skb, sizeof(*ip6h) + ndsize))) + return; + + lladdr = ipvlan_search_icmp6_ll_addr(skb, icmp_option); + if (!lladdr) + return; + + ether_addr_copy(lladdr, ipvlan->phy_dev->dev_addr); + + ip6h = ipv6_hdr(skb); + icmph = (struct icmp6hdr *)(ip6h + 1); + icmph->icmp6_cksum = 0; + icmph->icmp6_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, + ndsize, + IPPROTO_ICMPV6, + csum_partial(icmph, + ndsize, + 0)); + skb->ip_summed = CHECKSUM_COMPLETE; +} +#else +static void ipvlan_snat_patch_tx_ipv6(struct ipvl_dev *ipvlan, + struct sk_buff *skb) +{ +} +#endif + static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) { void *lyr3h; struct ipvl_addr *addr; - int addr_type; + int addr_type = -1; bool same_mac_addr; struct ipvl_dev *ipvlan = netdev_priv(dev); struct ethhdr *eth = skb_eth_hdr(skb); @@ -825,8 +931,6 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) } } else { /* Ok. It is a packet to outside on learnable. Fix source eth-address. */ - struct sk_buff *orig_skb = skb; - skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) return NET_XMIT_DROP; @@ -835,17 +939,10 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev) ether_addr_copy(skb_eth_hdr(skb)->h_source, ipvlan->phy_dev->dev_addr); - /* ToDo: Handle ICMPv6 for neighbours discovery.*/ - if (lyr3h && addr_type == IPVL_ARP) { - struct arphdr *arph; - /* must reparse new skb */ - if (skb != orig_skb && lyr3h && addr_type == IPVL_ARP) - lyr3h = ipvlan_get_L3_hdr(ipvlan->port, skb, - &addr_type); - arph = (struct arphdr *)lyr3h; - ether_addr_copy((u8 *)(arph + 1), - ipvlan->phy_dev->dev_addr); - } + if (addr_type == IPVL_ARP) + ipvlan_snat_patch_tx_arp(ipvlan, skb); + else if (addr_type == IPVL_ICMPV6 || addr_type == IPVL_IPV6) + ipvlan_snat_patch_tx_ipv6(ipvlan, skb); } tx_phy_dev: -- 2.25.1