1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/init.h> 4 #include <linux/netlink.h> 5 #include <linux/netfilter.h> 6 #include <linux/workqueue.h> 7 #include <linux/spinlock.h> 8 #include <linux/netfilter/nf_tables.h> 9 #include <net/ip.h> /* for ipv4 options. */ 10 #include <net/netfilter/nf_tables.h> 11 #include <net/netfilter/nf_tables_core.h> 12 #include <net/netfilter/nf_conntrack_core.h> 13 #include <linux/netfilter/nf_conntrack_common.h> 14 #include <net/netfilter/nf_flow_table.h> 15 #include <net/netfilter/nf_conntrack_helper.h> 16 17 struct nft_flow_offload { 18 struct nft_flowtable *flowtable; 19 }; 20 21 static int nft_flow_route(const struct nft_pktinfo *pkt, 22 const struct nf_conn *ct, 23 struct nf_flow_route *route, 24 enum ip_conntrack_dir dir) 25 { 26 struct dst_entry *this_dst = skb_dst(pkt->skb); 27 struct dst_entry *other_dst = NULL; 28 struct flowi fl; 29 30 memset(&fl, 0, sizeof(fl)); 31 switch (nft_pf(pkt)) { 32 case NFPROTO_IPV4: 33 fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip; 34 fl.u.ip4.flowi4_oif = nft_in(pkt)->ifindex; 35 break; 36 case NFPROTO_IPV6: 37 fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6; 38 fl.u.ip6.flowi6_oif = nft_in(pkt)->ifindex; 39 break; 40 } 41 42 nf_route(nft_net(pkt), &other_dst, &fl, false, nft_pf(pkt)); 43 if (!other_dst) 44 return -ENOENT; 45 46 route->tuple[dir].dst = this_dst; 47 route->tuple[!dir].dst = other_dst; 48 49 return 0; 50 } 51 52 static bool nft_flow_offload_skip(struct sk_buff *skb) 53 { 54 struct ip_options *opt = &(IPCB(skb)->opt); 55 56 if (unlikely(opt->optlen)) 57 return true; 58 if (skb_sec_path(skb)) 59 return true; 60 61 return false; 62 } 63 64 static void nft_flow_offload_eval(const struct nft_expr *expr, 65 struct nft_regs *regs, 66 const struct nft_pktinfo *pkt) 67 { 68 struct nft_flow_offload *priv = nft_expr_priv(expr); 69 struct nf_flowtable *flowtable = &priv->flowtable->data; 70 const struct nf_conn_help *help; 71 enum ip_conntrack_info ctinfo; 72 struct nf_flow_route route; 73 struct flow_offload *flow; 74 enum ip_conntrack_dir dir; 75 struct nf_conn *ct; 76 int ret; 77 78 if (nft_flow_offload_skip(pkt->skb)) 79 goto out; 80 81 ct = nf_ct_get(pkt->skb, &ctinfo); 82 if (!ct) 83 goto out; 84 85 switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) { 86 case IPPROTO_TCP: 87 case IPPROTO_UDP: 88 break; 89 default: 90 goto out; 91 } 92 93 help = nfct_help(ct); 94 if (help) 95 goto out; 96 97 if (ctinfo == IP_CT_NEW || 98 ctinfo == IP_CT_RELATED) 99 goto out; 100 101 if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status)) 102 goto out; 103 104 dir = CTINFO2DIR(ctinfo); 105 if (nft_flow_route(pkt, ct, &route, dir) < 0) 106 goto err_flow_route; 107 108 flow = flow_offload_alloc(ct, &route); 109 if (!flow) 110 goto err_flow_alloc; 111 112 ret = flow_offload_add(flowtable, flow); 113 if (ret < 0) 114 goto err_flow_add; 115 116 return; 117 118 err_flow_add: 119 flow_offload_free(flow); 120 err_flow_alloc: 121 dst_release(route.tuple[!dir].dst); 122 err_flow_route: 123 clear_bit(IPS_OFFLOAD_BIT, &ct->status); 124 out: 125 regs->verdict.code = NFT_BREAK; 126 } 127 128 static int nft_flow_offload_validate(const struct nft_ctx *ctx, 129 const struct nft_expr *expr, 130 const struct nft_data **data) 131 { 132 unsigned int hook_mask = (1 << NF_INET_FORWARD); 133 134 return nft_chain_validate_hooks(ctx->chain, hook_mask); 135 } 136 137 static int nft_flow_offload_init(const struct nft_ctx *ctx, 138 const struct nft_expr *expr, 139 const struct nlattr * const tb[]) 140 { 141 struct nft_flow_offload *priv = nft_expr_priv(expr); 142 u8 genmask = nft_genmask_next(ctx->net); 143 struct nft_flowtable *flowtable; 144 145 if (!tb[NFTA_FLOW_TABLE_NAME]) 146 return -EINVAL; 147 148 flowtable = nft_flowtable_lookup(ctx->table, tb[NFTA_FLOW_TABLE_NAME], 149 genmask); 150 if (IS_ERR(flowtable)) 151 return PTR_ERR(flowtable); 152 153 priv->flowtable = flowtable; 154 flowtable->use++; 155 156 return nf_ct_netns_get(ctx->net, ctx->family); 157 } 158 159 static void nft_flow_offload_destroy(const struct nft_ctx *ctx, 160 const struct nft_expr *expr) 161 { 162 struct nft_flow_offload *priv = nft_expr_priv(expr); 163 164 priv->flowtable->use--; 165 nf_ct_netns_put(ctx->net, ctx->family); 166 } 167 168 static int nft_flow_offload_dump(struct sk_buff *skb, const struct nft_expr *expr) 169 { 170 struct nft_flow_offload *priv = nft_expr_priv(expr); 171 172 if (nla_put_string(skb, NFTA_FLOW_TABLE_NAME, priv->flowtable->name)) 173 goto nla_put_failure; 174 175 return 0; 176 177 nla_put_failure: 178 return -1; 179 } 180 181 static struct nft_expr_type nft_flow_offload_type; 182 static const struct nft_expr_ops nft_flow_offload_ops = { 183 .type = &nft_flow_offload_type, 184 .size = NFT_EXPR_SIZE(sizeof(struct nft_flow_offload)), 185 .eval = nft_flow_offload_eval, 186 .init = nft_flow_offload_init, 187 .destroy = nft_flow_offload_destroy, 188 .validate = nft_flow_offload_validate, 189 .dump = nft_flow_offload_dump, 190 }; 191 192 static struct nft_expr_type nft_flow_offload_type __read_mostly = { 193 .name = "flow_offload", 194 .ops = &nft_flow_offload_ops, 195 .maxattr = NFTA_FLOW_MAX, 196 .owner = THIS_MODULE, 197 }; 198 199 static int flow_offload_netdev_event(struct notifier_block *this, 200 unsigned long event, void *ptr) 201 { 202 struct net_device *dev = netdev_notifier_info_to_dev(ptr); 203 204 if (event != NETDEV_DOWN) 205 return NOTIFY_DONE; 206 207 nf_flow_table_cleanup(dev); 208 209 return NOTIFY_DONE; 210 } 211 212 static struct notifier_block flow_offload_netdev_notifier = { 213 .notifier_call = flow_offload_netdev_event, 214 }; 215 216 static int __init nft_flow_offload_module_init(void) 217 { 218 int err; 219 220 err = register_netdevice_notifier(&flow_offload_netdev_notifier); 221 if (err) 222 goto err; 223 224 err = nft_register_expr(&nft_flow_offload_type); 225 if (err < 0) 226 goto register_expr; 227 228 return 0; 229 230 register_expr: 231 unregister_netdevice_notifier(&flow_offload_netdev_notifier); 232 err: 233 return err; 234 } 235 236 static void __exit nft_flow_offload_module_exit(void) 237 { 238 nft_unregister_expr(&nft_flow_offload_type); 239 unregister_netdevice_notifier(&flow_offload_netdev_notifier); 240 } 241 242 module_init(nft_flow_offload_module_init); 243 module_exit(nft_flow_offload_module_exit); 244 245 MODULE_LICENSE("GPL"); 246 MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>"); 247 MODULE_ALIAS_NFT_EXPR("flow_offload"); 248