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