1 // SPDX-License-Identifier: GPL-2.0 2 3 #include <linux/skbuff.h> 4 #include <linux/netfilter.h> 5 #include <linux/netfilter_ipv4.h> 6 #include <linux/netfilter_ipv6.h> 7 #include <linux/netfilter/nfnetlink.h> 8 #include <linux/netfilter/nf_tables.h> 9 #include <net/netfilter/nf_tables.h> 10 #include <net/netfilter/nf_tables_ipv4.h> 11 #include <net/netfilter/nf_tables_ipv6.h> 12 #include <net/route.h> 13 #include <net/ip.h> 14 15 #ifdef CONFIG_NF_TABLES_IPV4 16 static unsigned int nf_route_table_hook4(void *priv, 17 struct sk_buff *skb, 18 const struct nf_hook_state *state) 19 { 20 const struct iphdr *iph; 21 struct nft_pktinfo pkt; 22 __be32 saddr, daddr; 23 unsigned int ret; 24 u32 mark; 25 int err; 26 u8 tos; 27 28 nft_set_pktinfo(&pkt, skb, state); 29 nft_set_pktinfo_ipv4(&pkt); 30 31 mark = skb->mark; 32 iph = ip_hdr(skb); 33 saddr = iph->saddr; 34 daddr = iph->daddr; 35 tos = iph->tos; 36 37 ret = nft_do_chain(&pkt, priv); 38 if (ret == NF_ACCEPT) { 39 iph = ip_hdr(skb); 40 41 if (iph->saddr != saddr || 42 iph->daddr != daddr || 43 skb->mark != mark || 44 iph->tos != tos) { 45 err = ip_route_me_harder(state->net, state->sk, skb, RTN_UNSPEC); 46 if (err < 0) 47 ret = NF_DROP_ERR(err); 48 } 49 } 50 return ret; 51 } 52 53 static const struct nft_chain_type nft_chain_route_ipv4 = { 54 .name = "route", 55 .type = NFT_CHAIN_T_ROUTE, 56 .family = NFPROTO_IPV4, 57 .hook_mask = (1 << NF_INET_LOCAL_OUT), 58 .hooks = { 59 [NF_INET_LOCAL_OUT] = nf_route_table_hook4, 60 }, 61 }; 62 #endif 63 64 #ifdef CONFIG_NF_TABLES_IPV6 65 static unsigned int nf_route_table_hook6(void *priv, 66 struct sk_buff *skb, 67 const struct nf_hook_state *state) 68 { 69 struct in6_addr saddr, daddr; 70 struct nft_pktinfo pkt; 71 u32 mark, flowlabel; 72 unsigned int ret; 73 u8 hop_limit; 74 int err; 75 76 nft_set_pktinfo(&pkt, skb, state); 77 nft_set_pktinfo_ipv6(&pkt); 78 79 /* save source/dest address, mark, hoplimit, flowlabel, priority */ 80 memcpy(&saddr, &ipv6_hdr(skb)->saddr, sizeof(saddr)); 81 memcpy(&daddr, &ipv6_hdr(skb)->daddr, sizeof(daddr)); 82 mark = skb->mark; 83 hop_limit = ipv6_hdr(skb)->hop_limit; 84 85 /* flowlabel and prio (includes version, which shouldn't change either)*/ 86 flowlabel = *((u32 *)ipv6_hdr(skb)); 87 88 ret = nft_do_chain(&pkt, priv); 89 if (ret == NF_ACCEPT && 90 (memcmp(&ipv6_hdr(skb)->saddr, &saddr, sizeof(saddr)) || 91 memcmp(&ipv6_hdr(skb)->daddr, &daddr, sizeof(daddr)) || 92 skb->mark != mark || 93 ipv6_hdr(skb)->hop_limit != hop_limit || 94 flowlabel != *((u32 *)ipv6_hdr(skb)))) { 95 err = nf_ip6_route_me_harder(state->net, state->sk, skb); 96 if (err < 0) 97 ret = NF_DROP_ERR(err); 98 } 99 100 return ret; 101 } 102 103 static const struct nft_chain_type nft_chain_route_ipv6 = { 104 .name = "route", 105 .type = NFT_CHAIN_T_ROUTE, 106 .family = NFPROTO_IPV6, 107 .hook_mask = (1 << NF_INET_LOCAL_OUT), 108 .hooks = { 109 [NF_INET_LOCAL_OUT] = nf_route_table_hook6, 110 }, 111 }; 112 #endif 113 114 #ifdef CONFIG_NF_TABLES_INET 115 static unsigned int nf_route_table_inet(void *priv, 116 struct sk_buff *skb, 117 const struct nf_hook_state *state) 118 { 119 struct nft_pktinfo pkt; 120 121 switch (state->pf) { 122 case NFPROTO_IPV4: 123 return nf_route_table_hook4(priv, skb, state); 124 case NFPROTO_IPV6: 125 return nf_route_table_hook6(priv, skb, state); 126 default: 127 nft_set_pktinfo(&pkt, skb, state); 128 break; 129 } 130 131 return nft_do_chain(&pkt, priv); 132 } 133 134 static const struct nft_chain_type nft_chain_route_inet = { 135 .name = "route", 136 .type = NFT_CHAIN_T_ROUTE, 137 .family = NFPROTO_INET, 138 .hook_mask = (1 << NF_INET_LOCAL_OUT), 139 .hooks = { 140 [NF_INET_LOCAL_OUT] = nf_route_table_inet, 141 }, 142 }; 143 #endif 144 145 void __init nft_chain_route_init(void) 146 { 147 #ifdef CONFIG_NF_TABLES_IPV6 148 nft_register_chain_type(&nft_chain_route_ipv6); 149 #endif 150 #ifdef CONFIG_NF_TABLES_IPV4 151 nft_register_chain_type(&nft_chain_route_ipv4); 152 #endif 153 #ifdef CONFIG_NF_TABLES_INET 154 nft_register_chain_type(&nft_chain_route_inet); 155 #endif 156 } 157 158 void __exit nft_chain_route_fini(void) 159 { 160 #ifdef CONFIG_NF_TABLES_IPV6 161 nft_unregister_chain_type(&nft_chain_route_ipv6); 162 #endif 163 #ifdef CONFIG_NF_TABLES_IPV4 164 nft_unregister_chain_type(&nft_chain_route_ipv4); 165 #endif 166 #ifdef CONFIG_NF_TABLES_INET 167 nft_unregister_chain_type(&nft_chain_route_inet); 168 #endif 169 } 170