#include <linux/fcntl.h>
#include <linux/stat.h>
#include <linux/socket.h>
-#include <linux/in.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include <linux/inetdevice.h>
-#include <linux/igmp.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
-#include <linux/mroute.h>
#include <linux/init.h>
-#include <net/ip.h>
#include <net/protocol.h>
#include <linux/skbuff.h>
#include <net/sock.h>
-#include <net/icmp.h>
-#include <net/udp.h>
#include <net/raw.h>
-#include <net/route.h>
#include <linux/notifier.h>
#include <linux/if_arp.h>
-#include <linux/netfilter_ipv4.h>
-#include <net/ipip.h>
#include <net/checksum.h>
#include <net/netlink.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <linux/mroute6.h>
+#include <linux/pim.h>
#include <net/addrconf.h>
#include <linux/netfilter_ipv6.h>
#define MIF_EXISTS(idx) (vif6_table[idx].dev != NULL)
-static struct mfc6_cache *mfc6_cache_array[MFC_LINES]; /* Forwarding cache */
+static int mroute_do_assert; /* Set in PIM assert */
+#ifdef CONFIG_IPV6_PIMSM_V2
+static int mroute_do_pim;
+#else
+#define mroute_do_pim 0
+#endif
+
+static struct mfc6_cache *mfc6_cache_array[MFC6_LINES]; /* Forwarding cache */
static struct mfc6_cache *mfc_unres_queue; /* Queue of unresolved entries */
static atomic_t cache_resolve_queue_len; /* Size of unresolved */
static struct kmem_cache *mrt_cachep __read_mostly;
static int ip6_mr_forward(struct sk_buff *skb, struct mfc6_cache *cache);
-static int ip6mr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert);
+static int ip6mr_cache_report(struct sk_buff *pkt, mifi_t mifi, int assert);
static int ip6mr_fill_mroute(struct sk_buff *skb, struct mfc6_cache *c, struct rtmsg *rtm);
+#ifdef CONFIG_IPV6_PIMSM_V2
+static struct inet6_protocol pim6_protocol;
+#endif
+
static struct timer_list ipmr_expire_timer;
const char *name = vif->dev ? vif->dev->name : "none";
seq_printf(seq,
- "%2Zd %-10s %8ld %7ld %8ld %7ld %05X\n",
+ "%2td %-10s %8ld %7ld %8ld %7ld %05X\n",
vif - vif6_table,
name, vif->bytes_in, vif->pkt_in,
vif->bytes_out, vif->pkt_out,
};
#endif
+#ifdef CONFIG_IPV6_PIMSM_V2
+static int reg_vif_num = -1;
+
+static int pim6_rcv(struct sk_buff *skb)
+{
+ struct pimreghdr *pim;
+ struct ipv6hdr *encap;
+ struct net_device *reg_dev = NULL;
+
+ if (!pskb_may_pull(skb, sizeof(*pim) + sizeof(*encap)))
+ goto drop;
+
+ pim = (struct pimreghdr *)skb_transport_header(skb);
+ if (pim->type != ((PIM_VERSION << 4) | PIM_REGISTER) ||
+ (pim->flags & PIM_NULL_REGISTER) ||
+ (ip_compute_csum((void *)pim, sizeof(*pim)) != 0 &&
+ csum_fold(skb_checksum(skb, 0, skb->len, 0))))
+ goto drop;
+
+ /* check if the inner packet is destined to mcast group */
+ encap = (struct ipv6hdr *)(skb_transport_header(skb) +
+ sizeof(*pim));
+
+ if (!ipv6_addr_is_multicast(&encap->daddr) ||
+ encap->payload_len == 0 ||
+ ntohs(encap->payload_len) + sizeof(*pim) > skb->len)
+ goto drop;
+
+ read_lock(&mrt_lock);
+ if (reg_vif_num >= 0)
+ reg_dev = vif6_table[reg_vif_num].dev;
+ if (reg_dev)
+ dev_hold(reg_dev);
+ read_unlock(&mrt_lock);
+
+ if (reg_dev == NULL)
+ goto drop;
+
+ skb->mac_header = skb->network_header;
+ skb_pull(skb, (u8 *)encap - skb->data);
+ skb_reset_network_header(skb);
+ skb->dev = reg_dev;
+ skb->protocol = htons(ETH_P_IP);
+ skb->ip_summed = 0;
+ skb->pkt_type = PACKET_HOST;
+ dst_release(skb->dst);
+ reg_dev->stats.rx_bytes += skb->len;
+ reg_dev->stats.rx_packets++;
+ skb->dst = NULL;
+ nf_reset(skb);
+ netif_rx(skb);
+ dev_put(reg_dev);
+ return 0;
+ drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+static struct inet6_protocol pim6_protocol = {
+ .handler = pim6_rcv,
+};
+
+/* Service routines creating virtual interfaces: PIMREG */
+
+static int reg_vif_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ read_lock(&mrt_lock);
+ dev->stats.tx_bytes += skb->len;
+ dev->stats.tx_packets++;
+ ip6mr_cache_report(skb, reg_vif_num, MRT6MSG_WHOLEPKT);
+ read_unlock(&mrt_lock);
+ kfree_skb(skb);
+ return 0;
+}
+
+static void reg_vif_setup(struct net_device *dev)
+{
+ dev->type = ARPHRD_PIMREG;
+ dev->mtu = 1500 - sizeof(struct ipv6hdr) - 8;
+ dev->flags = IFF_NOARP;
+ dev->hard_start_xmit = reg_vif_xmit;
+ dev->destructor = free_netdev;
+}
+
+static struct net_device *ip6mr_reg_vif(void)
+{
+ struct net_device *dev;
+
+ dev = alloc_netdev(0, "pim6reg", reg_vif_setup);
+ if (dev == NULL)
+ return NULL;
+
+ if (register_netdevice(dev)) {
+ free_netdev(dev);
+ return NULL;
+ }
+ dev->iflink = 0;
+
+ if (dev_open(dev))
+ goto failure;
+
+ return dev;
+
+failure:
+ /* allow the register to be completed before unregistering. */
+ rtnl_unlock();
+ rtnl_lock();
+
+ unregister_netdevice(dev);
+ return NULL;
+}
+#endif
+
/*
* Delete a VIF entry
*/
return -EADDRNOTAVAIL;
}
+#ifdef CONFIG_IPV6_PIMSM_V2
+ if (vifi == reg_vif_num)
+ reg_vif_num = -1;
+#endif
+
if (vifi + 1 == maxvif) {
int tmp;
for (tmp = vifi - 1; tmp >= 0; tmp--) {
{
int vifi;
- cache->mfc_un.res.minvif = MAXVIFS;
+ cache->mfc_un.res.minvif = MAXMIFS;
cache->mfc_un.res.maxvif = 0;
- memset(cache->mfc_un.res.ttls, 255, MAXVIFS);
+ memset(cache->mfc_un.res.ttls, 255, MAXMIFS);
for (vifi = 0; vifi < maxvif; vifi++) {
if (MIF_EXISTS(vifi) && ttls[vifi] && ttls[vifi] < 255) {
return -EADDRINUSE;
switch (vifc->mif6c_flags) {
+#ifdef CONFIG_IPV6_PIMSM_V2
+ case MIFF_REGISTER:
+ /*
+ * Special Purpose VIF in PIM
+ * All the packets will be sent to the daemon
+ */
+ if (reg_vif_num >= 0)
+ return -EADDRINUSE;
+ dev = ip6mr_reg_vif();
+ if (!dev)
+ return -ENOBUFS;
+ break;
+#endif
case 0:
dev = dev_get_by_index(&init_net, vifc->mif6c_pifi);
if (!dev)
write_lock_bh(&mrt_lock);
dev_hold(dev);
v->dev = dev;
+#ifdef CONFIG_IPV6_PIMSM_V2
+ if (v->flags & MIFF_REGISTER)
+ reg_vif_num = vifi;
+#endif
if (vifi + 1 > maxvif)
maxvif = vifi + 1;
write_unlock_bh(&mrt_lock);
if (c == NULL)
return NULL;
memset(c, 0, sizeof(*c));
- c->mfc_un.res.minvif = MAXVIFS;
+ c->mfc_un.res.minvif = MAXMIFS;
return c;
}
struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct ipv6hdr));
if (ip6mr_fill_mroute(skb, c, NLMSG_DATA(nlh)) > 0) {
- nlh->nlmsg_len = skb->tail - (u8 *)nlh;
+ nlh->nlmsg_len = skb_tail_pointer(skb) - (u8 *)nlh;
} else {
nlh->nlmsg_type = NLMSG_ERROR;
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
* Called under mrt_lock.
*/
-static int ip6mr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert)
+static int ip6mr_cache_report(struct sk_buff *pkt, mifi_t mifi, int assert)
{
struct sk_buff *skb;
struct mrt6msg *msg;
int ret;
- skb = alloc_skb(sizeof(struct ipv6hdr) + sizeof(*msg), GFP_ATOMIC);
+#ifdef CONFIG_IPV6_PIMSM_V2
+ if (assert == MRT6MSG_WHOLEPKT)
+ skb = skb_realloc_headroom(pkt, -skb_network_offset(pkt)
+ +sizeof(*msg));
+ else
+#endif
+ skb = alloc_skb(sizeof(struct ipv6hdr) + sizeof(*msg), GFP_ATOMIC);
if (!skb)
return -ENOBUFS;
skb->ip_summed = CHECKSUM_UNNECESSARY;
+#ifdef CONFIG_IPV6_PIMSM_V2
+ if (assert == MRT6MSG_WHOLEPKT) {
+ /* Ugly, but we have no choice with this interface.
+ Duplicate old header, fix length etc.
+ And all this only to mangle msg->im6_msgtype and
+ to set msg->im6_mbz to "mbz" :-)
+ */
+ skb_push(skb, -skb_network_offset(pkt));
+
+ skb_push(skb, sizeof(*msg));
+ skb_reset_transport_header(skb);
+ msg = (struct mrt6msg *)skb_transport_header(skb);
+ msg->im6_mbz = 0;
+ msg->im6_msgtype = MRT6MSG_WHOLEPKT;
+ msg->im6_mif = reg_vif_num;
+ msg->im6_pad = 0;
+ ipv6_addr_copy(&msg->im6_src, &ipv6_hdr(pkt)->saddr);
+ ipv6_addr_copy(&msg->im6_dst, &ipv6_hdr(pkt)->daddr);
+
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ } else
+#endif
+ {
/*
* Copy the IP header
*/
msg->im6_mbz = 0;
msg->im6_msgtype = assert;
- msg->im6_mif = vifi;
+ msg->im6_mif = mifi;
msg->im6_pad = 0;
ipv6_addr_copy(&msg->im6_src, &ipv6_hdr(pkt)->saddr);
ipv6_addr_copy(&msg->im6_dst, &ipv6_hdr(pkt)->daddr);
skb->ip_summed = CHECKSUM_UNNECESSARY;
skb_pull(skb, sizeof(struct ipv6hdr));
+ }
if (mroute6_socket == NULL) {
kfree_skb(skb);
*/
static int
-ip6mr_cache_unresolved(vifi_t vifi, struct sk_buff *skb)
+ip6mr_cache_unresolved(mifi_t mifi, struct sk_buff *skb)
{
int err;
struct mfc6_cache *c;
/*
* Reflect first query at pim6sd
*/
- if ((err = ip6mr_cache_report(skb, vifi, MRT6MSG_NOCACHE)) < 0) {
+ if ((err = ip6mr_cache_report(skb, mifi, MRT6MSG_NOCACHE)) < 0) {
/* If the report failed throw the cache entry
out - Brad Parker
*/
* Setup for IP multicast routing
*/
-void __init ip6_mr_init(void)
+int __init ip6_mr_init(void)
{
+ int err;
+
mrt_cachep = kmem_cache_create("ip6_mrt_cache",
sizeof(struct mfc6_cache),
0, SLAB_HWCACHE_ALIGN,
NULL);
if (!mrt_cachep)
- panic("cannot allocate ip6_mrt_cache");
+ return -ENOMEM;
setup_timer(&ipmr_expire_timer, ipmr_expire_process, 0);
- register_netdevice_notifier(&ip6_mr_notifier);
+ err = register_netdevice_notifier(&ip6_mr_notifier);
+ if (err)
+ goto reg_notif_fail;
+#ifdef CONFIG_PROC_FS
+ err = -ENOMEM;
+ if (!proc_net_fops_create(&init_net, "ip6_mr_vif", 0, &ip6mr_vif_fops))
+ goto proc_vif_fail;
+ if (!proc_net_fops_create(&init_net, "ip6_mr_cache",
+ 0, &ip6mr_mfc_fops))
+ goto proc_cache_fail;
+#endif
+ return 0;
+reg_notif_fail:
+ kmem_cache_destroy(mrt_cachep);
#ifdef CONFIG_PROC_FS
- proc_net_fops_create(&init_net, "ip6_mr_vif", 0, &ip6mr_vif_fops);
- proc_net_fops_create(&init_net, "ip6_mr_cache", 0, &ip6mr_mfc_fops);
+proc_vif_fail:
+ unregister_netdevice_notifier(&ip6_mr_notifier);
+proc_cache_fail:
+ proc_net_remove(&init_net, "ip6_mr_vif");
#endif
+ return err;
}
+void ip6_mr_cleanup(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_net_remove(&init_net, "ip6_mr_cache");
+ proc_net_remove(&init_net, "ip6_mr_vif");
+#endif
+ unregister_netdevice_notifier(&ip6_mr_notifier);
+ del_timer(&ipmr_expire_timer);
+ kmem_cache_destroy(mrt_cachep);
+}
static int ip6mr_mfc_add(struct mf6cctl *mfc, int mrtsock)
{
int line;
struct mfc6_cache *uc, *c, **cp;
- unsigned char ttls[MAXVIFS];
+ unsigned char ttls[MAXMIFS];
int i;
- memset(ttls, 255, MAXVIFS);
- for (i = 0; i < MAXVIFS; i++) {
+ memset(ttls, 255, MAXMIFS);
+ for (i = 0; i < MAXMIFS; i++) {
if (IF_ISSET(i, &mfc->mf6cc_ifset))
ttls[i] = 1;
return -EINVAL;
if (copy_from_user(&vif, optval, sizeof(vif)))
return -EFAULT;
- if (vif.mif6c_mifi >= MAXVIFS)
+ if (vif.mif6c_mifi >= MAXMIFS)
return -ENFILE;
rtnl_lock();
ret = mif6_add(&vif, sk == mroute6_socket);
return ret;
/*
- * Spurious command, or MRT_VERSION which you cannot
+ * Control PIM assert (to activate pim will activate assert)
+ */
+ case MRT6_ASSERT:
+ {
+ int v;
+ if (get_user(v, (int __user *)optval))
+ return -EFAULT;
+ mroute_do_assert = !!v;
+ return 0;
+ }
+
+#ifdef CONFIG_IPV6_PIMSM_V2
+ case MRT6_PIM:
+ {
+ int v;
+ if (get_user(v, (int __user *)optval))
+ return -EFAULT;
+ v = !!v;
+ rtnl_lock();
+ ret = 0;
+ if (v != mroute_do_pim) {
+ mroute_do_pim = v;
+ mroute_do_assert = v;
+ if (mroute_do_pim)
+ ret = inet6_add_protocol(&pim6_protocol,
+ IPPROTO_PIM);
+ else
+ ret = inet6_del_protocol(&pim6_protocol,
+ IPPROTO_PIM);
+ if (ret < 0)
+ ret = -EAGAIN;
+ }
+ rtnl_unlock();
+ return ret;
+ }
+
+#endif
+ /*
+ * Spurious command, or MRT6_VERSION which you cannot
* set.
*/
default:
case MRT6_VERSION:
val = 0x0305;
break;
+#ifdef CONFIG_IPV6_PIMSM_V2
+ case MRT6_PIM:
+ val = mroute_do_pim;
+ break;
+#endif
+ case MRT6_ASSERT:
+ val = mroute_do_assert;
+ break;
default:
return -ENOPROTOOPT;
}
static inline int ip6mr_forward2_finish(struct sk_buff *skb)
{
- /* XXX stats */
+ IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_OUTFORWDATAGRAMS);
return dst_output(skb);
}
if (vif->dev == NULL)
goto out_free;
+#ifdef CONFIG_IPV6_PIMSM_V2
+ if (vif->flags & MIFF_REGISTER) {
+ vif->pkt_out++;
+ vif->bytes_out += skb->len;
+ vif->dev->stats.tx_bytes += skb->len;
+ vif->dev->stats.tx_packets++;
+ ip6mr_cache_report(skb, vifi, MRT6MSG_WHOLEPKT);
+ kfree_skb(skb);
+ return 0;
+ }
+#endif
+
ipv6h = ipv6_hdr(skb);
fl = (struct flowi) {
cache->mfc_un.res.pkt++;
cache->mfc_un.res.bytes += skb->len;
+ /*
+ * Wrong interface: drop packet and (maybe) send PIM assert.
+ */
+ if (vif6_table[vif].dev != skb->dev) {
+ int true_vifi;
+
+ cache->mfc_un.res.wrong_if++;
+ true_vifi = ip6mr_find_vif(skb->dev);
+
+ if (true_vifi >= 0 && mroute_do_assert &&
+ /* pimsm uses asserts, when switching from RPT to SPT,
+ so that we cannot check that packet arrived on an oif.
+ It is bad, but otherwise we would need to move pretty
+ large chunk of pimd to kernel. Ough... --ANK
+ */
+ (mroute_do_pim || cache->mfc_un.res.ttls[true_vifi] < 255) &&
+ time_after(jiffies,
+ cache->mfc_un.res.last_assert + MFC_ASSERT_THRESH)) {
+ cache->mfc_un.res.last_assert = jiffies;
+ ip6mr_cache_report(skb, true_vifi, MRT6MSG_WRONGMIF);
+ }
+ goto dont_forward;
+ }
+
vif6_table[vif].pkt_in++;
vif6_table[vif].bytes_in += skb->len;
return 0;
}
+dont_forward:
kfree_skb(skb);
return 0;
}
int ct;
struct rtnexthop *nhp;
struct net_device *dev = vif6_table[c->mf6c_parent].dev;
- u8 *b = skb->tail;
+ u8 *b = skb_tail_pointer(skb);
struct rtattr *mp_head;
if (dev)
}
}
mp_head->rta_type = RTA_MULTIPATH;
- mp_head->rta_len = skb->tail - (u8 *)mp_head;
+ mp_head->rta_len = skb_tail_pointer(skb) - (u8 *)mp_head;
rtm->rtm_type = RTN_MULTICAST;
return 1;