1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include <linux/kernel.h> 4 #include <linux/init.h> 5 #include <linux/module.h> 6 #include <linux/netlink.h> 7 #include <linux/netfilter.h> 8 #include <linux/netfilter/nf_tables.h> 9 #include <net/netfilter/nf_tables_core.h> 10 #include <net/netfilter/nf_tables.h> 11 #include <net/netfilter/nft_fib.h> 12 13 #include <net/inet_dscp.h> 14 #include <net/ip.h> 15 #include <net/ip_fib.h> 16 #include <net/route.h> 17 18 /* don't try to find route from mcast/bcast/zeronet */ 19 static __be32 get_saddr(__be32 addr) 20 { 21 if (ipv4_is_multicast(addr) || ipv4_is_lbcast(addr) || 22 ipv4_is_zeronet(addr)) 23 return 0; 24 return addr; 25 } 26 27 void nft_fib4_eval_type(const struct nft_expr *expr, struct nft_regs *regs, 28 const struct nft_pktinfo *pkt) 29 { 30 const struct nft_fib *priv = nft_expr_priv(expr); 31 int noff = skb_network_offset(pkt->skb); 32 u32 *dst = ®s->data[priv->dreg]; 33 const struct net_device *dev = NULL; 34 struct iphdr *iph, _iph; 35 __be32 addr; 36 37 if (priv->flags & NFTA_FIB_F_IIF) 38 dev = nft_in(pkt); 39 else if (priv->flags & NFTA_FIB_F_OIF) 40 dev = nft_out(pkt); 41 42 iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph); 43 if (!iph) { 44 regs->verdict.code = NFT_BREAK; 45 return; 46 } 47 48 if (priv->flags & NFTA_FIB_F_DADDR) 49 addr = iph->daddr; 50 else 51 addr = iph->saddr; 52 53 if (priv->flags & (NFTA_FIB_F_IIF | NFTA_FIB_F_OIF)) { 54 *dst = inet_dev_addr_type(nft_net(pkt), dev, addr); 55 return; 56 } 57 58 *dst = inet_addr_type_dev_table(nft_net(pkt), pkt->skb->dev, addr); 59 } 60 EXPORT_SYMBOL_GPL(nft_fib4_eval_type); 61 62 void nft_fib4_eval(const struct nft_expr *expr, struct nft_regs *regs, 63 const struct nft_pktinfo *pkt) 64 { 65 const struct nft_fib *priv = nft_expr_priv(expr); 66 int noff = skb_network_offset(pkt->skb); 67 u32 *dest = ®s->data[priv->dreg]; 68 struct iphdr *iph, _iph; 69 struct fib_result res; 70 struct flowi4 fl4 = { 71 .flowi4_scope = RT_SCOPE_UNIVERSE, 72 .flowi4_iif = LOOPBACK_IFINDEX, 73 .flowi4_proto = pkt->tprot, 74 .flowi4_uid = sock_net_uid(nft_net(pkt), NULL), 75 }; 76 const struct net_device *oif; 77 const struct net_device *found; 78 79 if (nft_fib_can_skip(pkt)) { 80 nft_fib_store_result(dest, priv, nft_in(pkt)); 81 return; 82 } 83 84 /* 85 * Do not set flowi4_oif, it restricts results (for example, asking 86 * for oif 3 will get RTN_UNICAST result even if the daddr exits 87 * on another interface. 88 * 89 * Search results for the desired outinterface instead. 90 */ 91 if (priv->flags & NFTA_FIB_F_OIF) 92 oif = nft_out(pkt); 93 else if (priv->flags & NFTA_FIB_F_IIF) 94 oif = nft_in(pkt); 95 else 96 oif = NULL; 97 98 fl4.flowi4_l3mdev = nft_fib_l3mdev_master_ifindex_rcu(pkt, oif); 99 100 iph = skb_header_pointer(pkt->skb, noff, sizeof(_iph), &_iph); 101 if (!iph) { 102 regs->verdict.code = NFT_BREAK; 103 return; 104 } 105 106 if (ipv4_is_zeronet(iph->saddr)) { 107 if (ipv4_is_lbcast(iph->daddr) || 108 ipv4_is_local_multicast(iph->daddr)) { 109 nft_fib_store_result(dest, priv, pkt->skb->dev); 110 return; 111 } 112 } 113 114 if (priv->flags & NFTA_FIB_F_MARK) 115 fl4.flowi4_mark = pkt->skb->mark; 116 117 fl4.flowi4_tos = inet_dscp_to_dsfield(ip4h_dscp(iph)); 118 119 if (priv->flags & NFTA_FIB_F_DADDR) { 120 fl4.daddr = iph->daddr; 121 fl4.saddr = get_saddr(iph->saddr); 122 } else { 123 if (nft_hook(pkt) == NF_INET_FORWARD && 124 priv->flags & NFTA_FIB_F_IIF) 125 fl4.flowi4_iif = nft_out(pkt)->ifindex; 126 127 fl4.daddr = iph->saddr; 128 fl4.saddr = get_saddr(iph->daddr); 129 } 130 131 *dest = 0; 132 133 if (fib_lookup(nft_net(pkt), &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE)) 134 return; 135 136 switch (res.type) { 137 case RTN_UNICAST: 138 break; 139 case RTN_LOCAL: /* Should not see RTN_LOCAL here */ 140 return; 141 default: 142 break; 143 } 144 145 if (!oif) { 146 found = FIB_RES_DEV(res); 147 } else { 148 if (!fib_info_nh_uses_dev(res.fi, oif)) 149 return; 150 found = oif; 151 } 152 153 nft_fib_store_result(dest, priv, found); 154 } 155 EXPORT_SYMBOL_GPL(nft_fib4_eval); 156 157 static struct nft_expr_type nft_fib4_type; 158 159 static const struct nft_expr_ops nft_fib4_type_ops = { 160 .type = &nft_fib4_type, 161 .size = NFT_EXPR_SIZE(sizeof(struct nft_fib)), 162 .eval = nft_fib4_eval_type, 163 .init = nft_fib_init, 164 .dump = nft_fib_dump, 165 .validate = nft_fib_validate, 166 .reduce = nft_fib_reduce, 167 }; 168 169 static const struct nft_expr_ops nft_fib4_ops = { 170 .type = &nft_fib4_type, 171 .size = NFT_EXPR_SIZE(sizeof(struct nft_fib)), 172 .eval = nft_fib4_eval, 173 .init = nft_fib_init, 174 .dump = nft_fib_dump, 175 .validate = nft_fib_validate, 176 .reduce = nft_fib_reduce, 177 }; 178 179 static const struct nft_expr_ops * 180 nft_fib4_select_ops(const struct nft_ctx *ctx, 181 const struct nlattr * const tb[]) 182 { 183 enum nft_fib_result result; 184 185 if (!tb[NFTA_FIB_RESULT]) 186 return ERR_PTR(-EINVAL); 187 188 result = ntohl(nla_get_be32(tb[NFTA_FIB_RESULT])); 189 190 switch (result) { 191 case NFT_FIB_RESULT_OIF: 192 return &nft_fib4_ops; 193 case NFT_FIB_RESULT_OIFNAME: 194 return &nft_fib4_ops; 195 case NFT_FIB_RESULT_ADDRTYPE: 196 return &nft_fib4_type_ops; 197 default: 198 return ERR_PTR(-EOPNOTSUPP); 199 } 200 } 201 202 static struct nft_expr_type nft_fib4_type __read_mostly = { 203 .name = "fib", 204 .select_ops = nft_fib4_select_ops, 205 .policy = nft_fib_policy, 206 .maxattr = NFTA_FIB_MAX, 207 .family = NFPROTO_IPV4, 208 .owner = THIS_MODULE, 209 }; 210 211 static int __init nft_fib4_module_init(void) 212 { 213 return nft_register_expr(&nft_fib4_type); 214 } 215 216 static void __exit nft_fib4_module_exit(void) 217 { 218 nft_unregister_expr(&nft_fib4_type); 219 } 220 221 module_init(nft_fib4_module_init); 222 module_exit(nft_fib4_module_exit); 223 MODULE_LICENSE("GPL"); 224 MODULE_AUTHOR("Florian Westphal <fw@strlen.de>"); 225 MODULE_ALIAS_NFT_AF_EXPR(2, "fib"); 226 MODULE_DESCRIPTION("nftables fib / ip route lookup support"); 227