1 /* 2 * Copyright (c) 2015 Pablo Neira Ayuso <pablo@netfilter.org> 3 * 4 * This program is free software; you can redistribute it and/or modify it 5 * under the terms of the GNU General Public License version 2 as published by 6 * the Free Software Foundation. 7 */ 8 9 #include <linux/kernel.h> 10 #include <linux/init.h> 11 #include <linux/module.h> 12 #include <linux/netlink.h> 13 #include <linux/netfilter.h> 14 #include <linux/netfilter/nf_tables.h> 15 #include <linux/ip.h> 16 #include <linux/ipv6.h> 17 #include <net/netfilter/nf_tables.h> 18 #include <net/netfilter/nf_dup_netdev.h> 19 #include <net/neighbour.h> 20 #include <net/ip.h> 21 22 struct nft_fwd_netdev { 23 enum nft_registers sreg_dev:8; 24 }; 25 26 static void nft_fwd_netdev_eval(const struct nft_expr *expr, 27 struct nft_regs *regs, 28 const struct nft_pktinfo *pkt) 29 { 30 struct nft_fwd_netdev *priv = nft_expr_priv(expr); 31 int oif = regs->data[priv->sreg_dev]; 32 33 nf_fwd_netdev_egress(pkt, oif); 34 regs->verdict.code = NF_STOLEN; 35 } 36 37 static const struct nla_policy nft_fwd_netdev_policy[NFTA_FWD_MAX + 1] = { 38 [NFTA_FWD_SREG_DEV] = { .type = NLA_U32 }, 39 [NFTA_FWD_SREG_ADDR] = { .type = NLA_U32 }, 40 [NFTA_FWD_NFPROTO] = { .type = NLA_U32 }, 41 }; 42 43 static int nft_fwd_netdev_init(const struct nft_ctx *ctx, 44 const struct nft_expr *expr, 45 const struct nlattr * const tb[]) 46 { 47 struct nft_fwd_netdev *priv = nft_expr_priv(expr); 48 49 if (tb[NFTA_FWD_SREG_DEV] == NULL) 50 return -EINVAL; 51 52 priv->sreg_dev = nft_parse_register(tb[NFTA_FWD_SREG_DEV]); 53 return nft_validate_register_load(priv->sreg_dev, sizeof(int)); 54 } 55 56 static const struct nft_expr_ops nft_fwd_netdev_ingress_ops; 57 58 static int nft_fwd_netdev_dump(struct sk_buff *skb, const struct nft_expr *expr) 59 { 60 struct nft_fwd_netdev *priv = nft_expr_priv(expr); 61 62 if (nft_dump_register(skb, NFTA_FWD_SREG_DEV, priv->sreg_dev)) 63 goto nla_put_failure; 64 65 return 0; 66 67 nla_put_failure: 68 return -1; 69 } 70 71 struct nft_fwd_neigh { 72 enum nft_registers sreg_dev:8; 73 enum nft_registers sreg_addr:8; 74 u8 nfproto; 75 }; 76 77 static void nft_fwd_neigh_eval(const struct nft_expr *expr, 78 struct nft_regs *regs, 79 const struct nft_pktinfo *pkt) 80 { 81 struct nft_fwd_neigh *priv = nft_expr_priv(expr); 82 void *addr = ®s->data[priv->sreg_addr]; 83 int oif = regs->data[priv->sreg_dev]; 84 unsigned int verdict = NF_STOLEN; 85 struct sk_buff *skb = pkt->skb; 86 struct net_device *dev; 87 int neigh_table; 88 89 switch (priv->nfproto) { 90 case NFPROTO_IPV4: { 91 struct iphdr *iph; 92 93 if (skb->protocol != htons(ETH_P_IP)) { 94 verdict = NFT_BREAK; 95 goto out; 96 } 97 if (skb_try_make_writable(skb, sizeof(*iph))) { 98 verdict = NF_DROP; 99 goto out; 100 } 101 iph = ip_hdr(skb); 102 ip_decrease_ttl(iph); 103 neigh_table = NEIGH_ARP_TABLE; 104 break; 105 } 106 case NFPROTO_IPV6: { 107 struct ipv6hdr *ip6h; 108 109 if (skb->protocol != htons(ETH_P_IPV6)) { 110 verdict = NFT_BREAK; 111 goto out; 112 } 113 if (skb_try_make_writable(skb, sizeof(*ip6h))) { 114 verdict = NF_DROP; 115 goto out; 116 } 117 ip6h = ipv6_hdr(skb); 118 ip6h->hop_limit--; 119 neigh_table = NEIGH_ND_TABLE; 120 break; 121 } 122 default: 123 verdict = NFT_BREAK; 124 goto out; 125 } 126 127 dev = dev_get_by_index_rcu(nft_net(pkt), oif); 128 if (dev == NULL) 129 return; 130 131 skb->dev = dev; 132 neigh_xmit(neigh_table, dev, addr, skb); 133 out: 134 regs->verdict.code = verdict; 135 } 136 137 static int nft_fwd_neigh_init(const struct nft_ctx *ctx, 138 const struct nft_expr *expr, 139 const struct nlattr * const tb[]) 140 { 141 struct nft_fwd_neigh *priv = nft_expr_priv(expr); 142 unsigned int addr_len; 143 int err; 144 145 if (!tb[NFTA_FWD_SREG_DEV] || 146 !tb[NFTA_FWD_SREG_ADDR] || 147 !tb[NFTA_FWD_NFPROTO]) 148 return -EINVAL; 149 150 priv->sreg_dev = nft_parse_register(tb[NFTA_FWD_SREG_DEV]); 151 priv->sreg_addr = nft_parse_register(tb[NFTA_FWD_SREG_ADDR]); 152 priv->nfproto = ntohl(nla_get_be32(tb[NFTA_FWD_NFPROTO])); 153 154 switch (priv->nfproto) { 155 case NFPROTO_IPV4: 156 addr_len = sizeof(struct in_addr); 157 break; 158 case NFPROTO_IPV6: 159 addr_len = sizeof(struct in6_addr); 160 break; 161 default: 162 return -EOPNOTSUPP; 163 } 164 165 err = nft_validate_register_load(priv->sreg_dev, sizeof(int)); 166 if (err < 0) 167 return err; 168 169 return nft_validate_register_load(priv->sreg_addr, addr_len); 170 } 171 172 static const struct nft_expr_ops nft_fwd_netdev_ingress_ops; 173 174 static int nft_fwd_neigh_dump(struct sk_buff *skb, const struct nft_expr *expr) 175 { 176 struct nft_fwd_neigh *priv = nft_expr_priv(expr); 177 178 if (nft_dump_register(skb, NFTA_FWD_SREG_DEV, priv->sreg_dev) || 179 nft_dump_register(skb, NFTA_FWD_SREG_ADDR, priv->sreg_addr) || 180 nla_put_be32(skb, NFTA_FWD_NFPROTO, htonl(priv->nfproto))) 181 goto nla_put_failure; 182 183 return 0; 184 185 nla_put_failure: 186 return -1; 187 } 188 189 static struct nft_expr_type nft_fwd_netdev_type; 190 static const struct nft_expr_ops nft_fwd_neigh_netdev_ops = { 191 .type = &nft_fwd_netdev_type, 192 .size = NFT_EXPR_SIZE(sizeof(struct nft_fwd_neigh)), 193 .eval = nft_fwd_neigh_eval, 194 .init = nft_fwd_neigh_init, 195 .dump = nft_fwd_neigh_dump, 196 }; 197 198 static const struct nft_expr_ops nft_fwd_netdev_ops = { 199 .type = &nft_fwd_netdev_type, 200 .size = NFT_EXPR_SIZE(sizeof(struct nft_fwd_netdev)), 201 .eval = nft_fwd_netdev_eval, 202 .init = nft_fwd_netdev_init, 203 .dump = nft_fwd_netdev_dump, 204 }; 205 206 static const struct nft_expr_ops * 207 nft_fwd_select_ops(const struct nft_ctx *ctx, 208 const struct nlattr * const tb[]) 209 { 210 if (tb[NFTA_FWD_SREG_ADDR]) 211 return &nft_fwd_neigh_netdev_ops; 212 if (tb[NFTA_FWD_SREG_DEV]) 213 return &nft_fwd_netdev_ops; 214 215 return ERR_PTR(-EOPNOTSUPP); 216 } 217 218 static struct nft_expr_type nft_fwd_netdev_type __read_mostly = { 219 .family = NFPROTO_NETDEV, 220 .name = "fwd", 221 .select_ops = nft_fwd_select_ops, 222 .policy = nft_fwd_netdev_policy, 223 .maxattr = NFTA_FWD_MAX, 224 .owner = THIS_MODULE, 225 }; 226 227 static int __init nft_fwd_netdev_module_init(void) 228 { 229 return nft_register_expr(&nft_fwd_netdev_type); 230 } 231 232 static void __exit nft_fwd_netdev_module_exit(void) 233 { 234 nft_unregister_expr(&nft_fwd_netdev_type); 235 } 236 237 module_init(nft_fwd_netdev_module_init); 238 module_exit(nft_fwd_netdev_module_exit); 239 240 MODULE_LICENSE("GPL"); 241 MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>"); 242 MODULE_ALIAS_NFT_AF_EXPR(5, "fwd"); 243