unsigned int dataoff,
                     u_int8_t pf, unsigned int hooknum);
 
+       /* called by gc worker if table is full */
+       bool (*can_early_drop)(const struct nf_conn *ct);
+
        /* Print out the per-protocol part of the tuple. Return like seq_* */
        void (*print_tuple)(struct seq_file *s,
                            const struct nf_conntrack_tuple *);
 
        struct delayed_work     dwork;
        u32                     last_bucket;
        bool                    exiting;
+       bool                    early_drop;
        long                    next_gc_run;
 };
 
        return false;
 }
 
+static bool gc_worker_skip_ct(const struct nf_conn *ct)
+{
+       return !nf_ct_is_confirmed(ct) || nf_ct_is_dying(ct);
+}
+
+static bool gc_worker_can_early_drop(const struct nf_conn *ct)
+{
+       const struct nf_conntrack_l4proto *l4proto;
+
+       if (!test_bit(IPS_ASSURED_BIT, &ct->status))
+               return true;
+
+       l4proto = __nf_ct_l4proto_find(nf_ct_l3num(ct), nf_ct_protonum(ct));
+       if (l4proto->can_early_drop && l4proto->can_early_drop(ct))
+               return true;
+
+       return false;
+}
+
 static void gc_worker(struct work_struct *work)
 {
        unsigned int min_interval = max(HZ / GC_MAX_BUCKETS_DIV, 1u);
        unsigned int i, goal, buckets = 0, expired_count = 0;
+       unsigned int nf_conntrack_max95 = 0;
        struct conntrack_gc_work *gc_work;
        unsigned int ratio, scanned = 0;
        unsigned long next_run;
 
        goal = nf_conntrack_htable_size / GC_MAX_BUCKETS_DIV;
        i = gc_work->last_bucket;
+       if (gc_work->early_drop)
+               nf_conntrack_max95 = nf_conntrack_max / 100u * 95u;
 
        do {
                struct nf_conntrack_tuple_hash *h;
                        i = 0;
 
                hlist_nulls_for_each_entry_rcu(h, n, &ct_hash[i], hnnode) {
+                       struct net *net;
+
                        tmp = nf_ct_tuplehash_to_ctrack(h);
 
                        scanned++;
                                expired_count++;
                                continue;
                        }
+
+                       if (nf_conntrack_max95 == 0 || gc_worker_skip_ct(tmp))
+                               continue;
+
+                       net = nf_ct_net(tmp);
+                       if (atomic_read(&net->ct.count) < nf_conntrack_max95)
+                               continue;
+
+                       /* need to take reference to avoid possible races */
+                       if (!atomic_inc_not_zero(&tmp->ct_general.use))
+                               continue;
+
+                       if (gc_worker_skip_ct(tmp)) {
+                               nf_ct_put(tmp);
+                               continue;
+                       }
+
+                       if (gc_worker_can_early_drop(tmp))
+                               nf_ct_kill(tmp);
+
+                       nf_ct_put(tmp);
                }
 
                /* could check get_nulls_value() here and restart if ct
 
        next_run = gc_work->next_gc_run;
        gc_work->last_bucket = i;
+       gc_work->early_drop = false;
        queue_delayed_work(system_long_wq, &gc_work->dwork, next_run);
 }
 
        if (nf_conntrack_max &&
            unlikely(atomic_read(&net->ct.count) > nf_conntrack_max)) {
                if (!early_drop(net, hash)) {
+                       if (!conntrack_gc_work.early_drop)
+                               conntrack_gc_work.early_drop = true;
                        atomic_dec(&net->ct.count);
                        net_warn_ratelimited("nf_conntrack: table full, dropping packet\n");
                        return ERR_PTR(-ENOMEM);
 
        return -NF_ACCEPT;
 }
 
+static bool dccp_can_early_drop(const struct nf_conn *ct)
+{
+       switch (ct->proto.dccp.state) {
+       case CT_DCCP_CLOSEREQ:
+       case CT_DCCP_CLOSING:
+       case CT_DCCP_TIMEWAIT:
+               return true;
+       default:
+               break;
+       }
+
+       return false;
+}
+
 static void dccp_print_tuple(struct seq_file *s,
                             const struct nf_conntrack_tuple *tuple)
 {
        .packet                 = dccp_packet,
        .get_timeouts           = dccp_get_timeouts,
        .error                  = dccp_error,
+       .can_early_drop         = dccp_can_early_drop,
        .print_tuple            = dccp_print_tuple,
        .print_conntrack        = dccp_print_conntrack,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
        .packet                 = dccp_packet,
        .get_timeouts           = dccp_get_timeouts,
        .error                  = dccp_error,
+       .can_early_drop         = dccp_can_early_drop,
        .print_tuple            = dccp_print_tuple,
        .print_conntrack        = dccp_print_conntrack,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
 
        return -NF_ACCEPT;
 }
 
+static bool sctp_can_early_drop(const struct nf_conn *ct)
+{
+       switch (ct->proto.sctp.state) {
+       case SCTP_CONNTRACK_SHUTDOWN_SENT:
+       case SCTP_CONNTRACK_SHUTDOWN_RECD:
+       case SCTP_CONNTRACK_SHUTDOWN_ACK_SENT:
+               return true;
+       default:
+               break;
+       }
+
+       return false;
+}
+
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
 
 #include <linux/netfilter/nfnetlink.h>
        .get_timeouts           = sctp_get_timeouts,
        .new                    = sctp_new,
        .error                  = sctp_error,
+       .can_early_drop         = sctp_can_early_drop,
        .me                     = THIS_MODULE,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
        .to_nlattr              = sctp_to_nlattr,
        .get_timeouts           = sctp_get_timeouts,
        .new                    = sctp_new,
        .error                  = sctp_error,
+       .can_early_drop         = sctp_can_early_drop,
        .me                     = THIS_MODULE,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
        .to_nlattr              = sctp_to_nlattr,
 
        return true;
 }
 
+static bool tcp_can_early_drop(const struct nf_conn *ct)
+{
+       switch (ct->proto.tcp.state) {
+       case TCP_CONNTRACK_FIN_WAIT:
+       case TCP_CONNTRACK_LAST_ACK:
+       case TCP_CONNTRACK_TIME_WAIT:
+       case TCP_CONNTRACK_CLOSE:
+       case TCP_CONNTRACK_CLOSE_WAIT:
+               return true;
+       default:
+               break;
+       }
+
+       return false;
+}
+
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
 
 #include <linux/netfilter/nfnetlink.h>
        .get_timeouts           = tcp_get_timeouts,
        .new                    = tcp_new,
        .error                  = tcp_error,
+       .can_early_drop         = tcp_can_early_drop,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
        .to_nlattr              = tcp_to_nlattr,
        .nlattr_size            = tcp_nlattr_size,
        .get_timeouts           = tcp_get_timeouts,
        .new                    = tcp_new,
        .error                  = tcp_error,
+       .can_early_drop         = tcp_can_early_drop,
 #if IS_ENABLED(CONFIG_NF_CT_NETLINK)
        .to_nlattr              = tcp_to_nlattr,
        .nlattr_size            = tcp_nlattr_size,