// SPDX-License-Identifier: GPL-2.0-only
      #include <linux/kernel.h>
      #include <linux/module.h>
      #include <linux/init.h>
      #include <linux/netlink.h>
      #include <linux/netfilter.h>
      #include <linux/workqueue.h>
      #include <linux/spinlock.h>
      #include <linux/netfilter/nf_conntrack_common.h>
      #include <linux/netfilter/nf_tables.h>
      #include <net/ip.h> /* for ipv4 options. */
      #include <net/netfilter/nf_tables.h>
      #include <net/netfilter/nf_tables_core.h>
      #include <net/netfilter/nf_conntrack_core.h>
      #include <net/netfilter/nf_conntrack_extend.h>
      #include <net/netfilter/nf_flow_table.h>
      
      struct nft_flow_offload {
              struct nft_flowtable        *flowtable;
      };
      
      static int nft_flow_route(const struct nft_pktinfo *pkt,
                                const struct nf_conn *ct,
                                struct nf_flow_route *route,
                                enum ip_conntrack_dir dir)
      {
              struct dst_entry *this_dst = skb_dst(pkt->skb);
              struct dst_entry *other_dst = NULL;
              struct flowi fl;
      
              memset(&fl, 0, sizeof(fl));
              switch (nft_pf(pkt)) {
              case NFPROTO_IPV4:
                      fl.u.ip4.daddr = ct->tuplehash[dir].tuple.src.u3.ip;
                      fl.u.ip4.flowi4_oif = nft_in(pkt)->ifindex;
                      break;
              case NFPROTO_IPV6:
                      fl.u.ip6.daddr = ct->tuplehash[dir].tuple.src.u3.in6;
                      fl.u.ip6.flowi6_oif = nft_in(pkt)->ifindex;
                      break;
              }
      
              nf_route(nft_net(pkt), &other_dst, &fl, false, nft_pf(pkt));
              if (!other_dst)
                      return -ENOENT;
      
              route->tuple[dir].dst                = this_dst;
              route->tuple[!dir].dst                = other_dst;
      
              return 0;
      }
      
      static bool nft_flow_offload_skip(struct sk_buff *skb, int family)
      {
              if (skb_sec_path(skb))
                      return true;
      
              if (family == NFPROTO_IPV4) {
                      const struct ip_options *opt;
      
                      opt = &(IPCB(skb)->opt);
      
                      if (unlikely(opt->optlen))
                              return true;
              }
      
              return false;
      }
      
      static void nft_flow_offload_eval(const struct nft_expr *expr,
                                        struct nft_regs *regs,
                                        const struct nft_pktinfo *pkt)
      {
              struct nft_flow_offload *priv = nft_expr_priv(expr);
              struct nf_flowtable *flowtable = &priv->flowtable->data;
              struct tcphdr _tcph, *tcph = NULL;
              enum ip_conntrack_info ctinfo;
              struct nf_flow_route route;
              struct flow_offload *flow;
              enum ip_conntrack_dir dir;
              struct nf_conn *ct;
              int ret;
      
              if (nft_flow_offload_skip(pkt->skb, nft_pf(pkt)))
                      goto out;
      
              ct = nf_ct_get(pkt->skb, &ctinfo);
              if (!ct)
                      goto out;
      
              switch (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum) {
              case IPPROTO_TCP:
                      tcph = skb_header_pointer(pkt->skb, pkt->xt.thoff,
                                                sizeof(_tcph), &_tcph);
                      if (unlikely(!tcph || tcph->fin || tcph->rst))
                              goto out;
                      break;
              case IPPROTO_UDP:
                      break;
              default:
                      goto out;
              }
      
              if (nf_ct_ext_exist(ct, NF_CT_EXT_HELPER) ||
                  ct->status & (IPS_SEQ_ADJUST | IPS_NAT_CLASH))
                      goto out;
      
              if (!nf_ct_is_confirmed(ct))
                      goto out;
      
              if (test_and_set_bit(IPS_OFFLOAD_BIT, &ct->status))
                      goto out;
      
              dir = CTINFO2DIR(ctinfo);
              if (nft_flow_route(pkt, ct, &route, dir) < 0)
                      goto err_flow_route;
      
              flow = flow_offload_alloc(ct);
              if (!flow)
                      goto err_flow_alloc;
      
              if (flow_offload_route_init(flow, &route) < 0)
                      goto err_flow_add;
      
              if (tcph) {
                      ct->proto.tcp.seen[0].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
                      ct->proto.tcp.seen[1].flags |= IP_CT_TCP_FLAG_BE_LIBERAL;
              }
      
              ret = flow_offload_add(flowtable, flow);
              if (ret < 0)
                      goto err_flow_add;
      
              dst_release(route.tuple[!dir].dst);
              return;
      
      err_flow_add:
              flow_offload_free(flow);
      err_flow_alloc:
              dst_release(route.tuple[!dir].dst);
      err_flow_route:
              clear_bit(IPS_OFFLOAD_BIT, &ct->status);
      out:
              regs->verdict.code = NFT_BREAK;
      }
      
      static int nft_flow_offload_validate(const struct