#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/init.h>
+#include <linux/netfilter.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#include <net/sock.h>
#include <net/ipv6.h>
-#include <net/checksum.h>
+#include <net/ip6_checksum.h>
#include <net/protocol.h>
#include <net/raw.h>
#include <net/rawv6.h>
#include <asm/uaccess.h>
#include <asm/system.h>
-DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics);
+DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics) __read_mostly;
/*
* The ICMP socket(s). This is the most convenient way to flow control
static DEFINE_PER_CPU(struct socket *, __icmpv6_socket) = NULL;
#define icmpv6_socket __get_cpu_var(__icmpv6_socket)
-static int icmpv6_rcv(struct sk_buff **pskb, unsigned int *nhoffp);
+static int icmpv6_rcv(struct sk_buff **pskb);
static struct inet6_protocol icmpv6_protocol = {
.handler = icmpv6_rcv,
if (len < 0)
return 1;
- ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len);
+ ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr);
if (ptr < 0)
return 0;
if (nexthdr == IPPROTO_ICMPV6) {
- u8 type;
- if (skb_copy_bits(skb, ptr+offsetof(struct icmp6hdr, icmp6_type),
- &type, 1)
- || !(type & ICMPV6_INFOMSG_MASK))
+ u8 _type, *tp;
+ tp = skb_header_pointer(skb,
+ ptr+offsetof(struct icmp6hdr, icmp6_type),
+ sizeof(_type), &_type);
+ if (tp == NULL ||
+ !(*tp & ICMPV6_INFOMSG_MASK))
return 1;
}
return 0;
*/
dst = ip6_route_output(sk, fl);
if (dst->error) {
- IP6_INC_STATS(Ip6OutNoRoutes);
+ IP6_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
} else if (dst->dev && (dst->dev->flags&IFF_LOOPBACK)) {
res = 1;
} else {
static __inline__ int opt_unrec(struct sk_buff *skb, __u32 offset)
{
- u8 optval;
+ u8 _optval, *op;
offset += skb->nh.raw - skb->data;
- if (skb_copy_bits(skb, offset, &optval, 1))
+ op = skb_header_pointer(skb, offset, sizeof(_optval), &_optval);
+ if (op == NULL)
return 1;
- return (optval&0xC0) == 0x80;
+ return (*op & 0xC0) == 0x80;
}
-int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hdr *thdr, int len)
+static int icmpv6_push_pending_frames(struct sock *sk, struct flowi *fl, struct icmp6hdr *thdr, int len)
{
struct sk_buff *skb;
struct icmp6hdr *icmp6h;
struct icmpv6_msg {
struct sk_buff *skb;
int offset;
+ uint8_t type;
};
static int icmpv6_getfrag(void *from, char *to, int offset, int len, int odd, struct sk_buff *skb)
csum = skb_copy_and_csum_bits(org_skb, msg->offset + offset,
to, len, csum);
skb->csum = csum_block_add(skb->csum, csum, odd);
+ if (!(msg->type & ICMPV6_INFOMSG_MASK))
+ nf_ct_attach(skb, org_skb);
return 0;
}
{
struct inet6_dev *idev = NULL;
struct ipv6hdr *hdr = skb->nh.ipv6h;
- struct sock *sk = icmpv6_socket->sk;
- struct ipv6_pinfo *np = inet6_sk(sk);
+ struct sock *sk;
+ struct ipv6_pinfo *np;
struct in6_addr *saddr = NULL;
struct dst_entry *dst;
struct icmp6hdr tmp_hdr;
int iif = 0;
int addr_type = 0;
int len;
- int hlimit = -1;
+ int hlimit, tclass;
int err = 0;
if ((u8*)hdr < skb->head || (u8*)(hdr+1) > skb->tail)
iif = skb->dev->ifindex;
/*
- * Must not send if we know that source is Anycast also.
- * for now we don't know that.
+ * 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)) {
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "icmpv6_send: addr_any/mcast source\n"));
+ LIMIT_NETDEBUG(KERN_DEBUG "icmpv6_send: addr_any/mcast source\n");
return;
}
* Never answer to a ICMP packet.
*/
if (is_ineligible(skb)) {
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "icmpv6_send: no reply to icmp error\n"));
+ LIMIT_NETDEBUG(KERN_DEBUG "icmpv6_send: no reply to icmp error\n");
return;
}
if (icmpv6_xmit_lock())
return;
+ sk = icmpv6_socket->sk;
+ np = inet6_sk(sk);
+
if (!icmpv6_xrlim_allow(sk, type, &fl))
goto out;
if (err)
goto out;
- if (hlimit < 0) {
- 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);
+ /*
+ * We won't send icmp if the destination is known
+ * anycast.
+ */
+ if (((struct rt6_info *)dst)->rt6i_flags & RTF_ANYCAST) {
+ LIMIT_NETDEBUG(KERN_DEBUG "icmpv6_send: acast source\n");
+ goto out_dst_release;
}
+ if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
+ goto out;
+
+ 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->cork.tclass;
+ if (tclass < 0)
+ tclass = 0;
+
msg.skb = skb;
msg.offset = skb->nh.raw - skb->data;
+ msg.type = type;
len = skb->len - msg.offset;
len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr) -sizeof(struct icmp6hdr));
if (len < 0) {
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "icmp: len problem\n"));
+ LIMIT_NETDEBUG(KERN_DEBUG "icmp: len problem\n");
goto out_dst_release;
}
err = ip6_append_data(sk, icmpv6_getfrag, &msg,
len + sizeof(struct icmp6hdr),
sizeof(struct icmp6hdr),
- hlimit, NULL, &fl, (struct rt6_info*)dst,
+ hlimit, tclass, NULL, &fl, (struct rt6_info*)dst,
MSG_DONTWAIT);
if (err) {
ip6_flush_pending_frames(sk);
err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, len + sizeof(struct icmp6hdr));
if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
- ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6OutDestUnreachs, type - ICMPV6_DEST_UNREACH);
- ICMP6_INC_STATS_BH(idev, Icmp6OutMsgs);
+ ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_OUTDESTUNREACHS, type - ICMPV6_DEST_UNREACH);
+ ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTMSGS);
out_put:
if (likely(idev != NULL))
static void icmpv6_echo_reply(struct sk_buff *skb)
{
- struct sock *sk = icmpv6_socket->sk;
+ struct sock *sk;
struct inet6_dev *idev;
- struct ipv6_pinfo *np = inet6_sk(sk);
+ struct ipv6_pinfo *np;
struct in6_addr *saddr = NULL;
struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
struct icmp6hdr tmp_hdr;
struct icmpv6_msg msg;
struct dst_entry *dst;
int err = 0;
- int hlimit = -1;
+ int hlimit;
+ int tclass;
saddr = &skb->nh.ipv6h->daddr;
if (icmpv6_xmit_lock())
return;
+ sk = icmpv6_socket->sk;
+ np = inet6_sk(sk);
+
if (!fl.oif && ipv6_addr_is_multicast(&fl.fl6_dst))
fl.oif = np->mcast_oif;
err = ip6_dst_lookup(sk, &dst, &fl);
if (err)
goto out;
+ if ((err = xfrm_lookup(&dst, &fl, sk, 0)) < 0)
+ goto out;
- if (hlimit < 0) {
- 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 (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->cork.tclass;
+ if (tclass < 0)
+ tclass = 0;
idev = in6_dev_get(skb->dev);
msg.skb = skb;
msg.offset = 0;
+ msg.type = ICMPV6_ECHO_REPLY;
err = ip6_append_data(sk, icmpv6_getfrag, &msg, skb->len + sizeof(struct icmp6hdr),
- sizeof(struct icmp6hdr), hlimit, NULL, &fl,
+ sizeof(struct icmp6hdr), hlimit, tclass, NULL, &fl,
(struct rt6_info*)dst, MSG_DONTWAIT);
if (err) {
}
err = icmpv6_push_pending_frames(sk, &fl, &tmp_hdr, skb->len + sizeof(struct icmp6hdr));
- ICMP6_INC_STATS_BH(idev, Icmp6OutEchoReplies);
- ICMP6_INC_STATS_BH(idev, Icmp6OutMsgs);
+ ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTECHOREPLIES);
+ ICMP6_INC_STATS_BH(idev, ICMP6_MIB_OUTMSGS);
out_put:
if (likely(idev != NULL))
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, skb->len - sizeof(struct ipv6hdr));
+ inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr);
if (inner_offset<0)
return;
} else {
hash = nexthdr & (MAX_INET_PROTOS - 1);
rcu_read_lock();
- ipprot = inet6_protos[hash];
- smp_read_barrier_depends();
+ ipprot = rcu_dereference(inet6_protos[hash]);
if (ipprot && ipprot->err_handler)
ipprot->err_handler(skb, NULL, type, code, inner_offset, info);
rcu_read_unlock();
read_lock(&raw_v6_lock);
if ((sk = sk_head(&raw_v6_htable[hash])) != NULL) {
- while((sk = __raw_v6_lookup(sk, nexthdr, daddr, saddr))) {
+ while((sk = __raw_v6_lookup(sk, nexthdr, daddr, saddr,
+ IP6CB(skb)->iif))) {
rawv6_err(sk, skb, NULL, type, code, inner_offset, info);
sk = sk_next(sk);
}
* Handle icmp messages
*/
-static int icmpv6_rcv(struct sk_buff **pskb, unsigned int *nhoffp)
+static int icmpv6_rcv(struct sk_buff **pskb)
{
struct sk_buff *skb = *pskb;
struct net_device *dev = skb->dev;
struct icmp6hdr *hdr;
int type;
- ICMP6_INC_STATS_BH(idev, Icmp6InMsgs);
+ ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INMSGS);
saddr = &skb->nh.ipv6h->saddr;
daddr = &skb->nh.ipv6h->daddr;
/* Perform checksum. */
- if (skb->ip_summed == CHECKSUM_HW) {
- skb->ip_summed = CHECKSUM_UNNECESSARY;
- if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
- skb->csum)) {
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "ICMPv6 hw checksum failed\n"));
- skb->ip_summed = CHECKSUM_NONE;
- }
- }
- if (skb->ip_summed == CHECKSUM_NONE) {
- if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
- skb_checksum(skb, 0, skb->len, 0))) {
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "ICMPv6 checksum failed [%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x > %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]\n",
- NIP6(*saddr), NIP6(*daddr)));
+ switch (skb->ip_summed) {
+ case CHECKSUM_HW:
+ if (!csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
+ skb->csum))
+ break;
+ /* fall through */
+ case CHECKSUM_NONE:
+ skb->csum = ~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));
goto discard_it;
}
}
type = hdr->icmp6_type;
if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB)
- ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6InDestUnreachs, type - ICMPV6_DEST_UNREACH);
+ ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_INDESTUNREACHS, type - ICMPV6_DEST_UNREACH);
else if (type >= ICMPV6_ECHO_REQUEST && type <= NDISC_REDIRECT)
- ICMP6_INC_STATS_OFFSET_BH(idev, Icmp6InEchos, type - ICMPV6_ECHO_REQUEST);
+ ICMP6_INC_STATS_OFFSET_BH(idev, ICMP6_MIB_INECHOS, type - ICMPV6_ECHO_REQUEST);
switch (type) {
case ICMPV6_ECHO_REQUEST:
break;
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:
- LIMIT_NETDEBUG(
- printk(KERN_DEBUG "icmpv6: msg of unknown type\n"));
+ LIMIT_NETDEBUG(KERN_DEBUG "icmpv6: msg of unknown type\n");
/* informational */
if (type & ICMPV6_INFOMSG_MASK)
return 0;
discard_it:
- ICMP6_INC_STATS_BH(idev, Icmp6InErrors);
+ ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS);
kfree_skb(skb);
return 0;
}
struct sock *sk;
int err, i, j;
- for (i = 0; i < NR_CPUS; i++) {
- if (!cpu_possible(i))
- continue;
-
+ for_each_possible_cpu(i) {
err = sock_create_kern(PF_INET6, SOCK_RAW, IPPROTO_ICMPV6,
&per_cpu(__icmpv6_socket, i));
if (err < 0) {
{
int i;
- for (i = 0; i < NR_CPUS; i++) {
- if (!cpu_possible(i))
- continue;
+ for_each_possible_cpu(i) {
sock_release(per_cpu(__icmpv6_socket, i));
}
inet6_del_protocol(&icmpv6_protocol, IPPROTO_ICMPV6);
}
-static struct icmp6_err {
+static const struct icmp6_err {
int err;
int fatal;
} tab_unreach[] = {