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> 77d208687SFelix Fietkau #include <linux/netdevice.h> 87d208687SFelix Fietkau #include <net/ip.h> 97d208687SFelix Fietkau #include <net/neighbour.h> 107d208687SFelix Fietkau #include <net/netfilter/nf_flow_table.h> 117d208687SFelix Fietkau /* For layer 4 checksum field offset. */ 127d208687SFelix Fietkau #include <linux/tcp.h> 137d208687SFelix Fietkau #include <linux/udp.h> 147d208687SFelix Fietkau 157d208687SFelix Fietkau static int nf_flow_nat_ip_tcp(struct sk_buff *skb, unsigned int thoff, 167d208687SFelix Fietkau __be32 addr, __be32 new_addr) 177d208687SFelix Fietkau { 187d208687SFelix Fietkau struct tcphdr *tcph; 197d208687SFelix Fietkau 207d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*tcph)) || 217d208687SFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*tcph))) 227d208687SFelix Fietkau return -1; 237d208687SFelix Fietkau 247d208687SFelix Fietkau tcph = (void *)(skb_network_header(skb) + thoff); 257d208687SFelix Fietkau inet_proto_csum_replace4(&tcph->check, skb, addr, new_addr, true); 267d208687SFelix Fietkau 277d208687SFelix Fietkau return 0; 287d208687SFelix Fietkau } 297d208687SFelix Fietkau 307d208687SFelix Fietkau static int nf_flow_nat_ip_udp(struct sk_buff *skb, unsigned int thoff, 317d208687SFelix Fietkau __be32 addr, __be32 new_addr) 327d208687SFelix Fietkau { 337d208687SFelix Fietkau struct udphdr *udph; 347d208687SFelix Fietkau 357d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*udph)) || 367d208687SFelix Fietkau skb_try_make_writable(skb, thoff + sizeof(*udph))) 377d208687SFelix Fietkau return -1; 387d208687SFelix Fietkau 397d208687SFelix Fietkau udph = (void *)(skb_network_header(skb) + thoff); 407d208687SFelix Fietkau if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) { 417d208687SFelix Fietkau inet_proto_csum_replace4(&udph->check, skb, addr, 427d208687SFelix Fietkau new_addr, true); 437d208687SFelix Fietkau if (!udph->check) 447d208687SFelix Fietkau udph->check = CSUM_MANGLED_0; 457d208687SFelix Fietkau } 467d208687SFelix Fietkau 477d208687SFelix Fietkau return 0; 487d208687SFelix Fietkau } 497d208687SFelix Fietkau 507d208687SFelix Fietkau static int nf_flow_nat_ip_l4proto(struct sk_buff *skb, struct iphdr *iph, 517d208687SFelix Fietkau unsigned int thoff, __be32 addr, 527d208687SFelix Fietkau __be32 new_addr) 537d208687SFelix Fietkau { 547d208687SFelix Fietkau switch (iph->protocol) { 557d208687SFelix Fietkau case IPPROTO_TCP: 567d208687SFelix Fietkau if (nf_flow_nat_ip_tcp(skb, thoff, addr, new_addr) < 0) 577d208687SFelix Fietkau return NF_DROP; 587d208687SFelix Fietkau break; 597d208687SFelix Fietkau case IPPROTO_UDP: 607d208687SFelix Fietkau if (nf_flow_nat_ip_udp(skb, thoff, addr, new_addr) < 0) 617d208687SFelix Fietkau return NF_DROP; 627d208687SFelix Fietkau break; 637d208687SFelix Fietkau } 647d208687SFelix Fietkau 657d208687SFelix Fietkau return 0; 667d208687SFelix Fietkau } 677d208687SFelix Fietkau 687d208687SFelix Fietkau static int nf_flow_snat_ip(const struct flow_offload *flow, struct sk_buff *skb, 697d208687SFelix Fietkau struct iphdr *iph, unsigned int thoff, 707d208687SFelix Fietkau enum flow_offload_tuple_dir dir) 717d208687SFelix Fietkau { 727d208687SFelix Fietkau __be32 addr, new_addr; 737d208687SFelix Fietkau 747d208687SFelix Fietkau switch (dir) { 757d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 767d208687SFelix Fietkau addr = iph->saddr; 777d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.dst_v4.s_addr; 787d208687SFelix Fietkau iph->saddr = new_addr; 797d208687SFelix Fietkau break; 807d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 817d208687SFelix Fietkau addr = iph->daddr; 827d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.src_v4.s_addr; 837d208687SFelix Fietkau iph->daddr = new_addr; 847d208687SFelix Fietkau break; 857d208687SFelix Fietkau default: 867d208687SFelix Fietkau return -1; 877d208687SFelix Fietkau } 887d208687SFelix Fietkau csum_replace4(&iph->check, addr, new_addr); 897d208687SFelix Fietkau 907d208687SFelix Fietkau return nf_flow_nat_ip_l4proto(skb, iph, thoff, addr, new_addr); 917d208687SFelix Fietkau } 927d208687SFelix Fietkau 937d208687SFelix Fietkau static int nf_flow_dnat_ip(const struct flow_offload *flow, struct sk_buff *skb, 947d208687SFelix Fietkau struct iphdr *iph, unsigned int thoff, 957d208687SFelix Fietkau enum flow_offload_tuple_dir dir) 967d208687SFelix Fietkau { 977d208687SFelix Fietkau __be32 addr, new_addr; 987d208687SFelix Fietkau 997d208687SFelix Fietkau switch (dir) { 1007d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_ORIGINAL: 1017d208687SFelix Fietkau addr = iph->daddr; 1027d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.src_v4.s_addr; 1037d208687SFelix Fietkau iph->daddr = new_addr; 1047d208687SFelix Fietkau break; 1057d208687SFelix Fietkau case FLOW_OFFLOAD_DIR_REPLY: 1067d208687SFelix Fietkau addr = iph->saddr; 1077d208687SFelix Fietkau new_addr = flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.dst_v4.s_addr; 1087d208687SFelix Fietkau iph->saddr = new_addr; 1097d208687SFelix Fietkau break; 1107d208687SFelix Fietkau default: 1117d208687SFelix Fietkau return -1; 1127d208687SFelix Fietkau } 1137d208687SFelix Fietkau csum_replace4(&iph->check, addr, new_addr); 1147d208687SFelix Fietkau 1157d208687SFelix Fietkau return nf_flow_nat_ip_l4proto(skb, iph, thoff, addr, new_addr); 1167d208687SFelix Fietkau } 1177d208687SFelix Fietkau 1187d208687SFelix Fietkau static int nf_flow_nat_ip(const struct flow_offload *flow, struct sk_buff *skb, 1197d208687SFelix Fietkau enum flow_offload_tuple_dir dir) 1207d208687SFelix Fietkau { 1217d208687SFelix Fietkau struct iphdr *iph = ip_hdr(skb); 1227d208687SFelix Fietkau unsigned int thoff = iph->ihl * 4; 1237d208687SFelix Fietkau 1247d208687SFelix Fietkau if (flow->flags & FLOW_OFFLOAD_SNAT && 1257d208687SFelix Fietkau (nf_flow_snat_port(flow, skb, thoff, iph->protocol, dir) < 0 || 1267d208687SFelix Fietkau nf_flow_snat_ip(flow, skb, iph, thoff, dir) < 0)) 1277d208687SFelix Fietkau return -1; 1287d208687SFelix Fietkau if (flow->flags & FLOW_OFFLOAD_DNAT && 1297d208687SFelix Fietkau (nf_flow_dnat_port(flow, skb, thoff, iph->protocol, dir) < 0 || 1307d208687SFelix Fietkau nf_flow_dnat_ip(flow, skb, iph, thoff, dir) < 0)) 1317d208687SFelix Fietkau return -1; 1327d208687SFelix Fietkau 1337d208687SFelix Fietkau return 0; 1347d208687SFelix Fietkau } 1357d208687SFelix Fietkau 1367d208687SFelix Fietkau static bool ip_has_options(unsigned int thoff) 1377d208687SFelix Fietkau { 1387d208687SFelix Fietkau return thoff != sizeof(struct iphdr); 1397d208687SFelix Fietkau } 1407d208687SFelix Fietkau 1417d208687SFelix Fietkau static int nf_flow_tuple_ip(struct sk_buff *skb, const struct net_device *dev, 1427d208687SFelix Fietkau struct flow_offload_tuple *tuple) 1437d208687SFelix Fietkau { 1447d208687SFelix Fietkau struct flow_ports *ports; 1457d208687SFelix Fietkau unsigned int thoff; 1467d208687SFelix Fietkau struct iphdr *iph; 1477d208687SFelix Fietkau 1487d208687SFelix Fietkau if (!pskb_may_pull(skb, sizeof(*iph))) 1497d208687SFelix Fietkau return -1; 1507d208687SFelix Fietkau 1517d208687SFelix Fietkau iph = ip_hdr(skb); 1527d208687SFelix Fietkau thoff = iph->ihl * 4; 1537d208687SFelix Fietkau 1547d208687SFelix Fietkau if (ip_is_fragment(iph) || 1557d208687SFelix Fietkau unlikely(ip_has_options(thoff))) 1567d208687SFelix Fietkau return -1; 1577d208687SFelix Fietkau 1587d208687SFelix Fietkau if (iph->protocol != IPPROTO_TCP && 1597d208687SFelix Fietkau iph->protocol != IPPROTO_UDP) 1607d208687SFelix Fietkau return -1; 1617d208687SFelix Fietkau 1627d208687SFelix Fietkau thoff = iph->ihl * 4; 1637d208687SFelix Fietkau if (!pskb_may_pull(skb, thoff + sizeof(*ports))) 1647d208687SFelix Fietkau return -1; 1657d208687SFelix Fietkau 1667d208687SFelix Fietkau ports = (struct flow_ports *)(skb_network_header(skb) + thoff); 1677d208687SFelix Fietkau 1687d208687SFelix Fietkau tuple->src_v4.s_addr = iph->saddr; 1697d208687SFelix Fietkau tuple->dst_v4.s_addr = iph->daddr; 1707d208687SFelix Fietkau tuple->src_port = ports->source; 1717d208687SFelix Fietkau tuple->dst_port = ports->dest; 1727d208687SFelix Fietkau tuple->l3proto = AF_INET; 1737d208687SFelix Fietkau tuple->l4proto = iph->protocol; 1747d208687SFelix Fietkau tuple->iifidx = dev->ifindex; 1757d208687SFelix Fietkau 1767d208687SFelix Fietkau return 0; 1777d208687SFelix Fietkau } 1787d208687SFelix Fietkau 1797d208687SFelix Fietkau /* Based on ip_exceeds_mtu(). */ 1807d208687SFelix Fietkau static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu) 1817d208687SFelix Fietkau { 1827d208687SFelix Fietkau if (skb->len <= mtu) 1837d208687SFelix Fietkau return false; 1847d208687SFelix Fietkau 1857d208687SFelix Fietkau if (skb_is_gso(skb) && skb_gso_validate_network_len(skb, mtu)) 1867d208687SFelix Fietkau return false; 1877d208687SFelix Fietkau 1887d208687SFelix Fietkau return true; 1897d208687SFelix Fietkau } 1907d208687SFelix Fietkau 1917d208687SFelix Fietkau unsigned int 1927d208687SFelix Fietkau nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb, 1937d208687SFelix Fietkau const struct nf_hook_state *state) 1947d208687SFelix Fietkau { 1957d208687SFelix Fietkau struct flow_offload_tuple_rhash *tuplehash; 1967d208687SFelix Fietkau struct nf_flowtable *flow_table = priv; 1977d208687SFelix Fietkau struct flow_offload_tuple tuple = {}; 1987d208687SFelix Fietkau enum flow_offload_tuple_dir dir; 1997d208687SFelix Fietkau struct flow_offload *flow; 2007d208687SFelix Fietkau struct net_device *outdev; 2017d208687SFelix Fietkau const struct rtable *rt; 2027d208687SFelix Fietkau struct iphdr *iph; 2037d208687SFelix Fietkau __be32 nexthop; 2047d208687SFelix Fietkau 2057d208687SFelix Fietkau if (skb->protocol != htons(ETH_P_IP)) 2067d208687SFelix Fietkau return NF_ACCEPT; 2077d208687SFelix Fietkau 2087d208687SFelix Fietkau if (nf_flow_tuple_ip(skb, state->in, &tuple) < 0) 2097d208687SFelix Fietkau return NF_ACCEPT; 2107d208687SFelix Fietkau 2117d208687SFelix Fietkau tuplehash = flow_offload_lookup(flow_table, &tuple); 2127d208687SFelix Fietkau if (tuplehash == NULL) 2137d208687SFelix Fietkau return NF_ACCEPT; 2147d208687SFelix Fietkau 2157d208687SFelix Fietkau outdev = dev_get_by_index_rcu(state->net, tuplehash->tuple.oifidx); 2167d208687SFelix Fietkau if (!outdev) 2177d208687SFelix Fietkau return NF_ACCEPT; 2187d208687SFelix Fietkau 2197d208687SFelix Fietkau dir = tuplehash->tuple.dir; 2207d208687SFelix Fietkau flow = container_of(tuplehash, struct flow_offload, tuplehash[dir]); 2217d208687SFelix Fietkau rt = (const struct rtable *)flow->tuplehash[dir].tuple.dst_cache; 2227d208687SFelix Fietkau 2233aeb51d7SFelix Fietkau if (unlikely(nf_flow_exceeds_mtu(skb, flow->tuplehash[dir].tuple.mtu)) && 2243aeb51d7SFelix Fietkau (ip_hdr(skb)->frag_off & htons(IP_DF)) != 0) 2257d208687SFelix Fietkau return NF_ACCEPT; 2267d208687SFelix Fietkau 2277d208687SFelix Fietkau if (skb_try_make_writable(skb, sizeof(*iph))) 2287d208687SFelix Fietkau return NF_DROP; 2297d208687SFelix Fietkau 2307d208687SFelix Fietkau if (flow->flags & (FLOW_OFFLOAD_SNAT | FLOW_OFFLOAD_DNAT) && 2317d208687SFelix Fietkau nf_flow_nat_ip(flow, skb, dir) < 0) 2327d208687SFelix Fietkau return NF_DROP; 2337d208687SFelix Fietkau 2347d208687SFelix Fietkau flow->timeout = (u32)jiffies + NF_FLOW_TIMEOUT; 2357d208687SFelix Fietkau iph = ip_hdr(skb); 2367d208687SFelix Fietkau ip_decrease_ttl(iph); 2377d208687SFelix Fietkau 2387d208687SFelix Fietkau skb->dev = outdev; 2397d208687SFelix Fietkau nexthop = rt_nexthop(rt, flow->tuplehash[!dir].tuple.src_v4.s_addr); 2407d208687SFelix Fietkau neigh_xmit(NEIGH_ARP_TABLE, outdev, &nexthop, skb); 2417d208687SFelix Fietkau 2427d208687SFelix Fietkau return NF_STOLEN; 2437d208687SFelix Fietkau } 2447d208687SFelix Fietkau EXPORT_SYMBOL_GPL(nf_flow_offload_ip_hook); 245