/* * Neighbour Discovery for IPv6 * Linux INET6 implementation * * Authors: * Pedro Roque * Mike Shaver * * $Id: ndisc.c,v 1.15 1997/04/29 09:38:48 mj Exp $ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ /* * Changes: * * Lars Fenneberg : fixed MTU setting on receipt * of an RA. * * Janos Farkas : kmalloc failure checks */ /* Set to 3 to get tracing... */ #define ND_DEBUG 2 #if ND_DEBUG >= 3 #define NDBG(x) printk x #else #define NDBG(x) #endif #define __NO_VERSION__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define NCACHE_NUM_BUCKETS 32 static struct inode ndisc_inode; static struct socket *ndisc_socket=&ndisc_inode.u.socket_i; unsigned long nd_rand_seed = 152L; struct ndisc_statistics nd_stats; static struct neigh_table nd_tbl; unsigned int ndisc_hash(void *primary_key); int ndisc_eth_resolv(unsigned char *h_dest, struct sk_buff *skb); static struct neigh_ops nd_neigh_ops = { ETH_P_IPV6, ndisc_hash, ndisc_eth_resolv, NULL }; static struct timer_list ndisc_timer; static struct timer_list ndisc_gc_timer; /* * Protocol variables */ unsigned long nd_reachable_time = RECHABLE_TIME; int nd_gc_interval = 5 * HZ; /* * garbage collection timeout must be greater than reachable time * since tstamp is updated by reachable confirmations only. * gc_staletime actually means the time after last confirmation * *NOT* after the last time the entry was used. */ int nd_gc_staletime = 3 * RECHABLE_TIME; static int ndisc_event_timer(struct nd_neigh *ndn); unsigned long ipv6_random(void) { nd_rand_seed=nd_rand_seed*69069L+1; return nd_rand_seed^jiffies; } static __inline__ unsigned long rand_reach_time(void) { unsigned long val; val = ipv6_random() % (MAX_RANDOM_FACTOR * ipv6_config.nd_base_reachable_time); if (val < (MIN_RANDOM_FACTOR * ipv6_config.nd_base_reachable_time)) val+= (MIN_RANDOM_FACTOR * ipv6_config.nd_base_reachable_time); return val; } unsigned int ndisc_hash(void *primary_key) { struct in6_addr *addr = (struct in6_addr *) primary_key; __u32 hash_val; addr = (struct in6_addr *) primary_key; hash_val = addr->s6_addr32[2] ^ addr->s6_addr32[3]; hash_val ^= hash_val >> 16; return (hash_val & (NCACHE_NUM_BUCKETS - 1)); } static int ndisc_gc_func(struct neighbour *neigh, void *arg); static void ndisc_periodic_timer(unsigned long arg) { static unsigned long last_rand = 0; unsigned long now = jiffies; /* * periodicly compute ReachableTime from random function */ if ((now - last_rand) > REACH_RANDOM_INTERVAL) { last_rand = now; nd_reachable_time = rand_reach_time(); } neigh_table_lock(&nd_tbl); start_bh_atomic(); if (atomic_read(&nd_tbl.tbl_lock) == 1) { ntbl_walk_table(&nd_tbl, ndisc_gc_func, 0, 0, NULL); ndisc_gc_timer.expires = now + nd_gc_interval; } else { #if ND_DEBUG >= 2 printk(KERN_DEBUG "ndisc_gc delayed: table locked\n"); #endif ndisc_gc_timer.expires = now + HZ; } end_bh_atomic(); neigh_table_unlock(&nd_tbl); add_timer(&ndisc_gc_timer); } static int ndisc_gc_func(struct neighbour *neigh, void *arg) { struct nd_neigh *ndn = (struct nd_neigh *) neigh; unsigned long now = jiffies; if (atomic_read(&ndn->ndn_refcnt) == 0) { switch (ndn->ndn_nud_state) { case NUD_REACHABLE: case NUD_STALE: if (now - ndn->ndn_tstamp < nd_gc_staletime) break; case NUD_FAILED: return 1; default: }; } return 0; } static __inline__ void ndisc_add_timer(struct nd_neigh *ndn, int timer) { unsigned long now = jiffies; unsigned long tval = ~0UL; ndn->ndn_expires = now + timer; if (del_timer(&ndisc_timer)) tval = ndisc_timer.expires; tval = min(tval, ndn->ndn_expires); ndisc_timer.expires = tval; add_timer(&ndisc_timer); } static void ndisc_del_timer(struct nd_neigh *ndn) { unsigned long tval = ~0UL; unsigned long neigh_val; if (del_timer(&ndisc_timer)) tval = ndisc_timer.expires; neigh_val = ndn->ndn_expires; ndn->ndn_expires = 0; if (tval == neigh_val) { int i; tval = ~0UL; neigh_table_lock(&nd_tbl); /* need to search the entire neighbour cache */ for (i=0; i < nd_tbl.tbl_size; i++) { struct neighbour *neigh, *head; head = nd_tbl.hash_buckets[i]; if ((neigh = head) == NULL) continue; do { struct nd_neigh *n; n = (struct nd_neigh *) neigh; if ((n->ndn_nud_state & NUD_IN_TIMER) && n->ndn_expires) tval = min(tval, n->ndn_expires); neigh = neigh->next; } while (neigh != head); } neigh_table_unlock(&nd_tbl); } if (tval == ~(0UL)) return; ndisc_timer.expires = tval; add_timer(&ndisc_timer); } static int ndisc_forced_gc(struct neighbour *neigh, void *arg) { struct nd_neigh *ndn = (struct nd_neigh *) neigh; if (atomic_read(&ndn->ndn_refcnt) == 0) { if (ndn->ndn_nud_state & NUD_IN_TIMER) ndisc_del_timer(ndn); return 1; } return 0; } static struct nd_neigh * ndisc_new_neigh(struct device *dev, struct in6_addr *addr) { struct nd_neigh *ndn; NDBG(("ndisc_new_neigh(")); if(dev) NDBG(("%s,", dev->name)); else NDBG(("[NULL],")); NDBG(("[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x]): ", addr->s6_addr16[0], addr->s6_addr16[1], addr->s6_addr16[2], addr->s6_addr16[3], addr->s6_addr16[4], addr->s6_addr16[5], addr->s6_addr16[6], addr->s6_addr16[7])); ndn = (struct nd_neigh *) neigh_alloc(sizeof(struct nd_neigh), &nd_neigh_ops); if (ndn == NULL) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "neigh_alloc: out of memory\n"); #endif start_bh_atomic(); if (atomic_read(&nd_tbl.tbl_lock) == 1) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "ndisc_alloc: forcing gc\n"); #endif ntbl_walk_table(&nd_tbl, ndisc_forced_gc, 0, 0, NULL); } end_bh_atomic(); #if ND_DEBUG >= 1 printk(KERN_DEBUG "ndisc_alloc failed\n"); #endif return NULL; } nd_stats.allocs++; ipv6_addr_copy(&ndn->ndn_addr, addr); ndn->ndn_plen = 128; ndn->ndn_type = ipv6_addr_type(addr); ndn->ndn_dev = dev; ndn->ndn_tstamp = jiffies; if ((ndn->ndn_type & IPV6_ADDR_MULTICAST)) { NDBG(("MULTICAST(NCF_NOARP) ")); ndn->ndn_flags |= NCF_NOARP; } if (dev->type == ARPHRD_LOOPBACK || dev->type == ARPHRD_SIT) { NDBG(("%s(NCF_NOARP) ", (dev->type==ARPHRD_LOOPBACK) ? "LOOPBACK" : "SIT")); ndn->ndn_flags |= NCF_NOARP; } neigh_insert(&nd_tbl, (struct neighbour *) ndn); NDBG(("returning ndn(%p)\n", ndn)); return ndn; } /* * Called when creating a new dest_cache entry for a given destination * is likely that an entry for the refered gateway exists in cache * */ struct neighbour * ndisc_get_neigh(struct device *dev, struct in6_addr *addr) { struct nd_neigh *neigh; /* * neighbour cache: * cached information about nexthop and addr resolution */ if (dev == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "ndisc_get_neigh: NULL device\n"); #endif return NULL; } neigh_table_lock(&nd_tbl); neigh = (struct nd_neigh *) neigh_lookup(&nd_tbl, (void *) addr, sizeof(struct in6_addr), dev); if (neigh == NULL) { neigh = ndisc_new_neigh(dev, addr); if (neigh == NULL) return NULL; } neigh_table_unlock(&nd_tbl); return neighbour_clone((struct neighbour *) neigh); } /* * return values * 0 - Address Resolution succeded, send packet * 1 - Address Resolution unfinished / packet queued */ int ndisc_eth_resolv(unsigned char *h_dest, struct sk_buff *skb) { struct nd_neigh *ndn = NULL; if (skb->dst) ndn = (struct nd_neigh *) skb->dst->neighbour; if (ndn == NULL) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "ndisc_eth_resolv: nexthop is NULL\n"); #endif goto discard; } if ((ndn->ndn_type & IPV6_ADDR_MULTICAST)) { struct in6_addr *daddr; daddr = &skb->nh.ipv6h->daddr; ipv6_mc_map(daddr, h_dest); return 0; } switch (ndn->ndn_nud_state) { case NUD_FAILED: case NUD_NONE: ndisc_event_send((struct neighbour *)ndn, skb); case NUD_INCOMPLETE: if (skb_queue_len(&ndn->neigh.arp_queue) >= NDISC_QUEUE_LEN) { struct sk_buff *buff; buff = ndn->neigh.arp_queue.prev; skb_unlink(buff); dev_kfree_skb(buff, FREE_WRITE); } skb_queue_head(&ndn->neigh.arp_queue, skb); return 1; default: ndisc_event_send((struct neighbour *)ndn, skb); }; if ((ndn->ndn_flags & NTF_COMPLETE) == 0) { #if ND_DEBUG >=1 /* This shouldn't happen */ printk(KERN_DEBUG "ND: using incomplete entry\n"); #endif } memcpy(h_dest, ndn->ndn_ha, skb->dev->addr_len); return 0; discard: dev_kfree_skb(skb, FREE_WRITE); return 1; } /* * Send a Neighbour Advertisement */ void ndisc_send_na(struct device *dev, struct nd_neigh *ndn, struct in6_addr *daddr, struct in6_addr *solicited_addr, int router, int solicited, int override, int inc_opt) { struct sock *sk = ndisc_socket->sk; struct nd_msg *msg; int len, opt_len; struct sk_buff *skb; int err; NDBG(("ndisc_send_na(")); if(dev) NDBG(("%s,", dev->name)); else NDBG(("[NULL]")); NDBG(("%p): ", ndn)); if(daddr) NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2], daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5], daddr->s6_addr16[6], daddr->s6_addr16[7])); if(solicited_addr) NDBG(("solicit_addr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", solicited_addr->s6_addr16[0], solicited_addr->s6_addr16[1], solicited_addr->s6_addr16[2], solicited_addr->s6_addr16[3], solicited_addr->s6_addr16[4], solicited_addr->s6_addr16[5], solicited_addr->s6_addr16[6], solicited_addr->s6_addr16[7])); NDBG(("rtr(%d)sol(%d)ovr(%d)iopt(%d)\n", router, solicited, override, inc_opt)); opt_len = ((dev->addr_len + 1) >> 3) + 1; len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr); #if ND_DEBUG >=1 if (dev == NULL) { printk(KERN_DEBUG "send_na: null device\n"); return; } #endif if (inc_opt) len += opt_len << 3; skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15, 0, 0, &err); if (skb == NULL) { printk(KERN_DEBUG "send_na: alloc skb failed\n"); return; } /* * build the MAC header */ if (dev->hard_header_len) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15); if (dev->hard_header) { dev->hard_header(skb, dev, ETH_P_IPV6, ndn->ndn_ha, NULL, len); skb->arp = 1; } } ip6_nd_hdr(sk, skb, dev, solicited_addr, daddr, IPPROTO_ICMPV6, len); msg = (struct nd_msg *) skb_put(skb, len); msg->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT; msg->icmph.icmp6_code = 0; msg->icmph.icmp6_cksum = 0; msg->icmph.icmp6_unused = 0; msg->icmph.icmp6_router = router; msg->icmph.icmp6_solicited = solicited; msg->icmph.icmp6_override = override; /* Set the target address. */ ipv6_addr_copy(&msg->target, solicited_addr); if (inc_opt) { /* Set the source link-layer address option. */ msg->opt.opt_type = ND_OPT_TARGET_LL_ADDR; msg->opt.opt_len = opt_len; memcpy(msg->opt.link_addr, dev->dev_addr, dev->addr_len); if ((opt_len << 3) - (2 + dev->addr_len)) { memset(msg->opt.link_addr + dev->addr_len, 0, (opt_len << 3) - (2 + dev->addr_len)); } } /* checksum */ msg->icmph.icmp6_cksum = csum_ipv6_magic(solicited_addr, daddr, len, IPPROTO_ICMPV6, csum_partial((__u8 *) msg, len, 0)); dev_queue_xmit(skb); } void ndisc_send_ns(struct device *dev, struct neighbour *neigh, struct in6_addr *solicit, struct in6_addr *daddr, struct in6_addr *saddr) { unsigned char ha[MAX_ADDR_LEN]; struct sock *sk = ndisc_socket->sk; struct sk_buff *skb; struct nd_msg *msg; int len, opt_len; void *h_dest; int err; NDBG(("ndisc_send_ns(%s,%p): ", (dev ? dev->name : "[NULL]"), neigh)); if(daddr) NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2], daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5], daddr->s6_addr16[6], daddr->s6_addr16[7])); if(saddr) NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2], saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5], saddr->s6_addr16[6], saddr->s6_addr16[7])); if(solicit) NDBG(("solicit[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", solicit->s6_addr16[0], solicit->s6_addr16[1], solicit->s6_addr16[2], solicit->s6_addr16[3], solicit->s6_addr16[4], solicit->s6_addr16[5], solicit->s6_addr16[6], solicit->s6_addr16[7])); NDBG(("\n")); /* length of addr in 8 octet groups.*/ opt_len = ((dev->addr_len + 1) >> 3) + 1; len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr) + (opt_len << 3); skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15, 0, 0, &err); if (skb == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "send_ns: alloc skb failed\n"); #endif return; } skb->pkt_type = PACKET_NDISC; if (saddr == NULL) { struct inet6_ifaddr *ifa; /* use link local address */ ifa = ipv6_get_lladdr(dev); if (ifa) saddr = &ifa->addr; } if ((ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST)) { nd_stats.snt_probes_mcast++; ipv6_mc_map(daddr, ha); h_dest = ha; } else { if (neigh == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "send_ns: ucast destination " "with null neighbour\n"); #endif return; } h_dest = neigh->ha; nd_stats.snt_probes_ucast++; } if (dev->hard_header_len) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15); if (dev->hard_header) { dev->hard_header(skb, dev, ETH_P_IPV6, h_dest, NULL, len); skb->arp = 1; } } ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len); msg = (struct nd_msg *)skb_put(skb, len); msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION; msg->icmph.icmp6_code = 0; msg->icmph.icmp6_cksum = 0; msg->icmph.icmp6_unused = 0; /* Set the target address. */ ipv6_addr_copy(&msg->target, solicit); /* Set the source link-layer address option. */ msg->opt.opt_type = ND_OPT_SOURCE_LL_ADDR; msg->opt.opt_len = opt_len; memcpy(msg->opt.link_addr, dev->dev_addr, dev->addr_len); if ((opt_len << 3) - (2 + dev->addr_len)) { memset(msg->opt.link_addr + dev->addr_len, 0, (opt_len << 3) - (2 + dev->addr_len)); } /* checksum */ msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len, IPPROTO_ICMPV6, csum_partial((__u8 *) msg, len, 0)); /* send it! */ dev_queue_xmit(skb); } void ndisc_send_rs(struct device *dev, struct in6_addr *saddr, struct in6_addr *daddr) { struct sock *sk = ndisc_socket->sk; struct sk_buff *skb; struct icmp6hdr *hdr; __u8 * opt; int len, opt_len; int err; NDBG(("ndisc_send_rs(%s): ", (dev ? dev->name : "[NULL]"))); if(daddr) NDBG(("daddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", daddr->s6_addr16[0], daddr->s6_addr16[1], daddr->s6_addr16[2], daddr->s6_addr16[3], daddr->s6_addr16[4], daddr->s6_addr16[5], daddr->s6_addr16[6], daddr->s6_addr16[7])); if(saddr) NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2], saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5], saddr->s6_addr16[6], saddr->s6_addr16[7])); NDBG(("\n")); /* length of addr in 8 octet groups.*/ opt_len = ((dev->addr_len + 1) >> 3) + 1; len = sizeof(struct icmp6hdr) + (opt_len << 3); skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15, 0, 0, &err); if (skb == NULL) { printk(KERN_DEBUG "send_ns: alloc skb failed\n"); return; } if (dev->hard_header_len) { skb_reserve(skb, (dev->hard_header_len + 15) & ~15); if (dev->hard_header) { unsigned char ha[MAX_ADDR_LEN]; ipv6_mc_map(daddr, ha); dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, len); skb->arp = 1; } } ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len); hdr = (struct icmp6hdr *) skb_put(skb, len); hdr->icmp6_type = NDISC_ROUTER_SOLICITATION; hdr->icmp6_code = 0; hdr->icmp6_cksum = 0; hdr->icmp6_unused = 0; opt = (u8*) (hdr + 1); /* Set the source link-layer address option. */ opt[0] = ND_OPT_SOURCE_LL_ADDR; opt[1] = opt_len; memcpy(opt + 2, dev->dev_addr, dev->addr_len); if ((opt_len << 3) - (2 + dev->addr_len)) { memset(opt + 2 + dev->addr_len, 0, (opt_len << 3) - (2 + dev->addr_len)); } /* checksum */ hdr->icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len, IPPROTO_ICMPV6, csum_partial((__u8 *) hdr, len, 0)); /* send it! */ dev_queue_xmit(skb); } static int ndisc_store_hwaddr(struct nd_neigh *ndn, __u8 *opt, int opt_len, int option) { while (*opt != option && opt_len) { int len; len = opt[1] << 3; if (len == 0) { printk(KERN_WARNING "nd: option has 0 len\n"); return -EINVAL; } opt += len; opt_len -= len; } if (*opt == option) { memcpy(ndn->neigh.ha, opt + 2, ndn->ndn_dev->addr_len); return 0; } return -EINVAL; } /* Called when a timer expires for a neighbour entry. */ static void ndisc_timer_handler(unsigned long arg) { unsigned long now = jiffies; unsigned long ntimer = ~0UL; int i; neigh_table_lock(&nd_tbl); for (i=0; i < nd_tbl.tbl_size; i++) { struct nd_neigh *ndn, *head; head = (struct nd_neigh *) nd_tbl.hash_buckets[i]; if ((ndn = head) == NULL) continue; do { if (ndn->ndn_nud_state & NUD_IN_TIMER) { unsigned long time; time = ndn->ndn_expires - now; if ((long) time <= 0) time = ndisc_event_timer(ndn); if (time) ntimer = min(ntimer, time); } ndn = (struct nd_neigh *) ndn->neigh.next; } while (ndn != head); } if (ntimer != (~0UL)) { ndisc_timer.expires = now + ntimer; add_timer(&ndisc_timer); } neigh_table_unlock(&nd_tbl); } static int ndisc_event_timer(struct nd_neigh *ndn) { struct in6_addr *daddr; struct in6_addr *target; struct in6_addr mcaddr; struct device *dev; int max_probes; if (ndn->ndn_nud_state == NUD_DELAY) ndn->ndn_nud_state = NUD_PROBE; max_probes = (ndn->ndn_nud_state == NUD_PROBE ? ipv6_config.nd_max_ucast_solicit: ipv6_config.nd_max_mcast_solicit); if (ndn->ndn_probes == max_probes) { struct sk_buff *skb; ndn->ndn_nud_state = NUD_FAILED; ndn->ndn_flags &= ~NTF_COMPLETE; nd_stats.res_failed++; while((skb=skb_dequeue(&ndn->neigh.arp_queue))) { /* * "The sender MUST return an ICMP * destination unreachable" */ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, ndn->ndn_dev); dev_kfree_skb(skb, FREE_WRITE); } return 0; } ndn->ndn_probes++; dev = ndn->ndn_dev; target = &ndn->ndn_addr; if (ndn->ndn_nud_state == NUD_INCOMPLETE) { addrconf_addr_solict_mult(&ndn->ndn_addr, &mcaddr); daddr = &mcaddr; ndn = NULL; } else { daddr = &ndn->ndn_addr; } ndisc_send_ns(dev, (struct neighbour *) ndn, target, daddr, NULL); return ipv6_config.nd_retrans_time; } void ndisc_event_send(struct neighbour *neigh, struct sk_buff *skb) { struct nd_neigh *ndn = (struct nd_neigh *) neigh; struct in6_addr daddr; unsigned long now = jiffies; struct in6_addr *saddr = NULL; if ((ndn->ndn_flags & NCF_NOARP)) return; switch (ndn->ndn_nud_state) { case NUD_FAILED: ndn->ndn_probes = 0; case NUD_NONE: if (skb && !skb->stamp.tv_sec) { /* * skb->stamp allows us to know if we are * originating the skb or forwarding it. * (it is set on netif_rx) */ saddr = &skb->nh.ipv6h->saddr; } ndn->ndn_nud_state = NUD_INCOMPLETE; addrconf_addr_solict_mult(&ndn->ndn_addr, &daddr); ndisc_send_ns(ndn->ndn_dev, NULL, &ndn->ndn_addr, &daddr, saddr); ndisc_add_timer(ndn, ipv6_config.nd_retrans_time); break; case NUD_REACHABLE: if ((now - ndn->ndn_tstamp) < nd_reachable_time) break; case NUD_STALE: ndn->ndn_nud_state = NUD_DELAY; ndisc_add_timer(ndn, ipv6_config.nd_delay_probe_time); } } /* * Received a neighbour announce */ void ndisc_event_na(struct nd_neigh *ndn, unsigned char *opt, int opt_len, int solicited, int override) { struct sk_buff *skb; NDBG(("ndisc_event_na(%p,%p,%d,%d,%d)\n", ndn, opt, opt_len, solicited, override)); if (ndn->ndn_nud_state == NUD_NONE) ndn->ndn_nud_state = NUD_INCOMPLETE; if (ndn->ndn_nud_state == NUD_INCOMPLETE || override) { if (opt_len == 0) { printk(KERN_DEBUG "no opt on NA\n"); } else { /* Record hardware address. */ ndn->ndn_flags |= NTF_COMPLETE; if (ndisc_store_hwaddr(ndn, opt, opt_len, ND_OPT_TARGET_LL_ADDR)) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "event_na: invalid TARGET_LL_ADDR\n"); #endif ndn->ndn_flags &= ~NTF_COMPLETE; ndn->ndn_nud_state = NUD_NONE; return; } } } if (solicited || override || ndn->ndn_nud_state == NUD_INCOMPLETE) { ndn->ndn_probes = 0; ndn->ndn_tstamp = jiffies; if (ndn->ndn_nud_state & NUD_IN_TIMER) ndisc_del_timer(ndn); if (solicited) ndn->ndn_nud_state = NUD_REACHABLE; else ndn->ndn_nud_state = NUD_STALE; } while ((skb=skb_dequeue(&ndn->neigh.arp_queue))) dev_queue_xmit(skb); } static struct nd_neigh * ndisc_event_ns(struct in6_addr *saddr, struct sk_buff *skb) { struct nd_neigh *ndn; u8 *opt; int len; NDBG(("ndisc_event_ns: ")); if(saddr) NDBG(("saddr[%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x] ", saddr->s6_addr16[0], saddr->s6_addr16[1], saddr->s6_addr16[2], saddr->s6_addr16[3], saddr->s6_addr16[4], saddr->s6_addr16[5], saddr->s6_addr16[6], saddr->s6_addr16[7])); NDBG(("\n")); opt = skb->h.raw; opt += sizeof(struct icmp6hdr) + sizeof(struct in6_addr); len = skb->tail - opt; neigh_table_lock(&nd_tbl); ndn = (struct nd_neigh *) neigh_lookup(&nd_tbl, saddr, sizeof(struct in6_addr), skb->dev); if (ndn == NULL) ndn = ndisc_new_neigh(skb->dev, saddr); neigh_table_unlock(&nd_tbl); if (ndn == NULL) return NULL; switch(ndn->ndn_nud_state) { case NUD_REACHABLE: case NUD_STALE: case NUD_DELAY: if (*opt != ND_OPT_SOURCE_LL_ADDR || len != ndn->ndn_dev->addr_len || memcmp(ndn->neigh.ha, opt + 2, len)) break; if (ndn->ndn_nud_state & NUD_IN_TIMER) ndisc_del_timer(ndn); /* FALLTHROUGH */ default: ndn->ndn_flags |= NTF_COMPLETE; if (ndisc_store_hwaddr(ndn, opt, len, ND_OPT_SOURCE_LL_ADDR)) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "event_ns: invalid SOURCE_LL_ADDR\n"); #endif ndn->ndn_flags &= ~NTF_COMPLETE; ndn->ndn_nud_state = NUD_NONE; return ndn; } ndn->ndn_nud_state = NUD_STALE; ndn->ndn_tstamp = jiffies; ndn->ndn_probes = 0; }; return ndn; } static void ndisc_ll_addr_update(struct nd_neigh *ndn, u8* opt, int len, int type) { switch(ndn->ndn_nud_state) { case NUD_REACHABLE: case NUD_STALE: case NUD_DELAY: if (len == ndn->ndn_dev->addr_len && memcmp(ndn->neigh.ha, opt + 2, len) == 0) break; if (ndn->ndn_nud_state & NUD_IN_TIMER) ndisc_del_timer(ndn); default: ndn->ndn_flags |= NTF_COMPLETE; if (ndisc_store_hwaddr(ndn, opt, len, type)) { #if ND_DEBUG >=1 printk(KERN_DEBUG "NDISC: invalid LL_ADDR\n"); #endif ndn->ndn_flags &= ~NTF_COMPLETE; ndn->ndn_nud_state = NUD_NONE; break; } ndn->ndn_nud_state = NUD_STALE; ndn->ndn_tstamp = jiffies; ndn->ndn_probes = 0; }; } static void ndisc_router_discovery(struct sk_buff *skb) { struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw; struct nd_neigh *ndn; struct inet6_dev *in6_dev; struct rt6_info *rt; int lifetime; int optlen; __u8 * opt = (__u8 *)(ra_msg + 1); NDBG(("ndisc_router_discovery(%p)\n", skb)); optlen = (skb->tail - skb->h.raw) - sizeof(struct ra_msg); if (skb->nh.ipv6h->hop_limit != 255) { printk(KERN_INFO "NDISC: fake router advertisment received\n"); return; } /* * set the RA_RECV flag in the interface */ in6_dev = ipv6_get_idev(skb->dev); if (in6_dev == NULL) { printk(KERN_DEBUG "RA: can't find in6 device\n"); return; } if (in6_dev->if_flags & IF_RS_SENT) { /* * flag that an RA was received after an RS was sent * out on this interface. */ in6_dev->if_flags |= IF_RA_RCVD; } lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime); rt = rt6_get_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); if (rt && lifetime == 0) { ip6_del_rt(rt); rt = NULL; } if (rt == NULL && lifetime) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "ndisc_rdisc: adding default router\n"); #endif rt = rt6_add_dflt_router(&skb->nh.ipv6h->saddr, skb->dev); if (rt == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "route_add failed\n"); #endif return; } ndn = (struct nd_neigh *) rt->rt6i_nexthop; if (ndn == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "nd: add default router: null " "neighbour\n"); #endif return; } ndn->ndn_flags |= NCF_ROUTER; } if (rt) rt->rt6i_expires = jiffies + (HZ * lifetime); if (ra_msg->icmph.icmp6_hop_limit) ipv6_config.hop_limit = ra_msg->icmph.icmp6_hop_limit; /* * Update Reachable Time and Retrans Timer */ if (ra_msg->retrans_timer) ipv6_config.nd_retrans_time = ntohl(ra_msg->retrans_timer); if (ra_msg->reachable_time) { __u32 rtime = ntohl(ra_msg->reachable_time); if (rtime != ipv6_config.nd_base_reachable_time) { ipv6_config.nd_base_reachable_time = rtime; nd_gc_staletime = 3 * rtime; nd_reachable_time = rand_reach_time(); } } /* * Process options. */ while(optlen > 0) { int len; len = (opt[1] << 3); if (len == 0) { printk(KERN_DEBUG "RA: opt has 0 len\n"); break; } switch(*opt) { case ND_OPT_SOURCE_LL_ADDR: if (rt == NULL) break; ndn = (struct nd_neigh *) rt->rt6i_nexthop; if (ndn) ndisc_ll_addr_update(ndn, opt, len, ND_OPT_SOURCE_LL_ADDR); break; case ND_OPT_PREFIX_INFO: addrconf_prefix_rcv(skb->dev, opt, len); break; case ND_OPT_MTU: if (rt) { int mtu; struct device *dev; mtu = htonl(*(__u32 *)(opt+4)); dev = rt->rt6i_dev; if (dev == NULL) break; if (mtu < 576) { printk(KERN_DEBUG "NDISC: router " "announcement with mtu = %d\n", mtu); break; } if (dev->change_mtu) dev->change_mtu(dev, mtu); else dev->mtu = mtu; } break; case ND_OPT_TARGET_LL_ADDR: case ND_OPT_REDIRECT_HDR: printk(KERN_DEBUG "got illegal option with RA"); break; default: printk(KERN_DEBUG "unkown option in RA\n"); }; optlen -= len; opt += len; } } void ndisc_forwarding_on(void) { /* * Forwarding was turned on. */ rt6_purge_dflt_routers(0); } void ndisc_forwarding_off(void) { /* * Forwarding was turned off. */ } static void ndisc_redirect_rcv(struct sk_buff *skb) { struct icmp6hdr *icmph; struct in6_addr *dest; struct in6_addr *target; /* new first hop to destination */ struct nd_neigh *ndn; struct rt6_info *rt; int on_link = 0; int optlen; u8 * opt; NDBG(("ndisc_redirect_rcv(%p)\n", skb)); if (skb->nh.ipv6h->hop_limit != 255) { printk(KERN_WARNING "NDISC: fake ICMP redirect received\n"); return; } if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr) & IPV6_ADDR_LINKLOCAL)) { printk(KERN_WARNING "ICMP redirect: source address is not linklocal\n"); return; } optlen = skb->tail - skb->h.raw; optlen -= sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr); if (optlen < 0) { printk(KERN_WARNING "ICMP redirect: packet too small\n"); return; } icmph = (struct icmp6hdr *) skb->h.raw; target = (struct in6_addr *) (icmph + 1); dest = target + 1; if (ipv6_addr_type(dest) & IPV6_ADDR_MULTICAST) { printk(KERN_WARNING "ICMP redirect for multicast addr\n"); return; } if (ipv6_addr_cmp(dest, target) == 0) { on_link = 1; } else if (!(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) { printk(KERN_WARNING "ICMP redirect: target address is not linklocal\n"); return; } /* passed validation tests */ rt = rt6_redirect(dest, &skb->nh.ipv6h->saddr, target, skb->dev, on_link); if (rt == NULL) { printk(KERN_WARNING "ICMP redirect: no route to host\n"); return; } ndn = (struct nd_neigh *) rt->rt6i_nexthop; opt = (u8 *) (dest + 1); while (optlen > 0) { int len; len = (opt[1] << 3); if (*opt == ND_OPT_TARGET_LL_ADDR) ndisc_ll_addr_update(ndn, opt, len, ND_OPT_TARGET_LL_ADDR); opt += len; optlen -= len; } } void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh, struct in6_addr *target) { struct sock *sk = ndisc_socket->sk; int len = sizeof(struct icmp6hdr) + 2 * sizeof(struct in6_addr); struct sk_buff *buff; struct nd_neigh *ndn = (struct nd_neigh *) neigh; struct inet6_ifaddr *ifp; struct icmp6hdr *icmph; struct in6_addr *addrp; struct device *dev; struct rt6_info *rt; int ta_len = 0; u8 *opt; int rd_len; int err; int hlen; dev = skb->dev; rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev, 0); if (rt == NULL || rt->u.dst.error) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "ndisc_send_redirect: hostunreach\n"); #endif return; } if (rt->rt6i_flags & RTF_GATEWAY) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "ndisc_send_redirect: not a neighbour\n"); #endif return; } if (ndn->ndn_nud_state == NUD_REACHABLE) { ta_len = ((dev->addr_len + 1) >> 3) + 1; len += (ta_len << 3); } rd_len = min(536 - len, ntohs(skb->nh.ipv6h->payload_len) + 8); rd_len &= ~0x7; len += rd_len; ifp = ipv6_get_lladdr(dev); if (ifp == NULL) { #if ND_DEBUG >= 1 printk(KERN_DEBUG "redirect: no link_local addr for dev\n"); #endif return; } buff = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15, 0, 0, &err); if (buff == NULL) { #if ND_DEBUG >= 2 printk(KERN_DEBUG "ndisc_send_redirect: alloc_skb failed\n"); #endif return; } hlen = 0; if (dev->hard_header_len) { skb_reserve(buff, (dev->hard_header_len + 15) & ~15); if (dev->hard_header) { dev->hard_header(buff, dev, ETH_P_IPV6, ndn->ndn_ha, NULL, len); buff->arp = 1; } } ip6_nd_hdr(sk, buff, dev, &ifp->addr, &skb->nh.ipv6h->saddr, IPPROTO_ICMPV6, len); icmph = (struct icmp6hdr *) skb_put(buff, len); memset(icmph, 0, sizeof(struct icmp6hdr)); icmph->icmp6_type = NDISC_REDIRECT; /* * copy target and destination addresses */ addrp = (struct in6_addr *)(icmph + 1); ipv6_addr_copy(addrp, target); addrp++; ipv6_addr_copy(addrp, &skb->nh.ipv6h->daddr); opt = (u8*) (addrp + 1); /* * include target_address option */ if (ta_len) { int zb; *(opt++) = ND_OPT_TARGET_LL_ADDR; *(opt++) = ta_len; memcpy(opt, neigh->ha, neigh->dev->addr_len); opt += neigh->dev->addr_len; /* * if link layer address doesn't end on a 8 byte * boundary memset(0) the remider */ zb = (neigh->dev->addr_len + 2) & 0x7; if (zb) { int comp; comp = 8 - zb; memset(opt, 0, comp); opt += comp; } } /* * build redirect option and copy skb over to the new packet. */ memset(opt, 0, 8); *(opt++) = ND_OPT_REDIRECT_HDR; *(opt++) = (rd_len >> 3); opt += 6; memcpy(opt, &skb->nh.ipv6h, rd_len - 8); icmph->icmp6_cksum = csum_ipv6_magic(&ifp->addr, &skb->nh.ipv6h->saddr, len, IPPROTO_ICMPV6, csum_partial((u8 *) icmph, len, 0)); dev_queue_xmit(buff); } /* Called by upper layers to validate neighbour cache entries. */ void ndisc_validate(struct neighbour *neigh) { struct nd_neigh *ndn = (struct nd_neigh *) neigh; if (neigh == NULL) return; if (ndn->ndn_nud_state == NUD_INCOMPLETE) return; if (ndn->ndn_nud_state == NUD_DELAY) ndisc_del_timer(ndn); nd_stats.rcv_upper_conf++; ndn->ndn_nud_state = NUD_REACHABLE; ndn->ndn_tstamp = jiffies; } int ndisc_rcv(struct sk_buff *skb, struct device *dev, struct in6_addr *saddr, struct in6_addr *daddr, struct ipv6_options *opt, unsigned short len) { struct nd_msg *msg = (struct nd_msg *) skb->h.raw; struct nd_neigh *ndn; struct inet6_ifaddr *ifp; NDBG(("ndisc_rcv(type=%d) ", msg->icmph.icmp6_type)); switch (msg->icmph.icmp6_type) { case NDISC_NEIGHBOUR_SOLICITATION: NDBG(("NS ")); if ((ifp = ipv6_chk_addr(&msg->target))) { int addr_type; if (ifp->flags & DAD_INCOMPLETE) { /* * DAD failed */ /* XXX Check if this came in over same interface * XXX we just sent an NS from! That is valid! -DaveM */ printk(KERN_DEBUG "%s: duplicate address\n", ifp->idev->dev->name); del_timer(&ifp->timer); return 0; } addr_type = ipv6_addr_type(saddr); if (addr_type & IPV6_ADDR_UNICAST) { int inc; /* * update / create cache entry * for the source adddress */ nd_stats.rcv_probes_ucast++; ndn = ndisc_event_ns(saddr, skb); if (ndn == NULL) return 0; inc = ipv6_addr_type(daddr); inc &= IPV6_ADDR_MULTICAST; ndisc_send_na(dev, ndn, saddr, &ifp->addr, ifp->idev->router, 1, inc, inc); } else { #if ND_DEBUG >= 1 /* FIXME */ printk(KERN_DEBUG "ns: non unicast saddr\n"); #endif } } break; case NDISC_NEIGHBOUR_ADVERTISEMENT: NDBG(("NA ")); neigh_table_lock(&nd_tbl); ndn = (struct nd_neigh *) neigh_lookup(&nd_tbl, (void *) &msg->target, sizeof(struct in6_addr), skb->dev); neigh_table_unlock(&nd_tbl); if (ndn) { if (ndn->ndn_flags & NCF_ROUTER) { if (msg->icmph.icmp6_router == 0) { /* * Change: router to host */ #if 0 struct rt6_info *rt; rt = ndisc_get_dflt_router(skb->dev, saddr); if (rt) ndisc_del_dflt_router(rt); #endif } } else { if (msg->icmph.icmp6_router) ndn->ndn_flags |= NCF_ROUTER; } ndisc_event_na(ndn, (unsigned char *) &msg->opt, skb->tail - (u8 *)&msg->opt /*opt_len*/, msg->icmph.icmp6_solicited, msg->icmph.icmp6_override); } break; }; if (ipv6_config.forwarding == 0) { switch (msg->icmph.icmp6_type) { case NDISC_ROUTER_ADVERTISEMENT: NDBG(("RA ")); if (ipv6_config.accept_ra) ndisc_router_discovery(skb); break; case NDISC_REDIRECT: NDBG(("REDIR ")); if (ipv6_config.accept_redirects) ndisc_redirect_rcv(skb); break; }; } return 0; } #ifdef CONFIG_PROC_FS int ndisc_get_info(char *buffer, char **start, off_t offset, int length, int dummy) { unsigned long now = jiffies; int len = 0; int i; neigh_table_lock(&nd_tbl); for (i = 0; i < nd_tbl.tbl_size; i++) { struct neighbour *neigh, *head; head = nd_tbl.hash_buckets[i]; if ((neigh = head) == NULL) continue; do { struct nd_neigh *ndn = (struct nd_neigh *) neigh; int j; for (j=0; j<16; j++) { sprintf(buffer + len, "%02x", ndn->ndn_addr.s6_addr[j]); len += 2; } len += sprintf(buffer + len, " %02x %02x %02x %02x %08lx %08lx %08lx %04x %04x %04lx %8s ", i, ndn->ndn_plen, ndn->ndn_type, ndn->ndn_nud_state, ndn->ndn_expires ? ndn->ndn_expires - now : 0, now - ndn->ndn_tstamp, nd_reachable_time, nd_gc_staletime, atomic_read(&ndn->ndn_refcnt), ndn->ndn_flags, ndn->ndn_dev ? ndn->ndn_dev->name : "NULLDEV"); if ((ndn->ndn_flags & NTF_COMPLETE)) { for (j=0; j< neigh->dev->addr_len; j++) { sprintf(buffer + len, "%02x", neigh->ha[j]); len += 2; } } else { len += sprintf(buffer + len, "000000000000"); } len += sprintf(buffer + len, "\n"); neigh = neigh->next; } while (neigh != head); } neigh_table_unlock(&nd_tbl); *start = buffer + offset; len -= offset; if (len > length) len = length; return len; } struct proc_dir_entry ndisc_proc_entry = { PROC_NET_NDISC, 5, "ndisc", S_IFREG | S_IRUGO, 1, 0, 0, 0, NULL, &ndisc_get_info }; #endif /* CONFIG_PROC_FS */ __initfunc(void ndisc_init(struct net_proto_family *ops)) { struct sock *sk; int err; ndisc_inode.i_mode = S_IFSOCK; ndisc_inode.i_sock = 1; ndisc_inode.i_uid = 0; ndisc_inode.i_gid = 0; ndisc_socket->inode = &ndisc_inode; ndisc_socket->state = SS_UNCONNECTED; ndisc_socket->type=SOCK_RAW; if((err=ops->create(ndisc_socket, IPPROTO_ICMPV6))<0) printk(KERN_DEBUG "Failed to create the NDISC control socket.\n"); MOD_DEC_USE_COUNT; sk = ndisc_socket->sk; sk->allocation = GFP_ATOMIC; sk->net_pinfo.af_inet6.hop_limit = 255; sk->net_pinfo.af_inet6.priority = 15; sk->num = 256; /* * Initialize the neighbour table */ neigh_table_init(&nd_tbl, &nd_neigh_ops, NCACHE_NUM_BUCKETS); /* General ND state machine timer. */ init_timer(&ndisc_timer); ndisc_timer.function = ndisc_timer_handler; ndisc_timer.data = 0L; ndisc_timer.expires = 0L; /* ND GC timer */ init_timer(&ndisc_gc_timer); ndisc_gc_timer.function = ndisc_periodic_timer; ndisc_gc_timer.data = 0L; ndisc_gc_timer.expires = jiffies + nd_gc_interval; add_timer(&ndisc_gc_timer); #ifdef CONFIG_PROC_FS proc_net_register(&ndisc_proc_entry); #endif } #ifdef MODULE void ndisc_cleanup(void) { #ifdef CONFIG_PROC_FS proc_net_unregister(ndisc_proc_entry.low_ino); #endif del_timer(&ndisc_gc_timer); del_timer(&ndisc_timer); } #endif