diff -urp v7.1/linux/Documentation/networking/ip-sysctl.rst linux/Documentation/networking/ip-sysctl.rst --- v7.1/linux/Documentation/networking/ip-sysctl.rst 2026-08-10 16:41:47.000000000 +0300 +++ linux/Documentation/networking/ip-sysctl.rst 2026-08-10 17:25:14.606556258 +0300 @@ -1957,6 +1957,19 @@ forwarding - BOOLEAN Enable IP forwarding on this interface. This controls whether packets received _on_ this interface can be forwarded. +forward_shared - BOOLEAN + Integer value determines if a source validation should allow + forwarding of packets with local source address. 1 means yes, + 0 means no. By default the flag is disabled and such packets + are not forwarded. + + If you enable this flag on internal network, the router will forward + packets from internal hosts with shared IP addresses no matter how + the rp_filter is set. This flag is activated only if it is + enabled both in specific device section and in "all" section. + + The forward_shared value could be ignored when rp_filter is set to 0. + mc_forwarding - BOOLEAN Do multicast routing. The kernel needs to be compiled with CONFIG_MROUTE and a multicast routing daemon is required. @@ -2115,6 +2128,15 @@ src_valid_mark - BOOLEAN Default value is 0. +rp_filter_mask - INTEGER + Integer value representing bitmask of the mediums for which the + reverse path protection is disabled. If the source validation + results in reverse path to interface with medium_id value in + the 1..31 range the access is allowed if the corresponding bit + is set in the bitmask. The bitmask value is considered only when + rp_filter is enabled. By default the bitmask is empty preserving + the original rp_filter semantic. + arp_filter - BOOLEAN - 1 - Allows you to have multiple network interfaces on the same subnet, and have the ARPs for each interface be answered @@ -2323,6 +2345,14 @@ drop_gratuitous_arp - BOOLEAN Default: 0 (disabled) +hidden - BOOLEAN + Hide addresses attached to this device from other devices. + Such addresses will never be selected by source address autoselection + mechanism, host does not answer broadcast ARP requests for them, + does not announce them as source address of ARP requests, but they + are still reachable via IP. This flag is activated only if it is + enabled both in specific device section and in "all" section. + tag - INTEGER Allows you to write a number, which can be used as required. diff -urp v7.1/linux/include/linux/inetdevice.h linux/include/linux/inetdevice.h --- v7.1/linux/include/linux/inetdevice.h 2026-08-10 16:41:47.000000000 +0300 +++ linux/include/linux/inetdevice.h 2026-08-10 17:25:14.607249516 +0300 @@ -101,9 +101,11 @@ static inline void ipv4_devconf_setall(s #define IN_DEV_MFORWARD(in_dev) IN_DEV_ANDCONF((in_dev), MC_FORWARDING) #define IN_DEV_BFORWARD(in_dev) IN_DEV_ANDCONF((in_dev), BC_FORWARDING) #define IN_DEV_RPFILTER(in_dev) IN_DEV_MAXCONF((in_dev), RP_FILTER) +#define IN_DEV_RPFILTER_MASK(in_dev) IN_DEV_CONF_GET(in_dev, RP_FILTER_MASK) #define IN_DEV_SRC_VMARK(in_dev) IN_DEV_ORCONF((in_dev), SRC_VMARK) #define IN_DEV_SOURCE_ROUTE(in_dev) IN_DEV_ANDCONF((in_dev), \ ACCEPT_SOURCE_ROUTE) +#define IN_DEV_FORWARD_SHARED(in_dev) IN_DEV_ANDCONF((in_dev), FORWARD_SHARED) #define IN_DEV_ACCEPT_LOCAL(in_dev) IN_DEV_ORCONF((in_dev), ACCEPT_LOCAL) #define IN_DEV_BOOTP_RELAY(in_dev) IN_DEV_ANDCONF((in_dev), BOOTP_RELAY) @@ -116,6 +118,7 @@ static inline void ipv4_devconf_setall(s SECURE_REDIRECTS) #define IN_DEV_IDTAG(in_dev) IN_DEV_CONF_GET(in_dev, TAG) #define IN_DEV_MEDIUM_ID(in_dev) IN_DEV_CONF_GET(in_dev, MEDIUM_ID) +#define IN_DEV_HIDDEN(in_dev) IN_DEV_ANDCONF((in_dev), HIDDEN) #define IN_DEV_PROMOTE_SECONDARIES(in_dev) \ IN_DEV_ORCONF((in_dev), \ PROMOTE_SECONDARIES) 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:39:34.000000000 +0300 +++ linux/include/net/flow.h 2026-08-10 17:25:14.607643408 +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:39:34.000000000 +0300 +++ linux/include/net/ip_fib.h 2026-08-10 17:25:14.608534310 +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) @@ -452,20 +454,22 @@ __be32 fib_compute_spec_dst(struct sk_bu bool fib_info_nh_uses_dev(struct fib_info *fi, const struct net_device *dev); int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst, dscp_t dscp, int oif, struct net_device *dev, - struct in_device *idev, u32 *itag); + struct in_device *idev, u32 *itag, int our); static inline enum skb_drop_reason fib_validate_source_reason(struct sk_buff *skb, __be32 src, __be32 dst, dscp_t dscp, int oif, struct net_device *dev, - struct in_device *idev, u32 *itag) + struct in_device *idev, u32 *itag, int our) { int err = fib_validate_source(skb, src, dst, dscp, oif, dev, idev, - itag); + itag, our); if (err < 0) return -err; 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:39:34.000000000 +0300 +++ linux/include/net/netfilter/nf_nat.h 2026-08-10 17:25:14.609123065 +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:39:34.000000000 +0300 +++ linux/include/net/route.h 2026-08-10 17:25:14.609478035 +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/ip.h linux/include/uapi/linux/ip.h --- v7.1/linux/include/uapi/linux/ip.h 2026-08-10 16:41:47.000000000 +0300 +++ linux/include/uapi/linux/ip.h 2026-08-10 17:25:14.609861778 +0300 @@ -189,6 +189,9 @@ enum IPV4_DEVCONF_DROP_GRATUITOUS_ARP, IPV4_DEVCONF_BC_FORWARDING, IPV4_DEVCONF_ARP_EVICT_NOCARRIER, + IPV4_DEVCONF_HIDDEN, + IPV4_DEVCONF_RP_FILTER_MASK, + IPV4_DEVCONF_FORWARD_SHARED, __IPV4_DEVCONF_MAX }; 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:39:34.000000000 +0300 +++ linux/include/uapi/linux/rtnetlink.h 2026-08-10 17:25:14.610283480 +0300 @@ -202,6 +202,13 @@ enum { RTM_GETTUNNEL, #define RTM_GETTUNNEL RTM_GETTUNNEL + RTM_NEWARPRULE = 124, +#define RTM_NEWARPRULE RTM_NEWARPRULE + RTM_DELARPRULE, +#define RTM_DELARPRULE RTM_DELARPRULE + RTM_GETARPRULE, +#define RTM_GETARPRULE RTM_GETARPRULE + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; @@ -432,9 +439,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 */ @@ -687,6 +696,54 @@ enum { #define NDUSEROPT_MAX (__NDUSEROPT_MAX - 1) +/****************************************************************************** + * Definitions used in ARP tables administration + ****/ + +#define ARPA_TABLE_INPUT 0 +#define ARPA_TABLE_OUTPUT 1 +#define ARPA_TABLE_FORWARD 2 +#define ARPA_TABLE_ALL -1 + +#define ARPM_F_PREFSRC 0x0001 +#define ARPM_F_WILDIIF 0x0002 +#define ARPM_F_WILDOIF 0x0004 +#define ARPM_F_BROADCAST 0x0008 +#define ARPM_F_UNICAST 0x0010 + +struct arpmsg +{ + unsigned char arpm_family; + unsigned char arpm_table; + unsigned char arpm_action; + unsigned char arpm_from_len; + unsigned char arpm_to_len; + unsigned char arpm__pad1; + unsigned short arpm__pad2; + unsigned arpm_pref; + unsigned arpm_flags; +}; + +enum +{ + ARPA_UNSPEC, + ARPA_FROM, /* FROM IP prefix */ + ARPA_TO, /* TO IP prefix */ + ARPA_LLFROM, /* FROM LL prefix */ + ARPA_LLTO, /* TO LL prefix */ + ARPA_LLSRC, /* New SRC lladdr */ + ARPA_LLDST, /* New DST lladdr */ + ARPA_IIF, /* In interface prefix */ + ARPA_OIF, /* Out interface prefix */ + ARPA_SRC, /* New IP SRC */ + ARPA_DST, /* New IP DST, not used */ + ARPA_PACKETS, /* Packets */ +}; + +#define ARPA_MAX ARPA_PACKETS + +#define ARPA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct arpmsg)))) + #ifndef __KERNEL__ /* RTnetlink multicast groups - backwards compatibility for userspace */ #define RTMGRP_LINK 1 @@ -707,6 +764,8 @@ enum { #define RTMGRP_DECnet_IFADDR 0x1000 #define RTMGRP_DECnet_ROUTE 0x4000 +#define RTMGRP_ARP 0x00010000 + #define RTMGRP_IPV6_PREFIX 0x20000 #endif @@ -790,6 +849,8 @@ enum rtnetlink_groups { #define RTNLGRP_IPV6_MCADDR RTNLGRP_IPV6_MCADDR RTNLGRP_IPV6_ACADDR, #define RTNLGRP_IPV6_ACADDR RTNLGRP_IPV6_ACADDR + RTNLGRP_ARP, +#define RTNLGRP_ARP RTNLGRP_ARP __RTNLGRP_MAX }; #define RTNLGRP_MAX (__RTNLGRP_MAX - 1) 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:39:34.000000000 +0300 +++ linux/net/bridge/br_netfilter_hooks.c 2026-08-10 17:25:14.610804195 +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:39:34.000000000 +0300 +++ linux/net/core/filter.c 2026-08-10 17:25:14.611265555 +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/arp.c linux/net/ipv4/arp.c --- v7.1/linux/net/ipv4/arp.c 2026-08-10 13:55:14.000000000 +0300 +++ linux/net/ipv4/arp.c 2026-08-10 17:25:14.613123754 +0300 @@ -67,6 +67,9 @@ * sending (e.g. insert 8021q tag). * Harald Welte : convert to make use of jenkins hash * Jesper D. Brouer: Proxy ARP PVLAN RFC 3069 support. + * Julian Anastasov: "hidden" flag: hide the + * interface and don't reply for it + * Julian Anastasov: ARP filtering via netlink */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -92,6 +95,7 @@ #include #include #include +#include #include #include #include @@ -184,6 +188,48 @@ struct neigh_table arp_tbl = { }; EXPORT_SYMBOL(arp_tbl); +struct arpf_node { + struct arpf_node * at_next; + u32 at_pref; + u32 at_from; + u32 at_from_mask; + u32 at_to; + u32 at_to_mask; + u32 at_src; + atomic_t at_packets; + atomic_t at_refcnt; + unsigned at_flags; + unsigned char at_from_len; + unsigned char at_to_len; + unsigned char at_action; + char at_dead; + unsigned char at_llfrom_len; + unsigned char at_llto_len; + unsigned char at_llsrc_len; + unsigned char at_lldst_len; + unsigned char at_iif_len; + unsigned char at_oif_len; + unsigned short at__pad1; + unsigned char at_llfrom[MAX_ADDR_LEN]; + unsigned char at_llto[MAX_ADDR_LEN]; + unsigned char at_llsrc[MAX_ADDR_LEN]; + unsigned char at_lldst[MAX_ADDR_LEN]; + char at_iif[IFNAMSIZ]; + char at_oif[IFNAMSIZ]; +}; + +static struct arpf_node *arp_tabs[3]; + +static struct kmem_cache *arpf_cachep; + +static DEFINE_RWLOCK(arpf_lock); + +static void +arpf_send(int table, struct net *net, struct sk_buff *skb, u32 sip, u32 tip, + unsigned char *from_hw, unsigned char *to_hw, + struct net_device *idev, struct net_device *odev, + struct dst_entry *dst); + int arp_mc_map(__be32 addr, u8 *haddr, struct net_device *dev, int dir) { switch (dev->type) { @@ -337,7 +383,9 @@ static void arp_solicit(struct neighbour struct net_device *dev = neigh->dev; __be32 target = *(__be32 *)neigh->primary_key; int probes = atomic_read(&neigh->probes); - struct in_device *in_dev; + struct in_device *in_dev, *in_dev2; + struct net_device *dev2; + int mode; struct dst_entry *dst = NULL; rcu_read_lock(); @@ -346,19 +394,33 @@ static void arp_solicit(struct neighbour rcu_read_unlock(); return; } - switch (IN_DEV_ARP_ANNOUNCE(in_dev)) { + mode = IN_DEV_ARP_ANNOUNCE(in_dev); + if (mode != 2 && skb && + (dev2 = __ip_dev_find(dev_net_rcu(dev), ip_hdr(skb)->saddr, + false)) != NULL && + (saddr = ip_hdr(skb)->saddr, + in_dev2 = __in_dev_get_rcu(dev2)) != NULL && + IN_DEV_HIDDEN(in_dev2)) { + saddr = 0; + goto get; + } + + switch (mode) { default: case 0: /* By default announce any local IP */ - if (skb && inet_addr_type_dev_table(dev_net(dev), dev, + if (saddr) + break; + if (skb && inet_addr_type_dev_table(dev_net_rcu(dev), dev, ip_hdr(skb)->saddr) == RTN_LOCAL) saddr = ip_hdr(skb)->saddr; break; case 1: /* Restrict announcements of saddr in same subnet */ if (!skb) break; - saddr = ip_hdr(skb)->saddr; - if (inet_addr_type_dev_table(dev_net(dev), dev, - saddr) == RTN_LOCAL) { + if (saddr || + (saddr = ip_hdr(skb)->saddr, + inet_addr_type_dev_table(dev_net_rcu(dev), dev, + saddr) == RTN_LOCAL)) { /* saddr should be known to target */ if (inet_addr_onlink(in_dev, target, saddr)) break; @@ -368,6 +430,8 @@ static void arp_solicit(struct neighbour case 2: /* Avoid secondary IPs, get a primary/preferred one */ break; } + +get: rcu_read_unlock(); if (!saddr) @@ -389,8 +453,8 @@ static void arp_solicit(struct neighbour if (skb && !(dev->priv_flags & IFF_XMIT_DST_RELEASE)) dst = skb_dst(skb); - arp_send_dst(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr, - dst_hw, dev->dev_addr, NULL, dst); + arpf_send(ARPA_TABLE_OUTPUT, dev_net(dev), skb, saddr, target, NULL, + dst_hw, NULL, dev, dst); } static int arp_ignore(struct in_device *in_dev, __be32 sip, __be32 tip) @@ -468,6 +532,22 @@ static int arp_filter(__be32 sip, __be32 return flag; } +static int arp_hidden(u32 tip, struct net_device *dev) +{ + struct net_device *dev2; + struct in_device *in_dev2; + int ret = 0; + + if (!IPV4_DEVCONF_ALL(dev_net_rcu(dev), HIDDEN)) + return 0; + + if ((dev2 = __ip_dev_find(dev_net_rcu(dev), tip, false)) && + dev2 != dev && + (in_dev2 = __in_dev_get_rcu(dev2)) && IN_DEV_HIDDEN(in_dev2)) + ret = 1; + return ret; +} + /* * Check if we can use proxy ARP for this path */ @@ -831,9 +911,10 @@ static int arp_process(struct net *net, if (sip == 0) { if (arp->ar_op == htons(ARPOP_REQUEST) && inet_addr_type_dev_table(net, dev, tip) == RTN_LOCAL && + !arp_hidden(tip, dev) && !arp_ignore(in_dev, sip, tip)) - arp_send_dst(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip, - sha, dev->dev_addr, sha, reply_dst); + arpf_send(ARPA_TABLE_INPUT, net, skb, sip, tip, sha, + tha, dev, NULL, reply_dst); goto out_consume_skb; } @@ -849,13 +930,14 @@ static int arp_process(struct net *net, dont_send = arp_ignore(in_dev, sip, tip); if (!dont_send && IN_DEV_ARPFILTER(in_dev)) dont_send = arp_filter(sip, tip, dev); + if (!dont_send && skb->pkt_type != PACKET_HOST) + dont_send = arp_hidden(tip,dev); if (!dont_send) { n = neigh_event_ns(&arp_tbl, sha, &sip, dev); if (n) { - arp_send_dst(ARPOP_REPLY, ETH_P_ARP, - sip, dev, tip, sha, - dev->dev_addr, sha, - reply_dst); + arpf_send(ARPA_TABLE_INPUT, net, skb, + sip, tip, sha, tha, dev, + NULL, reply_dst); neigh_release(n); } } @@ -873,10 +955,9 @@ static int arp_process(struct net *net, if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED || skb->pkt_type == PACKET_HOST || NEIGH_VAR(in_dev->arp_parms, PROXY_DELAY) == 0) { - arp_send_dst(ARPOP_REPLY, ETH_P_ARP, - sip, dev, tip, sha, - dev->dev_addr, sha, - reply_dst); + arpf_send(ARPA_TABLE_FORWARD, net, + skb, sip, tip, sha, tha, dev, + rt->dst.dev, reply_dst); } else { pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb); @@ -1368,6 +1449,577 @@ void arp_ifdown(struct net_device *dev) } +static void arpf_destroy(struct arpf_node *afp) +{ + if (!afp->at_dead) { + printk(KERN_ERR "Destroying alive arp table node %p\n", afp); + return; + } + kmem_cache_free(arpf_cachep, afp); +} + +static inline void arpf_put(struct arpf_node *afp) +{ + if (atomic_dec_and_test(&afp->at_refcnt)) + arpf_destroy(afp); +} + +static inline struct arpf_node * +arpf_lookup(int table, struct sk_buff *skb, u32 sip, u32 tip, + unsigned char *from_hw, unsigned char *to_hw, + struct net_device *idev, struct net_device *odev) +{ + int sz_iif = idev? strlen(idev->name) : 0; + int sz_oif = odev? strlen(odev->name) : 0; + int alen; + struct arpf_node *afp; + + if (ARPA_TABLE_OUTPUT != table) { + alen = idev->addr_len; + } else { + if (!from_hw) from_hw = (unsigned char *)odev->dev_addr; + if (!to_hw) to_hw = odev->broadcast; + alen = odev->addr_len; + } + + read_lock_bh(&arpf_lock); + for (afp = arp_tabs[table]; afp; afp = afp->at_next) { + if ((tip ^ afp->at_to) & afp->at_to_mask) + continue; + if ((sip ^ afp->at_from) & afp->at_from_mask) + continue; + if (afp->at_llfrom_len && + (afp->at_llfrom_len > alen || + memcmp(from_hw, afp->at_llfrom, afp->at_llfrom_len))) + continue; + if (afp->at_llto_len && + (afp->at_llto_len > alen || + memcmp(to_hw, afp->at_llto, afp->at_llto_len))) + continue; + if (afp->at_iif_len && + (afp->at_iif_len > sz_iif || + memcmp(afp->at_iif, idev->name, afp->at_iif_len) || + (sz_iif != afp->at_iif_len && + !(afp->at_flags & ARPM_F_WILDIIF)))) + continue; + if (afp->at_oif_len && + (afp->at_oif_len > sz_oif || + memcmp(afp->at_oif, odev->name, afp->at_oif_len) || + (sz_oif != afp->at_oif_len && + !(afp->at_flags & ARPM_F_WILDOIF)))) + continue; + if (afp->at_flags & ARPM_F_BROADCAST && + skb->pkt_type == PACKET_HOST) + continue; + if (afp->at_flags & ARPM_F_UNICAST && + skb->pkt_type != PACKET_HOST) + continue; + if (afp->at_llsrc_len && afp->at_llsrc_len != alen) + continue; + if (afp->at_lldst_len && afp->at_lldst_len != alen) + continue; + atomic_inc(&afp->at_refcnt); + atomic_inc(&afp->at_packets); + break; + } + read_unlock_bh(&arpf_lock); + return afp; +} + +static void +arpf_send(int table, struct net *net, struct sk_buff *skb, u32 sip, u32 tip, + unsigned char *from_hw, unsigned char *to_hw, + struct net_device *idev, struct net_device *odev, + struct dst_entry *dst) +{ + struct arpf_node *afp = NULL; + + if (!arp_tabs[table] || + !net_eq(net, &init_net) || + !(afp = arpf_lookup(table, skb, sip, tip, + from_hw, to_hw, idev, odev))) { + switch (table) { + case ARPA_TABLE_INPUT: + case ARPA_TABLE_FORWARD: + arp_send_dst(ARPOP_REPLY, ETH_P_ARP, sip, idev, tip, + from_hw, idev->dev_addr, from_hw, dst); + break; + case ARPA_TABLE_OUTPUT: + arp_send_dst(ARPOP_REQUEST, ETH_P_ARP, tip, odev, sip, + to_hw, odev->dev_addr, NULL, dst); + break; + } + return; + } + + /* deny? */ + if (!afp->at_action) goto out; + + switch (table) { + case ARPA_TABLE_INPUT: + case ARPA_TABLE_FORWARD: + arp_send_dst(ARPOP_REPLY, ETH_P_ARP, sip, idev, tip, + afp->at_lldst_len?afp->at_lldst:from_hw, + afp->at_llsrc_len?afp->at_llsrc:idev->dev_addr, + afp->at_lldst_len?afp->at_lldst:from_hw, dst); + break; + case ARPA_TABLE_OUTPUT: + if (afp->at_flags & ARPM_F_PREFSRC && afp->at_src == 0) { + struct rtable *rt; + struct flowi4 fl4 = { .daddr = tip, + .flowi4_oif = odev->ifindex }; + + rt = ip_route_output_key(net, &fl4); + if (IS_ERR(rt)) + break; + sip = fl4.saddr; + ip_rt_put(rt); + if (!sip) + break; + } + arp_send_dst(ARPOP_REQUEST, ETH_P_ARP, tip, odev, + afp->at_src?:sip, + afp->at_lldst_len?afp->at_lldst:to_hw, + afp->at_llsrc_len?afp->at_llsrc:odev->dev_addr, + NULL, dst); + break; + } + +out: + arpf_put(afp); +} + +static int +arpf_fill_node(struct sk_buff *skb, u32 portid, u32 seq, unsigned flags, + int event, int table, struct arpf_node *afp) +{ + struct arpmsg *am; + struct nlmsghdr *nlh; + u32 packets = atomic_read(&afp->at_packets); + + nlh = nlmsg_put(skb, portid, seq, event, sizeof(*am), 0); + if (nlh == NULL) + return -ENOBUFS; + nlh->nlmsg_flags = flags; + am = nlmsg_data(nlh); + am->arpm_family = AF_UNSPEC; + am->arpm_table = table; + am->arpm_action = afp->at_action; + am->arpm_from_len = afp->at_from_len; + am->arpm_to_len = afp->at_to_len; + am->arpm_pref = afp->at_pref; + am->arpm_flags = afp->at_flags; + if (afp->at_from_len && + nla_put(skb, ARPA_FROM, 4, &afp->at_from)) + goto nla_put_failure; + if (afp->at_to_len && + nla_put(skb, ARPA_TO, 4, &afp->at_to)) + goto nla_put_failure; + if ((afp->at_src || afp->at_flags & ARPM_F_PREFSRC) && + nla_put(skb, ARPA_SRC, 4, &afp->at_src)) + goto nla_put_failure; + if (afp->at_iif[0] && + nla_put(skb, ARPA_IIF, sizeof(afp->at_iif), afp->at_iif)) + goto nla_put_failure; + if (afp->at_oif[0] && + nla_put(skb, ARPA_OIF, sizeof(afp->at_oif), afp->at_oif)) + goto nla_put_failure; + if (afp->at_llfrom_len && + nla_put(skb, ARPA_LLFROM, afp->at_llfrom_len, afp->at_llfrom)) + goto nla_put_failure; + if (afp->at_llto_len && + nla_put(skb, ARPA_LLTO, afp->at_llto_len, afp->at_llto)) + goto nla_put_failure; + if (afp->at_llsrc_len && + nla_put(skb, ARPA_LLSRC, afp->at_llsrc_len, afp->at_llsrc)) + goto nla_put_failure; + if (afp->at_lldst_len && + nla_put(skb, ARPA_LLDST, afp->at_lldst_len, afp->at_lldst)) + goto nla_put_failure; + if (nla_put(skb, ARPA_PACKETS, 4, &packets)) + goto nla_put_failure; + nlmsg_end(skb, nlh); + return 0; + +nla_put_failure: + nlmsg_cancel(skb, nlh); + return -EMSGSIZE; +} + +static void +arpmsg_notify(struct sk_buff *oskb, struct nlmsghdr *nlh, int table, + struct arpf_node *afp, int event) +{ + struct sk_buff *skb; + u32 portid = oskb ? NETLINK_CB(oskb).portid : 0; + int payload = sizeof(struct arpmsg) + 256; + int err = -ENOBUFS; + + skb = nlmsg_new(nlmsg_total_size(payload), GFP_KERNEL); + if (!skb) + goto errout; + + err = arpf_fill_node(skb, portid, nlh->nlmsg_seq, 0, event, table, afp); + if (err < 0) { + kfree_skb(skb); + goto errout; + } + + rtnl_notify(skb, &init_net, portid, RTNLGRP_ARP, nlh, GFP_KERNEL); + return; +errout: + if (err < 0) + rtnl_set_sk_err(&init_net, RTNLGRP_ARP, err); +} + +static inline int +arpf_str_size(int a, struct nlattr **rta, int maxlen) +{ + int size = 0; + + if (rta[a] && (size = nla_len(rta[a]))) { + if (size > maxlen) + size = maxlen; + } + return size; +} + +static inline int +arpf_get_str(int a, struct nlattr **rta, unsigned char *p, + int maxlen, unsigned char *l) +{ + int size = arpf_str_size(a, rta, maxlen); + + if (size) { + memcpy(p, nla_data(rta[a]), size); + *l = size; + } + return size; +} + +#define ARPF_MATCH_U32(ind, field) ( \ + (!rta[ind] && r->at_ ## field == 0) || \ + (rta[ind] && \ + *(u32*) nla_data(rta[ind]) == r->at_ ## field)) + +#define ARPF_MATCH_STR(ind, field) ( \ + (!rta[ind] && r->at_ ## field ## _len == 0) || \ + (rta[ind] && r->at_ ## field ## _len && \ + r->at_ ## field ## _len < nla_len(rta[ind]) && \ + strcmp(nla_data(rta[ind]), r->at_ ## field) == 0)) + +#define ARPF_MATCH_DATA(ind, field) ( \ + (!rta[ind] && r->at_ ## field ## _len == 0) || \ + (rta[ind] && r->at_ ## field ## _len && \ + r->at_ ## field ## _len == nla_len(rta[ind]) && \ + memcmp(nla_data(rta[ind]), &r->at_ ## field, \ + r->at_ ## field ## _len) == 0)) + +/* RTM_NEWARPRULE/RTM_DELARPRULE/RTM_GETARPRULE */ + +static int arpf_rule_ctl(struct sk_buff *skb, struct nlmsghdr *n, + struct netlink_ext_ack *extack) +{ + struct net *net = sock_net(skb->sk); + struct nlattr *rta[ARPA_MAX + 1]; + struct arpmsg *am; + struct arpf_node *r, **rp, **prevp = 0, **delp = 0, *newp = 0; + unsigned pref = 1; + int size, ret; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (!net_eq(net, &init_net)) + return -EINVAL; + + ret = nlmsg_parse(n, sizeof(struct arpmsg), rta, ARPA_MAX, NULL, + extack); + if (ret < 0) + return ret; + + am = nlmsg_data(n); + ret = -EINVAL; + if (am->arpm_table >= sizeof(arp_tabs)/sizeof(arp_tabs[0])) + goto out; + if (!((~am->arpm_flags) & (ARPM_F_BROADCAST|ARPM_F_UNICAST))) + goto out; + if (am->arpm_action > 1) + goto out; + if (am->arpm_to_len > 32 || am->arpm_from_len > 32) + goto out; + if (am->arpm_flags & ARPM_F_WILDIIF && + (!rta[ARPA_IIF] || !nla_len(rta[ARPA_IIF]) || + !*(char*) nla_data(rta[ARPA_IIF]))) + am->arpm_flags &= ~ARPM_F_WILDIIF; + if (am->arpm_flags & ARPM_F_WILDOIF && + (!rta[ARPA_OIF] || !nla_len(rta[ARPA_OIF]) || + !*(char*) nla_data(rta[ARPA_OIF]))) + am->arpm_flags &= ~ARPM_F_WILDOIF; + switch (am->arpm_table) { + case ARPA_TABLE_INPUT: + if (rta[ARPA_SRC] || rta[ARPA_OIF]) + goto out; + break; + case ARPA_TABLE_OUTPUT: + if (rta[ARPA_IIF]) + goto out; + if (am->arpm_flags & (ARPM_F_BROADCAST|ARPM_F_UNICAST)) + goto out; + break; + case ARPA_TABLE_FORWARD: + if (rta[ARPA_SRC]) + goto out; + break; + } + if (rta[ARPA_SRC] && !*(u32*) nla_data(rta[ARPA_SRC])) + am->arpm_flags |= ARPM_F_PREFSRC; + else + am->arpm_flags &= ~ARPM_F_PREFSRC; + + for (rp = &arp_tabs[am->arpm_table]; (r=*rp) != NULL; rp=&r->at_next) { + if (pref < r->at_pref) + prevp = rp; + if (am->arpm_pref == r->at_pref || + (!am->arpm_pref && + am->arpm_to_len == r->at_to_len && + am->arpm_from_len == r->at_from_len && + !((am->arpm_flags ^ r->at_flags) & + (ARPM_F_BROADCAST | ARPM_F_UNICAST | + ARPM_F_WILDIIF | ARPM_F_WILDOIF)) && + ARPF_MATCH_U32(ARPA_TO, to) && + ARPF_MATCH_U32(ARPA_FROM, from) && + ARPF_MATCH_DATA(ARPA_LLFROM, llfrom) && + ARPF_MATCH_DATA(ARPA_LLTO, llto) && + ARPF_MATCH_STR(ARPA_IIF, iif) && + ARPF_MATCH_STR(ARPA_OIF, oif) && + (n->nlmsg_type != RTM_DELARPRULE || + /* DEL matches more keys */ + (am->arpm_flags == r->at_flags && + am->arpm_action == r->at_action && + ARPF_MATCH_U32(ARPA_SRC, src) && + ARPF_MATCH_DATA(ARPA_LLSRC, llsrc) && + ARPF_MATCH_DATA(ARPA_LLDST, lldst) + ) + ) + ) + ) + break; + if (am->arpm_pref && r->at_pref > am->arpm_pref) { + r = NULL; + break; + } + pref = r->at_pref+1; + } + + /* + * r=NULL: *rp != NULL (stopped before next pref), pref: not valid + * *rp == NULL (not found), pref: ready to use + * r!=NULL: found, pref: not valid + * + * prevp=NULL: no free slot + * prevp!=NULL: free slot for rule + */ + + if (n->nlmsg_type == RTM_DELARPRULE) { + if (!r) + return -ESRCH; + delp = rp; + goto dequeue; + } + + if (r) { + /* Existing rule */ + ret = -EEXIST; + if (n->nlmsg_flags&NLM_F_EXCL) + goto out; + + if (n->nlmsg_flags&NLM_F_REPLACE) { + pref = r->at_pref; + prevp = delp = rp; + goto replace; + } + } + + if (n->nlmsg_flags&NLM_F_APPEND) { + if (r) { + pref = r->at_pref+1; + for (rp=&r->at_next; (r=*rp) != NULL; rp=&r->at_next) { + if (pref != r->at_pref) + break; + pref ++; + } + ret = -EBUSY; + if (!pref) + goto out; + } else if (am->arpm_pref) + pref = am->arpm_pref; + prevp = rp; + } + + if (!(n->nlmsg_flags&NLM_F_CREATE)) { + ret = -ENOENT; + if (n->nlmsg_flags&NLM_F_EXCL || r) + ret = 0; + goto out; + } + + if (!(n->nlmsg_flags&NLM_F_APPEND)) { + if (!prevp) { + ret = -EBUSY; + if (r || *rp || + (!am->arpm_pref && arp_tabs[am->arpm_table])) + goto out; + prevp = rp; + pref = am->arpm_pref? : 99; + } else { + if (r || !am->arpm_pref) { + pref = (*prevp)->at_pref - 1; + if (am->arpm_pref && am->arpm_pref < pref) + pref = am->arpm_pref; + } else { + prevp = rp; + pref = am->arpm_pref; + } + } + } + +replace: + + ret = -ENOMEM; + r = kmem_cache_alloc(arpf_cachep, GFP_KERNEL); + if (!r) + return ret; + memset(r, 0, sizeof(*r)); + + arpf_get_str(ARPA_LLFROM, rta, r->at_llfrom, MAX_ADDR_LEN, + &r->at_llfrom_len); + arpf_get_str(ARPA_LLTO, rta, r->at_llto, MAX_ADDR_LEN, + &r->at_llto_len); + arpf_get_str(ARPA_LLSRC, rta, r->at_llsrc, MAX_ADDR_LEN, + &r->at_llsrc_len); + arpf_get_str(ARPA_LLDST, rta, r->at_lldst, MAX_ADDR_LEN, + &r->at_lldst_len); + + if (delp) + r->at_next = (*delp)->at_next; + else if (*prevp) + r->at_next = *prevp; + + r->at_pref = pref; + r->at_from_len = am->arpm_from_len; + r->at_from_mask = inet_make_mask(r->at_from_len); + if (rta[ARPA_FROM]) + r->at_from = *(u32*) nla_data(rta[ARPA_FROM]); + r->at_from &= r->at_from_mask; + r->at_to_len = am->arpm_to_len; + r->at_to_mask = inet_make_mask(r->at_to_len); + if (rta[ARPA_TO]) + r->at_to = *(u32*) nla_data(rta[ARPA_TO]); + r->at_to &= r->at_to_mask; + if (rta[ARPA_SRC]) + r->at_src = *(u32*) nla_data(rta[ARPA_SRC]); + if (rta[ARPA_PACKETS]) { + u32 packets = *(u32*) nla_data(rta[ARPA_PACKETS]); + atomic_set(&r->at_packets, packets); + } + atomic_set(&r->at_refcnt, 1); + r->at_flags = am->arpm_flags; + r->at_action = am->arpm_action; + + if (rta[ARPA_IIF] && (size = nla_len(rta[ARPA_IIF]))) { + if (size >= sizeof(r->at_iif)) + size = sizeof(r->at_iif)-1; + memcpy(r->at_iif, nla_data(rta[ARPA_IIF]), size); + r->at_iif_len = strlen(r->at_iif); + } + if (rta[ARPA_OIF] && (size = nla_len(rta[ARPA_OIF]))) { + if (size >= sizeof(r->at_oif)) + size = sizeof(r->at_oif)-1; + memcpy(r->at_oif, nla_data(rta[ARPA_OIF]), size); + r->at_oif_len = strlen(r->at_oif); + } + + newp = r; + +dequeue: + + if (delp) { + r = *delp; + write_lock_bh(&arpf_lock); + if (newp) { + if (!rta[ARPA_PACKETS]) + atomic_set(&newp->at_packets, + atomic_read(&r->at_packets)); + *delp = newp; + } else { + *delp = r->at_next; + } + r->at_dead = 1; + write_unlock_bh(&arpf_lock); + arpmsg_notify(skb, n, am->arpm_table, r, RTM_DELARPRULE); + arpf_put(r); + prevp = 0; + } + + if (newp) { + if (prevp) { + write_lock_bh(&arpf_lock); + *prevp = newp; + write_unlock_bh(&arpf_lock); + } + arpmsg_notify(skb, n, am->arpm_table, newp, RTM_NEWARPRULE); + } + + ret = 0; + +out: + return ret; +} + +static int arpf_dump_table(int t, struct sk_buff *skb, + struct netlink_callback *cb) +{ + int idx, ret = -1; + struct arpf_node *afp; + int s_idx = cb->args[1]; + + for (idx=0, afp = arp_tabs[t]; afp; afp = afp->at_next, idx++) { + if (idx < s_idx) + continue; + if (arpf_fill_node(skb, NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWARPRULE, t, afp) < 0) + goto out; + } + + ret = skb->len; + +out: + cb->args[1] = idx; + + return ret; +} + +static int arpf_dump_rules(struct sk_buff *skb, struct netlink_callback *cb) +{ + int idx; + int s_idx = cb->args[0]; + + read_lock_bh(&arpf_lock); + for (idx = 0; idx < sizeof(arp_tabs)/sizeof(arp_tabs[0]); idx++) { + if (idx < s_idx) + continue; + if (idx > s_idx) + memset(&cb->args[1], 0, sizeof(cb->args)-1*sizeof(cb->args[0])); + if (arpf_dump_table(idx, skb, cb) < 0) + break; + } + read_unlock_bh(&arpf_lock); + cb->args[0] = idx; + + return skb->len; +} + /* * Called once on startup. */ @@ -1511,8 +2163,22 @@ static struct pernet_operations arp_net_ .exit = arp_net_exit, }; +static const struct rtnl_msg_handler arpf_msg_handlers[] __initconst = { + {.msgtype = RTM_NEWARPRULE, .doit = arpf_rule_ctl}, + {.msgtype = RTM_DELARPRULE, .doit = arpf_rule_ctl}, + {.msgtype = RTM_GETARPRULE, .dumpit = arpf_dump_rules}, +}; + void __init arp_init(void) { + arpf_cachep = kmem_cache_create("ip_arpf_cache", + sizeof(struct arpf_node), 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!arpf_cachep) + panic("IP: failed to allocate ip_arpf_cache\n"); + + rtnl_register_many(arpf_msg_handlers); + neigh_table_init(NEIGH_ARP_TABLE, &arp_tbl); dev_add_pack(&arp_packet_type); diff -urp v7.1/linux/net/ipv4/devinet.c linux/net/ipv4/devinet.c --- v7.1/linux/net/ipv4/devinet.c 2026-08-10 16:41:47.000000000 +0300 +++ linux/net/ipv4/devinet.c 2026-08-10 17:25:14.613890143 +0300 @@ -1417,9 +1417,16 @@ no_in_dev: if (!in_dev) continue; - addr = in_dev_select_addr(in_dev, scope); - if (addr) - goto out_unlock; + in_dev_for_each_ifa_rcu(ifa, in_dev) { + if (READ_ONCE(ifa->ifa_flags) & IFA_F_SECONDARY) + break; + if (!IN_DEV_HIDDEN(in_dev) && + ifa->ifa_scope != RT_SCOPE_LINK && + ifa->ifa_scope <= scope) { + addr = ifa->ifa_local; + goto out_unlock; + } + } } out_unlock: rcu_read_unlock(); @@ -2653,13 +2660,16 @@ static struct devinet_sysctl_table { DEVINET_SYSCTL_RW_ENTRY(SEND_REDIRECTS, "send_redirects"), DEVINET_SYSCTL_RW_ENTRY(ACCEPT_SOURCE_ROUTE, "accept_source_route"), + DEVINET_SYSCTL_RW_ENTRY(FORWARD_SHARED, "forward_shared"), DEVINET_SYSCTL_RW_ENTRY(ACCEPT_LOCAL, "accept_local"), DEVINET_SYSCTL_RW_ENTRY(SRC_VMARK, "src_valid_mark"), DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP, "proxy_arp"), DEVINET_SYSCTL_RW_ENTRY(MEDIUM_ID, "medium_id"), + DEVINET_SYSCTL_RW_ENTRY(RP_FILTER_MASK, "rp_filter_mask"), DEVINET_SYSCTL_RW_ENTRY(BOOTP_RELAY, "bootp_relay"), DEVINET_SYSCTL_RW_ENTRY(LOG_MARTIANS, "log_martians"), DEVINET_SYSCTL_RW_ENTRY(TAG, "tag"), + DEVINET_SYSCTL_RW_ENTRY(HIDDEN, "hidden"), DEVINET_SYSCTL_RW_ENTRY(ARPFILTER, "arp_filter"), DEVINET_SYSCTL_RW_ENTRY(ARP_ANNOUNCE, "arp_announce"), DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"), 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:41:47.000000000 +0300 +++ linux/net/ipv4/fib_frontend.c 2026-08-10 17:25:14.614444022 +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; @@ -344,14 +348,20 @@ EXPORT_SYMBOL_GPL(fib_info_nh_uses_dev); */ static int __fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst, dscp_t dscp, int oif, struct net_device *dev, - int rpf, struct in_device *idev, u32 *itag) + int rpf, struct in_device *idev, u32 *itag, + int our) { struct net *net = dev_net(dev); enum skb_drop_reason reason; struct flow_keys flkeys; + u32 table; + unsigned char prefixlen; + unsigned char scope; int ret, no_addr; struct fib_result res; struct flowi4 fl4; + int fwdsh; + unsigned int rpf_mask; bool dev_match; fl4.flowi4_oif = 0; @@ -365,10 +375,13 @@ 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; + fwdsh = IN_DEV_FORWARD_SHARED(idev); fl4.flowi4_mark = IN_DEV_SRC_VMARK(idev) ? skb->mark : 0; + rpf_mask = IN_DEV_RPFILTER_MASK(idev); if (!fib4_rules_early_flow_dissect(net, skb, &fl4, &flkeys)) { fl4.flowi4_proto = 0; fl4.fl4_sport = 0; @@ -379,7 +392,12 @@ static int __fib_validate_source(struct if (fib_lookup(net, &fl4, &res, 0)) goto last_resort; - if (res.type != RTN_UNICAST) { + if (fwdsh) { + fwdsh = (res.type == RTN_LOCAL && !our); + if (fwdsh) + rpf = 0; + } + if (res.type != RTN_UNICAST && !fwdsh) { if (res.type != RTN_LOCAL) { reason = SKB_DROP_REASON_IP_INVALID_SOURCE; goto e_inval; @@ -400,17 +418,36 @@ static int __fib_validate_source(struct ret = FIB_RES_NHC(res)->nhc_scope >= RT_SCOPE_HOST; return ret; } + if (rpf_mask && rpf) { + int omi = 0; + + idev = __in_dev_get_rcu(FIB_RES_DEV(res)); + if (idev) + omi = IN_DEV_MEDIUM_ID(idev); + if (omi >= 1 && omi <= 31 && ((1 << omi) & rpf_mask)) + rpf = 0; + } 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; + if (fwdsh) + fl4.flowi4_iif = LOOPBACK_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: @@ -428,7 +465,7 @@ e_rpf: /* Ignore rp_filter for packets protected by IPsec. */ int fib_validate_source(struct sk_buff *skb, __be32 src, __be32 dst, dscp_t dscp, int oif, struct net_device *dev, - struct in_device *idev, u32 *itag) + struct in_device *idev, u32 *itag, int our) { int r = secpath_exists(skb) ? 0 : IN_DEV_RPFILTER(idev); struct net *net = dev_net(dev); @@ -457,7 +494,7 @@ ok: full_check: return __fib_validate_source(skb, src, dst, dscp, oif, dev, r, idev, - itag); + itag, our); } static inline __be32 sk_extract_addr(struct sockaddr *addr) @@ -1468,9 +1505,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 +1550,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:39:34.000000000 +0300 +++ linux/net/ipv4/fib_rules.c 2026-08-10 17:25:14.615020628 +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:39:34.000000000 +0300 +++ linux/net/ipv4/fib_semantics.c 2026-08-10 17:26:06.799800184 +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:39:34.000000000 +0300 +++ linux/net/ipv4/fib_trie.c 2026-08-10 17:25:14.616459927 +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:39:34.000000000 +0300 +++ linux/net/ipv4/netfilter/iptable_nat.c 2026-08-10 17:25:14.617246389 +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:39:34.000000000 +0300 +++ linux/net/ipv4/route.c 2026-08-10 17:25:14.617970134 +0300 @@ -1730,7 +1730,7 @@ ip_mc_validate_source(struct sk_buff *sk return SKB_DROP_REASON_IP_INVALID_SOURCE; } else { reason = fib_validate_source_reason(skb, saddr, 0, dscp, 0, - dev, in_dev, itag); + dev, in_dev, itag, 1); if (reason) return reason; } @@ -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); @@ -1831,7 +1831,7 @@ __mkroute_input(struct sk_buff *skb, con } err = fib_validate_source(skb, saddr, daddr, dscp, FIB_RES_OIF(*res), - in_dev->dev, in_dev, &itag); + in_dev->dev, in_dev, &itag, 0); if (err < 0) { reason = -err; ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr, @@ -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(), @@ -2220,7 +2223,7 @@ ip_route_use_hint(struct sk_buff *skb, _ goto skip_validate_source; reason = fib_validate_source_reason(skb, saddr, daddr, dscp, 0, dev, - in_dev, &tag); + in_dev, &tag, 0); if (reason) goto martian_source; @@ -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; @@ -2372,7 +2386,7 @@ ip_route_input_slow(struct sk_buff *skb, err = -EINVAL; if (res->type == RTN_LOCAL) { reason = fib_validate_source_reason(skb, saddr, daddr, dscp, - 0, dev, in_dev, &itag); + 0, dev, in_dev, &itag, 1); if (reason) goto martian_source; goto local_input; @@ -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,10 +2413,14 @@ 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, - dev, in_dev, &itag); + dev, in_dev, &itag, 1); if (reason) goto martian_source; } @@ -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:39:34.000000000 +0300 +++ linux/net/netfilter/nf_nat_core.c 2026-08-10 17:25:14.618985362 +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:39:34.000000000 +0300 +++ linux/net/netfilter/nf_nat_masquerade.c 2026-08-10 17:25:14.619439170 +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); diff -urp v7.1/linux/security/selinux/nlmsgtab.c linux/security/selinux/nlmsgtab.c --- v7.1/linux/security/selinux/nlmsgtab.c 2026-08-10 13:55:14.000000000 +0300 +++ linux/security/selinux/nlmsgtab.c 2026-08-10 17:25:14.620109355 +0300 @@ -94,6 +94,9 @@ static const struct nlmsg_perm nlmsg_rou { RTM_NEWTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_DELTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, { RTM_GETTUNNEL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWARPRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELARPRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETARPRULE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, }; static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = { @@ -181,7 +184,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 * structures at the top of this file with the new mappings * before updating the BUILD_BUG_ON() macro! */ - BUILD_BUG_ON(RTM_MAX != (RTM_NEWTUNNEL + 3)); + BUILD_BUG_ON(RTM_MAX != (RTM_NEWARPRULE + 3)); if (selinux_policycap_netlink_xperm()) { *perm = NETLINK_ROUTE_SOCKET__NLMSG;