X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=net%2Fipv6%2Ficmp.c;h=ce7992982557d8cc28037bbb6388ede8f0e62d46;hb=148a03bc0b0e3ef153d0cade7bc88e9b14edfb7a;hp=5395afe55ca5a0e61b03f07eb10400625d9a0b0a;hpb=41a76906b3225997036efd88cbaae69d60b1e947;p=safe%2Fjmp%2Flinux-2.6 diff --git a/net/ipv6/icmp.c b/net/ipv6/icmp.c index 5395afe..ce79929 100644 --- a/net/ipv6/icmp.c +++ b/net/ipv6/icmp.c @@ -5,8 +5,6 @@ * Authors: * Pedro Roque * - * $Id: icmp.c,v 1.38 2002/02/08 03:57:19 davem Exp $ - * * Based on net/ipv4/icmp.c * * RFC 1885 @@ -42,6 +40,7 @@ #include #include #include +#include #ifdef CONFIG_SYSCTL #include @@ -64,15 +63,11 @@ #include #include #include +#include #include #include -DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics) __read_mostly; -EXPORT_SYMBOL(icmpv6_statistics); -DEFINE_SNMP_STAT(struct icmpv6msg_mib, icmpv6msg_statistics) __read_mostly; -EXPORT_SYMBOL(icmpv6msg_statistics); - /* * The ICMP socket(s). This is the most convenient way to flow control * our ICMP output as well as maintain a clean interface throughout @@ -80,42 +75,47 @@ EXPORT_SYMBOL(icmpv6msg_statistics); * * On SMP we have one ICMP socket per-cpu. */ -static DEFINE_PER_CPU(struct socket *, __icmpv6_socket) = NULL; -#define icmpv6_socket __get_cpu_var(__icmpv6_socket) +static inline struct sock *icmpv6_sk(struct net *net) +{ + return net->ipv6.icmp_sk[smp_processor_id()]; +} static int icmpv6_rcv(struct sk_buff *skb); -static struct inet6_protocol icmpv6_protocol = { +static const struct inet6_protocol icmpv6_protocol = { .handler = icmpv6_rcv, .flags = INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL, }; -static __inline__ int icmpv6_xmit_lock(void) +static __inline__ struct sock *icmpv6_xmit_lock(struct net *net) { + struct sock *sk; + local_bh_disable(); - if (unlikely(!spin_trylock(&icmpv6_socket->sk->sk_lock.slock))) { + sk = icmpv6_sk(net); + 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. */ local_bh_enable(); - return 1; + return NULL; } - return 0; + return sk; } -static __inline__ void icmpv6_xmit_unlock(void) +static __inline__ void icmpv6_xmit_unlock(struct sock *sk) { - spin_unlock_bh(&icmpv6_socket->sk->sk_lock.slock); + spin_unlock_bh(&sk->sk_lock.slock); } /* * Slightly more convenient version of icmpv6_send. */ -void icmpv6_param_prob(struct sk_buff *skb, int code, int pos) +void icmpv6_param_prob(struct sk_buff *skb, u8 code, int pos) { - icmpv6_send(skb, ICMPV6_PARAMPROB, code, pos, skb->dev); + icmpv6_send(skb, ICMPV6_PARAMPROB, code, pos); kfree_skb(skb); } @@ -157,10 +157,11 @@ static int is_ineligible(struct sk_buff *skb) /* * Check the ICMP output rate limit */ -static inline int icmpv6_xrlim_allow(struct sock *sk, int type, +static inline int icmpv6_xrlim_allow(struct sock *sk, u8 type, struct flowi *fl) { struct dst_entry *dst; + struct net *net = sock_net(sk); int res = 0; /* Informational messages are not limited. */ @@ -176,15 +177,15 @@ static inline int icmpv6_xrlim_allow(struct sock *sk, int type, * XXX: perhaps the expire for routing entries cloned by * this lookup should be more aggressive (not longer than timeout). */ - dst = ip6_route_output(sk, fl); + dst = ip6_route_output(net, sk, fl); if (dst->error) { - IP6_INC_STATS(ip6_dst_idev(dst), + IP6_INC_STATS(net, ip6_dst_idev(dst), IPSTATS_MIB_OUTNOROUTES); } else if (dst->dev && (dst->dev->flags&IFF_LOOPBACK)) { res = 1; } else { struct rt6_info *rt = (struct rt6_info *)dst; - int tmo = init_net.ipv6.sysctl.icmpv6_time; + int tmo = net->ipv6.sysctl.icmpv6_time; /* Give more bandwidth to wider prefixes. */ if (rt->rt6i_dst.plen < 128) @@ -228,7 +229,7 @@ static int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6h->icmp6_cksum = 0; if (skb_queue_len(&sk->sk_write_queue) == 1) { - skb->csum = csum_partial((char *)icmp6h, + skb->csum = csum_partial(icmp6h, sizeof(struct icmp6hdr), skb->csum); icmp6h->icmp6_cksum = csum_ipv6_magic(&fl->fl6_src, &fl->fl6_dst, @@ -241,7 +242,7 @@ static int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct tmp_csum = csum_add(tmp_csum, skb->csum); } - tmp_csum = csum_partial((char *)icmp6h, + tmp_csum = csum_partial(icmp6h, sizeof(struct icmp6hdr), tmp_csum); icmp6h->icmp6_cksum = csum_ipv6_magic(&fl->fl6_src, &fl->fl6_dst, @@ -300,9 +301,9 @@ static inline void mip6_addr_swap(struct sk_buff *skb) {} /* * Send an ICMP message in response to a packet in error */ -void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, - struct net_device *dev) +void icmpv6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info) { + struct net *net = dev_net(skb->dev); struct inet6_dev *idev = NULL; struct ipv6hdr *hdr = ipv6_hdr(skb); struct sock *sk; @@ -317,7 +318,7 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, int iif = 0; int addr_type = 0; int len; - int hlimit, tclass; + int hlimit; int err = 0; if ((u8 *)hdr < skb->head || @@ -332,7 +333,7 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, */ addr_type = ipv6_addr_type(&hdr->daddr); - if (ipv6_chk_addr(&hdr->daddr, skb->dev, 0)) + if (ipv6_chk_addr(net, &hdr->daddr, skb->dev, 0)) saddr = &hdr->daddr; /* @@ -389,10 +390,9 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, fl.fl_icmp_code = code; security_skb_classify_flow(skb, &fl); - if (icmpv6_xmit_lock()) + sk = icmpv6_xmit_lock(net); + if (sk == NULL) return; - - sk = icmpv6_socket->sk; np = inet6_sk(sk); if (!icmpv6_xrlim_allow(sk, type, &fl)) @@ -422,7 +422,7 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, /* No need to clone since we're just using its address. */ dst2 = dst; - err = xfrm_lookup(&dst, &fl, sk, 0); + err = xfrm_lookup(net, &dst, &fl, sk, 0); switch (err) { case 0: if (dst != dst2) @@ -436,37 +436,33 @@ void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info, } if (xfrm_decode_session_reverse(skb, &fl2, AF_INET6)) - goto out; + goto relookup_failed; - if (ip6_dst_lookup(sk, &dst2, &fl)) - goto out; + if (ip6_dst_lookup(sk, &dst2, &fl2)) + goto relookup_failed; - err = xfrm_lookup(&dst2, &fl, sk, XFRM_LOOKUP_ICMP); - if (err == -ENOENT) { + err = xfrm_lookup(net, &dst2, &fl2, sk, XFRM_LOOKUP_ICMP); + switch (err) { + case 0: + dst_release(dst); + dst = dst2; + break; + case -EPERM: + goto out_dst_release; + default: +relookup_failed: if (!dst) goto out; - goto route_done; + break; } - dst_release(dst); - dst = dst2; - - if (err) - goto out; - route_done: if (ipv6_addr_is_multicast(&fl.fl6_dst)) hlimit = np->mcast_hops; else hlimit = np->hop_limit; if (hlimit < 0) - hlimit = dst_metric(dst, RTAX_HOPLIMIT); - if (hlimit < 0) - hlimit = ipv6_get_hoplimit(dst->dev); - - tclass = np->tclass; - if (tclass < 0) - tclass = 0; + hlimit = ip6_dst_hoplimit(dst); msg.skb = skb; msg.offset = skb_network_offset(skb); @@ -483,10 +479,11 @@ route_done: err = ip6_append_data(sk, icmpv6_getfrag, &msg, len + sizeof(struct icmp6hdr), - sizeof(struct icmp6hdr), - hlimit, tclass, NULL, &fl, (struct rt6_info*)dst, - MSG_DONTWAIT); + sizeof(struct icmp6hdr), hlimit, + np->tclass, NULL, &fl, (struct rt6_info*)dst, + MSG_DONTWAIT, np->dontfrag); if (err) { + ICMP6_INC_STATS_BH(net, idev, ICMP6_MIB_OUTMSGS); ip6_flush_pending_frames(sk); goto out_put; } @@ -498,13 +495,14 @@ out_put: out_dst_release: dst_release(dst); out: - icmpv6_xmit_unlock(); + icmpv6_xmit_unlock(sk); } EXPORT_SYMBOL(icmpv6_send); static void icmpv6_echo_reply(struct sk_buff *skb) { + struct net *net = dev_net(skb->dev); struct sock *sk; struct inet6_dev *idev; struct ipv6_pinfo *np; @@ -516,7 +514,6 @@ static void icmpv6_echo_reply(struct sk_buff *skb) struct dst_entry *dst; int err = 0; int hlimit; - int tclass; saddr = &ipv6_hdr(skb)->daddr; @@ -535,10 +532,9 @@ static void icmpv6_echo_reply(struct sk_buff *skb) fl.fl_icmp_type = ICMPV6_ECHO_REPLY; security_skb_classify_flow(skb, &fl); - if (icmpv6_xmit_lock()) + sk = icmpv6_xmit_lock(net); + if (sk == NULL) return; - - sk = icmpv6_socket->sk; np = inet6_sk(sk); if (!fl.oif && ipv6_addr_is_multicast(&fl.fl6_dst)) @@ -547,7 +543,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb) err = ip6_dst_lookup(sk, &dst, &fl); if (err) goto out; - if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0) + if ((err = xfrm_lookup(net, &dst, &fl, sk, 0)) < 0) goto out; if (ipv6_addr_is_multicast(&fl.fl6_dst)) @@ -555,13 +551,7 @@ static void icmpv6_echo_reply(struct sk_buff *skb) else hlimit = np->hop_limit; if (hlimit < 0) - hlimit = dst_metric(dst, RTAX_HOPLIMIT); - if (hlimit < 0) - hlimit = ipv6_get_hoplimit(dst->dev); - - tclass = np->tclass; - if (tclass < 0) - tclass = 0; + hlimit = ip6_dst_hoplimit(dst); idev = in6_dev_get(skb->dev); @@ -570,10 +560,12 @@ static void icmpv6_echo_reply(struct sk_buff *skb) msg.type = ICMPV6_ECHO_REPLY; err = ip6_append_data(sk, icmpv6_getfrag, &msg, skb->len + sizeof(struct icmp6hdr), - sizeof(struct icmp6hdr), hlimit, tclass, NULL, &fl, - (struct rt6_info*)dst, MSG_DONTWAIT); + sizeof(struct icmp6hdr), hlimit, np->tclass, NULL, &fl, + (struct rt6_info*)dst, MSG_DONTWAIT, + np->dontfrag); if (err) { + ICMP6_INC_STATS_BH(net, idev, ICMP6_MIB_OUTMSGS); ip6_flush_pending_frames(sk); goto out_put; } @@ -584,12 +576,12 @@ out_put: in6_dev_put(idev); dst_release(dst); out: - icmpv6_xmit_unlock(); + icmpv6_xmit_unlock(sk); } -static void icmpv6_notify(struct sk_buff *skb, int type, int code, __be32 info) +static void icmpv6_notify(struct sk_buff *skb, u8 type, u8 code, __be32 info) { - struct inet6_protocol *ipprot; + const struct inet6_protocol *ipprot; int inner_offset; int hash; u8 nexthdr; @@ -640,12 +632,13 @@ static int icmpv6_rcv(struct sk_buff *skb) struct in6_addr *saddr, *daddr; struct ipv6hdr *orig_hdr; struct icmp6hdr *hdr; - int type; + u8 type; if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) { + struct sec_path *sp = skb_sec_path(skb); int nh; - if (!(skb->sp && skb->sp->xvec[skb->sp->len - 1]->props.flags & + if (!(sp && sp->xvec[sp->len - 1]->props.flags & XFRM_STATE_ICMP)) goto drop_no_count; @@ -661,7 +654,7 @@ static int icmpv6_rcv(struct sk_buff *skb) skb_set_network_header(skb, nh); } - ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INMSGS); + ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INMSGS); saddr = &ipv6_hdr(skb)->saddr; daddr = &ipv6_hdr(skb)->daddr; @@ -677,19 +670,20 @@ static int icmpv6_rcv(struct sk_buff *skb) skb->csum = ~csum_unfold(csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6, 0)); if (__skb_checksum_complete(skb)) { - LIMIT_NETDEBUG(KERN_DEBUG "ICMPv6 checksum failed [" NIP6_FMT " > " NIP6_FMT "]\n", - NIP6(*saddr), NIP6(*daddr)); + LIMIT_NETDEBUG(KERN_DEBUG "ICMPv6 checksum failed [%pI6 > %pI6]\n", + saddr, daddr); goto discard_it; } } - __skb_pull(skb, sizeof(*hdr)); + if (!pskb_pull(skb, sizeof(*hdr))) + goto discard_it; hdr = icmp6_hdr(skb); type = hdr->icmp6_type; - ICMP6MSGIN_INC_STATS_BH(idev, type); + ICMP6MSGIN_INC_STATS_BH(dev_net(dev), idev, type); switch (type) { case ICMPV6_ECHO_REQUEST: @@ -768,25 +762,46 @@ static int icmpv6_rcv(struct sk_buff *skb) return 0; discard_it: - ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS); + ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INERRORS); drop_no_count: kfree_skb(skb); return 0; } +void icmpv6_flow_init(struct sock *sk, struct flowi *fl, + u8 type, + const struct in6_addr *saddr, + const struct in6_addr *daddr, + int oif) +{ + memset(fl, 0, sizeof(*fl)); + ipv6_addr_copy(&fl->fl6_src, saddr); + ipv6_addr_copy(&fl->fl6_dst, daddr); + fl->proto = IPPROTO_ICMPV6; + fl->fl_icmp_type = type; + fl->fl_icmp_code = 0; + fl->oif = oif; + security_sk_classify_flow(sk, fl); +} + /* - * Special lock-class for __icmpv6_socket: + * Special lock-class for __icmpv6_sk: */ static struct lock_class_key icmpv6_socket_sk_dst_lock_key; -int __init icmpv6_init(struct net_proto_family *ops) +static int __net_init icmpv6_sk_init(struct net *net) { struct sock *sk; int err, i, j; + net->ipv6.icmp_sk = + kzalloc(nr_cpu_ids * sizeof(struct sock *), GFP_KERNEL); + if (net->ipv6.icmp_sk == NULL) + return -ENOMEM; + for_each_possible_cpu(i) { - err = sock_create_kern(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6, - &per_cpu(__icmpv6_socket, i)); + err = inet_ctl_sock_create(&sk, PF_INET6, + SOCK_RAW, IPPROTO_ICMPV6, net); if (err < 0) { printk(KERN_ERR "Failed to initialize the ICMP6 control socket " @@ -795,12 +810,12 @@ int __init icmpv6_init(struct net_proto_family *ops) goto fail; } - sk = per_cpu(__icmpv6_socket, i)->sk; - sk->sk_allocation = GFP_ATOMIC; + net->ipv6.icmp_sk[i] = sk; + /* * Split off their lock-class, because sk->sk_dst_lock * gets used from softirqs, which is safe for - * __icmpv6_socket (because those never get directly used + * __icmpv6_sk (because those never get directly used * via userspace syscalls), but unsafe for normal sockets. */ lockdep_set_class(&sk->sk_dst_lock, @@ -811,39 +826,57 @@ int __init icmpv6_init(struct net_proto_family *ops) */ sk->sk_sndbuf = (2 * ((64 * 1024) + sizeof(struct sk_buff))); - - sk->sk_prot->unhash(sk); - } - - - if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0) { - printk(KERN_ERR "Failed to register ICMP6 protocol\n"); - err = -EAGAIN; - goto fail; } - return 0; fail: - for (j = 0; j < i; j++) { - if (!cpu_possible(j)) - continue; - sock_release(per_cpu(__icmpv6_socket, j)); - } - + for (j = 0; j < i; j++) + inet_ctl_sock_destroy(net->ipv6.icmp_sk[j]); + kfree(net->ipv6.icmp_sk); return err; } -void icmpv6_cleanup(void) +static void __net_exit icmpv6_sk_exit(struct net *net) { int i; for_each_possible_cpu(i) { - sock_release(per_cpu(__icmpv6_socket, i)); + inet_ctl_sock_destroy(net->ipv6.icmp_sk[i]); } + kfree(net->ipv6.icmp_sk); +} + +static struct pernet_operations icmpv6_sk_ops = { + .init = icmpv6_sk_init, + .exit = icmpv6_sk_exit, +}; + +int __init icmpv6_init(void) +{ + int err; + + err = register_pernet_subsys(&icmpv6_sk_ops); + if (err < 0) + return err; + + err = -EAGAIN; + if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0) + goto fail; + return 0; + +fail: + printk(KERN_ERR "Failed to register ICMP6 protocol\n"); + unregister_pernet_subsys(&icmpv6_sk_ops); + return err; +} + +void icmpv6_cleanup(void) +{ + unregister_pernet_subsys(&icmpv6_sk_ops); inet6_del_protocol(&icmpv6_protocol, IPPROTO_ICMPV6); } + static const struct icmp6_err { int err; int fatal; @@ -870,7 +903,7 @@ static const struct icmp6_err { }, }; -int icmpv6_err_convert(int type, int code, int *err) +int icmpv6_err_convert(u8 type, u8 code, int *err) { int fatal = 0; @@ -907,23 +940,26 @@ EXPORT_SYMBOL(icmpv6_err_convert); #ifdef CONFIG_SYSCTL ctl_table ipv6_icmp_table_template[] = { { - .ctl_name = NET_IPV6_ICMP_RATELIMIT, .procname = "ratelimit", .data = &init_net.ipv6.sysctl.icmpv6_time, .maxlen = sizeof(int), .mode = 0644, - .proc_handler = &proc_dointvec + .proc_handler = proc_dointvec_ms_jiffies, }, - { .ctl_name = 0 }, + { }, }; -struct ctl_table *ipv6_icmp_sysctl_init(struct net *net) +struct ctl_table * __net_init ipv6_icmp_sysctl_init(struct net *net) { struct ctl_table *table; table = kmemdup(ipv6_icmp_table_template, sizeof(ipv6_icmp_table_template), GFP_KERNEL); + + if (table) + table[0].data = &net->ipv6.sysctl.icmpv6_time; + return table; } #endif