developer | ec4ebe4 | 2022-04-12 11:17:45 +0800 | [diff] [blame] | 1 | From: Jonas Gorski <jogo@openwrt.org> |
| 2 | Subject: ipv6: allow rejecting with "source address failed policy" |
| 3 | |
| 4 | RFC6204 L-14 requires rejecting traffic from invalid addresses with |
| 5 | ICMPv6 Destination Unreachable, Code 5 (Source address failed ingress/ |
| 6 | egress policy) on the LAN side, so add an appropriate rule for that. |
| 7 | |
| 8 | Signed-off-by: Jonas Gorski <jogo@openwrt.org> |
| 9 | --- |
| 10 | include/net/netns/ipv6.h | 1 + |
| 11 | include/uapi/linux/fib_rules.h | 4 +++ |
| 12 | include/uapi/linux/rtnetlink.h | 1 + |
| 13 | net/ipv4/fib_semantics.c | 4 +++ |
| 14 | net/ipv4/fib_trie.c | 1 + |
| 15 | net/ipv4/ipmr.c | 1 + |
| 16 | net/ipv6/fib6_rules.c | 4 +++ |
| 17 | net/ipv6/ip6mr.c | 2 ++ |
| 18 | net/ipv6/route.c | 58 +++++++++++++++++++++++++++++++++++++++++- |
| 19 | 9 files changed, 75 insertions(+), 1 deletion(-) |
| 20 | |
| 21 | --- a/include/net/netns/ipv6.h |
| 22 | +++ b/include/net/netns/ipv6.h |
| 23 | @@ -84,6 +84,7 @@ struct netns_ipv6 { |
| 24 | unsigned int fib6_rules_require_fldissect; |
| 25 | bool fib6_has_custom_rules; |
| 26 | struct rt6_info *ip6_prohibit_entry; |
| 27 | + struct rt6_info *ip6_policy_failed_entry; |
| 28 | struct rt6_info *ip6_blk_hole_entry; |
| 29 | struct fib6_table *fib6_local_tbl; |
| 30 | struct fib_rules_ops *fib6_rules_ops; |
| 31 | --- a/include/uapi/linux/fib_rules.h |
| 32 | +++ b/include/uapi/linux/fib_rules.h |
| 33 | @@ -82,6 +82,10 @@ enum { |
| 34 | FR_ACT_BLACKHOLE, /* Drop without notification */ |
| 35 | FR_ACT_UNREACHABLE, /* Drop with ENETUNREACH */ |
| 36 | FR_ACT_PROHIBIT, /* Drop with EACCES */ |
| 37 | + FR_ACT_RES9, |
| 38 | + FR_ACT_RES10, |
| 39 | + FR_ACT_RES11, |
| 40 | + FR_ACT_POLICY_FAILED, /* Drop with EACCES */ |
| 41 | __FR_ACT_MAX, |
| 42 | }; |
| 43 | |
| 44 | --- a/include/uapi/linux/rtnetlink.h |
| 45 | +++ b/include/uapi/linux/rtnetlink.h |
| 46 | @@ -235,6 +235,7 @@ enum { |
| 47 | RTN_THROW, /* Not in this table */ |
| 48 | RTN_NAT, /* Translate this address */ |
| 49 | RTN_XRESOLVE, /* Use external resolver */ |
| 50 | + RTN_POLICY_FAILED, /* Failed ingress/egress policy */ |
| 51 | __RTN_MAX |
| 52 | }; |
| 53 | |
| 54 | --- a/net/ipv4/fib_semantics.c |
| 55 | +++ b/net/ipv4/fib_semantics.c |
| 56 | @@ -142,6 +142,10 @@ const struct fib_prop fib_props[RTN_MAX |
| 57 | .error = -EINVAL, |
| 58 | .scope = RT_SCOPE_NOWHERE, |
| 59 | }, |
| 60 | + [RTN_POLICY_FAILED] = { |
| 61 | + .error = -EACCES, |
| 62 | + .scope = RT_SCOPE_UNIVERSE, |
| 63 | + }, |
| 64 | }; |
| 65 | |
| 66 | static void rt_fibinfo_free(struct rtable __rcu **rtp) |
| 67 | --- a/net/ipv4/fib_trie.c |
| 68 | +++ b/net/ipv4/fib_trie.c |
| 69 | @@ -2596,6 +2596,7 @@ static const char *const rtn_type_names[ |
| 70 | [RTN_THROW] = "THROW", |
| 71 | [RTN_NAT] = "NAT", |
| 72 | [RTN_XRESOLVE] = "XRESOLVE", |
| 73 | + [RTN_POLICY_FAILED] = "POLICY_FAILED", |
| 74 | }; |
| 75 | |
| 76 | static inline const char *rtn_type(char *buf, size_t len, unsigned int t) |
| 77 | --- a/net/ipv4/ipmr.c |
| 78 | +++ b/net/ipv4/ipmr.c |
| 79 | @@ -173,6 +173,7 @@ static int ipmr_rule_action(struct fib_r |
| 80 | case FR_ACT_UNREACHABLE: |
| 81 | return -ENETUNREACH; |
| 82 | case FR_ACT_PROHIBIT: |
| 83 | + case FR_ACT_POLICY_FAILED: |
| 84 | return -EACCES; |
| 85 | case FR_ACT_BLACKHOLE: |
| 86 | default: |
| 87 | --- a/net/ipv6/fib6_rules.c |
| 88 | +++ b/net/ipv6/fib6_rules.c |
| 89 | @@ -216,6 +216,10 @@ static int __fib6_rule_action(struct fib |
| 90 | err = -EACCES; |
| 91 | rt = net->ipv6.ip6_prohibit_entry; |
| 92 | goto discard_pkt; |
| 93 | + case FR_ACT_POLICY_FAILED: |
| 94 | + err = -EACCES; |
| 95 | + rt = net->ipv6.ip6_policy_failed_entry; |
| 96 | + goto discard_pkt; |
| 97 | } |
| 98 | |
| 99 | tb_id = fib_rule_get_table(rule, arg); |
| 100 | --- a/net/ipv6/ip6mr.c |
| 101 | +++ b/net/ipv6/ip6mr.c |
| 102 | @@ -161,6 +161,8 @@ static int ip6mr_rule_action(struct fib_ |
| 103 | return -ENETUNREACH; |
| 104 | case FR_ACT_PROHIBIT: |
| 105 | return -EACCES; |
| 106 | + case FR_ACT_POLICY_FAILED: |
| 107 | + return -EACCES; |
| 108 | case FR_ACT_BLACKHOLE: |
| 109 | default: |
| 110 | return -EINVAL; |
| 111 | --- a/net/ipv6/route.c |
| 112 | +++ b/net/ipv6/route.c |
| 113 | @@ -94,6 +94,8 @@ static int ip6_pkt_discard(struct sk_bu |
| 114 | static int ip6_pkt_discard_out(struct net *net, struct sock *sk, struct sk_buff *skb); |
| 115 | static int ip6_pkt_prohibit(struct sk_buff *skb); |
| 116 | static int ip6_pkt_prohibit_out(struct net *net, struct sock *sk, struct sk_buff *skb); |
| 117 | +static int ip6_pkt_policy_failed(struct sk_buff *skb); |
| 118 | +static int ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb); |
| 119 | static void ip6_link_failure(struct sk_buff *skb); |
| 120 | static void ip6_rt_update_pmtu(struct dst_entry *dst, struct sock *sk, |
| 121 | struct sk_buff *skb, u32 mtu, |
| 122 | @@ -327,6 +329,18 @@ static const struct rt6_info ip6_prohibi |
| 123 | .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP), |
| 124 | }; |
| 125 | |
| 126 | +static const struct rt6_info ip6_policy_failed_entry_template = { |
| 127 | + .dst = { |
| 128 | + .__refcnt = ATOMIC_INIT(1), |
| 129 | + .__use = 1, |
| 130 | + .obsolete = DST_OBSOLETE_FORCE_CHK, |
| 131 | + .error = -EACCES, |
| 132 | + .input = ip6_pkt_policy_failed, |
| 133 | + .output = ip6_pkt_policy_failed_out, |
| 134 | + }, |
| 135 | + .rt6i_flags = (RTF_REJECT | RTF_NONEXTHOP), |
| 136 | +}; |
| 137 | + |
| 138 | static const struct rt6_info ip6_blk_hole_entry_template = { |
| 139 | .dst = { |
| 140 | .__refcnt = ATOMIC_INIT(1), |
| 141 | @@ -1048,6 +1062,7 @@ static const int fib6_prop[RTN_MAX + 1] |
| 142 | [RTN_BLACKHOLE] = -EINVAL, |
| 143 | [RTN_UNREACHABLE] = -EHOSTUNREACH, |
| 144 | [RTN_PROHIBIT] = -EACCES, |
| 145 | + [RTN_POLICY_FAILED] = -EACCES, |
| 146 | [RTN_THROW] = -EAGAIN, |
| 147 | [RTN_NAT] = -EINVAL, |
| 148 | [RTN_XRESOLVE] = -EINVAL, |
| 149 | @@ -1085,6 +1100,10 @@ static void ip6_rt_init_dst_reject(struc |
| 150 | rt->dst.output = ip6_pkt_prohibit_out; |
| 151 | rt->dst.input = ip6_pkt_prohibit; |
| 152 | break; |
| 153 | + case RTN_POLICY_FAILED: |
| 154 | + rt->dst.output = ip6_pkt_policy_failed_out; |
| 155 | + rt->dst.input = ip6_pkt_policy_failed; |
| 156 | + break; |
| 157 | case RTN_THROW: |
| 158 | case RTN_UNREACHABLE: |
| 159 | default: |
| 160 | @@ -4453,6 +4472,17 @@ static int ip6_pkt_prohibit_out(struct n |
| 161 | return ip6_pkt_drop(skb, ICMPV6_ADM_PROHIBITED, IPSTATS_MIB_OUTNOROUTES); |
| 162 | } |
| 163 | |
| 164 | +static int ip6_pkt_policy_failed(struct sk_buff *skb) |
| 165 | +{ |
| 166 | + return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_INNOROUTES); |
| 167 | +} |
| 168 | + |
| 169 | +static int ip6_pkt_policy_failed_out(struct net *net, struct sock *sk, struct sk_buff *skb) |
| 170 | +{ |
| 171 | + skb->dev = skb_dst(skb)->dev; |
| 172 | + return ip6_pkt_drop(skb, ICMPV6_POLICY_FAIL, IPSTATS_MIB_OUTNOROUTES); |
| 173 | +} |
| 174 | + |
| 175 | /* |
| 176 | * Allocate a dst for local (unicast / anycast) address. |
| 177 | */ |
| 178 | @@ -4933,7 +4963,8 @@ static int rtm_to_fib6_config(struct sk_ |
| 179 | if (rtm->rtm_type == RTN_UNREACHABLE || |
| 180 | rtm->rtm_type == RTN_BLACKHOLE || |
| 181 | rtm->rtm_type == RTN_PROHIBIT || |
| 182 | - rtm->rtm_type == RTN_THROW) |
| 183 | + rtm->rtm_type == RTN_THROW || |
| 184 | + rtm->rtm_type == RTN_POLICY_FAILED) |
| 185 | cfg->fc_flags |= RTF_REJECT; |
| 186 | |
| 187 | if (rtm->rtm_type == RTN_LOCAL) |
| 188 | @@ -6084,6 +6115,8 @@ static int ip6_route_dev_notify(struct n |
| 189 | #ifdef CONFIG_IPV6_MULTIPLE_TABLES |
| 190 | net->ipv6.ip6_prohibit_entry->dst.dev = dev; |
| 191 | net->ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(dev); |
| 192 | + net->ipv6.ip6_policy_failed_entry->dst.dev = dev; |
| 193 | + net->ipv6.ip6_policy_failed_entry->rt6i_idev = in6_dev_get(dev); |
| 194 | net->ipv6.ip6_blk_hole_entry->dst.dev = dev; |
| 195 | net->ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(dev); |
| 196 | #endif |
| 197 | @@ -6095,6 +6128,7 @@ static int ip6_route_dev_notify(struct n |
| 198 | in6_dev_put_clear(&net->ipv6.ip6_null_entry->rt6i_idev); |
| 199 | #ifdef CONFIG_IPV6_MULTIPLE_TABLES |
| 200 | in6_dev_put_clear(&net->ipv6.ip6_prohibit_entry->rt6i_idev); |
| 201 | + in6_dev_put_clear(&net->ipv6.ip6_policy_failed_entry->rt6i_idev); |
| 202 | in6_dev_put_clear(&net->ipv6.ip6_blk_hole_entry->rt6i_idev); |
| 203 | #endif |
| 204 | } |
| 205 | @@ -6287,6 +6321,8 @@ static int __net_init ip6_route_net_init |
| 206 | |
| 207 | #ifdef CONFIG_IPV6_MULTIPLE_TABLES |
| 208 | net->ipv6.fib6_has_custom_rules = false; |
| 209 | + |
| 210 | + |
| 211 | net->ipv6.ip6_prohibit_entry = kmemdup(&ip6_prohibit_entry_template, |
| 212 | sizeof(*net->ipv6.ip6_prohibit_entry), |
| 213 | GFP_KERNEL); |
| 214 | @@ -6297,11 +6333,21 @@ static int __net_init ip6_route_net_init |
| 215 | ip6_template_metrics, true); |
| 216 | INIT_LIST_HEAD(&net->ipv6.ip6_prohibit_entry->rt6i_uncached); |
| 217 | |
| 218 | + net->ipv6.ip6_policy_failed_entry = |
| 219 | + kmemdup(&ip6_policy_failed_entry_template, |
| 220 | + sizeof(*net->ipv6.ip6_policy_failed_entry), GFP_KERNEL); |
| 221 | + if (!net->ipv6.ip6_policy_failed_entry) |
| 222 | + goto out_ip6_prohibit_entry; |
| 223 | + net->ipv6.ip6_policy_failed_entry->dst.ops = &net->ipv6.ip6_dst_ops; |
| 224 | + dst_init_metrics(&net->ipv6.ip6_policy_failed_entry->dst, |
| 225 | + ip6_template_metrics, true); |
| 226 | + INIT_LIST_HEAD(&net->ipv6.ip6_policy_failed_entry->rt6i_uncached); |
| 227 | + |
| 228 | net->ipv6.ip6_blk_hole_entry = kmemdup(&ip6_blk_hole_entry_template, |
| 229 | sizeof(*net->ipv6.ip6_blk_hole_entry), |
| 230 | GFP_KERNEL); |
| 231 | if (!net->ipv6.ip6_blk_hole_entry) |
| 232 | - goto out_ip6_prohibit_entry; |
| 233 | + goto out_ip6_policy_failed_entry; |
| 234 | net->ipv6.ip6_blk_hole_entry->dst.ops = &net->ipv6.ip6_dst_ops; |
| 235 | dst_init_metrics(&net->ipv6.ip6_blk_hole_entry->dst, |
| 236 | ip6_template_metrics, true); |
| 237 | @@ -6325,6 +6371,8 @@ out: |
| 238 | return ret; |
| 239 | |
| 240 | #ifdef CONFIG_IPV6_MULTIPLE_TABLES |
| 241 | +out_ip6_policy_failed_entry: |
| 242 | + kfree(net->ipv6.ip6_policy_failed_entry); |
| 243 | out_ip6_prohibit_entry: |
| 244 | kfree(net->ipv6.ip6_prohibit_entry); |
| 245 | out_ip6_null_entry: |
| 246 | @@ -6344,6 +6392,7 @@ static void __net_exit ip6_route_net_exi |
| 247 | kfree(net->ipv6.ip6_null_entry); |
| 248 | #ifdef CONFIG_IPV6_MULTIPLE_TABLES |
| 249 | kfree(net->ipv6.ip6_prohibit_entry); |
| 250 | + kfree(net->ipv6.ip6_policy_failed_entry); |
| 251 | kfree(net->ipv6.ip6_blk_hole_entry); |
| 252 | #endif |
| 253 | dst_entries_destroy(&net->ipv6.ip6_dst_ops); |
| 254 | @@ -6421,6 +6470,9 @@ void __init ip6_route_init_special_entri |
| 255 | init_net.ipv6.ip6_prohibit_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev); |
| 256 | init_net.ipv6.ip6_blk_hole_entry->dst.dev = init_net.loopback_dev; |
| 257 | init_net.ipv6.ip6_blk_hole_entry->rt6i_idev = in6_dev_get(init_net.loopback_dev); |
| 258 | + init_net.ipv6.ip6_policy_failed_entry->dst.dev = init_net.loopback_dev; |
| 259 | + init_net.ipv6.ip6_policy_failed_entry->rt6i_idev = |
| 260 | + in6_dev_get(init_net.loopback_dev); |
| 261 | #endif |
| 262 | } |
| 263 | |