2008-04-03 04:22:53 +04:00
/*
* Linux IPv6 multicast routing support for BSD pim6sd
* Based on net / ipv4 / ipmr . c .
*
* ( c ) 2004 Mickael Hoerdt , < hoerdt @ clarinet . u - strasbg . fr >
* LSIIT Laboratory , Strasbourg , France
* ( c ) 2004 Jean - Philippe Andriot , < jean - philippe . andriot @ 6 WIND . com >
* 6 WIND , Paris , France
* Copyright ( C ) 2007 , 2008 USAGI / WIDE Project
* YOSHIFUJI Hideaki < yoshfuji @ linux - ipv6 . org >
*
* 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 .
*
*/
# include <asm/system.h>
# include <asm/uaccess.h>
# include <linux/types.h>
# include <linux/sched.h>
# include <linux/errno.h>
# include <linux/timer.h>
# include <linux/mm.h>
# include <linux/kernel.h>
# include <linux/fcntl.h>
# include <linux/stat.h>
# include <linux/socket.h>
# include <linux/inet.h>
# include <linux/netdevice.h>
# include <linux/inetdevice.h>
# include <linux/proc_fs.h>
# include <linux/seq_file.h>
# include <linux/init.h>
# include <net/protocol.h>
# include <linux/skbuff.h>
# include <net/sock.h>
# include <net/raw.h>
# include <linux/notifier.h>
# include <linux/if_arp.h>
# include <net/checksum.h>
# include <net/netlink.h>
# include <net/ipv6.h>
# include <net/ip6_route.h>
# include <linux/mroute6.h>
2008-04-03 04:22:54 +04:00
# include <linux/pim.h>
2008-04-03 04:22:53 +04:00
# include <net/addrconf.h>
# include <linux/netfilter_ipv6.h>
/* Big lock, protecting vif table, mrt cache and mroute socket state.
Note that the changes are semaphored via rtnl_lock .
*/
static DEFINE_RWLOCK ( mrt_lock ) ;
/*
* Multicast router control variables
*/
2008-12-11 03:15:08 +03:00
# define MIF_EXISTS(_net, _idx) ((_net)->ipv6.vif6_table[_idx].dev != NULL)
2008-04-03 04:22:53 +04:00
static struct mfc6_cache * mfc_unres_queue ; /* Queue of unresolved entries */
/* Special spinlock for queue of unresolved entries */
static DEFINE_SPINLOCK ( mfc_unres_lock ) ;
/* We return to original Alan's scheme. Hash table of resolved
entries is changed only in process context and protected
with weak lock mrt_lock . Queue of unresolved entries is protected
with strong spinlock mfc_unres_lock .
In this case data path is free of exclusive locks at all .
*/
static struct kmem_cache * mrt_cachep __read_mostly ;
static int ip6_mr_forward ( struct sk_buff * skb , struct mfc6_cache * cache ) ;
2008-12-11 03:30:15 +03:00
static int ip6mr_cache_report ( struct net * net , struct sk_buff * pkt ,
mifi_t mifi , int assert ) ;
2008-04-03 04:22:53 +04:00
static int ip6mr_fill_mroute ( struct sk_buff * skb , struct mfc6_cache * c , struct rtmsg * rtm ) ;
2008-12-11 03:30:15 +03:00
static void mroute_clean_tables ( struct net * net ) ;
2008-04-03 04:22:53 +04:00
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
static struct inet6_protocol pim6_protocol ;
# endif
2008-04-03 04:22:53 +04:00
static struct timer_list ipmr_expire_timer ;
# ifdef CONFIG_PROC_FS
struct ipmr_mfc_iter {
2008-12-11 03:29:48 +03:00
struct seq_net_private p ;
2008-04-03 04:22:53 +04:00
struct mfc6_cache * * cache ;
int ct ;
} ;
2008-12-11 03:29:48 +03:00
static struct mfc6_cache * ipmr_mfc_seq_idx ( struct net * net ,
struct ipmr_mfc_iter * it , loff_t pos )
2008-04-03 04:22:53 +04:00
{
struct mfc6_cache * mfc ;
2008-12-11 03:29:48 +03:00
it - > cache = net - > ipv6 . mfc6_cache_array ;
2008-04-03 04:22:53 +04:00
read_lock ( & mrt_lock ) ;
2008-12-11 03:24:07 +03:00
for ( it - > ct = 0 ; it - > ct < MFC6_LINES ; it - > ct + + )
2008-12-11 03:29:48 +03:00
for ( mfc = net - > ipv6 . mfc6_cache_array [ it - > ct ] ;
2008-12-11 03:24:07 +03:00
mfc ; mfc = mfc - > next )
2008-04-03 04:22:53 +04:00
if ( pos - - = = 0 )
return mfc ;
read_unlock ( & mrt_lock ) ;
it - > cache = & mfc_unres_queue ;
spin_lock_bh ( & mfc_unres_lock ) ;
for ( mfc = mfc_unres_queue ; mfc ; mfc = mfc - > next )
2008-12-11 03:29:48 +03:00
if ( net_eq ( mfc6_net ( mfc ) , net ) & &
pos - - = = 0 )
2008-04-03 04:22:53 +04:00
return mfc ;
spin_unlock_bh ( & mfc_unres_lock ) ;
it - > cache = NULL ;
return NULL ;
}
/*
* The / proc interfaces to multicast routing / proc / ip6_mr_cache / proc / ip6_mr_vif
*/
struct ipmr_vif_iter {
2008-12-11 03:29:48 +03:00
struct seq_net_private p ;
2008-04-03 04:22:53 +04:00
int ct ;
} ;
2008-12-11 03:29:48 +03:00
static struct mif_device * ip6mr_vif_seq_idx ( struct net * net ,
struct ipmr_vif_iter * iter ,
2008-04-03 04:22:53 +04:00
loff_t pos )
{
2008-12-11 03:29:48 +03:00
for ( iter - > ct = 0 ; iter - > ct < net - > ipv6 . maxvif ; + + iter - > ct ) {
if ( ! MIF_EXISTS ( net , iter - > ct ) )
2008-04-03 04:22:53 +04:00
continue ;
if ( pos - - = = 0 )
2008-12-11 03:29:48 +03:00
return & net - > ipv6 . vif6_table [ iter - > ct ] ;
2008-04-03 04:22:53 +04:00
}
return NULL ;
}
static void * ip6mr_vif_seq_start ( struct seq_file * seq , loff_t * pos )
__acquires ( mrt_lock )
{
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
read_lock ( & mrt_lock ) ;
2008-12-11 03:29:48 +03:00
return * pos ? ip6mr_vif_seq_idx ( net , seq - > private , * pos - 1 )
: SEQ_START_TOKEN ;
2008-04-03 04:22:53 +04:00
}
static void * ip6mr_vif_seq_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct ipmr_vif_iter * iter = seq - > private ;
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
+ + * pos ;
if ( v = = SEQ_START_TOKEN )
2008-12-11 03:29:48 +03:00
return ip6mr_vif_seq_idx ( net , iter , 0 ) ;
2008-04-03 04:22:53 +04:00
2008-12-11 03:29:48 +03:00
while ( + + iter - > ct < net - > ipv6 . maxvif ) {
if ( ! MIF_EXISTS ( net , iter - > ct ) )
2008-04-03 04:22:53 +04:00
continue ;
2008-12-11 03:29:48 +03:00
return & net - > ipv6 . vif6_table [ iter - > ct ] ;
2008-04-03 04:22:53 +04:00
}
return NULL ;
}
static void ip6mr_vif_seq_stop ( struct seq_file * seq , void * v )
__releases ( mrt_lock )
{
read_unlock ( & mrt_lock ) ;
}
static int ip6mr_vif_seq_show ( struct seq_file * seq , void * v )
{
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
if ( v = = SEQ_START_TOKEN ) {
seq_puts ( seq ,
" Interface BytesIn PktsIn BytesOut PktsOut Flags \n " ) ;
} else {
const struct mif_device * vif = v ;
const char * name = vif - > dev ? vif - > dev - > name : " none " ;
seq_printf ( seq ,
2008-06-02 13:59:02 +04:00
" %2td %-10s %8ld %7ld %8ld %7ld %05X \n " ,
2008-12-11 03:29:48 +03:00
vif - net - > ipv6 . vif6_table ,
2008-04-03 04:22:53 +04:00
name , vif - > bytes_in , vif - > pkt_in ,
vif - > bytes_out , vif - > pkt_out ,
vif - > flags ) ;
}
return 0 ;
}
static struct seq_operations ip6mr_vif_seq_ops = {
. start = ip6mr_vif_seq_start ,
. next = ip6mr_vif_seq_next ,
. stop = ip6mr_vif_seq_stop ,
. show = ip6mr_vif_seq_show ,
} ;
static int ip6mr_vif_open ( struct inode * inode , struct file * file )
{
2008-12-11 03:29:48 +03:00
return seq_open_net ( inode , file , & ip6mr_vif_seq_ops ,
sizeof ( struct ipmr_vif_iter ) ) ;
2008-04-03 04:22:53 +04:00
}
static struct file_operations ip6mr_vif_fops = {
. owner = THIS_MODULE ,
. open = ip6mr_vif_open ,
. read = seq_read ,
. llseek = seq_lseek ,
2008-12-11 03:29:48 +03:00
. release = seq_release_net ,
2008-04-03 04:22:53 +04:00
} ;
static void * ipmr_mfc_seq_start ( struct seq_file * seq , loff_t * pos )
{
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
return * pos ? ipmr_mfc_seq_idx ( net , seq - > private , * pos - 1 )
: SEQ_START_TOKEN ;
2008-04-03 04:22:53 +04:00
}
static void * ipmr_mfc_seq_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct mfc6_cache * mfc = v ;
struct ipmr_mfc_iter * it = seq - > private ;
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
+ + * pos ;
if ( v = = SEQ_START_TOKEN )
2008-12-11 03:29:48 +03:00
return ipmr_mfc_seq_idx ( net , seq - > private , 0 ) ;
2008-04-03 04:22:53 +04:00
if ( mfc - > next )
return mfc - > next ;
if ( it - > cache = = & mfc_unres_queue )
goto end_of_list ;
2008-12-11 03:29:48 +03:00
BUG_ON ( it - > cache ! = net - > ipv6 . mfc6_cache_array ) ;
2008-04-03 04:22:53 +04:00
2008-12-11 03:24:07 +03:00
while ( + + it - > ct < MFC6_LINES ) {
2008-12-11 03:29:48 +03:00
mfc = net - > ipv6 . mfc6_cache_array [ it - > ct ] ;
2008-04-03 04:22:53 +04:00
if ( mfc )
return mfc ;
}
/* exhausted cache_array, show unresolved */
read_unlock ( & mrt_lock ) ;
it - > cache = & mfc_unres_queue ;
it - > ct = 0 ;
spin_lock_bh ( & mfc_unres_lock ) ;
mfc = mfc_unres_queue ;
if ( mfc )
return mfc ;
end_of_list :
spin_unlock_bh ( & mfc_unres_lock ) ;
it - > cache = NULL ;
return NULL ;
}
static void ipmr_mfc_seq_stop ( struct seq_file * seq , void * v )
{
struct ipmr_mfc_iter * it = seq - > private ;
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
if ( it - > cache = = & mfc_unres_queue )
spin_unlock_bh ( & mfc_unres_lock ) ;
2008-12-11 03:29:48 +03:00
else if ( it - > cache = = net - > ipv6 . mfc6_cache_array )
2008-04-03 04:22:53 +04:00
read_unlock ( & mrt_lock ) ;
}
static int ipmr_mfc_seq_show ( struct seq_file * seq , void * v )
{
int n ;
2008-12-11 03:29:48 +03:00
struct net * net = seq_file_net ( seq ) ;
2008-04-03 04:22:53 +04:00
if ( v = = SEQ_START_TOKEN ) {
seq_puts ( seq ,
" Group "
" Origin "
" Iif Pkts Bytes Wrong Oifs \n " ) ;
} else {
const struct mfc6_cache * mfc = v ;
const struct ipmr_mfc_iter * it = seq - > private ;
2008-12-04 09:22:16 +03:00
seq_printf ( seq , " %pI6 %pI6 %-3hd " ,
2008-10-29 02:09:23 +03:00
& mfc - > mf6c_mcastgrp , & mfc - > mf6c_origin ,
2008-12-04 09:21:47 +03:00
mfc - > mf6c_parent ) ;
2008-04-03 04:22:53 +04:00
if ( it - > cache ! = & mfc_unres_queue ) {
2008-12-04 09:21:47 +03:00
seq_printf ( seq , " %8lu %8lu %8lu " ,
mfc - > mfc_un . res . pkt ,
mfc - > mfc_un . res . bytes ,
mfc - > mfc_un . res . wrong_if ) ;
2008-04-03 04:22:53 +04:00
for ( n = mfc - > mfc_un . res . minvif ;
n < mfc - > mfc_un . res . maxvif ; n + + ) {
2008-12-11 03:29:48 +03:00
if ( MIF_EXISTS ( net , n ) & &
2008-04-03 04:22:53 +04:00
mfc - > mfc_un . res . ttls [ n ] < 255 )
seq_printf ( seq ,
" %2d:%-3d " ,
n , mfc - > mfc_un . res . ttls [ n ] ) ;
}
2008-12-04 09:21:47 +03:00
} else {
/* unresolved mfc_caches don't contain
* pkt , bytes and wrong_if values
*/
seq_printf ( seq , " %8lu %8lu %8lu " , 0ul , 0ul , 0ul ) ;
2008-04-03 04:22:53 +04:00
}
seq_putc ( seq , ' \n ' ) ;
}
return 0 ;
}
static struct seq_operations ipmr_mfc_seq_ops = {
. start = ipmr_mfc_seq_start ,
. next = ipmr_mfc_seq_next ,
. stop = ipmr_mfc_seq_stop ,
. show = ipmr_mfc_seq_show ,
} ;
static int ipmr_mfc_open ( struct inode * inode , struct file * file )
{
2008-12-11 03:29:48 +03:00
return seq_open_net ( inode , file , & ipmr_mfc_seq_ops ,
sizeof ( struct ipmr_mfc_iter ) ) ;
2008-04-03 04:22:53 +04:00
}
static struct file_operations ip6mr_mfc_fops = {
. owner = THIS_MODULE ,
. open = ipmr_mfc_open ,
. read = seq_read ,
. llseek = seq_lseek ,
2008-12-11 03:29:48 +03:00
. release = seq_release_net ,
2008-04-03 04:22:53 +04:00
} ;
# endif
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
static int pim6_rcv ( struct sk_buff * skb )
{
struct pimreghdr * pim ;
struct ipv6hdr * encap ;
struct net_device * reg_dev = NULL ;
2008-12-11 03:30:15 +03:00
struct net * net = dev_net ( skb - > dev ) ;
int reg_vif_num = net - > ipv6 . mroute_reg_vif_num ;
2008-04-03 04:22:54 +04:00
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 ) | |
2009-01-28 09:39:59 +03:00
( csum_ipv6_magic ( & ipv6_hdr ( skb ) - > saddr , & ipv6_hdr ( skb ) - > daddr ,
sizeof ( * pim ) , IPPROTO_PIM ,
csum_partial ( ( void * ) pim , sizeof ( * pim ) , 0 ) ) & &
2008-04-27 09:28:58 +04:00
csum_fold ( skb_checksum ( skb , 0 , skb - > len , 0 ) ) ) )
2008-04-03 04:22:54 +04:00
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 )
2008-12-11 03:30:15 +03:00
reg_dev = net - > ipv6 . vif6_table [ reg_vif_num ] . dev ;
2008-04-03 04:22:54 +04:00
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 ;
2009-01-28 09:39:59 +03:00
skb - > protocol = htons ( ETH_P_IPV6 ) ;
2008-04-03 04:22:54 +04:00
skb - > ip_summed = 0 ;
skb - > pkt_type = PACKET_HOST ;
dst_release ( skb - > dst ) ;
2008-05-22 01:17:54 +04:00
reg_dev - > stats . rx_bytes + = skb - > len ;
reg_dev - > stats . rx_packets + + ;
2008-04-03 04:22:54 +04:00
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 )
{
2008-12-11 03:30:15 +03:00
struct net * net = dev_net ( dev ) ;
2008-04-03 04:22:54 +04:00
read_lock ( & mrt_lock ) ;
2008-05-22 01:17:54 +04:00
dev - > stats . tx_bytes + = skb - > len ;
dev - > stats . tx_packets + + ;
2008-12-11 03:30:15 +03:00
ip6mr_cache_report ( net , skb , net - > ipv6 . mroute_reg_vif_num ,
MRT6MSG_WHOLEPKT ) ;
2008-04-03 04:22:54 +04:00
read_unlock ( & mrt_lock ) ;
kfree_skb ( skb ) ;
return 0 ;
}
2008-11-21 07:28:35 +03:00
static const struct net_device_ops reg_vif_netdev_ops = {
. ndo_start_xmit = reg_vif_xmit ,
} ;
2008-04-03 04:22:54 +04:00
static void reg_vif_setup ( struct net_device * dev )
{
dev - > type = ARPHRD_PIMREG ;
dev - > mtu = 1500 - sizeof ( struct ipv6hdr ) - 8 ;
dev - > flags = IFF_NOARP ;
2008-11-21 07:28:35 +03:00
dev - > netdev_ops = & reg_vif_netdev_ops ;
2008-04-03 04:22:54 +04:00
dev - > destructor = free_netdev ;
}
2008-12-11 03:30:15 +03:00
static struct net_device * ip6mr_reg_vif ( struct net * net )
2008-04-03 04:22:54 +04:00
{
struct net_device * dev ;
2008-05-22 01:17:54 +04:00
dev = alloc_netdev ( 0 , " pim6reg " , reg_vif_setup ) ;
2008-04-03 04:22:54 +04:00
if ( dev = = NULL )
return NULL ;
2008-12-11 03:30:15 +03:00
dev_net_set ( dev , net ) ;
2008-04-03 04:22:54 +04:00
if ( register_netdevice ( dev ) ) {
free_netdev ( dev ) ;
return NULL ;
}
dev - > iflink = 0 ;
if ( dev_open ( dev ) )
goto failure ;
2008-07-15 07:54:54 +04:00
dev_hold ( dev ) ;
2008-04-03 04:22:54 +04:00
return dev ;
failure :
/* allow the register to be completed before unregistering. */
rtnl_unlock ( ) ;
rtnl_lock ( ) ;
unregister_netdevice ( dev ) ;
return NULL ;
}
# endif
2008-04-03 04:22:53 +04:00
/*
* Delete a VIF entry
*/
2008-12-11 03:30:15 +03:00
static int mif6_delete ( struct net * net , int vifi )
2008-04-03 04:22:53 +04:00
{
struct mif_device * v ;
struct net_device * dev ;
2009-01-28 09:39:59 +03:00
struct inet6_dev * in6_dev ;
2008-12-11 03:30:15 +03:00
if ( vifi < 0 | | vifi > = net - > ipv6 . maxvif )
2008-04-03 04:22:53 +04:00
return - EADDRNOTAVAIL ;
2008-12-11 03:30:15 +03:00
v = & net - > ipv6 . vif6_table [ vifi ] ;
2008-04-03 04:22:53 +04:00
write_lock_bh ( & mrt_lock ) ;
dev = v - > dev ;
v - > dev = NULL ;
if ( ! dev ) {
write_unlock_bh ( & mrt_lock ) ;
return - EADDRNOTAVAIL ;
}
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
2008-12-11 03:30:15 +03:00
if ( vifi = = net - > ipv6 . mroute_reg_vif_num )
net - > ipv6 . mroute_reg_vif_num = - 1 ;
2008-04-03 04:22:54 +04:00
# endif
2008-12-11 03:30:15 +03:00
if ( vifi + 1 = = net - > ipv6 . maxvif ) {
2008-04-03 04:22:53 +04:00
int tmp ;
for ( tmp = vifi - 1 ; tmp > = 0 ; tmp - - ) {
2008-12-11 03:30:15 +03:00
if ( MIF_EXISTS ( net , tmp ) )
2008-04-03 04:22:53 +04:00
break ;
}
2008-12-11 03:30:15 +03:00
net - > ipv6 . maxvif = tmp + 1 ;
2008-04-03 04:22:53 +04:00
}
write_unlock_bh ( & mrt_lock ) ;
dev_set_allmulti ( dev , - 1 ) ;
2009-01-28 09:39:59 +03:00
in6_dev = __in6_dev_get ( dev ) ;
if ( in6_dev )
in6_dev - > cnf . mc_forwarding - - ;
2008-04-03 04:22:53 +04:00
if ( v - > flags & MIFF_REGISTER )
unregister_netdevice ( dev ) ;
dev_put ( dev ) ;
return 0 ;
}
2008-12-11 03:22:34 +03:00
static inline void ip6mr_cache_free ( struct mfc6_cache * c )
{
release_net ( mfc6_net ( c ) ) ;
kmem_cache_free ( mrt_cachep , c ) ;
}
2008-04-03 04:22:53 +04:00
/* Destroy an unresolved cache entry, killing queued skbs
and reporting error to netlink readers .
*/
static void ip6mr_destroy_unres ( struct mfc6_cache * c )
{
struct sk_buff * skb ;
2008-12-11 03:30:15 +03:00
struct net * net = mfc6_net ( c ) ;
2008-04-03 04:22:53 +04:00
2008-12-11 03:30:15 +03:00
atomic_dec ( & net - > ipv6 . cache_resolve_queue_len ) ;
2008-04-03 04:22:53 +04:00
while ( ( skb = skb_dequeue ( & c - > mfc_un . unres . unresolved ) ) ! = NULL ) {
if ( ipv6_hdr ( skb ) - > version = = 0 ) {
struct nlmsghdr * nlh = ( struct nlmsghdr * ) skb_pull ( skb , sizeof ( struct ipv6hdr ) ) ;
nlh - > nlmsg_type = NLMSG_ERROR ;
nlh - > nlmsg_len = NLMSG_LENGTH ( sizeof ( struct nlmsgerr ) ) ;
skb_trim ( skb , nlh - > nlmsg_len ) ;
( ( struct nlmsgerr * ) NLMSG_DATA ( nlh ) ) - > error = - ETIMEDOUT ;
2008-12-11 03:30:15 +03:00
rtnl_unicast ( skb , net , NETLINK_CB ( skb ) . pid ) ;
2008-04-03 04:22:53 +04:00
} else
kfree_skb ( skb ) ;
}
2008-12-11 03:22:34 +03:00
ip6mr_cache_free ( c ) ;
2008-04-03 04:22:53 +04:00
}
/* Single timer process for all the unresolved queue. */
static void ipmr_do_expire_process ( unsigned long dummy )
{
unsigned long now = jiffies ;
unsigned long expires = 10 * HZ ;
struct mfc6_cache * c , * * cp ;
cp = & mfc_unres_queue ;
while ( ( c = * cp ) ! = NULL ) {
if ( time_after ( c - > mfc_un . unres . expires , now ) ) {
/* not yet... */
unsigned long interval = c - > mfc_un . unres . expires - now ;
if ( interval < expires )
expires = interval ;
cp = & c - > next ;
continue ;
}
* cp = c - > next ;
ip6mr_destroy_unres ( c ) ;
}
2008-12-11 03:27:21 +03:00
if ( mfc_unres_queue ! = NULL )
2008-04-03 04:22:53 +04:00
mod_timer ( & ipmr_expire_timer , jiffies + expires ) ;
}
static void ipmr_expire_process ( unsigned long dummy )
{
if ( ! spin_trylock ( & mfc_unres_lock ) ) {
mod_timer ( & ipmr_expire_timer , jiffies + 1 ) ;
return ;
}
2008-12-11 03:27:21 +03:00
if ( mfc_unres_queue ! = NULL )
2008-04-03 04:22:53 +04:00
ipmr_do_expire_process ( dummy ) ;
spin_unlock ( & mfc_unres_lock ) ;
}
/* Fill oifs list. It is called under write locked mrt_lock. */
static void ip6mr_update_thresholds ( struct mfc6_cache * cache , unsigned char * ttls )
{
int vifi ;
2008-12-11 03:30:15 +03:00
struct net * net = mfc6_net ( cache ) ;
2008-04-03 04:22:53 +04:00
2008-04-10 13:40:10 +04:00
cache - > mfc_un . res . minvif = MAXMIFS ;
2008-04-03 04:22:53 +04:00
cache - > mfc_un . res . maxvif = 0 ;
2008-04-10 13:40:10 +04:00
memset ( cache - > mfc_un . res . ttls , 255 , MAXMIFS ) ;
2008-04-03 04:22:53 +04:00
2008-12-11 03:30:15 +03:00
for ( vifi = 0 ; vifi < net - > ipv6 . maxvif ; vifi + + ) {
if ( MIF_EXISTS ( net , vifi ) & &
2008-12-11 03:15:08 +03:00
ttls [ vifi ] & & ttls [ vifi ] < 255 ) {
2008-04-03 04:22:53 +04:00
cache - > mfc_un . res . ttls [ vifi ] = ttls [ vifi ] ;
if ( cache - > mfc_un . res . minvif > vifi )
cache - > mfc_un . res . minvif = vifi ;
if ( cache - > mfc_un . res . maxvif < = vifi )
cache - > mfc_un . res . maxvif = vifi + 1 ;
}
}
}
2008-12-11 03:30:15 +03:00
static int mif6_add ( struct net * net , struct mif6ctl * vifc , int mrtsock )
2008-04-03 04:22:53 +04:00
{
int vifi = vifc - > mif6c_mifi ;
2008-12-11 03:30:15 +03:00
struct mif_device * v = & net - > ipv6 . vif6_table [ vifi ] ;
2008-04-03 04:22:53 +04:00
struct net_device * dev ;
2009-01-28 09:39:59 +03:00
struct inet6_dev * in6_dev ;
2008-07-15 07:54:23 +04:00
int err ;
2008-04-03 04:22:53 +04:00
/* Is vif busy ? */
2008-12-11 03:30:15 +03:00
if ( MIF_EXISTS ( net , vifi ) )
2008-04-03 04:22:53 +04:00
return - EADDRINUSE ;
switch ( vifc - > mif6c_flags ) {
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
case MIFF_REGISTER :
/*
* Special Purpose VIF in PIM
* All the packets will be sent to the daemon
*/
2008-12-11 03:30:15 +03:00
if ( net - > ipv6 . mroute_reg_vif_num > = 0 )
2008-04-03 04:22:54 +04:00
return - EADDRINUSE ;
2008-12-11 03:30:15 +03:00
dev = ip6mr_reg_vif ( net ) ;
2008-04-03 04:22:54 +04:00
if ( ! dev )
return - ENOBUFS ;
2008-07-15 07:54:23 +04:00
err = dev_set_allmulti ( dev , 1 ) ;
if ( err ) {
unregister_netdevice ( dev ) ;
2008-07-15 07:54:54 +04:00
dev_put ( dev ) ;
2008-07-15 07:54:23 +04:00
return err ;
}
2008-04-03 04:22:54 +04:00
break ;
# endif
2008-04-03 04:22:53 +04:00
case 0 :
2008-12-11 03:30:15 +03:00
dev = dev_get_by_index ( net , vifc - > mif6c_pifi ) ;
2008-04-03 04:22:53 +04:00
if ( ! dev )
return - EADDRNOTAVAIL ;
2008-07-15 07:54:23 +04:00
err = dev_set_allmulti ( dev , 1 ) ;
2008-07-15 07:54:54 +04:00
if ( err ) {
dev_put ( dev ) ;
2008-07-15 07:54:23 +04:00
return err ;
2008-07-15 07:54:54 +04:00
}
2008-04-03 04:22:53 +04:00
break ;
default :
return - EINVAL ;
}
2009-01-28 09:39:59 +03:00
in6_dev = __in6_dev_get ( dev ) ;
if ( in6_dev )
in6_dev - > cnf . mc_forwarding + + ;
2008-04-03 04:22:53 +04:00
/*
* Fill in the VIF structures
*/
v - > rate_limit = vifc - > vifc_rate_limit ;
v - > flags = vifc - > mif6c_flags ;
if ( ! mrtsock )
v - > flags | = VIFF_STATIC ;
v - > threshold = vifc - > vifc_threshold ;
v - > bytes_in = 0 ;
v - > bytes_out = 0 ;
v - > pkt_in = 0 ;
v - > pkt_out = 0 ;
v - > link = dev - > ifindex ;
if ( v - > flags & MIFF_REGISTER )
v - > link = dev - > iflink ;
/* And finish update writing critical data */
write_lock_bh ( & mrt_lock ) ;
v - > dev = dev ;
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
if ( v - > flags & MIFF_REGISTER )
2008-12-11 03:30:15 +03:00
net - > ipv6 . mroute_reg_vif_num = vifi ;
2008-04-03 04:22:54 +04:00
# endif
2008-12-11 03:30:15 +03:00
if ( vifi + 1 > net - > ipv6 . maxvif )
net - > ipv6 . maxvif = vifi + 1 ;
2008-04-03 04:22:53 +04:00
write_unlock_bh ( & mrt_lock ) ;
return 0 ;
}
2008-12-11 03:30:15 +03:00
static struct mfc6_cache * ip6mr_cache_find ( struct net * net ,
struct in6_addr * origin ,
struct in6_addr * mcastgrp )
2008-04-03 04:22:53 +04:00
{
int line = MFC6_HASH ( mcastgrp , origin ) ;
struct mfc6_cache * c ;
2008-12-11 03:30:15 +03:00
for ( c = net - > ipv6 . mfc6_cache_array [ line ] ; c ; c = c - > next ) {
2008-04-03 04:22:53 +04:00
if ( ipv6_addr_equal ( & c - > mf6c_origin , origin ) & &
ipv6_addr_equal ( & c - > mf6c_mcastgrp , mcastgrp ) )
break ;
}
return c ;
}
/*
* Allocate a multicast cache entry
*/
2008-12-11 03:22:34 +03:00
static struct mfc6_cache * ip6mr_cache_alloc ( struct net * net )
2008-04-03 04:22:53 +04:00
{
2008-12-04 09:27:25 +03:00
struct mfc6_cache * c = kmem_cache_zalloc ( mrt_cachep , GFP_KERNEL ) ;
2008-04-03 04:22:53 +04:00
if ( c = = NULL )
return NULL ;
2008-04-10 13:40:10 +04:00
c - > mfc_un . res . minvif = MAXMIFS ;
2008-12-11 03:22:34 +03:00
mfc6_net_set ( c , net ) ;
2008-04-03 04:22:53 +04:00
return c ;
}
2008-12-11 03:22:34 +03:00
static struct mfc6_cache * ip6mr_cache_alloc_unres ( struct net * net )
2008-04-03 04:22:53 +04:00
{
2008-12-04 09:27:25 +03:00
struct mfc6_cache * c = kmem_cache_zalloc ( mrt_cachep , GFP_ATOMIC ) ;
2008-04-03 04:22:53 +04:00
if ( c = = NULL )
return NULL ;
skb_queue_head_init ( & c - > mfc_un . unres . unresolved ) ;
c - > mfc_un . unres . expires = jiffies + 10 * HZ ;
2008-12-11 03:22:34 +03:00
mfc6_net_set ( c , net ) ;
2008-04-03 04:22:53 +04:00
return c ;
}
/*
* A cache entry has gone into a resolved state from queued
*/
static void ip6mr_cache_resolve ( struct mfc6_cache * uc , struct mfc6_cache * c )
{
struct sk_buff * skb ;
/*
* Play the pending entries through our router
*/
while ( ( skb = __skb_dequeue ( & uc - > mfc_un . unres . unresolved ) ) ) {
if ( ipv6_hdr ( skb ) - > version = = 0 ) {
int err ;
struct nlmsghdr * nlh = ( struct nlmsghdr * ) skb_pull ( skb , sizeof ( struct ipv6hdr ) ) ;
if ( ip6mr_fill_mroute ( skb , c , NLMSG_DATA ( nlh ) ) > 0 ) {
2008-04-05 17:17:39 +04:00
nlh - > nlmsg_len = skb_tail_pointer ( skb ) - ( u8 * ) nlh ;
2008-04-03 04:22:53 +04:00
} else {
nlh - > nlmsg_type = NLMSG_ERROR ;
nlh - > nlmsg_len = NLMSG_LENGTH ( sizeof ( struct nlmsgerr ) ) ;
skb_trim ( skb , nlh - > nlmsg_len ) ;
( ( struct nlmsgerr * ) NLMSG_DATA ( nlh ) ) - > error = - EMSGSIZE ;
}
2008-12-11 03:30:15 +03:00
err = rtnl_unicast ( skb , mfc6_net ( uc ) , NETLINK_CB ( skb ) . pid ) ;
2008-04-03 04:22:53 +04:00
} else
ip6_mr_forward ( skb , c ) ;
}
}
/*
* Bounce a cache query up to pim6sd . We could use netlink for this but pim6sd
* expects the following bizarre scheme .
*
* Called under mrt_lock .
*/
2008-12-11 03:30:15 +03:00
static int ip6mr_cache_report ( struct net * net , struct sk_buff * pkt , mifi_t mifi ,
int assert )
2008-04-03 04:22:53 +04:00
{
struct sk_buff * skb ;
struct mrt6msg * msg ;
int ret ;
2008-04-03 04:22:54 +04:00
# 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 ) ;
2008-04-03 04:22:53 +04:00
if ( ! skb )
return - ENOBUFS ;
/* I suppose that internal messages
* do not require checksums */
skb - > ip_summed = CHECKSUM_UNNECESSARY ;
2008-04-03 04:22:54 +04:00
# 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 ;
2008-12-11 03:30:15 +03:00
msg - > im6_mif = net - > ipv6 . mroute_reg_vif_num ;
2008-04-03 04:22:54 +04:00
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
{
2008-04-03 04:22:53 +04:00
/*
* Copy the IP header
*/
skb_put ( skb , sizeof ( struct ipv6hdr ) ) ;
skb_reset_network_header ( skb ) ;
skb_copy_to_linear_data ( skb , ipv6_hdr ( pkt ) , sizeof ( struct ipv6hdr ) ) ;
/*
* Add our header
*/
skb_put ( skb , sizeof ( * msg ) ) ;
skb_reset_transport_header ( skb ) ;
msg = ( struct mrt6msg * ) skb_transport_header ( skb ) ;
msg - > im6_mbz = 0 ;
msg - > im6_msgtype = assert ;
2008-04-10 13:40:10 +04:00
msg - > im6_mif = mifi ;
2008-04-03 04:22:53 +04:00
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 - > dst = dst_clone ( pkt - > dst ) ;
skb - > ip_summed = CHECKSUM_UNNECESSARY ;
2008-04-03 04:22:54 +04:00
}
2008-04-03 04:22:53 +04:00
2008-12-11 03:30:15 +03:00
if ( net - > ipv6 . mroute6_sk = = NULL ) {
2008-04-03 04:22:53 +04:00
kfree_skb ( skb ) ;
return - EINVAL ;
}
/*
* Deliver to user space multicast routing algorithms
*/
2008-12-11 03:30:15 +03:00
ret = sock_queue_rcv_skb ( net - > ipv6 . mroute6_sk , skb ) ;
2008-12-11 03:07:08 +03:00
if ( ret < 0 ) {
2008-04-03 04:22:53 +04:00
if ( net_ratelimit ( ) )
printk ( KERN_WARNING " mroute6: pending queue full, dropping entries. \n " ) ;
kfree_skb ( skb ) ;
}
return ret ;
}
/*
* Queue a packet for resolution . It gets locked cache entry !
*/
static int
2008-12-11 03:30:15 +03:00
ip6mr_cache_unresolved ( struct net * net , mifi_t mifi , struct sk_buff * skb )
2008-04-03 04:22:53 +04:00
{
int err ;
struct mfc6_cache * c ;
spin_lock_bh ( & mfc_unres_lock ) ;
for ( c = mfc_unres_queue ; c ; c = c - > next ) {
2008-12-11 03:30:15 +03:00
if ( net_eq ( mfc6_net ( c ) , net ) & &
2008-12-11 03:27:21 +03:00
ipv6_addr_equal ( & c - > mf6c_mcastgrp , & ipv6_hdr ( skb ) - > daddr ) & &
2008-04-03 04:22:53 +04:00
ipv6_addr_equal ( & c - > mf6c_origin , & ipv6_hdr ( skb ) - > saddr ) )
break ;
}
if ( c = = NULL ) {
/*
* Create a new entry if allowable
*/
2008-12-11 03:30:15 +03:00
if ( atomic_read ( & net - > ipv6 . cache_resolve_queue_len ) > = 10 | |
( c = ip6mr_cache_alloc_unres ( net ) ) = = NULL ) {
2008-04-03 04:22:53 +04:00
spin_unlock_bh ( & mfc_unres_lock ) ;
kfree_skb ( skb ) ;
return - ENOBUFS ;
}
/*
* Fill in the new cache entry
*/
c - > mf6c_parent = - 1 ;
c - > mf6c_origin = ipv6_hdr ( skb ) - > saddr ;
c - > mf6c_mcastgrp = ipv6_hdr ( skb ) - > daddr ;
/*
* Reflect first query at pim6sd
*/
2008-12-11 03:30:15 +03:00
err = ip6mr_cache_report ( net , skb , mifi , MRT6MSG_NOCACHE ) ;
if ( err < 0 ) {
2008-04-03 04:22:53 +04:00
/* If the report failed throw the cache entry
out - Brad Parker
*/
spin_unlock_bh ( & mfc_unres_lock ) ;
2008-12-11 03:22:34 +03:00
ip6mr_cache_free ( c ) ;
2008-04-03 04:22:53 +04:00
kfree_skb ( skb ) ;
return err ;
}
2008-12-11 03:30:15 +03:00
atomic_inc ( & net - > ipv6 . cache_resolve_queue_len ) ;
2008-04-03 04:22:53 +04:00
c - > next = mfc_unres_queue ;
mfc_unres_queue = c ;
ipmr_do_expire_process ( 1 ) ;
}
/*
* See if we can append the packet
*/
if ( c - > mfc_un . unres . unresolved . qlen > 3 ) {
kfree_skb ( skb ) ;
err = - ENOBUFS ;
} else {
skb_queue_tail ( & c - > mfc_un . unres . unresolved , skb ) ;
err = 0 ;
}
spin_unlock_bh ( & mfc_unres_lock ) ;
return err ;
}
/*
* MFC6 cache manipulation by user space
*/
2008-12-11 03:30:15 +03:00
static int ip6mr_mfc_delete ( struct net * net , struct mf6cctl * mfc )
2008-04-03 04:22:53 +04:00
{
int line ;
struct mfc6_cache * c , * * cp ;
line = MFC6_HASH ( & mfc - > mf6cc_mcastgrp . sin6_addr , & mfc - > mf6cc_origin . sin6_addr ) ;
2008-12-11 03:30:15 +03:00
for ( cp = & net - > ipv6 . mfc6_cache_array [ line ] ;
2008-12-11 03:24:07 +03:00
( c = * cp ) ! = NULL ; cp = & c - > next ) {
2008-04-03 04:22:53 +04:00
if ( ipv6_addr_equal ( & c - > mf6c_origin , & mfc - > mf6cc_origin . sin6_addr ) & &
ipv6_addr_equal ( & c - > mf6c_mcastgrp , & mfc - > mf6cc_mcastgrp . sin6_addr ) ) {
write_lock_bh ( & mrt_lock ) ;
* cp = c - > next ;
write_unlock_bh ( & mrt_lock ) ;
2008-12-11 03:22:34 +03:00
ip6mr_cache_free ( c ) ;
2008-04-03 04:22:53 +04:00
return 0 ;
}
}
return - ENOENT ;
}
static int ip6mr_device_event ( struct notifier_block * this ,
unsigned long event , void * ptr )
{
struct net_device * dev = ptr ;
2008-12-11 03:30:15 +03:00
struct net * net = dev_net ( dev ) ;
2008-04-03 04:22:53 +04:00
struct mif_device * v ;
int ct ;
if ( event ! = NETDEV_UNREGISTER )
return NOTIFY_DONE ;
2008-12-11 03:30:15 +03:00
v = & net - > ipv6 . vif6_table [ 0 ] ;
for ( ct = 0 ; ct < net - > ipv6 . maxvif ; ct + + , v + + ) {
2008-04-03 04:22:53 +04:00
if ( v - > dev = = dev )
2008-12-11 03:30:15 +03:00
mif6_delete ( net , ct ) ;
2008-04-03 04:22:53 +04:00
}
return NOTIFY_DONE ;
}
static struct notifier_block ip6_mr_notifier = {
. notifier_call = ip6mr_device_event
} ;
/*
* Setup for IP multicast routing
*/
2008-12-11 03:15:08 +03:00
static int __net_init ip6mr_net_init ( struct net * net )
{
int err = 0 ;
net - > ipv6 . vif6_table = kcalloc ( MAXMIFS , sizeof ( struct mif_device ) ,
GFP_KERNEL ) ;
if ( ! net - > ipv6 . vif6_table ) {
err = - ENOMEM ;
goto fail ;
}
2008-12-11 03:24:07 +03:00
/* Forwarding cache */
net - > ipv6 . mfc6_cache_array = kcalloc ( MFC6_LINES ,
sizeof ( struct mfc6_cache * ) ,
GFP_KERNEL ) ;
if ( ! net - > ipv6 . mfc6_cache_array ) {
err = - ENOMEM ;
goto fail_mfc6_cache ;
}
2008-12-11 03:29:24 +03:00
# ifdef CONFIG_IPV6_PIMSM_V2
net - > ipv6 . mroute_reg_vif_num = - 1 ;
# endif
2008-12-11 03:29:48 +03:00
# ifdef CONFIG_PROC_FS
err = - ENOMEM ;
if ( ! proc_net_fops_create ( net , " ip6_mr_vif " , 0 , & ip6mr_vif_fops ) )
goto proc_vif_fail ;
if ( ! proc_net_fops_create ( net , " ip6_mr_cache " , 0 , & ip6mr_mfc_fops ) )
goto proc_cache_fail ;
# endif
2008-12-11 03:24:07 +03:00
return 0 ;
2008-12-11 03:29:48 +03:00
# ifdef CONFIG_PROC_FS
proc_cache_fail :
proc_net_remove ( net , " ip6_mr_vif " ) ;
proc_vif_fail :
kfree ( net - > ipv6 . mfc6_cache_array ) ;
# endif
2008-12-11 03:24:07 +03:00
fail_mfc6_cache :
kfree ( net - > ipv6 . vif6_table ) ;
2008-12-11 03:15:08 +03:00
fail :
return err ;
}
static void __net_exit ip6mr_net_exit ( struct net * net )
{
2008-12-11 03:29:48 +03:00
# ifdef CONFIG_PROC_FS
proc_net_remove ( net , " ip6_mr_cache " ) ;
proc_net_remove ( net , " ip6_mr_vif " ) ;
# endif
2008-12-11 03:30:15 +03:00
mroute_clean_tables ( net ) ;
2008-12-11 03:24:07 +03:00
kfree ( net - > ipv6 . mfc6_cache_array ) ;
2008-12-11 03:15:08 +03:00
kfree ( net - > ipv6 . vif6_table ) ;
}
static struct pernet_operations ip6mr_net_ops = {
. init = ip6mr_net_init ,
. exit = ip6mr_net_exit ,
} ;
2008-07-03 08:13:30 +04:00
int __init ip6_mr_init ( void )
2008-04-03 04:22:53 +04:00
{
2008-07-03 08:13:30 +04:00
int err ;
2008-04-03 04:22:53 +04:00
mrt_cachep = kmem_cache_create ( " ip6_mrt_cache " ,
sizeof ( struct mfc6_cache ) ,
0 , SLAB_HWCACHE_ALIGN ,
NULL ) ;
if ( ! mrt_cachep )
2008-07-03 08:13:30 +04:00
return - ENOMEM ;
2008-04-03 04:22:53 +04:00
2008-12-11 03:15:08 +03:00
err = register_pernet_subsys ( & ip6mr_net_ops ) ;
if ( err )
goto reg_pernet_fail ;
2008-04-03 04:22:53 +04:00
setup_timer ( & ipmr_expire_timer , ipmr_expire_process , 0 ) ;
2008-07-03 08:13:30 +04:00
err = register_netdevice_notifier ( & ip6_mr_notifier ) ;
if ( err )
goto reg_notif_fail ;
return 0 ;
2008-11-11 03:34:11 +03:00
reg_notif_fail :
del_timer ( & ipmr_expire_timer ) ;
2008-12-11 03:15:08 +03:00
unregister_pernet_subsys ( & ip6mr_net_ops ) ;
reg_pernet_fail :
2008-11-11 03:34:11 +03:00
kmem_cache_destroy ( mrt_cachep ) ;
2008-07-03 08:13:30 +04:00
return err ;
2008-04-03 04:22:53 +04:00
}
2008-07-03 08:13:30 +04:00
void ip6_mr_cleanup ( void )
{
unregister_netdevice_notifier ( & ip6_mr_notifier ) ;
del_timer ( & ipmr_expire_timer ) ;
2008-12-11 03:15:08 +03:00
unregister_pernet_subsys ( & ip6mr_net_ops ) ;
2008-07-03 08:13:30 +04:00
kmem_cache_destroy ( mrt_cachep ) ;
}
2008-04-03 04:22:53 +04:00
2008-12-11 03:30:15 +03:00
static int ip6mr_mfc_add ( struct net * net , struct mf6cctl * mfc , int mrtsock )
2008-04-03 04:22:53 +04:00
{
int line ;
struct mfc6_cache * uc , * c , * * cp ;
2008-04-10 13:40:10 +04:00
unsigned char ttls [ MAXMIFS ] ;
2008-04-03 04:22:53 +04:00
int i ;
2008-04-10 13:40:10 +04:00
memset ( ttls , 255 , MAXMIFS ) ;
for ( i = 0 ; i < MAXMIFS ; i + + ) {
2008-04-03 04:22:53 +04:00
if ( IF_ISSET ( i , & mfc - > mf6cc_ifset ) )
ttls [ i ] = 1 ;
}
line = MFC6_HASH ( & mfc - > mf6cc_mcastgrp . sin6_addr , & mfc - > mf6cc_origin . sin6_addr ) ;
2008-12-11 03:30:15 +03:00
for ( cp = & net - > ipv6 . mfc6_cache_array [ line ] ;
2008-12-11 03:24:07 +03:00
( c = * cp ) ! = NULL ; cp = & c - > next ) {
2008-04-03 04:22:53 +04:00
if ( ipv6_addr_equal ( & c - > mf6c_origin , & mfc - > mf6cc_origin . sin6_addr ) & &
ipv6_addr_equal ( & c - > mf6c_mcastgrp , & mfc - > mf6cc_mcastgrp . sin6_addr ) )
break ;
}
if ( c ! = NULL ) {
write_lock_bh ( & mrt_lock ) ;
c - > mf6c_parent = mfc - > mf6cc_parent ;
ip6mr_update_thresholds ( c , ttls ) ;
if ( ! mrtsock )
c - > mfc_flags | = MFC_STATIC ;
write_unlock_bh ( & mrt_lock ) ;
return 0 ;
}
if ( ! ipv6_addr_is_multicast ( & mfc - > mf6cc_mcastgrp . sin6_addr ) )
return - EINVAL ;
2008-12-11 03:30:15 +03:00
c = ip6mr_cache_alloc ( net ) ;
2008-04-03 04:22:53 +04:00
if ( c = = NULL )
return - ENOMEM ;
c - > mf6c_origin = mfc - > mf6cc_origin . sin6_addr ;
c - > mf6c_mcastgrp = mfc - > mf6cc_mcastgrp . sin6_addr ;
c - > mf6c_parent = mfc - > mf6cc_parent ;
ip6mr_update_thresholds ( c , ttls ) ;
if ( ! mrtsock )
c - > mfc_flags | = MFC_STATIC ;
write_lock_bh ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
c - > next = net - > ipv6 . mfc6_cache_array [ line ] ;
net - > ipv6 . mfc6_cache_array [ line ] = c ;
2008-04-03 04:22:53 +04:00
write_unlock_bh ( & mrt_lock ) ;
/*
* Check to see if we resolved a queued list . If so we
* need to send on the frames and tidy up .
*/
spin_lock_bh ( & mfc_unres_lock ) ;
for ( cp = & mfc_unres_queue ; ( uc = * cp ) ! = NULL ;
cp = & uc - > next ) {
2008-12-11 03:30:15 +03:00
if ( net_eq ( mfc6_net ( uc ) , net ) & &
2008-12-11 03:27:21 +03:00
ipv6_addr_equal ( & uc - > mf6c_origin , & c - > mf6c_origin ) & &
2008-04-03 04:22:53 +04:00
ipv6_addr_equal ( & uc - > mf6c_mcastgrp , & c - > mf6c_mcastgrp ) ) {
* cp = uc - > next ;
2008-12-11 03:30:15 +03:00
atomic_dec ( & net - > ipv6 . cache_resolve_queue_len ) ;
2008-04-03 04:22:53 +04:00
break ;
}
}
2008-12-11 03:27:21 +03:00
if ( mfc_unres_queue = = NULL )
del_timer ( & ipmr_expire_timer ) ;
2008-04-03 04:22:53 +04:00
spin_unlock_bh ( & mfc_unres_lock ) ;
if ( uc ) {
ip6mr_cache_resolve ( uc , c ) ;
2008-12-11 03:22:34 +03:00
ip6mr_cache_free ( uc ) ;
2008-04-03 04:22:53 +04:00
}
return 0 ;
}
/*
* Close the multicast socket , and clear the vif tables etc
*/
2008-12-11 03:30:15 +03:00
static void mroute_clean_tables ( struct net * net )
2008-04-03 04:22:53 +04:00
{
int i ;
/*
* Shut down all active vif entries
*/
2008-12-11 03:30:15 +03:00
for ( i = 0 ; i < net - > ipv6 . maxvif ; i + + ) {
if ( ! ( net - > ipv6 . vif6_table [ i ] . flags & VIFF_STATIC ) )
mif6_delete ( net , i ) ;
2008-04-03 04:22:53 +04:00
}
/*
* Wipe the cache
*/
2008-12-11 03:24:07 +03:00
for ( i = 0 ; i < MFC6_LINES ; i + + ) {
2008-04-03 04:22:53 +04:00
struct mfc6_cache * c , * * cp ;
2008-12-11 03:30:15 +03:00
cp = & net - > ipv6 . mfc6_cache_array [ i ] ;
2008-04-03 04:22:53 +04:00
while ( ( c = * cp ) ! = NULL ) {
if ( c - > mfc_flags & MFC_STATIC ) {
cp = & c - > next ;
continue ;
}
write_lock_bh ( & mrt_lock ) ;
* cp = c - > next ;
write_unlock_bh ( & mrt_lock ) ;
2008-12-11 03:22:34 +03:00
ip6mr_cache_free ( c ) ;
2008-04-03 04:22:53 +04:00
}
}
2008-12-11 03:30:15 +03:00
if ( atomic_read ( & net - > ipv6 . cache_resolve_queue_len ) ! = 0 ) {
2008-12-11 03:27:21 +03:00
struct mfc6_cache * c , * * cp ;
2008-04-03 04:22:53 +04:00
spin_lock_bh ( & mfc_unres_lock ) ;
2008-12-11 03:27:21 +03:00
cp = & mfc_unres_queue ;
while ( ( c = * cp ) ! = NULL ) {
2008-12-11 03:30:15 +03:00
if ( ! net_eq ( mfc6_net ( c ) , net ) ) {
2008-12-11 03:27:21 +03:00
cp = & c - > next ;
continue ;
}
* cp = c - > next ;
2008-04-03 04:22:53 +04:00
ip6mr_destroy_unres ( c ) ;
}
spin_unlock_bh ( & mfc_unres_lock ) ;
}
}
static int ip6mr_sk_init ( struct sock * sk )
{
int err = 0 ;
2008-12-11 03:30:15 +03:00
struct net * net = sock_net ( sk ) ;
2008-04-03 04:22:53 +04:00
rtnl_lock ( ) ;
write_lock_bh ( & mrt_lock ) ;
2009-01-28 09:39:59 +03:00
if ( likely ( net - > ipv6 . mroute6_sk = = NULL ) ) {
2008-12-11 03:30:15 +03:00
net - > ipv6 . mroute6_sk = sk ;
2009-01-28 09:39:59 +03:00
net - > ipv6 . devconf_all - > mc_forwarding + + ;
}
2008-04-03 04:22:53 +04:00
else
err = - EADDRINUSE ;
write_unlock_bh ( & mrt_lock ) ;
rtnl_unlock ( ) ;
return err ;
}
int ip6mr_sk_done ( struct sock * sk )
{
int err = 0 ;
2008-12-11 03:30:15 +03:00
struct net * net = sock_net ( sk ) ;
2008-04-03 04:22:53 +04:00
rtnl_lock ( ) ;
2008-12-11 03:30:15 +03:00
if ( sk = = net - > ipv6 . mroute6_sk ) {
2008-04-03 04:22:53 +04:00
write_lock_bh ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
net - > ipv6 . mroute6_sk = NULL ;
2009-01-28 09:39:59 +03:00
net - > ipv6 . devconf_all - > mc_forwarding - - ;
2008-04-03 04:22:53 +04:00
write_unlock_bh ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
mroute_clean_tables ( net ) ;
2008-04-03 04:22:53 +04:00
} else
err = - EACCES ;
rtnl_unlock ( ) ;
return err ;
}
/*
* Socket options and virtual interface manipulation . The whole
* virtual interface system is a complete heap , but unfortunately
* that ' s how BSD mrouted happens to think . Maybe one day with a proper
* MOSPF / PIM router set up we can clean this up .
*/
int ip6_mroute_setsockopt ( struct sock * sk , int optname , char __user * optval , int optlen )
{
int ret ;
struct mif6ctl vif ;
struct mf6cctl mfc ;
mifi_t mifi ;
2008-12-11 03:30:15 +03:00
struct net * net = sock_net ( sk ) ;
2008-04-03 04:22:53 +04:00
if ( optname ! = MRT6_INIT ) {
2008-12-11 03:30:15 +03:00
if ( sk ! = net - > ipv6 . mroute6_sk & & ! capable ( CAP_NET_ADMIN ) )
2008-04-03 04:22:53 +04:00
return - EACCES ;
}
switch ( optname ) {
case MRT6_INIT :
if ( sk - > sk_type ! = SOCK_RAW | |
inet_sk ( sk ) - > num ! = IPPROTO_ICMPV6 )
return - EOPNOTSUPP ;
if ( optlen < sizeof ( int ) )
return - EINVAL ;
return ip6mr_sk_init ( sk ) ;
case MRT6_DONE :
return ip6mr_sk_done ( sk ) ;
case MRT6_ADD_MIF :
if ( optlen < sizeof ( vif ) )
return - EINVAL ;
if ( copy_from_user ( & vif , optval , sizeof ( vif ) ) )
return - EFAULT ;
2008-04-10 13:40:10 +04:00
if ( vif . mif6c_mifi > = MAXMIFS )
2008-04-03 04:22:53 +04:00
return - ENFILE ;
rtnl_lock ( ) ;
2008-12-11 03:30:15 +03:00
ret = mif6_add ( net , & vif , sk = = net - > ipv6 . mroute6_sk ) ;
2008-04-03 04:22:53 +04:00
rtnl_unlock ( ) ;
return ret ;
case MRT6_DEL_MIF :
if ( optlen < sizeof ( mifi_t ) )
return - EINVAL ;
if ( copy_from_user ( & mifi , optval , sizeof ( mifi_t ) ) )
return - EFAULT ;
rtnl_lock ( ) ;
2008-12-11 03:30:15 +03:00
ret = mif6_delete ( net , mifi ) ;
2008-04-03 04:22:53 +04:00
rtnl_unlock ( ) ;
return ret ;
/*
* Manipulate the forwarding caches . These live
* in a sort of kernel / user symbiosis .
*/
case MRT6_ADD_MFC :
case MRT6_DEL_MFC :
if ( optlen < sizeof ( mfc ) )
return - EINVAL ;
if ( copy_from_user ( & mfc , optval , sizeof ( mfc ) ) )
return - EFAULT ;
rtnl_lock ( ) ;
if ( optname = = MRT6_DEL_MFC )
2008-12-11 03:30:15 +03:00
ret = ip6mr_mfc_delete ( net , & mfc ) ;
2008-04-03 04:22:53 +04:00
else
2008-12-11 03:30:15 +03:00
ret = ip6mr_mfc_add ( net , & mfc ,
sk = = net - > ipv6 . mroute6_sk ) ;
2008-04-03 04:22:53 +04:00
rtnl_unlock ( ) ;
return ret ;
2008-04-03 04:22:54 +04:00
/*
* Control PIM assert ( to activate pim will activate assert )
*/
case MRT6_ASSERT :
{
int v ;
if ( get_user ( v , ( int __user * ) optval ) )
return - EFAULT ;
2008-12-11 03:30:15 +03:00
net - > ipv6 . mroute_do_assert = ! ! v ;
2008-04-03 04:22:54 +04:00
return 0 ;
}
# ifdef CONFIG_IPV6_PIMSM_V2
case MRT6_PIM :
{
2008-04-10 10:41:28 +04:00
int v ;
2008-04-03 04:22:54 +04:00
if ( get_user ( v , ( int __user * ) optval ) )
return - EFAULT ;
v = ! ! v ;
rtnl_lock ( ) ;
ret = 0 ;
2008-12-11 03:30:15 +03:00
if ( v ! = net - > ipv6 . mroute_do_pim ) {
net - > ipv6 . mroute_do_pim = v ;
net - > ipv6 . mroute_do_assert = v ;
if ( net - > ipv6 . mroute_do_pim )
2008-04-03 04:22:54 +04:00
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
2008-04-03 04:22:53 +04:00
/*
2008-04-23 15:35:13 +04:00
* Spurious command , or MRT6_VERSION which you cannot
2008-04-03 04:22:53 +04:00
* set .
*/
default :
return - ENOPROTOOPT ;
}
}
/*
* Getsock opt support for the multicast routing system .
*/
int ip6_mroute_getsockopt ( struct sock * sk , int optname , char __user * optval ,
int __user * optlen )
{
int olr ;
int val ;
2008-12-11 03:30:15 +03:00
struct net * net = sock_net ( sk ) ;
2008-04-03 04:22:53 +04:00
switch ( optname ) {
case MRT6_VERSION :
val = 0x0305 ;
break ;
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
case MRT6_PIM :
2008-12-11 03:30:15 +03:00
val = net - > ipv6 . mroute_do_pim ;
2008-04-03 04:22:54 +04:00
break ;
# endif
case MRT6_ASSERT :
2008-12-11 03:30:15 +03:00
val = net - > ipv6 . mroute_do_assert ;
2008-04-03 04:22:54 +04:00
break ;
2008-04-03 04:22:53 +04:00
default :
return - ENOPROTOOPT ;
}
if ( get_user ( olr , optlen ) )
return - EFAULT ;
olr = min_t ( int , olr , sizeof ( int ) ) ;
if ( olr < 0 )
return - EINVAL ;
if ( put_user ( olr , optlen ) )
return - EFAULT ;
if ( copy_to_user ( optval , & val , olr ) )
return - EFAULT ;
return 0 ;
}
/*
* The IP multicast ioctl support routines .
*/
int ip6mr_ioctl ( struct sock * sk , int cmd , void __user * arg )
{
struct sioc_sg_req6 sr ;
struct sioc_mif_req6 vr ;
struct mif_device * vif ;
struct mfc6_cache * c ;
2008-12-11 03:30:15 +03:00
struct net * net = sock_net ( sk ) ;
2008-04-03 04:22:53 +04:00
switch ( cmd ) {
case SIOCGETMIFCNT_IN6 :
if ( copy_from_user ( & vr , arg , sizeof ( vr ) ) )
return - EFAULT ;
2008-12-11 03:30:15 +03:00
if ( vr . mifi > = net - > ipv6 . maxvif )
2008-04-03 04:22:53 +04:00
return - EINVAL ;
read_lock ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
vif = & net - > ipv6 . vif6_table [ vr . mifi ] ;
if ( MIF_EXISTS ( net , vr . mifi ) ) {
2008-04-03 04:22:53 +04:00
vr . icount = vif - > pkt_in ;
vr . ocount = vif - > pkt_out ;
vr . ibytes = vif - > bytes_in ;
vr . obytes = vif - > bytes_out ;
read_unlock ( & mrt_lock ) ;
if ( copy_to_user ( arg , & vr , sizeof ( vr ) ) )
return - EFAULT ;
return 0 ;
}
read_unlock ( & mrt_lock ) ;
return - EADDRNOTAVAIL ;
case SIOCGETSGCNT_IN6 :
if ( copy_from_user ( & sr , arg , sizeof ( sr ) ) )
return - EFAULT ;
read_lock ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
c = ip6mr_cache_find ( net , & sr . src . sin6_addr , & sr . grp . sin6_addr ) ;
2008-04-03 04:22:53 +04:00
if ( c ) {
sr . pktcnt = c - > mfc_un . res . pkt ;
sr . bytecnt = c - > mfc_un . res . bytes ;
sr . wrong_if = c - > mfc_un . res . wrong_if ;
read_unlock ( & mrt_lock ) ;
if ( copy_to_user ( arg , & sr , sizeof ( sr ) ) )
return - EFAULT ;
return 0 ;
}
read_unlock ( & mrt_lock ) ;
return - EADDRNOTAVAIL ;
default :
return - ENOIOCTLCMD ;
}
}
static inline int ip6mr_forward2_finish ( struct sk_buff * skb )
{
2008-10-08 22:09:27 +04:00
IP6_INC_STATS_BH ( dev_net ( skb - > dst - > dev ) , ip6_dst_idev ( skb - > dst ) ,
IPSTATS_MIB_OUTFORWDATAGRAMS ) ;
2008-04-03 04:22:53 +04:00
return dst_output ( skb ) ;
}
/*
* Processing handlers for ip6mr_forward
*/
static int ip6mr_forward2 ( struct sk_buff * skb , struct mfc6_cache * c , int vifi )
{
struct ipv6hdr * ipv6h ;
2008-12-11 03:30:15 +03:00
struct net * net = mfc6_net ( c ) ;
struct mif_device * vif = & net - > ipv6 . vif6_table [ vifi ] ;
2008-04-03 04:22:53 +04:00
struct net_device * dev ;
struct dst_entry * dst ;
struct flowi fl ;
if ( vif - > dev = = NULL )
goto out_free ;
2008-04-03 04:22:54 +04:00
# ifdef CONFIG_IPV6_PIMSM_V2
if ( vif - > flags & MIFF_REGISTER ) {
vif - > pkt_out + + ;
vif - > bytes_out + = skb - > len ;
2008-05-22 01:17:54 +04:00
vif - > dev - > stats . tx_bytes + = skb - > len ;
vif - > dev - > stats . tx_packets + + ;
2008-12-11 03:30:15 +03:00
ip6mr_cache_report ( net , skb , vifi , MRT6MSG_WHOLEPKT ) ;
2008-12-15 10:15:49 +03:00
goto out_free ;
2008-04-03 04:22:54 +04:00
}
# endif
2008-04-03 04:22:53 +04:00
ipv6h = ipv6_hdr ( skb ) ;
fl = ( struct flowi ) {
. oif = vif - > link ,
. nl_u = { . ip6_u =
{ . daddr = ipv6h - > daddr , }
}
} ;
2008-12-11 03:30:15 +03:00
dst = ip6_route_output ( net , NULL , & fl ) ;
2008-04-03 04:22:53 +04:00
if ( ! dst )
goto out_free ;
dst_release ( skb - > dst ) ;
skb - > dst = dst ;
/*
* RFC1584 teaches , that DVMRP / PIM router must deliver packets locally
* not only before forwarding , but after forwarding on all output
* interfaces . It is clear , if mrouter runs a multicasting
* program , it should receive packets not depending to what interface
* program is joined .
* If we will not make it , the program will have to join on all
* interfaces . On the other hand , multihoming host ( or router , but
* not mrouter ) cannot join to more than one interface - it will
* result in receiving multiple packets .
*/
dev = vif - > dev ;
skb - > dev = dev ;
vif - > pkt_out + + ;
vif - > bytes_out + = skb - > len ;
/* We are about to write */
/* XXX: extension headers? */
if ( skb_cow ( skb , sizeof ( * ipv6h ) + LL_RESERVED_SPACE ( dev ) ) )
goto out_free ;
ipv6h = ipv6_hdr ( skb ) ;
ipv6h - > hop_limit - - ;
IP6CB ( skb ) - > flags | = IP6SKB_FORWARDED ;
return NF_HOOK ( PF_INET6 , NF_INET_FORWARD , skb , skb - > dev , dev ,
ip6mr_forward2_finish ) ;
out_free :
kfree_skb ( skb ) ;
return 0 ;
}
static int ip6mr_find_vif ( struct net_device * dev )
{
2008-12-11 03:30:15 +03:00
struct net * net = dev_net ( dev ) ;
2008-04-03 04:22:53 +04:00
int ct ;
2008-12-11 03:30:15 +03:00
for ( ct = net - > ipv6 . maxvif - 1 ; ct > = 0 ; ct - - ) {
if ( net - > ipv6 . vif6_table [ ct ] . dev = = dev )
2008-04-03 04:22:53 +04:00
break ;
}
return ct ;
}
static int ip6_mr_forward ( struct sk_buff * skb , struct mfc6_cache * cache )
{
int psend = - 1 ;
int vif , ct ;
2008-12-11 03:30:15 +03:00
struct net * net = mfc6_net ( cache ) ;
2008-04-03 04:22:53 +04:00
vif = cache - > mf6c_parent ;
cache - > mfc_un . res . pkt + + ;
cache - > mfc_un . res . bytes + = skb - > len ;
2008-04-03 04:22:54 +04:00
/*
* Wrong interface : drop packet and ( maybe ) send PIM assert .
*/
2008-12-11 03:30:15 +03:00
if ( net - > ipv6 . vif6_table [ vif ] . dev ! = skb - > dev ) {
2008-04-03 04:22:54 +04:00
int true_vifi ;
cache - > mfc_un . res . wrong_if + + ;
true_vifi = ip6mr_find_vif ( skb - > dev ) ;
2008-12-11 03:30:15 +03:00
if ( true_vifi > = 0 & & net - > ipv6 . mroute_do_assert & &
2008-04-03 04:22:54 +04:00
/* 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
*/
2008-12-11 03:30:15 +03:00
( net - > ipv6 . mroute_do_pim | |
2008-12-11 03:28:44 +03:00
cache - > mfc_un . res . ttls [ true_vifi ] < 255 ) & &
2008-04-03 04:22:54 +04:00
time_after ( jiffies ,
cache - > mfc_un . res . last_assert + MFC_ASSERT_THRESH ) ) {
cache - > mfc_un . res . last_assert = jiffies ;
2008-12-11 03:30:15 +03:00
ip6mr_cache_report ( net , skb , true_vifi , MRT6MSG_WRONGMIF ) ;
2008-04-03 04:22:54 +04:00
}
goto dont_forward ;
}
2008-12-11 03:30:15 +03:00
net - > ipv6 . vif6_table [ vif ] . pkt_in + + ;
net - > ipv6 . vif6_table [ vif ] . bytes_in + = skb - > len ;
2008-04-03 04:22:53 +04:00
/*
* Forward the frame
*/
for ( ct = cache - > mfc_un . res . maxvif - 1 ; ct > = cache - > mfc_un . res . minvif ; ct - - ) {
if ( ipv6_hdr ( skb ) - > hop_limit > cache - > mfc_un . res . ttls [ ct ] ) {
if ( psend ! = - 1 ) {
struct sk_buff * skb2 = skb_clone ( skb , GFP_ATOMIC ) ;
if ( skb2 )
ip6mr_forward2 ( skb2 , cache , psend ) ;
}
psend = ct ;
}
}
if ( psend ! = - 1 ) {
ip6mr_forward2 ( skb , cache , psend ) ;
return 0 ;
}
2008-04-03 04:22:54 +04:00
dont_forward :
2008-04-03 04:22:53 +04:00
kfree_skb ( skb ) ;
return 0 ;
}
/*
* Multicast packets for forwarding arrive here
*/
int ip6_mr_input ( struct sk_buff * skb )
{
struct mfc6_cache * cache ;
2008-12-11 03:30:15 +03:00
struct net * net = dev_net ( skb - > dev ) ;
2008-04-03 04:22:53 +04:00
read_lock ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
cache = ip6mr_cache_find ( net ,
& ipv6_hdr ( skb ) - > saddr , & ipv6_hdr ( skb ) - > daddr ) ;
2008-04-03 04:22:53 +04:00
/*
* No usable cache entry
*/
if ( cache = = NULL ) {
int vif ;
vif = ip6mr_find_vif ( skb - > dev ) ;
if ( vif > = 0 ) {
2008-12-11 03:30:15 +03:00
int err = ip6mr_cache_unresolved ( net , vif , skb ) ;
2008-04-03 04:22:53 +04:00
read_unlock ( & mrt_lock ) ;
return err ;
}
read_unlock ( & mrt_lock ) ;
kfree_skb ( skb ) ;
return - ENODEV ;
}
ip6_mr_forward ( skb , cache ) ;
read_unlock ( & mrt_lock ) ;
return 0 ;
}
static int
ip6mr_fill_mroute ( struct sk_buff * skb , struct mfc6_cache * c , struct rtmsg * rtm )
{
int ct ;
struct rtnexthop * nhp ;
2008-12-11 03:30:15 +03:00
struct net * net = mfc6_net ( c ) ;
struct net_device * dev = net - > ipv6 . vif6_table [ c - > mf6c_parent ] . dev ;
2008-04-05 17:17:39 +04:00
u8 * b = skb_tail_pointer ( skb ) ;
2008-04-03 04:22:53 +04:00
struct rtattr * mp_head ;
if ( dev )
RTA_PUT ( skb , RTA_IIF , 4 , & dev - > ifindex ) ;
mp_head = ( struct rtattr * ) skb_put ( skb , RTA_LENGTH ( 0 ) ) ;
for ( ct = c - > mfc_un . res . minvif ; ct < c - > mfc_un . res . maxvif ; ct + + ) {
if ( c - > mfc_un . res . ttls [ ct ] < 255 ) {
if ( skb_tailroom ( skb ) < RTA_ALIGN ( RTA_ALIGN ( sizeof ( * nhp ) ) + 4 ) )
goto rtattr_failure ;
nhp = ( struct rtnexthop * ) skb_put ( skb , RTA_ALIGN ( sizeof ( * nhp ) ) ) ;
nhp - > rtnh_flags = 0 ;
nhp - > rtnh_hops = c - > mfc_un . res . ttls [ ct ] ;
2008-12-11 03:30:15 +03:00
nhp - > rtnh_ifindex = net - > ipv6 . vif6_table [ ct ] . dev - > ifindex ;
2008-04-03 04:22:53 +04:00
nhp - > rtnh_len = sizeof ( * nhp ) ;
}
}
mp_head - > rta_type = RTA_MULTIPATH ;
2008-04-05 17:17:39 +04:00
mp_head - > rta_len = skb_tail_pointer ( skb ) - ( u8 * ) mp_head ;
2008-04-03 04:22:53 +04:00
rtm - > rtm_type = RTN_MULTICAST ;
return 1 ;
rtattr_failure :
nlmsg_trim ( skb , b ) ;
return - EMSGSIZE ;
}
2008-12-11 03:30:15 +03:00
int ip6mr_get_route ( struct net * net ,
struct sk_buff * skb , struct rtmsg * rtm , int nowait )
2008-04-03 04:22:53 +04:00
{
int err ;
struct mfc6_cache * cache ;
struct rt6_info * rt = ( struct rt6_info * ) skb - > dst ;
read_lock ( & mrt_lock ) ;
2008-12-11 03:30:15 +03:00
cache = ip6mr_cache_find ( net , & rt - > rt6i_src . addr , & rt - > rt6i_dst . addr ) ;
2008-04-03 04:22:53 +04:00
if ( ! cache ) {
struct sk_buff * skb2 ;
struct ipv6hdr * iph ;
struct net_device * dev ;
int vif ;
if ( nowait ) {
read_unlock ( & mrt_lock ) ;
return - EAGAIN ;
}
dev = skb - > dev ;
if ( dev = = NULL | | ( vif = ip6mr_find_vif ( dev ) ) < 0 ) {
read_unlock ( & mrt_lock ) ;
return - ENODEV ;
}
/* really correct? */
skb2 = alloc_skb ( sizeof ( struct ipv6hdr ) , GFP_ATOMIC ) ;
if ( ! skb2 ) {
read_unlock ( & mrt_lock ) ;
return - ENOMEM ;
}
skb_reset_transport_header ( skb2 ) ;
skb_put ( skb2 , sizeof ( struct ipv6hdr ) ) ;
skb_reset_network_header ( skb2 ) ;
iph = ipv6_hdr ( skb2 ) ;
iph - > version = 0 ;
iph - > priority = 0 ;
iph - > flow_lbl [ 0 ] = 0 ;
iph - > flow_lbl [ 1 ] = 0 ;
iph - > flow_lbl [ 2 ] = 0 ;
iph - > payload_len = 0 ;
iph - > nexthdr = IPPROTO_NONE ;
iph - > hop_limit = 0 ;
ipv6_addr_copy ( & iph - > saddr , & rt - > rt6i_src . addr ) ;
ipv6_addr_copy ( & iph - > daddr , & rt - > rt6i_dst . addr ) ;
2008-12-11 03:30:15 +03:00
err = ip6mr_cache_unresolved ( net , vif , skb2 ) ;
2008-04-03 04:22:53 +04:00
read_unlock ( & mrt_lock ) ;
return err ;
}
if ( ! nowait & & ( rtm - > rtm_flags & RTM_F_NOTIFY ) )
cache - > mfc_flags | = MFC_NOTIFY ;
err = ip6mr_fill_mroute ( skb , cache , rtm ) ;
read_unlock ( & mrt_lock ) ;
return err ;
}