diff -urp v7.1/linux/include/net/flow.h linux/include/net/flow.h --- v7.1/linux/include/net/flow.h 2026-08-10 16:34:52.000000000 +0300 +++ linux/include/net/flow.h 2026-08-10 16:29:52.000000000 +0300 @@ -91,6 +91,7 @@ struct flowi4 { #define fl4_icmp_code uli.icmpt.code #define fl4_mh_type uli.mht.type #define fl4_gre_key uli.gre_key + __be32 fl4_gw; } __attribute__((__aligned__(BITS_PER_LONG/8))); static inline void flowi4_init_output(struct flowi4 *fl4, int oif, @@ -116,6 +117,7 @@ static inline void flowi4_init_output(st fl4->fl4_dport = dport; fl4->fl4_sport = sport; fl4->flowi4_multipath_hash = 0; + fl4->fl4_gw = 0; } /* Reset some input parameters after previous lookup */ diff -urp v7.1/linux/include/net/ip_fib.h linux/include/net/ip_fib.h --- v7.1/linux/include/net/ip_fib.h 2026-08-10 16:34:58.000000000 +0300 +++ linux/include/net/ip_fib.h 2026-08-10 16:28:22.000000000 +0300 @@ -436,6 +436,8 @@ static inline bool fib4_rules_early_flow return true; } +u32 fib_result_table(struct fib_result *res); + #endif /* CONFIG_IP_MULTIPLE_TABLES */ static inline bool fib_dscp_masked_match(dscp_t dscp, const struct flowi4 *fl4) @@ -466,6 +468,8 @@ fib_validate_source_reason(struct sk_buf return SKB_NOT_DROPPED_YET; } +void fib_select_default(const struct flowi4 *flp, struct fib_result *res); + #ifdef CONFIG_IP_ROUTE_CLASSID static inline int fib_num_tclassid_users(struct net *net) { diff -urp v7.1/linux/include/net/netfilter/nf_nat.h linux/include/net/netfilter/nf_nat.h --- v7.1/linux/include/net/netfilter/nf_nat.h 2026-08-10 16:34:52.000000000 +0300 +++ linux/include/net/netfilter/nf_nat.h 2026-08-10 16:29:52.000000000 +0300 @@ -35,6 +35,11 @@ struct nf_conn_nat { #endif }; +/* Call input routing for SNAT-ed traffic */ +unsigned int ip_nat_route_input(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state); + /* Set up the info structure to map into this range. */ unsigned int nf_nat_setup_info(struct nf_conn *ct, const struct nf_nat_range2 *range, diff -urp v7.1/linux/include/net/route.h linux/include/net/route.h --- v7.1/linux/include/net/route.h 2026-08-10 16:34:52.000000000 +0300 +++ linux/include/net/route.h 2026-08-10 16:29:52.000000000 +0300 @@ -260,6 +260,10 @@ unsigned int inet_addr_type_dev_table(st void ip_rt_multicast_event(struct in_device *); int ip_rt_ioctl(struct net *, unsigned int cmd, struct rtentry *rt); void ip_rt_get_source(u8 *src, struct sk_buff *skb, struct rtable *rt); +enum skb_drop_reason ip_route_input_lookup(struct sk_buff*, __be32 dst, + __be32 src, dscp_t dscp, + struct net_device *devin, + __be32 lsrc); struct rtable *rt_dst_alloc(struct net_device *dev, unsigned int flags, u16 type, bool noxfrm); struct rtable *rt_dst_clone(struct net_device *dev, struct rtable *rt); diff -urp v7.1/linux/include/uapi/linux/rtnetlink.h linux/include/uapi/linux/rtnetlink.h --- v7.1/linux/include/uapi/linux/rtnetlink.h 2026-08-10 16:34:58.000000000 +0300 +++ linux/include/uapi/linux/rtnetlink.h 2026-08-10 16:28:22.000000000 +0300 @@ -432,9 +432,11 @@ struct rtnexthop { #define RTNH_F_LINKDOWN 16 /* carrier-down on nexthop */ #define RTNH_F_UNRESOLVED 32 /* The entry is unresolved (ipmr) */ #define RTNH_F_TRAP 64 /* Nexthop is trapping packets */ +#define RTNH_F_SUSPECT 128 /* We don't know the real state */ +#define RTNH_F_BADSTATE (RTNH_F_DEAD | RTNH_F_SUSPECT) #define RTNH_COMPARE_MASK (RTNH_F_DEAD | RTNH_F_LINKDOWN | \ - RTNH_F_OFFLOAD | RTNH_F_TRAP) + RTNH_F_OFFLOAD | RTNH_F_TRAP | RTNH_F_SUSPECT) /* Macros to handle hexthops */ diff -urp v7.1/linux/net/bridge/br_netfilter_hooks.c linux/net/bridge/br_netfilter_hooks.c --- v7.1/linux/net/bridge/br_netfilter_hooks.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/bridge/br_netfilter_hooks.c 2026-08-10 16:29:52.000000000 +0300 @@ -389,6 +389,9 @@ static int br_nf_pre_routing_finish(stru nf_bridge->frag_max_size = IPCB(skb)->frag_max_size; + /* Old skb->dst is not expected, it is lost in all cases */ + skb_dst_drop(skb); + if (nf_bridge->pkt_otherhost) { skb->pkt_type = PACKET_OTHERHOST; nf_bridge->pkt_otherhost = false; diff -urp v7.1/linux/net/core/filter.c linux/net/core/filter.c --- v7.1/linux/net/core/filter.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/core/filter.c 2026-08-10 16:29:52.000000000 +0300 @@ -6169,6 +6169,7 @@ static int bpf_ipv4_fib_lookup(struct ne fl4.fl4_sport = params->sport; fl4.fl4_dport = params->dport; fl4.flowi4_multipath_hash = 0; + fl4.fl4_gw = 0; if (flags & BPF_FIB_LOOKUP_DIRECT) { u32 tbid = l3mdev_fib_table_rcu(dev) ? : RT_TABLE_MAIN; diff -urp v7.1/linux/net/ipv4/fib_frontend.c linux/net/ipv4/fib_frontend.c --- v7.1/linux/net/ipv4/fib_frontend.c 2026-08-10 16:35:04.000000000 +0300 +++ linux/net/ipv4/fib_frontend.c 2026-08-10 16:29:52.000000000 +0300 @@ -50,6 +50,8 @@ #ifndef CONFIG_IP_MULTIPLE_TABLES +#define FIB_RES_TABLE(r) (RT_TABLE_MAIN) + static int __net_init fib4_rules_init(struct net *net) { struct fib_table *local_table, *main_table; @@ -74,6 +76,8 @@ fail: } #else +#define FIB_RES_TABLE(r) (fib_result_table(r)) + struct fib_table *fib_new_table(struct net *net, u32 id) { struct fib_table *tb, *alias = NULL; @@ -348,6 +352,9 @@ static int __fib_validate_source(struct { struct net *net = dev_net(dev); enum skb_drop_reason reason; + u32 table; + unsigned char prefixlen; + unsigned char scope; struct flow_keys flkeys; int ret, no_addr; struct fib_result res; @@ -365,6 +372,7 @@ static int __fib_validate_source(struct fl4.flowi4_flags = 0; fl4.flowi4_uid = sock_net_uid(net, NULL); fl4.flowi4_multipath_hash = 0; + fl4.fl4_gw = 0; no_addr = idev->ifa_list == NULL; @@ -402,15 +410,23 @@ static int __fib_validate_source(struct } if (no_addr) goto last_resort; - if (rpf == 1) - goto e_rpf; + table = FIB_RES_TABLE(&res); + prefixlen = res.prefixlen; + scope = res.scope; fl4.flowi4_oif = dev->ifindex; ret = 0; if (fib_lookup(net, &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE) == 0) { - if (res.type == RTN_UNICAST) + if (res.type == RTN_UNICAST && + ((table == FIB_RES_TABLE(&res) && + res.prefixlen >= prefixlen && res.scope >= scope) || + !rpf)) { ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST; + return ret; + } } + if (rpf == 1) + goto e_rpf; return ret; last_resort: @@ -1468,9 +1484,7 @@ static int fib_inetaddr_event(struct not switch (event) { case NETDEV_UP: fib_add_ifaddr(ifa); -#ifdef CONFIG_IP_ROUTE_MULTIPATH fib_sync_up(dev, RTNH_F_DEAD); -#endif atomic_inc(&net->ipv4.dev_addr_genid); rt_cache_flush(net); break; @@ -1515,9 +1529,7 @@ static int fib_netdev_event(struct notif in_dev_for_each_ifa_rtnl(ifa, in_dev) { fib_add_ifaddr(ifa); } -#ifdef CONFIG_IP_ROUTE_MULTIPATH fib_sync_up(dev, RTNH_F_DEAD); -#endif atomic_inc(&net->ipv4.dev_addr_genid); rt_cache_flush(net); break; diff -urp v7.1/linux/net/ipv4/fib_rules.c linux/net/ipv4/fib_rules.c --- v7.1/linux/net/ipv4/fib_rules.c 2026-08-10 16:34:58.000000000 +0300 +++ linux/net/ipv4/fib_rules.c 2026-08-10 16:28:22.000000000 +0300 @@ -81,6 +81,11 @@ unsigned int fib4_rules_seq_read(const s return fib_rules_seq_read(net, AF_INET); } +u32 fib_result_table(struct fib_result *res) +{ + return res->table ? res->table->tb_id : RT_TABLE_UNSPEC; +} + int __fib_lookup(struct net *net, struct flowi4 *flp, struct fib_result *res, unsigned int flags) { diff -urp v7.1/linux/net/ipv4/fib_semantics.c linux/net/ipv4/fib_semantics.c --- v7.1/linux/net/ipv4/fib_semantics.c 2026-08-10 16:35:04.000000000 +0300 +++ linux/net/ipv4/fib_semantics.c 2026-08-10 16:29:52.000000000 +0300 @@ -50,6 +50,8 @@ #include "fib_lookup.h" +DEFINE_RWLOCK(fib_nhflags_lock); + /* for_nexthops and change_nexthops only used when nexthop object * is not set in a fib_info. The logic within can reference fib_nh. */ @@ -577,35 +579,77 @@ errout: static int fib_detect_death(struct fib_info *fi, int order, struct fib_info **last_resort, int *last_idx, - int dflt) + int dflt, int *last_nhsel, + const struct flowi4 *flp) { - const struct fib_nh_common *nhc = fib_info_nhc(fi, 0); + struct fib_nh_common *nhc; struct neighbour *n; - int state = NUD_NONE; + int nhsel; + int state; + int flag, dead = 1; + + /* change_nexthops(fi) { */ + for (nhsel = 0; nhsel < fib_info_num_path(fi); nhsel++) { + nhc = fib_info_nhc(fi, nhsel); + if (flp->flowi4_oif && flp->flowi4_oif != nhc->nhc_oif) + continue; + if (flp->fl4_gw && flp->fl4_gw != nhc->nhc_gw.ipv4 && + nhc->nhc_gw.ipv4 && nhc->nhc_scope == RT_SCOPE_LINK) + continue; + if (nhc->nhc_flags & RTNH_F_DEAD) + continue; - if (likely(nhc->nhc_gw_family == AF_INET)) - n = neigh_lookup(&arp_tbl, &nhc->nhc_gw.ipv4, nhc->nhc_dev); - else if (IS_ENABLED(CONFIG_IPV6) && nhc->nhc_gw_family == AF_INET6) - n = neigh_lookup(&nd_tbl, &nhc->nhc_gw.ipv6, nhc->nhc_dev); - else - n = NULL; + flag = 0; + if (nhc->nhc_dev->flags & IFF_NOARP) { + dead = 0; + goto setfl; + } - if (n) { - state = READ_ONCE(n->nud_state); - neigh_release(n); - } else { - return 0; - } - if (state == NUD_REACHABLE) - return 0; - if ((state & NUD_VALID) && order != dflt) - return 0; - if ((state & NUD_VALID) || - (*last_idx < 0 && order > dflt && state != NUD_INCOMPLETE)) { - *last_resort = fi; - *last_idx = order; + state = NUD_NONE; + if (!nhc->nhc_gw_family || nhc->nhc_scope != RT_SCOPE_LINK || + (nhc->nhc_gw_family == AF_INET && !nhc->nhc_gw.ipv4)) + n = neigh_lookup(&arp_tbl, &flp->daddr, + nhc->nhc_dev); + else if (likely(nhc->nhc_gw_family == AF_INET)) + n = neigh_lookup(&arp_tbl, &nhc->nhc_gw.ipv4, + nhc->nhc_dev); + else if (IS_ENABLED(CONFIG_IPV6) && + nhc->nhc_gw_family == AF_INET6) + n = neigh_lookup(&nd_tbl, &nhc->nhc_gw.ipv6, + nhc->nhc_dev); + else + n = NULL; + if (n) { + state = READ_ONCE(n->nud_state); + neigh_release(n); + } + if (state == NUD_REACHABLE || + ((state & NUD_VALID) && order != dflt)) { + dead = 0; + goto setfl; + } + if (!(state & NUD_VALID)) + flag = 1; + if (!dead) + goto setfl; + if ((state & NUD_VALID) || + (*last_idx < 0 && order >= dflt)) { + *last_resort = fi; + *last_idx = order; + *last_nhsel = nhsel; + } + +setfl: + read_lock_bh(&fib_nhflags_lock); + if (flag) + nhc->nhc_flags |= RTNH_F_SUSPECT; + else + nhc->nhc_flags &= ~RTNH_F_SUSPECT; + read_unlock_bh(&fib_nhflags_lock); } - return 1; + /* } endfor_nexthops(fi) */ + + return dead; } int fib_nh_common_init(struct net *net, struct fib_nh_common *nhc, @@ -1142,6 +1186,7 @@ static int fib_check_nh_v6_gw(struct net static int fib_check_nh_v4_gw(struct net *net, struct fib_nh *nh, u32 table, u8 scope, struct netlink_ext_ack *extack) { + struct fib_info *fi = nh->nh_parent; struct net_device *dev; struct fib_result res; int err = 0; @@ -1159,8 +1204,12 @@ static int fib_check_nh_v4_gw(struct net return -ENODEV; } if (!(dev->flags & IFF_UP)) { - NL_SET_ERR_MSG(extack, "Nexthop device is not up"); - return -ENETDOWN; + if (fi->fib_protocol != RTPROT_STATIC) { + NL_SET_ERR_MSG(extack, + "Nexthop device is not up"); + return -ENETDOWN; + } + nh->fib_nh_flags |= RTNH_F_DEAD; } addr_type = inet_addr_type_dev_table(net, dev, nh->fib_nh_gw4); if (addr_type != RTN_UNICAST) { @@ -1204,11 +1253,30 @@ static int fib_check_nh_v4_gw(struct net err = fib_lookup(net, &fl4, &res, FIB_LOOKUP_IGNORE_LINKSTATE); } + } + + if (err) { + struct in_device *in_dev; - if (err) { + if (err != -ENETUNREACH || + fi->fib_protocol != RTPROT_STATIC) { NL_SET_ERR_MSG(extack, "Nexthop has invalid gateway"); goto out; } + in_dev = inetdev_by_index(net, nh->fib_nh_oif); + if (in_dev == NULL || + in_dev->dev->flags & IFF_UP) { + NL_SET_ERR_MSG(extack, + "Nexthop has invalid gateway"); + goto out; + } + nh->fib_nh_flags |= RTNH_F_DEAD; + nh->fib_nh_scope = RT_SCOPE_LINK; + nh->fib_nh_dev = in_dev->dev; + netdev_hold(nh->fib_nh_dev, &nh->fib_nh_dev_tracker, + GFP_ATOMIC); + err = 0; + goto out; } err = -EINVAL; @@ -1227,7 +1295,16 @@ static int fib_check_nh_v4_gw(struct net netdev_hold(dev, &nh->fib_nh_dev_tracker, GFP_ATOMIC); if (!netif_carrier_ok(dev)) nh->fib_nh_flags |= RTNH_F_LINKDOWN; - err = (dev->flags & IFF_UP) ? 0 : -ENETDOWN; + if (!(dev->flags & IFF_UP)) { + if (fi->fib_protocol != RTPROT_STATIC) { + err = -ENETDOWN; + NL_SET_ERR_MSG(extack, + "Device for nexthop is not up"); + goto out; + } + nh->fib_nh_flags |= RTNH_F_DEAD; + } + err = 0; out: rcu_read_unlock(); return err; @@ -1236,6 +1313,7 @@ out: static int fib_check_nh_nongw(struct net *net, struct fib_nh *nh, struct netlink_ext_ack *extack) { + struct fib_info *fi = nh->nh_parent; struct in_device *in_dev; int err; @@ -1253,8 +1331,11 @@ static int fib_check_nh_nongw(struct net goto out; err = -ENETDOWN; if (!(in_dev->dev->flags & IFF_UP)) { - NL_SET_ERR_MSG(extack, "Device for nexthop is not up"); - goto out; + if (fi->fib_protocol != RTPROT_STATIC) { + NL_SET_ERR_MSG(extack, "Device for nexthop is not up"); + goto out; + } + nh->fib_nh_flags |= RTNH_F_DEAD; } nh->fib_nh_dev = in_dev->dev; @@ -1947,10 +2028,15 @@ int fib_sync_down_dev(struct net_device prev_fi = fi; dead = 0; change_nexthops(fi) { - if (nexthop_nh->fib_nh_flags & RTNH_F_DEAD) - dead++; - else if (nexthop_nh->fib_nh_dev == dev && - nexthop_nh->fib_nh_scope != scope) { + if (nexthop_nh->fib_nh_flags & RTNH_F_DEAD) { + if (fi->fib_protocol != RTPROT_STATIC || + !nexthop_nh->fib_nh_dev || + !__in_dev_get_rtnl(nexthop_nh->fib_nh_dev) || + nexthop_nh->fib_nh_dev->flags&IFF_UP) + dead++; + } else if (nexthop_nh->fib_nh_dev == dev && + nexthop_nh->fib_nh_scope != scope) { + write_lock_bh(&fib_nhflags_lock); switch (event) { case NETDEV_DOWN: case NETDEV_UNREGISTER: @@ -1962,7 +2048,11 @@ int fib_sync_down_dev(struct net_device } call_fib_nh_notifiers(nexthop_nh, FIB_EVENT_NH_DEL); - dead++; + write_unlock_bh(&fib_nhflags_lock); + if (fi->fib_protocol != RTPROT_STATIC || + force || + !__in_dev_get_rtnl(dev)) + dead++; } #ifdef CONFIG_IP_ROUTE_MULTIPATH if (event == NETDEV_UNREGISTER && @@ -1992,20 +2082,19 @@ int fib_sync_down_dev(struct net_device } /* Must be invoked inside of an RCU protected region. */ -static void fib_select_default(const struct flowi4 *flp, struct fib_result *res) +void fib_select_default(const struct flowi4 *flp, struct fib_result *res) { struct fib_info *fi = NULL, *last_resort = NULL; struct hlist_head *fa_head = res->fa_head; struct fib_table *tb = res->table; u8 slen = 32 - res->prefixlen; - int order = -1, last_idx = -1; + int order = -1, last_idx = -1, last_nhsel = 0; struct fib_alias *fa, *fa1 = NULL; u32 last_prio = res->fi->fib_priority; dscp_t last_dscp = 0; hlist_for_each_entry_rcu(fa, fa_head, fa_list) { struct fib_info *next_fi = fa->fa_info; - struct fib_nh_common *nhc; if (fa->fa_slen != slen) continue; @@ -2028,10 +2117,6 @@ static void fib_select_default(const str fa->fa_type != RTN_UNICAST) continue; - nhc = fib_info_nhc(next_fi, 0); - if (!nhc->nhc_gw_family || nhc->nhc_scope != RT_SCOPE_LINK) - continue; - fib_alias_accessed(fa); if (!fi) { @@ -2039,7 +2124,8 @@ static void fib_select_default(const str break; fa1 = fa; } else if (!fib_detect_death(fi, order, &last_resort, - &last_idx, fa1->fa_default)) { + &last_idx, fa1->fa_default, + &last_nhsel, flp)) { fib_result_assign(res, fi); fa1->fa_default = order; goto out; @@ -2049,28 +2135,39 @@ static void fib_select_default(const str } if (order <= 0 || !fi) { + if (fi && fib_info_num_path(fi) > 1 && + fib_detect_death(fi, order, &last_resort, &last_idx, + fa1->fa_default, &last_nhsel, flp) && + last_resort == fi) { + read_lock_bh(&fib_nhflags_lock); + fi->fib_nh[last_nhsel].fib_nh_flags &= ~RTNH_F_SUSPECT; + read_unlock_bh(&fib_nhflags_lock); + } if (fa1) fa1->fa_default = -1; goto out; } if (!fib_detect_death(fi, order, &last_resort, &last_idx, - fa1->fa_default)) { + fa1->fa_default, &last_nhsel, flp)) { fib_result_assign(res, fi); fa1->fa_default = order; goto out; } - if (last_idx >= 0) + if (last_idx >= 0) { fib_result_assign(res, last_resort); + read_lock_bh(&fib_nhflags_lock); + last_resort->fib_nh[last_nhsel].fib_nh_flags &= ~RTNH_F_SUSPECT; + read_unlock_bh(&fib_nhflags_lock); + } fa1->fa_default = last_idx; out: return; } /* - * Dead device goes up. We wake up dead nexthops. - * It takes sense only on multipath routes. + * Dead device goes up or new address is added. We wake up dead nexthops. * * only used when fib_nh is built into fib_info */ @@ -2079,8 +2176,10 @@ int fib_sync_up(struct net_device *dev, struct fib_info *prev_fi; struct hlist_head *head; struct fib_nh *nh; - int ret; + struct fib_result res; + int ret, rep; +repeat: if (!(dev->flags & IFF_UP)) return 0; @@ -2094,6 +2193,7 @@ int fib_sync_up(struct net_device *dev, prev_fi = NULL; head = fib_nh_head(dev); ret = 0; + rep = 0; hlist_for_each_entry(nh, head, nh_hash) { struct fib_info *fi = nh->nh_parent; @@ -2107,16 +2207,39 @@ int fib_sync_up(struct net_device *dev, prev_fi = fi; alive = 0; change_nexthops(fi) { - if (!(nexthop_nh->fib_nh_flags & nh_flags)) { - alive++; + if (!(nexthop_nh->fib_nh_flags & nh_flags)) continue; - } if (!nexthop_nh->fib_nh_dev || !(nexthop_nh->fib_nh_dev->flags & IFF_UP)) continue; if (nexthop_nh->fib_nh_dev != dev || !__in_dev_get_rtnl(dev)) continue; + if ((nh_flags & RTNH_F_DEAD) && + nexthop_nh->fib_nh_gw4 && + nexthop_nh->fib_nh_gw_family == AF_INET && + fi->fib_protocol == RTPROT_STATIC) { + struct flowi4 fl4 = { + .daddr = nexthop_nh->fib_nh_gw4, + .flowi4_scope = nexthop_nh->fib_nh_scope, + .flowi4_oif = nexthop_nh->fib_nh_oif, + }; + + rcu_read_lock(); + if (fib_lookup(dev_net_rcu(dev), &fl4, &res, + FIB_LOOKUP_IGNORE_LINKSTATE) != 0) { + rcu_read_unlock(); + continue; + } + if (res.type != RTN_UNICAST && + res.type != RTN_LOCAL) { + rcu_read_unlock(); + continue; + } + nexthop_nh->fib_nh_scope = res.scope; + rcu_read_unlock(); + rep = 1; + } alive++; nexthop_nh->fib_nh_flags &= ~nh_flags; call_fib_nh_notifiers(nexthop_nh, FIB_EVENT_NH_ADD); @@ -2129,6 +2252,8 @@ int fib_sync_up(struct net_device *dev, fib_rebalance(fi); } + if (rep) + goto repeat; return ret; } @@ -2208,23 +2333,16 @@ void fib_select_multipath(struct fib_res void fib_select_path(struct net *net, struct fib_result *res, struct flowi4 *fl4, const struct sk_buff *skb) { - if (fl4->flowi4_oif) - goto check_saddr; - + if (res->type == RTN_UNICAST) + fib_select_default(fl4, res); #ifdef CONFIG_IP_ROUTE_MULTIPATH if (fib_info_num_path(res->fi) > 1) { int h = fib_multipath_hash(net, fl4, skb, NULL); fib_select_multipath(res, h, fl4); } - else #endif - if (!res->prefixlen && - res->table->tb_num_default > 1 && - res->type == RTN_UNICAST) - fib_select_default(fl4, res); -check_saddr: if (!fl4->saddr) { struct net_device *l3mdev; diff -urp v7.1/linux/net/ipv4/fib_trie.c linux/net/ipv4/fib_trie.c --- v7.1/linux/net/ipv4/fib_trie.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/ipv4/fib_trie.c 2026-08-10 16:29:52.000000000 +0300 @@ -1413,6 +1413,10 @@ bool fib_lookup_good_nhc(const struct fi if (flp->flowi4_oif && flp->flowi4_oif != nhc->nhc_oif) return false; + if (flp->fl4_gw && flp->fl4_gw != nhc->nhc_gw.ipv4 && + nhc->nhc_gw.ipv4 && nhc->nhc_scope == RT_SCOPE_LINK) + return false; + return true; } diff -urp v7.1/linux/net/ipv4/netfilter/iptable_nat.c linux/net/ipv4/netfilter/iptable_nat.c --- v7.1/linux/net/ipv4/netfilter/iptable_nat.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/ipv4/netfilter/iptable_nat.c 2026-08-10 16:29:52.000000000 +0300 @@ -48,6 +48,13 @@ static const struct nf_hook_ops nf_nat_i .hooknum = NF_INET_LOCAL_OUT, .priority = NF_IP_PRI_NAT_DST, }, + /* Before routing, route before mangling */ + { + .hook = ip_nat_route_input, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_LAST-1, + }, { .hook = ipt_do_table, .pf = NFPROTO_IPV4, diff -urp v7.1/linux/net/ipv4/route.c linux/net/ipv4/route.c --- v7.1/linux/net/ipv4/route.c 2026-08-10 16:34:58.000000000 +0300 +++ linux/net/ipv4/route.c 2026-08-10 16:29:52.000000000 +0300 @@ -1811,7 +1811,7 @@ static void ip_handle_martian_source(str static enum skb_drop_reason __mkroute_input(struct sk_buff *skb, const struct fib_result *res, struct in_device *in_dev, __be32 daddr, - __be32 saddr, dscp_t dscp) + __be32 saddr, dscp_t dscp, __be32 lsrc) { enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; struct fib_nh_common *nhc = FIB_RES_NHC(*res); @@ -1842,7 +1842,7 @@ __mkroute_input(struct sk_buff *skb, con do_cache = res->fi && !itag; if (out_dev == in_dev && err && IN_DEV_TX_REDIRECTS(out_dev) && - skb->protocol == htons(ETH_P_IP)) { + skb->protocol == htons(ETH_P_IP) && !lsrc) { __be32 gw; gw = nhc->nhc_gw_family == AF_INET ? nhc->nhc_gw.ipv4 : 0; @@ -2167,9 +2167,12 @@ int fib_multipath_hash(const struct net static enum skb_drop_reason ip_mkroute_input(struct sk_buff *skb, struct fib_result *res, + const struct flowi4 *fl4, struct in_device *in_dev, __be32 daddr, - __be32 saddr, dscp_t dscp, struct flow_keys *hkeys) + __be32 saddr, dscp_t dscp, struct flow_keys *hkeys, + __be32 lsrc) { + fib_select_default(fl4, res); #ifdef CONFIG_IP_ROUTE_MULTIPATH if (res->fi && fib_info_num_path(res->fi) > 1) { int h = fib_multipath_hash(res->fi->fib_net, NULL, skb, hkeys); @@ -2180,7 +2183,7 @@ ip_mkroute_input(struct sk_buff *skb, st #endif /* create a routing cache entry */ - return __mkroute_input(skb, res, in_dev, daddr, saddr, dscp); + return __mkroute_input(skb, res, in_dev, daddr, saddr, dscp, lsrc); } /* Implements all the saddr-related checks as ip_route_input_slow(), @@ -2261,7 +2264,7 @@ static struct net_device *ip_rt_get_dev( static enum skb_drop_reason ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr, - dscp_t dscp, struct net_device *dev, + dscp_t dscp, struct net_device *dev, __be32 lsrc, struct fib_result *res) { enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; @@ -2330,20 +2333,29 @@ ip_route_input_slow(struct sk_buff *skb, } } + if (lsrc) { + if (ipv4_is_multicast(lsrc) || ipv4_is_lbcast(lsrc) || + ipv4_is_zeronet(lsrc) || ipv4_is_loopback(lsrc)) { + reason = SKB_DROP_REASON_IP_INVALID_SOURCE; + goto martian_source; + } + } + /* * Now we are ready to route packet. */ fl4.flowi4_l3mdev = 0; fl4.flowi4_oif = 0; - fl4.flowi4_iif = dev->ifindex; + fl4.flowi4_iif = lsrc ? LOOPBACK_IFINDEX : dev->ifindex; fl4.flowi4_mark = skb->mark; fl4.flowi4_dscp = dscp; fl4.flowi4_scope = RT_SCOPE_UNIVERSE; fl4.flowi4_flags = 0; fl4.daddr = daddr; - fl4.saddr = saddr; + fl4.saddr = lsrc? : saddr; fl4.flowi4_uid = sock_net_uid(net, NULL); fl4.flowi4_multipath_hash = 0; + fl4.fl4_gw = 0; if (fib4_rules_early_flow_dissect(net, skb, &fl4, &_flkeys)) { flkeys = &_flkeys; @@ -2354,6 +2366,8 @@ ip_route_input_slow(struct sk_buff *skb, } err = fib_lookup(net, &fl4, res, 0); + fl4.flowi4_iif = dev->ifindex; + fl4.saddr = saddr; if (err != 0) { if (!IN_DEV_FORWARD(in_dev)) err = -EHOSTUNREACH; @@ -2388,8 +2402,8 @@ ip_route_input_slow(struct sk_buff *skb, } make_route: - reason = ip_mkroute_input(skb, res, in_dev, daddr, saddr, dscp, - flkeys); + reason = ip_mkroute_input(skb, res, &fl4, in_dev, daddr, saddr, dscp, + flkeys, lsrc); out: return reason; @@ -2399,6 +2413,10 @@ brd_input: reason = SKB_DROP_REASON_INVALID_PROTO; goto out; } + if (lsrc) { + reason = SKB_DROP_REASON_IP_INVALID_SOURCE; + goto out; + } if (!ipv4_is_zeronet(saddr)) { reason = fib_validate_source_reason(skb, saddr, 0, dscp, 0, @@ -2491,9 +2509,9 @@ martian_source: /* called with rcu_read_lock held */ static enum skb_drop_reason -ip_route_input_rcu(struct sk_buff *skb, __be32 daddr, __be32 saddr, - dscp_t dscp, struct net_device *dev, - struct fib_result *res) +ip_route_input_common_rcu(struct sk_buff *skb, __be32 daddr, __be32 saddr, + dscp_t dscp, struct net_device *dev, __be32 lsrc, + struct fib_result *res) { /* Multicast recognition logic is moved from route cache to here. * The problem was that too many Ethernet cards have broken/missing @@ -2540,7 +2558,15 @@ ip_route_input_rcu(struct sk_buff *skb, return reason; } - return ip_route_input_slow(skb, daddr, saddr, dscp, dev, res); + return ip_route_input_slow(skb, daddr, saddr, dscp, dev, lsrc, res); +} + +static enum skb_drop_reason +ip_route_input_rcu(struct sk_buff *skb, __be32 daddr, __be32 saddr, + dscp_t dscp, struct net_device *dev, + struct fib_result *res) +{ + return ip_route_input_common_rcu(skb, daddr, saddr, dscp, dev, 0, res); } enum skb_drop_reason ip_route_input_noref(struct sk_buff *skb, __be32 daddr, @@ -2558,6 +2584,22 @@ enum skb_drop_reason ip_route_input_nore } EXPORT_SYMBOL(ip_route_input_noref); +enum skb_drop_reason +ip_route_input_lookup(struct sk_buff *skb, __be32 daddr, __be32 saddr, + dscp_t dscp, struct net_device *dev, __be32 lsrc) +{ + enum skb_drop_reason reason; + struct fib_result res; + + rcu_read_lock(); + reason = ip_route_input_common_rcu(skb, daddr, saddr, dscp, dev, lsrc, + &res); + rcu_read_unlock(); + + return reason; +} +EXPORT_SYMBOL(ip_route_input_lookup); + /* called with rcu_read_lock() */ static struct rtable *__mkroute_output(const struct fib_result *res, const struct flowi4 *fl4, int orig_oif, @@ -2806,6 +2848,7 @@ struct rtable *ip_route_output_key_hash_ fl4->daddr = fl4->saddr = htonl(INADDR_LOOPBACK); dev_out = net->loopback_dev; fl4->flowi4_oif = LOOPBACK_IFINDEX; + fl4->fl4_gw = 0; res->type = RTN_LOCAL; flags |= RTCF_LOCAL; goto make_route; @@ -2863,6 +2906,7 @@ struct rtable *ip_route_output_key_hash_ orig_oif = FIB_RES_OIF(*res); fl4->flowi4_oif = dev_out->ifindex; + fl4->fl4_gw = 0; flags |= RTCF_LOCAL; goto make_route; } diff -urp v7.1/linux/net/netfilter/nf_nat_core.c linux/net/netfilter/nf_nat_core.c --- v7.1/linux/net/netfilter/nf_nat_core.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/netfilter/nf_nat_core.c 2026-08-10 16:29:52.000000000 +0300 @@ -1309,6 +1309,49 @@ static const struct nf_nat_hook nat_hook .remove_nat_bysrc = nf_nat_cleanup_conntrack, }; +unsigned int ip_nat_route_input(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + struct iphdr *iph; + struct nf_conn *conn; + enum ip_conntrack_info ctinfo; + enum ip_conntrack_dir dir; + unsigned long statusbit; + __be32 saddr; + + if (!(conn = nf_ct_get(skb, &ctinfo))) + return NF_ACCEPT; + + if (!(conn->status & IPS_NAT_DONE_MASK)) + return NF_ACCEPT; + dir = CTINFO2DIR(ctinfo); + statusbit = IPS_SRC_NAT; + if (dir == IP_CT_DIR_REPLY) + statusbit ^= IPS_NAT_MASK; + if (!(conn->status & statusbit)) + return NF_ACCEPT; + + if (skb_dst(skb)) + return NF_ACCEPT; + + if (skb->len < sizeof(struct iphdr)) + return NF_ACCEPT; + + /* use daddr in other direction as masquerade address (lsrc) */ + iph = ip_hdr(skb); + saddr = conn->tuplehash[!dir].tuple.dst.u3.ip; + if (saddr == iph->saddr) + return NF_ACCEPT; + + if (ip_route_input_lookup(skb, iph->daddr, iph->saddr, ip4h_dscp(iph), + skb->dev, saddr)) + return NF_DROP; + + return NF_ACCEPT; +} +EXPORT_SYMBOL_GPL(ip_nat_route_input); + static int __init nf_nat_init(void) { int ret, i; diff -urp v7.1/linux/net/netfilter/nf_nat_masquerade.c linux/net/netfilter/nf_nat_masquerade.c --- v7.1/linux/net/netfilter/nf_nat_masquerade.c 2026-08-10 16:34:52.000000000 +0300 +++ linux/net/netfilter/nf_nat_masquerade.c 2026-08-10 16:29:52.000000000 +0300 @@ -33,8 +33,8 @@ nf_nat_masquerade_ipv4(struct sk_buff *s struct nf_conn_nat *nat; enum ip_conntrack_info ctinfo; struct nf_nat_range2 newrange; - const struct rtable *rt; - __be32 newsrc, nh; + struct rtable *rt; + __be32 newsrc; WARN_ON(hooknum != NF_INET_POST_ROUTING); @@ -49,12 +49,23 @@ nf_nat_masquerade_ipv4(struct sk_buff *s if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u3.ip == 0) return NF_ACCEPT; - rt = skb_rtable(skb); - nh = rt_nexthop(rt, ip_hdr(skb)->daddr); - newsrc = inet_select_addr(out, nh, RT_SCOPE_UNIVERSE); - if (!newsrc) { - pr_info("%s ate my IP address\n", out->name); - return NF_DROP; + { + struct flowi4 fl4 = { .flowi4_dscp = ip4h_dscp(ip_hdr(skb)), + .flowi4_mark = skb->mark, + .flowi4_oif = out->ifindex, + .daddr = ip_hdr(skb)->daddr, + .fl4_gw = skb_rtable(skb)->rt_gw4 }; + rt = ip_route_output_key(dev_net_rcu(out), &fl4); + if (IS_ERR(rt)) { + /* Funky routing can do this. */ + if (net_ratelimit()) + pr_info("%s:" + " No route: Rusty's brain broke!\n", + out->name); + return NF_DROP; + } + newsrc = fl4.saddr; + ip_rt_put(rt); } nat = nf_ct_nat_ext_add(ct);