// SPDX-License-Identifier: GPL-2.0-or-later /* * Internet Control Message Protocol (ICMPv6) * Linux INET6 implementation * * Authors: * Pedro Roque <roque@di.fc.ul.pt> * * Based on net/ipv4/icmp.c * * RFC 1885
*/
/* * Changes: * * Andi Kleen : exception handling * Andi Kleen add rate limits. never reply to a icmp. * add more length checks and other fixes. * yoshfuji : ensure to sent parameter problem for * fragments. * YOSHIFUJI Hideaki @USAGI: added sysctl for icmp rate limit. * Randy Dunlap and * YOSHIFUJI Hideaki @USAGI: Per-interface statistics support * Kazunori MIYAZAWA @USAGI: change output process to use ip6_append_data
*/
/* Called with BH disabled */ staticstruct sock *icmpv6_xmit_lock(struct net *net)
{ struct sock *sk;
sk = this_cpu_read(ipv6_icmp_sk); if (unlikely(!spin_trylock(&sk->sk_lock.slock))) { /* This can happen if the output path (f.e. SIT or * ip6ip6 tunnel) signals dst_link_failure() for an * outgoing ICMP6 packet.
*/ return NULL;
}
sock_net_set(sk, net); return sk;
}
/* * Figure out, may we reply to this packet with icmp error. * * We do not reply, if: * - it was icmp error message. * - it is truncated, so that it is known, that protocol is ICMPV6 * (i.e. in the middle of some exthdr) * * --ANK (980726)
*/
staticbool is_ineligible(conststruct sk_buff *skb)
{ int ptr = (u8 *)(ipv6_hdr(skb) + 1) - skb->data; int len = skb->len - ptr;
__u8 nexthdr = ipv6_hdr(skb)->nexthdr;
__be16 frag_off;
/* Based on RFC 8200, Section 4.5 Fragment Header, return * false if this is a fragment packet with no icmp header info.
*/ if (!tp && frag_off != 0) returnfalse; elseif (!tp || !(*tp & ICMPV6_INFOMSG_MASK)) returntrue;
} returnfalse;
}
staticbool icmpv6_mask_allow(struct net *net, int type)
{ if (type > ICMPV6_MSG_MAX) returntrue;
/* Limit if icmp type is set in ratemask. */ if (!test_bit(type, net->ipv6.sysctl.icmpv6_ratemask)) returntrue;
returnfalse;
}
staticbool icmpv6_global_allow(struct net *net, int type, bool *apply_ratelimit)
{ if (icmpv6_mask_allow(net, type)) returntrue;
/* * Look up the output route. * XXX: perhaps the expire for routing entries cloned by * this lookup should be more aggressive (not longer than timeout).
*/
dst = ip6_route_output(net, sk, fl6);
rcu_read_lock();
dev = dst_dev_rcu(dst); if (dst->error) {
IP6_INC_STATS(net, ip6_dst_idev(dst),
IPSTATS_MIB_OUTNOROUTES);
} elseif (dev && (dev->flags & IFF_LOOPBACK)) {
res = true;
} else { struct rt6_info *rt = dst_rt6_info(dst); int tmo = net->ipv6.sysctl.icmpv6_time; struct inet_peer *peer;
/* Give more bandwidth to wider prefixes. */ if (rt->rt6i_dst.plen < 128)
tmo >>= ((128 - rt->rt6i_dst.plen)>>5);
rt6_get_prefsrc(rt, &prefsrc);
res = !ipv6_addr_any(&prefsrc);
}
dst_release(dst); return res;
}
/* * an inline helper for the "simple" if statement below * checks if parameter problem report is caused by an * unrecognized IPv6 option that has the Option Type * highest-order two bits set to 10
*/
err = ip6_dst_lookup(net, sk, &dst, fl6); if (err) return ERR_PTR(err);
/* * We won't send icmp if the destination is known * anycast unless we need to treat anycast as unicast.
*/ if (!READ_ONCE(net->ipv6.sysctl.icmpv6_error_anycast_as_unicast) &&
ipv6_anycast_destination(dst, &fl6->daddr)) {
net_dbg_ratelimited("icmp6_send: acast source\n");
dst_release(dst); return ERR_PTR(-EINVAL);
}
/* No need to clone since we're just using its address. */
dst2 = dst;
dst = xfrm_lookup(net, dst, flowi6_to_flowi(fl6), sk, 0); if (!IS_ERR(dst)) { if (dst != dst2) return dst;
} else { if (PTR_ERR(dst) == -EPERM)
dst = NULL; else return dst;
}
err = xfrm_decode_session_reverse(net, skb, flowi6_to_flowi(&fl2), AF_INET6); if (err) goto relookup_failed;
err = ip6_dst_lookup(net, sk, &dst2, &fl2); if (err) goto relookup_failed;
/* for local traffic to local address, skb dev is the loopback * device. Check if there is a dst attached to the skb and if so * get the real device index. Same is needed for replies to a link * local address on a device enslaved to an L3 master device
*/ if (unlikely(dev->ifindex == LOOPBACK_IFINDEX || netif_is_l3_master(skb->dev))) { conststruct rt6_info *rt6 = skb_rt6_info(skb);
/* The destination could be an external IP in Ext Hdr (SRv6, RPL, etc.), * and ip6_null_entry could be set to skb if no route is found.
*/ if (rt6 && rt6->rt6i_idev)
dev = rt6->rt6i_idev->dev;
}
net = dev_net_rcu(skb->dev);
mark = IP6_REPLY_MARK(net, skb->mark); /* * Make sure we respect the rules * i.e. RFC 1885 2.4(e) * Rule (e.1) is enforced by not using icmp6_send * in any code that processes icmp errors.
*/
addr_type = ipv6_addr_type(&hdr->daddr);
if (__ipv6_addr_needs_scope_id(addr_type)) {
iif = icmp6_iif(skb);
} else { /* * The source device is used for looking up which routing table * to use for sending an ICMP error.
*/
iif = l3mdev_master_ifindex(skb->dev);
}
/* * Must not send error if the source does not uniquely * identify a single node (RFC2463 Section 2.4). * We check unspecified / multicast addresses here, * and anycast addresses will be checked later.
*/ if ((addr_type == IPV6_ADDR_ANY) || (addr_type & IPV6_ADDR_MULTICAST)) {
net_dbg_ratelimited("icmp6_send: addr_any/mcast source [%pI6c > %pI6c]\n",
&hdr->saddr, &hdr->daddr); goto out;
}
/* * Never answer to a ICMP packet.
*/ if (is_ineligible(skb)) {
net_dbg_ratelimited("icmp6_send: no reply to icmp error [%pI6c > %pI6c]\n",
&hdr->saddr, &hdr->daddr); goto out;
}
/* Needed by both icmpv6_global_allow and icmpv6_xmit_lock */
local_bh_disable();
/* Check global sysctl_icmp_msgs_per_sec ratelimit */ if (!(skb->dev->flags & IFF_LOOPBACK) &&
!icmpv6_global_allow(net, type, &apply_ratelimit)) goto out_bh_enable;
mip6_addr_swap(skb, parm);
sk = icmpv6_xmit_lock(net); if (!sk) goto out_bh_enable;
memset(&fl6, 0, sizeof(fl6));
fl6.flowi6_proto = IPPROTO_ICMPV6;
fl6.daddr = hdr->saddr; if (force_saddr)
saddr = force_saddr; if (saddr) {
fl6.saddr = *saddr;
} elseif (!icmpv6_rt_has_prefsrc(sk, type, &fl6)) { /* select a more meaningful saddr from input if */ struct net_device *in_netdev;
/* Slightly more convenient version of icmp6_send with drop reasons.
*/ void icmpv6_param_prob_reason(struct sk_buff *skb, u8 code, int pos, enum skb_drop_reason reason)
{
icmp6_send(skb, ICMPV6_PARAMPROB, code, pos, NULL, IP6CB(skb));
kfree_skb_reason(skb, reason);
}
/* Generate icmpv6 with type/code ICMPV6_DEST_UNREACH/ICMPV6_ADDR_UNREACH * if sufficient data bytes are available * @nhs is the size of the tunnel header(s) : * Either an IPv4 header for SIT encap * an IPv4 header + GRE header for GRE encap
*/ int ip6_err_gen_icmpv6_unreach(struct sk_buff *skb, int nhs, int type, unsignedint data_len)
{ struct in6_addr temp_saddr; struct rt6_info *rt; struct sk_buff *skb2;
u32 info = 0;
if (!pskb_may_pull(skb, nhs + sizeof(struct ipv6hdr) + 8)) return 1;
/* RFC 4884 (partial) support for ICMP extensions */ if (data_len < 128 || (data_len & 7) || skb->len < data_len)
data_len = 0;
nexthdr = ((struct ipv6hdr *)skb->data)->nexthdr; if (ipv6_ext_hdr(nexthdr)) { /* now skip over extension headers */
inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr),
&nexthdr, &frag_off); if (inner_offset < 0) {
SKB_DR_SET(reason, IPV6_BAD_EXTHDR); goto out;
}
} else {
inner_offset = sizeof(struct ipv6hdr);
}
/* Checkin header including 8 bytes of inner protocol header. */
reason = pskb_may_pull_reason(skb, inner_offset + 8); if (reason != SKB_NOT_DROPPED_YET) goto out;
/* BUGGG_FUTURE: we should try to parse exthdrs in this packet. Without this we will not able f.e. to make source routed pmtu discovery. Corresponding argument (opt) to notifiers is already added. --ANK (980726)
*/
switch (type) { case ICMPV6_ECHO_REQUEST: if (!net->ipv6.sysctl.icmpv6_echo_ignore_all)
reason = icmpv6_echo_reply(skb); break; case ICMPV6_EXT_ECHO_REQUEST: if (!net->ipv6.sysctl.icmpv6_echo_ignore_all &&
READ_ONCE(net->ipv4.sysctl_icmp_echo_enable_probe))
reason = icmpv6_echo_reply(skb); break;
case ICMPV6_ECHO_REPLY: case ICMPV6_EXT_ECHO_REPLY:
ping_rcv(skb); return 0;
case ICMPV6_PKT_TOOBIG: /* BUGGG_FUTURE: if packet contains rthdr, we cannot update standard destination cache. Seems, only "advanced" destination cache will allow to solve this problem --ANK (980726)
*/ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr))) goto discard_it;
hdr = icmp6_hdr(skb);
/* to notify */
fallthrough; case ICMPV6_DEST_UNREACH: case ICMPV6_TIME_EXCEED: case ICMPV6_PARAMPROB:
reason = icmpv6_notify(skb, type, hdr->icmp6_code,
hdr->icmp6_mtu); break;
case NDISC_ROUTER_SOLICITATION: case NDISC_ROUTER_ADVERTISEMENT: case NDISC_NEIGHBOUR_SOLICITATION: case NDISC_NEIGHBOUR_ADVERTISEMENT: case NDISC_REDIRECT:
reason = ndisc_rcv(skb); break;
case ICMPV6_MGM_QUERY:
igmp6_event_query(skb); return 0;
case ICMPV6_MGM_REPORT:
igmp6_event_report(skb); return 0;
case ICMPV6_MGM_REDUCTION: case ICMPV6_NI_QUERY: case ICMPV6_NI_REPLY: case ICMPV6_MLD2_REPORT: case ICMPV6_DHAAD_REQUEST: case ICMPV6_DHAAD_REPLY: case ICMPV6_MOBILE_PREFIX_SOL: case ICMPV6_MOBILE_PREFIX_ADV: break;
default: /* informational */ if (type & ICMPV6_INFOMSG_MASK) break;
net_dbg_ratelimited("icmpv6: msg of unknown type [%pI6c > %pI6c]\n",
saddr, daddr);
/* * error of unknown type. * must pass to upper level
*/
/* until the v6 path can be better sorted assume failure and * preserve the status quo behaviour for the rest of the paths to here
*/ if (reason)
kfree_skb_reason(skb, reason); else
consume_skb(skb);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.