17d208687SFelix Fietkau #include <linux/kernel.h> 27d208687SFelix Fietkau #include <linux/init.h> 37d208687SFelix Fietkau #include <linux/module.h> 47d208687SFelix Fietkau #include <linux/netfilter.h> 57d208687SFelix Fietkau #include <linux/rhashtable.h> 67d208687SFelix Fietkau #include <linux/ip.h> 7a908fdecSFelix Fietkau #include <linux/ipv6.h> 87d208687SFelix Fietkau #include <linux/netdevice.h> 97d208687SFelix Fietkau #include <net/ip.h> 10a908fdecSFelix Fietkau #include <net/ipv6.h> 11a908fdecSFelix Fietkau #include <net/ip6_route.h> 127d208687SFelix Fietkau #include <net/neighbour.h> 137d208687SFelix Fietkau #include <net/netfilter/nf_flow_table.h> 147d208687SFelix Fietkau /* For layer 4 checksum field offset. */ 157d208687SFelix Fietkau #include <linux/tcp.h> 167d208687SFelix Fietkau #include <linux/udp.h> 177d208687SFelix Fietkau 1833894c36SFelix Fietkau static int nf_flow_state_check(struct flow_offload *flow, int proto, 19b6f27d32SFelix Fietkau struct sk_buff *skb, unsigned int thoff) 20b6f27d32SFelix Fietkau { 21b6f27d32SFelix Fietkau struct tcphdr *tcph; 22b6f27d32SFelix Fietkau 2333894c36SFelix Fietkau if (proto != IPPROTO_TCP) 2433894c36SFelix Fietkau return 0; 2533894c36SFelix Fietkau 26b6f27d32SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*tcph))) 27b6f27d32SFelix Fietkau return -1; 28b6f27d32SFelix Fietkau 29b6f27d32SFelix Fietkau tcph = (void *)(skb_network_header(skb) + thoff); 30b6f27d32SFelix Fietkau if (unlikely(tcph->fin || tcph->rst)) { 31b6f27d32SFelix Fietkau flow_offload_teardown(flow); 32b6f27d32SFelix Fietkau return -1; 33b6f27d32SFelix Fietkau } 34b6f27d32SFelix Fietkau 35b6f27d32SFelix Fietkau return 0; 36b6f27d32SFelix Fietkau } 37b6f27d32SFelix Fietkau 387d208687SFelix Fietkau static int nf_flow_nat_ip_tcp(struct sk_buff *skb, unsigned int thoff, 397d208687SFelix Fietkau __be32 addr, __be32 new_addr) 407d208687SFelix Fietkau { 417d208687SFelix Fietkau struct tcphdr *tcph; 427d208687SFelix Fietkau 437d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) || 447d208687SFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*tcph))) 457d208687SFelix Fietkau return -1; 467d208687SFelix Fietkau 477d208687SFelix Fietkau tcph = (void *)(skb_network_header(skb) + thoff); 487d208687SFelix Fietkau inet_proto_csum_replace4(&tcph->check, skb, addr, new_addr, true); 497d208687SFelix Fietkau 507d208687SFelix Fietkau return 0; 517d208687SFelix Fietkau } 527d208687SFelix Fietkau 537d208687SFelix Fietkau static int nf_flow_nat_ip_udp(struct sk_buff *skb, unsigned int thoff, 547d208687SFelix Fietkau __be32 addr, __be32 new_addr) 557d208687SFelix Fietkau { 567d208687SFelix Fietkau struct udphdr *udph; 577d208687SFelix Fietkau 587d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*udph)) || 597d208687SFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*udph))) 607d208687SFelix Fietkau return -1; 617d208687SFelix Fietkau 627d208687SFelix Fietkau udph = (void *)(skb_network_header(skb) + thoff); 637d208687SFelix Fietkau if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { 647d208687SFelix Fietkau inet_proto_csum_replace4(&udph->check, skb, addr, 657d208687SFelix Fietkau new_addr, true); 667d208687SFelix Fietkau if (!udph->check) 677d208687SFelix Fietkau udph->check = CSUM_MANGLED_0; 687d208687SFelix Fietkau } 697d208687SFelix Fietkau 707d208687SFelix Fietkau return 0; 717d208687SFelix Fietkau } 727d208687SFelix Fietkau 737d208687SFelix Fietkau static int nf_flow_nat_ip_l4proto(struct sk_buff *skb, struct iphdr *iph, 747d208687SFelix Fietkau unsigned int thoff, __be32 addr, 757d208687SFelix Fietkau __be32 new_addr) 767d208687SFelix Fietkau { 777d208687SFelix Fietkau switch (iph->protocol) { 787d208687SFelix Fietkau case IPPROTO_TCP: 797d208687SFelix Fietkau if (nf_flow_nat_ip_tcp(skb, thoff, addr, new_addr) < 0) 807d208687SFelix Fietkau return NF_DROP; 817d208687SFelix Fietkau break; 827d208687SFelix Fietkau case IPPROTO_UDP: 837d208687SFelix Fietkau if (nf_flow_nat_ip_udp(skb, thoff, addr, new_addr) < 0) 847d208687SFelix Fietkau return NF_DROP; 857d208687SFelix Fietkau break; 867d208687SFelix Fietkau } 877d208687SFelix Fietkau 887d208687SFelix Fietkau return 0; 897d208687SFelix Fietkau } 907d208687SFelix Fietkau 917d208687SFelix Fietkau static int nf_flow_snat_ip(const struct flow_offload *flow, struct sk_buff *skb, 927d208687SFelix Fietkau struct iphdr *iph, unsigned int thoff, 937d208687SFelix Fietkau enum flow_offload_tuple_dir dir) 947d208687SFelix Fietkau { 957d208687SFelix Fietkau __be32 addr, new_addr; 967d208687SFelix Fietkau 977d208687SFelix Fietkau switch (dir) { 987d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 997d208687SFelix Fietkau addr = iph->saddr; 1007d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v4.s_addr; 1017d208687SFelix Fietkau iph->saddr = new_addr; 1027d208687SFelix Fietkau break; 1037d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 1047d208687SFelix Fietkau addr = iph->daddr; 1057d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v4.s_addr; 1067d208687SFelix Fietkau iph->daddr = new_addr; 1077d208687SFelix Fietkau break; 1087d208687SFelix Fietkau default: 1097d208687SFelix Fietkau return -1; 1107d208687SFelix Fietkau } 1117d208687SFelix Fietkau csum_replace4(&iph->check, addr, new_addr); 1127d208687SFelix Fietkau 1137d208687SFelix Fietkau return nf_flow_nat_ip_l4proto(skb, iph, thoff, addr, new_addr); 1147d208687SFelix Fietkau } 1157d208687SFelix Fietkau 1167d208687SFelix Fietkau static int nf_flow_dnat_ip(const struct flow_offload *flow, struct sk_buff *skb, 1177d208687SFelix Fietkau struct iphdr *iph, unsigned int thoff, 1187d208687SFelix Fietkau enum flow_offload_tuple_dir dir) 1197d208687SFelix Fietkau { 1207d208687SFelix Fietkau __be32 addr, new_addr; 1217d208687SFelix Fietkau 1227d208687SFelix Fietkau switch (dir) { 1237d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 1247d208687SFelix Fietkau addr = iph->daddr; 1257d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v4.s_addr; 1267d208687SFelix Fietkau iph->daddr = new_addr; 1277d208687SFelix Fietkau break; 1287d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 1297d208687SFelix Fietkau addr = iph->saddr; 1307d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v4.s_addr; 1317d208687SFelix Fietkau iph->saddr = new_addr; 1327d208687SFelix Fietkau break; 1337d208687SFelix Fietkau default: 1347d208687SFelix Fietkau return -1; 1357d208687SFelix Fietkau } 1367d208687SFelix Fietkau csum_replace4(&iph->check, addr, new_addr); 1377d208687SFelix Fietkau 1387d208687SFelix Fietkau return nf_flow_nat_ip_l4proto(skb, iph, thoff, addr, new_addr); 1397d208687SFelix Fietkau } 1407d208687SFelix Fietkau 1417d208687SFelix Fietkau static int nf_flow_nat_ip(const struct flow_offload *flow, struct sk_buff *skb, 142b6f27d32SFelix Fietkau unsigned int thoff, enum flow_offload_tuple_dir dir) 1437d208687SFelix Fietkau { 1447d208687SFelix Fietkau struct iphdr *iph = ip_hdr(skb); 1457d208687SFelix Fietkau 1467d208687SFelix Fietkau if (flow->flags & FLOW_OFFLOAD_SNAT && 1477d208687SFelix Fietkau (nf_flow_snat_port(flow, skb, thoff, iph->protocol, dir) < 0 || 1487d208687SFelix Fietkau nf_flow_snat_ip(flow, skb, iph, thoff, dir) < 0)) 1497d208687SFelix Fietkau return -1; 1507d208687SFelix Fietkau if (flow->flags & FLOW_OFFLOAD_DNAT && 1517d208687SFelix Fietkau (nf_flow_dnat_port(flow, skb, thoff, iph->protocol, dir) < 0 || 1527d208687SFelix Fietkau nf_flow_dnat_ip(flow, skb, iph, thoff, dir) < 0)) 1537d208687SFelix Fietkau return -1; 1547d208687SFelix Fietkau 1557d208687SFelix Fietkau return 0; 1567d208687SFelix Fietkau } 1577d208687SFelix Fietkau 1587d208687SFelix Fietkau static bool ip_has_options(unsigned int thoff) 1597d208687SFelix Fietkau { 1607d208687SFelix Fietkau return thoff != sizeof(struct iphdr); 1617d208687SFelix Fietkau } 1627d208687SFelix Fietkau 1637d208687SFelix Fietkau static int nf_flow_tuple_ip(struct sk_buff *skb, const struct net_device *dev, 1647d208687SFelix Fietkau struct flow_offload_tuple *tuple) 1657d208687SFelix Fietkau { 1667d208687SFelix Fietkau struct flow_ports *ports; 1677d208687SFelix Fietkau unsigned int thoff; 1687d208687SFelix Fietkau struct iphdr *iph; 1697d208687SFelix Fietkau 1707d208687SFelix Fietkau if (!pskb_may_pull(skb, sizeof(*iph))) 1717d208687SFelix Fietkau return -1; 1727d208687SFelix Fietkau 1737d208687SFelix Fietkau iph = ip_hdr(skb); 1747d208687SFelix Fietkau thoff = iph->ihl * 4; 1757d208687SFelix Fietkau 1767d208687SFelix Fietkau if (ip_is_fragment(iph) || 1777d208687SFelix Fietkau unlikely(ip_has_options(thoff))) 1787d208687SFelix Fietkau return -1; 1797d208687SFelix Fietkau 1807d208687SFelix Fietkau if (iph->protocol != IPPROTO_TCP && 1817d208687SFelix Fietkau iph->protocol != IPPROTO_UDP) 1827d208687SFelix Fietkau return -1; 1837d208687SFelix Fietkau 1847d208687SFelix Fietkau thoff = iph->ihl * 4; 1857d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*ports))) 1867d208687SFelix Fietkau return -1; 1877d208687SFelix Fietkau 1887d208687SFelix Fietkau ports = (struct flow_ports *)(skb_network_header(skb) + thoff); 1897d208687SFelix Fietkau 1907d208687SFelix Fietkau tuple->src_v4.s_addr = iph->saddr; 1917d208687SFelix Fietkau tuple->dst_v4.s_addr = iph->daddr; 1927d208687SFelix Fietkau tuple->src_port = ports->source; 1937d208687SFelix Fietkau tuple->dst_port = ports->dest; 1947d208687SFelix Fietkau tuple->l3proto = AF_INET; 1957d208687SFelix Fietkau tuple->l4proto = iph->protocol; 1967d208687SFelix Fietkau tuple->iifidx = dev->ifindex; 1977d208687SFelix Fietkau 1987d208687SFelix Fietkau return 0; 1997d208687SFelix Fietkau } 2007d208687SFelix Fietkau 2017d208687SFelix Fietkau /* Based on ip_exceeds_mtu(). */ 2027d208687SFelix Fietkau static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu) 2037d208687SFelix Fietkau { 2047d208687SFelix Fietkau if (skb->len <= mtu) 2057d208687SFelix Fietkau return false; 2067d208687SFelix Fietkau 2077d208687SFelix Fietkau if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu)) 2087d208687SFelix Fietkau return false; 2097d208687SFelix Fietkau 2107d208687SFelix Fietkau return true; 2117d208687SFelix Fietkau } 2127d208687SFelix Fietkau 2137d208687SFelix Fietkau unsigned int 2147d208687SFelix Fietkau nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, 2157d208687SFelix Fietkau const struct nf_hook_state *state) 2167d208687SFelix Fietkau { 2177d208687SFelix Fietkau struct flow_offload_tuple_rhash *tuplehash; 2187d208687SFelix Fietkau struct nf_flowtable *flow_table = priv; 2197d208687SFelix Fietkau struct flow_offload_tuple tuple = {}; 2207d208687SFelix Fietkau enum flow_offload_tuple_dir dir; 2217d208687SFelix Fietkau struct flow_offload *flow; 2227d208687SFelix Fietkau struct net_device *outdev; 2232a79fd39SJason A. Donenfeld struct rtable *rt; 224b6f27d32SFelix Fietkau unsigned int thoff; 2257d208687SFelix Fietkau struct iphdr *iph; 2267d208687SFelix Fietkau __be32 nexthop; 2277d208687SFelix Fietkau 2287d208687SFelix Fietkau if (skb->protocol != htons(ETH_P_IP)) 2297d208687SFelix Fietkau return NF_ACCEPT; 2307d208687SFelix Fietkau 2317d208687SFelix Fietkau if (nf_flow_tuple_ip(skb, state->in, &tuple) < 0) 2327d208687SFelix Fietkau return NF_ACCEPT; 2337d208687SFelix Fietkau 2347d208687SFelix Fietkau tuplehash = flow_offload_lookup(flow_table, &tuple); 2357d208687SFelix Fietkau if (tuplehash == NULL) 2367d208687SFelix Fietkau return NF_ACCEPT; 2377d208687SFelix Fietkau 2387d208687SFelix Fietkau outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx); 2397d208687SFelix Fietkau if (!outdev) 2407d208687SFelix Fietkau return NF_ACCEPT; 2417d208687SFelix Fietkau 2427d208687SFelix Fietkau dir = tuplehash->tuple.dir; 2437d208687SFelix Fietkau flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); 2442a79fd39SJason A. Donenfeld rt = (struct rtable *)flow->tuplehash[dir].tuple.dst_cache; 2457d208687SFelix Fietkau 2463aeb51d7SFelix Fietkau if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)) && 2473aeb51d7SFelix Fietkau (ip_hdr(skb)->frag_off & htons(IP_DF)) != 0) 2487d208687SFelix Fietkau return NF_ACCEPT; 2497d208687SFelix Fietkau 2507d208687SFelix Fietkau if (skb_try_make_writable(skb, sizeof(*iph))) 2517d208687SFelix Fietkau return NF_DROP; 2527d208687SFelix Fietkau 253b6f27d32SFelix Fietkau thoff = ip_hdr(skb)->ihl * 4; 25433894c36SFelix Fietkau if (nf_flow_state_check(flow, ip_hdr(skb)->protocol, skb, thoff)) 255b6f27d32SFelix Fietkau return NF_ACCEPT; 256b6f27d32SFelix Fietkau 25728c5ed2fSTaehee Yoo if (nf_flow_nat_ip(flow, skb, thoff, dir) < 0) 2587d208687SFelix Fietkau return NF_DROP; 2597d208687SFelix Fietkau 2607d208687SFelix Fietkau flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT; 2617d208687SFelix Fietkau iph = ip_hdr(skb); 2627d208687SFelix Fietkau ip_decrease_ttl(iph); 2637d208687SFelix Fietkau 2647d208687SFelix Fietkau skb->dev = outdev; 2657d208687SFelix Fietkau nexthop = rt_nexthop(rt, flow->tuplehash[!dir].tuple.src_v4.s_addr); 2662a79fd39SJason A. Donenfeld skb_dst_set_noref(skb, &rt->dst); 2677d208687SFelix Fietkau neigh_xmit(NEIGH_ARP_TABLE, outdev, &nexthop, skb); 2687d208687SFelix Fietkau 2697d208687SFelix Fietkau return NF_STOLEN; 2707d208687SFelix Fietkau } 2717d208687SFelix Fietkau EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); 272a908fdecSFelix Fietkau 273a908fdecSFelix Fietkau static int nf_flow_nat_ipv6_tcp(struct sk_buff *skb, unsigned int thoff, 274a908fdecSFelix Fietkau struct in6_addr *addr, 275a908fdecSFelix Fietkau struct in6_addr *new_addr) 276a908fdecSFelix Fietkau { 277a908fdecSFelix Fietkau struct tcphdr *tcph; 278a908fdecSFelix Fietkau 279a908fdecSFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) || 280a908fdecSFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*tcph))) 281a908fdecSFelix Fietkau return -1; 282a908fdecSFelix Fietkau 283a908fdecSFelix Fietkau tcph = (void *)(skb_network_header(skb) + thoff); 284a908fdecSFelix Fietkau inet_proto_csum_replace16(&tcph->check, skb, addr->s6_addr32, 285a908fdecSFelix Fietkau new_addr->s6_addr32, true); 286a908fdecSFelix Fietkau 287a908fdecSFelix Fietkau return 0; 288a908fdecSFelix Fietkau } 289a908fdecSFelix Fietkau 290a908fdecSFelix Fietkau static int nf_flow_nat_ipv6_udp(struct sk_buff *skb, unsigned int thoff, 291a908fdecSFelix Fietkau struct in6_addr *addr, 292a908fdecSFelix Fietkau struct in6_addr *new_addr) 293a908fdecSFelix Fietkau { 294a908fdecSFelix Fietkau struct udphdr *udph; 295a908fdecSFelix Fietkau 296a908fdecSFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*udph)) || 297a908fdecSFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*udph))) 298a908fdecSFelix Fietkau return -1; 299a908fdecSFelix Fietkau 300a908fdecSFelix Fietkau udph = (void *)(skb_network_header(skb) + thoff); 301a908fdecSFelix Fietkau if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { 302a908fdecSFelix Fietkau inet_proto_csum_replace16(&udph->check, skb, addr->s6_addr32, 303a908fdecSFelix Fietkau new_addr->s6_addr32, true); 304a908fdecSFelix Fietkau if (!udph->check) 305a908fdecSFelix Fietkau udph->check = CSUM_MANGLED_0; 306a908fdecSFelix Fietkau } 307a908fdecSFelix Fietkau 308a908fdecSFelix Fietkau return 0; 309a908fdecSFelix Fietkau } 310a908fdecSFelix Fietkau 311a908fdecSFelix Fietkau static int nf_flow_nat_ipv6_l4proto(struct sk_buff *skb, struct ipv6hdr *ip6h, 312a908fdecSFelix Fietkau unsigned int thoff, struct in6_addr *addr, 313a908fdecSFelix Fietkau struct in6_addr *new_addr) 314a908fdecSFelix Fietkau { 315a908fdecSFelix Fietkau switch (ip6h->nexthdr) { 316a908fdecSFelix Fietkau case IPPROTO_TCP: 317a908fdecSFelix Fietkau if (nf_flow_nat_ipv6_tcp(skb, thoff, addr, new_addr) < 0) 318a908fdecSFelix Fietkau return NF_DROP; 319a908fdecSFelix Fietkau break; 320a908fdecSFelix Fietkau case IPPROTO_UDP: 321a908fdecSFelix Fietkau if (nf_flow_nat_ipv6_udp(skb, thoff, addr, new_addr) < 0) 322a908fdecSFelix Fietkau return NF_DROP; 323a908fdecSFelix Fietkau break; 324a908fdecSFelix Fietkau } 325a908fdecSFelix Fietkau 326a908fdecSFelix Fietkau return 0; 327a908fdecSFelix Fietkau } 328a908fdecSFelix Fietkau 329a908fdecSFelix Fietkau static int nf_flow_snat_ipv6(const struct flow_offload *flow, 330a908fdecSFelix Fietkau struct sk_buff *skb, struct ipv6hdr *ip6h, 331a908fdecSFelix Fietkau unsigned int thoff, 332a908fdecSFelix Fietkau enum flow_offload_tuple_dir dir) 333a908fdecSFelix Fietkau { 334a908fdecSFelix Fietkau struct in6_addr addr, new_addr; 335a908fdecSFelix Fietkau 336a908fdecSFelix Fietkau switch (dir) { 337a908fdecSFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 338a908fdecSFelix Fietkau addr = ip6h->saddr; 339a908fdecSFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v6; 340a908fdecSFelix Fietkau ip6h->saddr = new_addr; 341a908fdecSFelix Fietkau break; 342a908fdecSFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 343a908fdecSFelix Fietkau addr = ip6h->daddr; 344a908fdecSFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v6; 345a908fdecSFelix Fietkau ip6h->daddr = new_addr; 346a908fdecSFelix Fietkau break; 347a908fdecSFelix Fietkau default: 348a908fdecSFelix Fietkau return -1; 349a908fdecSFelix Fietkau } 350a908fdecSFelix Fietkau 351a908fdecSFelix Fietkau return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr); 352a908fdecSFelix Fietkau } 353a908fdecSFelix Fietkau 354a908fdecSFelix Fietkau static int nf_flow_dnat_ipv6(const struct flow_offload *flow, 355a908fdecSFelix Fietkau struct sk_buff *skb, struct ipv6hdr *ip6h, 356a908fdecSFelix Fietkau unsigned int thoff, 357a908fdecSFelix Fietkau enum flow_offload_tuple_dir dir) 358a908fdecSFelix Fietkau { 359a908fdecSFelix Fietkau struct in6_addr addr, new_addr; 360a908fdecSFelix Fietkau 361a908fdecSFelix Fietkau switch (dir) { 362a908fdecSFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 363a908fdecSFelix Fietkau addr = ip6h->daddr; 364a908fdecSFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v6; 365a908fdecSFelix Fietkau ip6h->daddr = new_addr; 366a908fdecSFelix Fietkau break; 367a908fdecSFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 368a908fdecSFelix Fietkau addr = ip6h->saddr; 369a908fdecSFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v6; 370a908fdecSFelix Fietkau ip6h->saddr = new_addr; 371a908fdecSFelix Fietkau break; 372a908fdecSFelix Fietkau default: 373a908fdecSFelix Fietkau return -1; 374a908fdecSFelix Fietkau } 375a908fdecSFelix Fietkau 376a908fdecSFelix Fietkau return nf_flow_nat_ipv6_l4proto(skb, ip6h, thoff, &addr, &new_addr); 377a908fdecSFelix Fietkau } 378a908fdecSFelix Fietkau 379a908fdecSFelix Fietkau static int nf_flow_nat_ipv6(const struct flow_offload *flow, 380a908fdecSFelix Fietkau struct sk_buff *skb, 381a908fdecSFelix Fietkau enum flow_offload_tuple_dir dir) 382a908fdecSFelix Fietkau { 383a908fdecSFelix Fietkau struct ipv6hdr *ip6h = ipv6_hdr(skb); 384a908fdecSFelix Fietkau unsigned int thoff = sizeof(*ip6h); 385a908fdecSFelix Fietkau 386a908fdecSFelix Fietkau if (flow->flags & FLOW_OFFLOAD_SNAT && 387a908fdecSFelix Fietkau (nf_flow_snat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 || 388a908fdecSFelix Fietkau nf_flow_snat_ipv6(flow, skb, ip6h, thoff, dir) < 0)) 389a908fdecSFelix Fietkau return -1; 390a908fdecSFelix Fietkau if (flow->flags & FLOW_OFFLOAD_DNAT && 391a908fdecSFelix Fietkau (nf_flow_dnat_port(flow, skb, thoff, ip6h->nexthdr, dir) < 0 || 392a908fdecSFelix Fietkau nf_flow_dnat_ipv6(flow, skb, ip6h, thoff, dir) < 0)) 393a908fdecSFelix Fietkau return -1; 394a908fdecSFelix Fietkau 395a908fdecSFelix Fietkau return 0; 396a908fdecSFelix Fietkau } 397a908fdecSFelix Fietkau 398a908fdecSFelix Fietkau static int nf_flow_tuple_ipv6(struct sk_buff *skb, const struct net_device *dev, 399a908fdecSFelix Fietkau struct flow_offload_tuple *tuple) 400a908fdecSFelix Fietkau { 401a908fdecSFelix Fietkau struct flow_ports *ports; 402a908fdecSFelix Fietkau struct ipv6hdr *ip6h; 403a908fdecSFelix Fietkau unsigned int thoff; 404a908fdecSFelix Fietkau 405a908fdecSFelix Fietkau if (!pskb_may_pull(skb, sizeof(*ip6h))) 406a908fdecSFelix Fietkau return -1; 407a908fdecSFelix Fietkau 408a908fdecSFelix Fietkau ip6h = ipv6_hdr(skb); 409a908fdecSFelix Fietkau 410a908fdecSFelix Fietkau if (ip6h->nexthdr != IPPROTO_TCP && 411a908fdecSFelix Fietkau ip6h->nexthdr != IPPROTO_UDP) 412a908fdecSFelix Fietkau return -1; 413a908fdecSFelix Fietkau 414a908fdecSFelix Fietkau thoff = sizeof(*ip6h); 415a908fdecSFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*ports))) 416a908fdecSFelix Fietkau return -1; 417a908fdecSFelix Fietkau 418a908fdecSFelix Fietkau ports = (struct flow_ports *)(skb_network_header(skb) + thoff); 419a908fdecSFelix Fietkau 420a908fdecSFelix Fietkau tuple->src_v6 = ip6h->saddr; 421a908fdecSFelix Fietkau tuple->dst_v6 = ip6h->daddr; 422a908fdecSFelix Fietkau tuple->src_port = ports->source; 423a908fdecSFelix Fietkau tuple->dst_port = ports->dest; 424a908fdecSFelix Fietkau tuple->l3proto = AF_INET6; 425a908fdecSFelix Fietkau tuple->l4proto = ip6h->nexthdr; 426a908fdecSFelix Fietkau tuple->iifidx = dev->ifindex; 427a908fdecSFelix Fietkau 428a908fdecSFelix Fietkau return 0; 429a908fdecSFelix Fietkau } 430a908fdecSFelix Fietkau 431a908fdecSFelix Fietkau unsigned int 432a908fdecSFelix Fietkau nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb, 433a908fdecSFelix Fietkau const struct nf_hook_state *state) 434a908fdecSFelix Fietkau { 435a908fdecSFelix Fietkau struct flow_offload_tuple_rhash *tuplehash; 436a908fdecSFelix Fietkau struct nf_flowtable *flow_table = priv; 437a908fdecSFelix Fietkau struct flow_offload_tuple tuple = {}; 438a908fdecSFelix Fietkau enum flow_offload_tuple_dir dir; 439a908fdecSFelix Fietkau struct flow_offload *flow; 440a908fdecSFelix Fietkau struct net_device *outdev; 441a908fdecSFelix Fietkau struct in6_addr *nexthop; 442a908fdecSFelix Fietkau struct ipv6hdr *ip6h; 443a908fdecSFelix Fietkau struct rt6_info *rt; 444a908fdecSFelix Fietkau 445a908fdecSFelix Fietkau if (skb->protocol != htons(ETH_P_IPV6)) 446a908fdecSFelix Fietkau return NF_ACCEPT; 447a908fdecSFelix Fietkau 448a908fdecSFelix Fietkau if (nf_flow_tuple_ipv6(skb, state->in, &tuple) < 0) 449a908fdecSFelix Fietkau return NF_ACCEPT; 450a908fdecSFelix Fietkau 451a908fdecSFelix Fietkau tuplehash = flow_offload_lookup(flow_table, &tuple); 452a908fdecSFelix Fietkau if (tuplehash == NULL) 453a908fdecSFelix Fietkau return NF_ACCEPT; 454a908fdecSFelix Fietkau 455a908fdecSFelix Fietkau outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx); 456a908fdecSFelix Fietkau if (!outdev) 457a908fdecSFelix Fietkau return NF_ACCEPT; 458a908fdecSFelix Fietkau 459a908fdecSFelix Fietkau dir = tuplehash->tuple.dir; 460a908fdecSFelix Fietkau flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); 461a908fdecSFelix Fietkau rt = (struct rt6_info *)flow->tuplehash[dir].tuple.dst_cache; 462a908fdecSFelix Fietkau 463a908fdecSFelix Fietkau if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu))) 464a908fdecSFelix Fietkau return NF_ACCEPT; 465a908fdecSFelix Fietkau 46633894c36SFelix Fietkau if (nf_flow_state_check(flow, ipv6_hdr(skb)->nexthdr, skb, 46733894c36SFelix Fietkau sizeof(*ip6h))) 468b6f27d32SFelix Fietkau return NF_ACCEPT; 469b6f27d32SFelix Fietkau 470a908fdecSFelix Fietkau if (skb_try_make_writable(skb, sizeof(*ip6h))) 471a908fdecSFelix Fietkau return NF_DROP; 472a908fdecSFelix Fietkau 47328c5ed2fSTaehee Yoo if (nf_flow_nat_ipv6(flow, skb, dir) < 0) 474a908fdecSFelix Fietkau return NF_DROP; 475a908fdecSFelix Fietkau 476a908fdecSFelix Fietkau flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT; 477a908fdecSFelix Fietkau ip6h = ipv6_hdr(skb); 478a908fdecSFelix Fietkau ip6h->hop_limit--; 479a908fdecSFelix Fietkau 480a908fdecSFelix Fietkau skb->dev = outdev; 481a908fdecSFelix Fietkau nexthop = rt6_nexthop(rt, &flow->tuplehash[!dir].tuple.src_v6); 4822a79fd39SJason A. Donenfeld skb_dst_set_noref(skb, &rt->dst); 483a908fdecSFelix Fietkau neigh_xmit(NEIGH_ND_TABLE, outdev, nexthop, skb); 484a908fdecSFelix Fietkau 485a908fdecSFelix Fietkau return NF_STOLEN; 486a908fdecSFelix Fietkau } 487a908fdecSFelix Fietkau EXPORT_SYMBOL_GPL(nf_flow_offload_ipv6_hook); 488