2005-04-17 02:20:36 +04:00
/*
* Internet Control Message Protocol ( ICMPv6 )
* Linux INET6 implementation
*
* Authors :
* Pedro Roque < roque @ di . fc . ul . pt >
*
* $ Id : icmp . c , v 1.38 2002 / 02 / 08 03 : 57 : 19 davem Exp $
*
* Based on net / ipv4 / icmp . c
*
* RFC 1885
*
* 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 :
*
* Andi Kleen : exception handling
* Andi Kleen add rate limits . never reply to a icmp .
* add more length checks and other fixes .
* yoshfuji : ensure to sent parameter problem for
* fragments .
* YOSHIFUJI Hideaki @ USAGI : added sysctl for icmp rate limit .
* Randy Dunlap and
* YOSHIFUJI Hideaki @ USAGI : Per - interface statistics support
* Kazunori MIYAZAWA @ USAGI : change output process to use ip6_append_data
*/
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/socket.h>
# include <linux/in.h>
# include <linux/kernel.h>
# include <linux/sockios.h>
# include <linux/net.h>
# include <linux/skbuff.h>
# include <linux/init.h>
2006-02-16 02:24:15 +03:00
# include <linux/netfilter.h>
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_SYSCTL
# include <linux/sysctl.h>
# endif
# include <linux/inet.h>
# include <linux/netdevice.h>
# include <linux/icmpv6.h>
# include <net/ip.h>
# include <net/sock.h>
# include <net/ipv6.h>
# include <net/ip6_checksum.h>
# include <net/protocol.h>
# include <net/raw.h>
# include <net/rawv6.h>
# include <net/transp_v6.h>
# include <net/ip6_route.h>
# include <net/addrconf.h>
# include <net/icmp.h>
2007-12-12 21:44:43 +03:00
# include <net/xfrm.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <asm/system.h>
2005-08-26 23:05:31 +04:00
DEFINE_SNMP_STAT ( struct icmpv6_mib , icmpv6_statistics ) __read_mostly ;
2007-02-22 16:05:40 +03:00
EXPORT_SYMBOL ( icmpv6_statistics ) ;
2007-09-17 03:52:35 +04:00
DEFINE_SNMP_STAT ( struct icmpv6msg_mib , icmpv6msg_statistics ) __read_mostly ;
EXPORT_SYMBOL ( icmpv6msg_statistics ) ;
2005-04-17 02:20:36 +04:00
/*
* The ICMP socket ( s ) . This is the most convenient way to flow control
* our ICMP output as well as maintain a clean interface throughout
* all layers . All Socketless IP sends will soon be gone .
*
* On SMP we have one ICMP socket per - cpu .
*/
static DEFINE_PER_CPU ( struct socket * , __icmpv6_socket ) = NULL ;
# define icmpv6_socket __get_cpu_var(__icmpv6_socket)
2007-10-15 23:50:28 +04:00
static int icmpv6_rcv ( struct sk_buff * skb ) ;
2005-04-17 02:20:36 +04:00
static struct inet6_protocol icmpv6_protocol = {
. handler = icmpv6_rcv ,
2007-12-12 21:44:43 +03:00
. flags = INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL ,
2005-04-17 02:20:36 +04:00
} ;
static __inline__ int icmpv6_xmit_lock ( void )
{
local_bh_disable ( ) ;
if ( unlikely ( ! spin_trylock ( & icmpv6_socket - > sk - > sk_lock . slock ) ) ) {
/* This can happen if the output path (f.e. SIT or
* ip6ip6 tunnel ) signals dst_link_failure ( ) for an
* outgoing ICMP6 packet .
*/
local_bh_enable ( ) ;
return 1 ;
}
return 0 ;
}
static __inline__ void icmpv6_xmit_unlock ( void )
{
spin_unlock_bh ( & icmpv6_socket - > sk - > sk_lock . slock ) ;
}
2007-02-09 17:24:49 +03:00
/*
2005-04-17 02:20:36 +04:00
* Slightly more convenient version of icmpv6_send .
*/
void icmpv6_param_prob ( struct sk_buff * skb , int code , int pos )
{
icmpv6_send ( skb , ICMPV6_PARAMPROB , code , pos , skb - > dev ) ;
kfree_skb ( skb ) ;
}
/*
* Figure out , may we reply to this packet with icmp error .
*
* We do not reply , if :
* - it was icmp error message .
* - it is truncated , so that it is known , that protocol is ICMPV6
* ( i . e . in the middle of some exthdr )
*
* - - ANK ( 980726 )
*/
static int is_ineligible ( struct sk_buff * skb )
{
2007-04-26 04:54:47 +04:00
int ptr = ( u8 * ) ( ipv6_hdr ( skb ) + 1 ) - skb - > data ;
2005-04-17 02:20:36 +04:00
int len = skb - > len - ptr ;
2007-04-26 04:54:47 +04:00
__u8 nexthdr = ipv6_hdr ( skb ) - > nexthdr ;
2005-04-17 02:20:36 +04:00
if ( len < 0 )
return 1 ;
2005-04-25 07:16:19 +04:00
ptr = ipv6_skip_exthdr ( skb , ptr , & nexthdr ) ;
2005-04-17 02:20:36 +04:00
if ( ptr < 0 )
return 0 ;
if ( nexthdr = = IPPROTO_ICMPV6 ) {
u8 _type , * tp ;
tp = skb_header_pointer ( skb ,
ptr + offsetof ( struct icmp6hdr , icmp6_type ) ,
sizeof ( _type ) , & _type ) ;
if ( tp = = NULL | |
! ( * tp & ICMPV6_INFOMSG_MASK ) )
return 1 ;
}
return 0 ;
}
2007-02-09 17:24:49 +03:00
/*
* Check the ICMP output rate limit
2005-04-17 02:20:36 +04:00
*/
static inline int icmpv6_xrlim_allow ( struct sock * sk , int type ,
struct flowi * fl )
{
struct dst_entry * dst ;
int res = 0 ;
/* Informational messages are not limited. */
if ( type & ICMPV6_INFOMSG_MASK )
return 1 ;
/* Do not limit pmtu discovery, it would break it. */
if ( type = = ICMPV6_PKT_TOOBIG )
return 1 ;
2007-02-09 17:24:49 +03:00
/*
2005-04-17 02:20:36 +04:00
* Look up the output route .
* XXX : perhaps the expire for routing entries cloned by
* this lookup should be more aggressive ( not longer than timeout ) .
*/
dst = ip6_route_output ( sk , fl ) ;
if ( dst - > error ) {
2006-11-04 14:11:37 +03:00
IP6_INC_STATS ( ip6_dst_idev ( dst ) ,
IPSTATS_MIB_OUTNOROUTES ) ;
2005-04-17 02:20:36 +04:00
} else if ( dst - > dev & & ( dst - > dev - > flags & IFF_LOOPBACK ) ) {
res = 1 ;
} else {
struct rt6_info * rt = ( struct rt6_info * ) dst ;
2008-01-10 14:02:40 +03:00
int tmo = init_net . ipv6 . sysctl . icmpv6_time ;
2005-04-17 02:20:36 +04:00
/* Give more bandwidth to wider prefixes. */
if ( rt - > rt6i_dst . plen < 128 )
tmo > > = ( ( 128 - rt - > rt6i_dst . plen ) > > 5 ) ;
res = xrlim_allow ( dst , tmo ) ;
}
dst_release ( dst ) ;
return res ;
}
/*
* an inline helper for the " simple " if statement below
* checks if parameter problem report is caused by an
2007-02-09 17:24:49 +03:00
* unrecognized IPv6 option that has the Option Type
2005-04-17 02:20:36 +04:00
* highest - order two bits set to 10
*/
static __inline__ int opt_unrec ( struct sk_buff * skb , __u32 offset )
{
u8 _optval , * op ;
2007-03-11 04:16:10 +03:00
offset + = skb_network_offset ( skb ) ;
2005-04-17 02:20:36 +04:00
op = skb_header_pointer ( skb , offset , sizeof ( _optval ) , & _optval ) ;
if ( op = = NULL )
return 1 ;
return ( * op & 0xC0 ) = = 0x80 ;
}
static int icmpv6_push_pending_frames ( struct sock * sk , struct flowi * fl , struct icmp6hdr * thdr , int len )
{
struct sk_buff * skb ;
struct icmp6hdr * icmp6h ;
int err = 0 ;
if ( ( skb = skb_peek ( & sk - > sk_write_queue ) ) = = NULL )
goto out ;
2007-03-13 20:03:22 +03:00
icmp6h = icmp6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
memcpy ( icmp6h , thdr , sizeof ( struct icmp6hdr ) ) ;
icmp6h - > icmp6_cksum = 0 ;
if ( skb_queue_len ( & sk - > sk_write_queue ) = = 1 ) {
skb - > csum = csum_partial ( ( char * ) icmp6h ,
sizeof ( struct icmp6hdr ) , skb - > csum ) ;
icmp6h - > icmp6_cksum = csum_ipv6_magic ( & fl - > fl6_src ,
& fl - > fl6_dst ,
len , fl - > proto ,
skb - > csum ) ;
} else {
2006-11-15 08:35:48 +03:00
__wsum tmp_csum = 0 ;
2005-04-17 02:20:36 +04:00
skb_queue_walk ( & sk - > sk_write_queue , skb ) {
tmp_csum = csum_add ( tmp_csum , skb - > csum ) ;
}
tmp_csum = csum_partial ( ( char * ) icmp6h ,
sizeof ( struct icmp6hdr ) , tmp_csum ) ;
2006-11-15 08:35:48 +03:00
icmp6h - > icmp6_cksum = csum_ipv6_magic ( & fl - > fl6_src ,
& fl - > fl6_dst ,
len , fl - > proto ,
tmp_csum ) ;
2005-04-17 02:20:36 +04:00
}
ip6_push_pending_frames ( sk ) ;
out :
return err ;
}
struct icmpv6_msg {
struct sk_buff * skb ;
int offset ;
2006-02-16 02:24:15 +03:00
uint8_t type ;
2005-04-17 02:20:36 +04:00
} ;
static int icmpv6_getfrag ( void * from , char * to , int offset , int len , int odd , struct sk_buff * skb )
{
struct icmpv6_msg * msg = ( struct icmpv6_msg * ) from ;
struct sk_buff * org_skb = msg - > skb ;
2006-11-15 08:36:54 +03:00
__wsum csum = 0 ;
2005-04-17 02:20:36 +04:00
csum = skb_copy_and_csum_bits ( org_skb , msg - > offset + offset ,
to , len , csum ) ;
skb - > csum = csum_block_add ( skb - > csum , csum , odd ) ;
2006-02-16 02:24:15 +03:00
if ( ! ( msg - > type & ICMPV6_INFOMSG_MASK ) )
nf_ct_attach ( skb , org_skb ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-06-27 10:56:32 +04:00
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
2006-08-24 06:27:25 +04:00
static void mip6_addr_swap ( struct sk_buff * skb )
{
2007-04-26 04:54:47 +04:00
struct ipv6hdr * iph = ipv6_hdr ( skb ) ;
2006-08-24 06:27:25 +04:00
struct inet6_skb_parm * opt = IP6CB ( skb ) ;
struct ipv6_destopt_hao * hao ;
struct in6_addr tmp ;
int off ;
if ( opt - > dsthao ) {
off = ipv6_find_tlv ( skb , opt - > dsthao , IPV6_TLV_HAO ) ;
if ( likely ( off > = 0 ) ) {
2007-04-11 07:50:43 +04:00
hao = ( struct ipv6_destopt_hao * )
( skb_network_header ( skb ) + off ) ;
2006-08-24 06:27:25 +04:00
ipv6_addr_copy ( & tmp , & iph - > saddr ) ;
ipv6_addr_copy ( & iph - > saddr , & hao - > addr ) ;
ipv6_addr_copy ( & hao - > addr , & tmp ) ;
}
}
}
# else
static inline void mip6_addr_swap ( struct sk_buff * skb ) { }
# endif
2005-04-17 02:20:36 +04:00
/*
* Send an ICMP message in response to a packet in error
*/
2007-02-09 17:24:49 +03:00
void icmpv6_send ( struct sk_buff * skb , int type , int code , __u32 info ,
2005-04-17 02:20:36 +04:00
struct net_device * dev )
{
struct inet6_dev * idev = NULL ;
2007-04-26 04:54:47 +04:00
struct ipv6hdr * hdr = ipv6_hdr ( skb ) ;
2005-06-14 01:59:44 +04:00
struct sock * sk ;
struct ipv6_pinfo * np ;
2005-04-17 02:20:36 +04:00
struct in6_addr * saddr = NULL ;
struct dst_entry * dst ;
2007-12-12 21:44:43 +03:00
struct dst_entry * dst2 ;
2005-04-17 02:20:36 +04:00
struct icmp6hdr tmp_hdr ;
struct flowi fl ;
2007-12-12 21:44:43 +03:00
struct flowi fl2 ;
2005-04-17 02:20:36 +04:00
struct icmpv6_msg msg ;
int iif = 0 ;
int addr_type = 0 ;
int len ;
2005-09-08 05:19:03 +04:00
int hlimit , tclass ;
2005-04-17 02:20:36 +04:00
int err = 0 ;
2007-04-20 07:29:13 +04:00
if ( ( u8 * ) hdr < skb - > head | |
( skb - > network_header + sizeof ( * hdr ) ) > skb - > tail )
2005-04-17 02:20:36 +04:00
return ;
/*
2007-02-09 17:24:49 +03:00
* Make sure we respect the rules
2005-04-17 02:20:36 +04:00
* i . e . RFC 1885 2.4 ( e )
* Rule ( e .1 ) is enforced by not using icmpv6_send
* in any code that processes icmp errors .
*/
addr_type = ipv6_addr_type ( & hdr - > daddr ) ;
2008-01-11 09:43:18 +03:00
if ( ipv6_chk_addr ( & init_net , & hdr - > daddr , skb - > dev , 0 ) )
2005-04-17 02:20:36 +04:00
saddr = & hdr - > daddr ;
/*
* Dest addr check
*/
if ( ( addr_type & IPV6_ADDR_MULTICAST | | skb - > pkt_type ! = PACKET_HOST ) ) {
if ( type ! = ICMPV6_PKT_TOOBIG & &
2007-02-09 17:24:49 +03:00
! ( type = = ICMPV6_PARAMPROB & &
code = = ICMPV6_UNK_OPTION & &
2005-04-17 02:20:36 +04:00
( opt_unrec ( skb , info ) ) ) )
return ;
saddr = NULL ;
}
addr_type = ipv6_addr_type ( & hdr - > saddr ) ;
/*
* Source addr check
*/
if ( addr_type & IPV6_ADDR_LINKLOCAL )
iif = skb - > dev - > ifindex ;
/*
2005-12-21 16:57:06 +03:00
* Must not send error if the source does not uniquely
* identify a single node ( RFC2463 Section 2.4 ) .
* We check unspecified / multicast addresses here ,
* and anycast addresses will be checked later .
2005-04-17 02:20:36 +04:00
*/
if ( ( addr_type = = IPV6_ADDR_ANY ) | | ( addr_type & IPV6_ADDR_MULTICAST ) ) {
2005-08-10 07:50:53 +04:00
LIMIT_NETDEBUG ( KERN_DEBUG " icmpv6_send: addr_any/mcast source \n " ) ;
2005-04-17 02:20:36 +04:00
return ;
}
2007-02-09 17:24:49 +03:00
/*
2005-04-17 02:20:36 +04:00
* Never answer to a ICMP packet .
*/
if ( is_ineligible ( skb ) ) {
2005-08-10 07:50:53 +04:00
LIMIT_NETDEBUG ( KERN_DEBUG " icmpv6_send: no reply to icmp error \n " ) ;
2005-04-17 02:20:36 +04:00
return ;
}
2006-08-24 06:27:25 +04:00
mip6_addr_swap ( skb ) ;
2005-04-17 02:20:36 +04:00
memset ( & fl , 0 , sizeof ( fl ) ) ;
fl . proto = IPPROTO_ICMPV6 ;
ipv6_addr_copy ( & fl . fl6_dst , & hdr - > saddr ) ;
if ( saddr )
ipv6_addr_copy ( & fl . fl6_src , saddr ) ;
fl . oif = iif ;
fl . fl_icmp_type = type ;
fl . fl_icmp_code = code ;
2006-08-05 10:12:42 +04:00
security_skb_classify_flow ( skb , & fl ) ;
2005-04-17 02:20:36 +04:00
if ( icmpv6_xmit_lock ( ) )
return ;
2005-06-14 01:59:44 +04:00
sk = icmpv6_socket - > sk ;
np = inet6_sk ( sk ) ;
2005-04-17 02:20:36 +04:00
if ( ! icmpv6_xrlim_allow ( sk , type , & fl ) )
goto out ;
tmp_hdr . icmp6_type = type ;
tmp_hdr . icmp6_code = code ;
tmp_hdr . icmp6_cksum = 0 ;
tmp_hdr . icmp6_pointer = htonl ( info ) ;
if ( ! fl . oif & & ipv6_addr_is_multicast ( & fl . fl6_dst ) )
fl . oif = np - > mcast_oif ;
err = ip6_dst_lookup ( sk , & dst , & fl ) ;
if ( err )
goto out ;
2005-12-21 16:57:06 +03:00
/*
* We won ' t send icmp if the destination is known
* anycast .
*/
if ( ( ( struct rt6_info * ) dst ) - > rt6i_flags & RTF_ANYCAST ) {
LIMIT_NETDEBUG ( KERN_DEBUG " icmpv6_send: acast source \n " ) ;
goto out_dst_release ;
}
2007-12-12 21:44:43 +03:00
/* No need to clone since we're just using its address. */
dst2 = dst ;
err = xfrm_lookup ( & dst , & fl , sk , 0 ) ;
switch ( err ) {
case 0 :
if ( dst ! = dst2 )
goto route_done ;
break ;
case - EPERM :
dst = NULL ;
break ;
default :
goto out ;
}
if ( xfrm_decode_session_reverse ( skb , & fl2 , AF_INET6 ) )
goto out ;
if ( ip6_dst_lookup ( sk , & dst2 , & fl ) )
2005-09-09 02:11:55 +04:00
goto out ;
2005-04-17 02:20:36 +04:00
2007-12-12 21:44:43 +03:00
err = xfrm_lookup ( & dst2 , & fl , sk , XFRM_LOOKUP_ICMP ) ;
if ( err = = - ENOENT ) {
if ( ! dst )
goto out ;
goto route_done ;
}
dst_release ( dst ) ;
dst = dst2 ;
if ( err )
goto out ;
route_done :
2005-04-17 02:20:36 +04:00
if ( ipv6_addr_is_multicast ( & fl . fl6_dst ) )
hlimit = np - > mcast_hops ;
else
hlimit = np - > hop_limit ;
if ( hlimit < 0 )
hlimit = dst_metric ( dst , RTAX_HOPLIMIT ) ;
if ( hlimit < 0 )
hlimit = ipv6_get_hoplimit ( dst - > dev ) ;
2006-09-14 07:01:28 +04:00
tclass = np - > tclass ;
2005-09-08 05:19:03 +04:00
if ( tclass < 0 )
tclass = 0 ;
2005-04-17 02:20:36 +04:00
msg . skb = skb ;
2007-03-11 04:16:10 +03:00
msg . offset = skb_network_offset ( skb ) ;
2006-02-16 02:24:15 +03:00
msg . type = type ;
2005-04-17 02:20:36 +04:00
len = skb - > len - msg . offset ;
len = min_t ( unsigned int , len , IPV6_MIN_MTU - sizeof ( struct ipv6hdr ) - sizeof ( struct icmp6hdr ) ) ;
if ( len < 0 ) {
2005-08-10 07:50:53 +04:00
LIMIT_NETDEBUG ( KERN_DEBUG " icmp: len problem \n " ) ;
2005-04-17 02:20:36 +04:00
goto out_dst_release ;
}
idev = in6_dev_get ( skb - > dev ) ;
err = ip6_append_data ( sk , icmpv6_getfrag , & msg ,
len + sizeof ( struct icmp6hdr ) ,
sizeof ( struct icmp6hdr ) ,
2005-09-08 05:19:03 +04:00
hlimit , tclass , NULL , & fl , ( struct rt6_info * ) dst ,
2005-04-17 02:20:36 +04:00
MSG_DONTWAIT ) ;
if ( err ) {
ip6_flush_pending_frames ( sk ) ;
goto out_put ;
}
err = icmpv6_push_pending_frames ( sk , & fl , & tmp_hdr , len + sizeof ( struct icmp6hdr ) ) ;
out_put :
if ( likely ( idev ! = NULL ) )
in6_dev_put ( idev ) ;
out_dst_release :
dst_release ( dst ) ;
out :
icmpv6_xmit_unlock ( ) ;
}
2007-02-22 16:05:40 +03:00
EXPORT_SYMBOL ( icmpv6_send ) ;
2005-04-17 02:20:36 +04:00
static void icmpv6_echo_reply ( struct sk_buff * skb )
{
2005-06-14 01:59:44 +04:00
struct sock * sk ;
2005-04-17 02:20:36 +04:00
struct inet6_dev * idev ;
2005-06-14 01:59:44 +04:00
struct ipv6_pinfo * np ;
2005-04-17 02:20:36 +04:00
struct in6_addr * saddr = NULL ;
2007-03-13 20:03:22 +03:00
struct icmp6hdr * icmph = icmp6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
struct icmp6hdr tmp_hdr ;
struct flowi fl ;
struct icmpv6_msg msg ;
struct dst_entry * dst ;
int err = 0 ;
int hlimit ;
2005-09-08 05:19:03 +04:00
int tclass ;
2005-04-17 02:20:36 +04:00
2007-04-26 04:54:47 +04:00
saddr = & ipv6_hdr ( skb ) - > daddr ;
2005-04-17 02:20:36 +04:00
if ( ! ipv6_unicast_destination ( skb ) )
saddr = NULL ;
memcpy ( & tmp_hdr , icmph , sizeof ( tmp_hdr ) ) ;
tmp_hdr . icmp6_type = ICMPV6_ECHO_REPLY ;
memset ( & fl , 0 , sizeof ( fl ) ) ;
fl . proto = IPPROTO_ICMPV6 ;
2007-04-26 04:54:47 +04:00
ipv6_addr_copy ( & fl . fl6_dst , & ipv6_hdr ( skb ) - > saddr ) ;
2005-04-17 02:20:36 +04:00
if ( saddr )
ipv6_addr_copy ( & fl . fl6_src , saddr ) ;
fl . oif = skb - > dev - > ifindex ;
fl . fl_icmp_type = ICMPV6_ECHO_REPLY ;
2006-08-05 10:12:42 +04:00
security_skb_classify_flow ( skb , & fl ) ;
2005-04-17 02:20:36 +04:00
if ( icmpv6_xmit_lock ( ) )
return ;
2005-06-14 01:59:44 +04:00
sk = icmpv6_socket - > sk ;
np = inet6_sk ( sk ) ;
2005-04-17 02:20:36 +04:00
if ( ! fl . oif & & ipv6_addr_is_multicast ( & fl . fl6_dst ) )
fl . oif = np - > mcast_oif ;
err = ip6_dst_lookup ( sk , & dst , & fl ) ;
if ( err )
goto out ;
if ( ( err = xfrm_lookup ( & dst , & fl , sk , 0 ) ) < 0 )
2005-09-09 02:11:55 +04:00
goto out ;
2005-04-17 02:20:36 +04:00
if ( ipv6_addr_is_multicast ( & fl . fl6_dst ) )
hlimit = np - > mcast_hops ;
else
hlimit = np - > hop_limit ;
if ( hlimit < 0 )
hlimit = dst_metric ( dst , RTAX_HOPLIMIT ) ;
if ( hlimit < 0 )
hlimit = ipv6_get_hoplimit ( dst - > dev ) ;
2006-09-14 07:01:28 +04:00
tclass = np - > tclass ;
2005-09-08 05:19:03 +04:00
if ( tclass < 0 )
tclass = 0 ;
2005-04-17 02:20:36 +04:00
idev = in6_dev_get ( skb - > dev ) ;
msg . skb = skb ;
msg . offset = 0 ;
2006-02-16 02:24:15 +03:00
msg . type = ICMPV6_ECHO_REPLY ;
2005-04-17 02:20:36 +04:00
err = ip6_append_data ( sk , icmpv6_getfrag , & msg , skb - > len + sizeof ( struct icmp6hdr ) ,
2005-09-08 05:19:03 +04:00
sizeof ( struct icmp6hdr ) , hlimit , tclass , NULL , & fl ,
2005-04-17 02:20:36 +04:00
( struct rt6_info * ) dst , MSG_DONTWAIT ) ;
if ( err ) {
ip6_flush_pending_frames ( sk ) ;
goto out_put ;
}
err = icmpv6_push_pending_frames ( sk , & fl , & tmp_hdr , skb - > len + sizeof ( struct icmp6hdr ) ) ;
2007-02-09 17:24:49 +03:00
out_put :
2005-04-17 02:20:36 +04:00
if ( likely ( idev ! = NULL ) )
in6_dev_put ( idev ) ;
dst_release ( dst ) ;
2007-02-09 17:24:49 +03:00
out :
2005-04-17 02:20:36 +04:00
icmpv6_xmit_unlock ( ) ;
}
2006-11-08 11:21:01 +03:00
static void icmpv6_notify ( struct sk_buff * skb , int type , int code , __be32 info )
2005-04-17 02:20:36 +04:00
{
struct inet6_protocol * ipprot ;
int inner_offset ;
int hash ;
u8 nexthdr ;
if ( ! pskb_may_pull ( skb , sizeof ( struct ipv6hdr ) ) )
return ;
nexthdr = ( ( struct ipv6hdr * ) skb - > data ) - > nexthdr ;
if ( ipv6_ext_hdr ( nexthdr ) ) {
/* now skip over extension headers */
2005-04-25 07:16:19 +04:00
inner_offset = ipv6_skip_exthdr ( skb , sizeof ( struct ipv6hdr ) , & nexthdr ) ;
2005-04-17 02:20:36 +04:00
if ( inner_offset < 0 )
return ;
} else {
inner_offset = sizeof ( struct ipv6hdr ) ;
}
/* Checkin header including 8 bytes of inner protocol header. */
if ( ! pskb_may_pull ( skb , inner_offset + 8 ) )
return ;
/* BUGGG_FUTURE: we should try to parse exthdrs in this packet.
Without this we will not able f . e . to make source routed
pmtu discovery .
Corresponding argument ( opt ) to notifiers is already added .
- - ANK ( 980726 )
*/
hash = nexthdr & ( MAX_INET_PROTOS - 1 ) ;
rcu_read_lock ( ) ;
ipprot = rcu_dereference ( inet6_protos [ hash ] ) ;
if ( ipprot & & ipprot - > err_handler )
ipprot - > err_handler ( skb , NULL , type , code , inner_offset , info ) ;
rcu_read_unlock ( ) ;
2007-11-20 09:35:57 +03:00
raw6_icmp_error ( skb , nexthdr , type , code , inner_offset , info ) ;
2005-04-17 02:20:36 +04:00
}
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
/*
* Handle icmp messages
*/
2007-10-15 23:50:28 +04:00
static int icmpv6_rcv ( struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
struct net_device * dev = skb - > dev ;
struct inet6_dev * idev = __in6_dev_get ( dev ) ;
struct in6_addr * saddr , * daddr ;
struct ipv6hdr * orig_hdr ;
struct icmp6hdr * hdr ;
int type ;
2007-12-13 05:54:16 +03:00
if ( ! xfrm6_policy_check ( NULL , XFRM_POLICY_IN , skb ) ) {
2007-12-12 21:44:43 +03:00
int nh ;
2007-12-13 05:54:16 +03:00
if ( ! ( skb - > sp & & skb - > sp - > xvec [ skb - > sp - > len - 1 ] - > props . flags &
XFRM_STATE_ICMP ) )
goto drop_no_count ;
2007-12-12 21:44:43 +03:00
if ( ! pskb_may_pull ( skb , sizeof ( * hdr ) + sizeof ( * orig_hdr ) ) )
goto drop_no_count ;
nh = skb_network_offset ( skb ) ;
skb_set_network_header ( skb , sizeof ( * hdr ) ) ;
if ( ! xfrm6_policy_check_reverse ( NULL , XFRM_POLICY_IN , skb ) )
goto drop_no_count ;
skb_set_network_header ( skb , nh ) ;
}
2005-04-17 02:20:36 +04:00
ICMP6_INC_STATS_BH ( idev , ICMP6_MIB_INMSGS ) ;
2007-04-26 04:54:47 +04:00
saddr = & ipv6_hdr ( skb ) - > saddr ;
daddr = & ipv6_hdr ( skb ) - > daddr ;
2005-04-17 02:20:36 +04:00
/* Perform checksum. */
2005-11-11 00:01:24 +03:00
switch ( skb - > ip_summed ) {
2006-08-30 03:44:56 +04:00
case CHECKSUM_COMPLETE :
2005-11-11 00:01:24 +03:00
if ( ! csum_ipv6_magic ( saddr , daddr , skb - > len , IPPROTO_ICMPV6 ,
skb - > csum ) )
break ;
/* fall through */
case CHECKSUM_NONE :
2006-11-15 08:35:48 +03:00
skb - > csum = ~ csum_unfold ( csum_ipv6_magic ( saddr , daddr , skb - > len ,
IPPROTO_ICMPV6 , 0 ) ) ;
2005-11-11 00:01:24 +03:00
if ( __skb_checksum_complete ( skb ) ) {
2006-01-14 01:29:07 +03:00
LIMIT_NETDEBUG ( KERN_DEBUG " ICMPv6 checksum failed [ " NIP6_FMT " > " NIP6_FMT " ] \n " ,
2005-08-10 07:50:53 +04:00
NIP6 ( * saddr ) , NIP6 ( * daddr ) ) ;
2005-04-17 02:20:36 +04:00
goto discard_it ;
}
}
2007-12-12 21:44:43 +03:00
__skb_pull ( skb , sizeof ( * hdr ) ) ;
2005-04-17 02:20:36 +04:00
2007-03-13 20:03:22 +03:00
hdr = icmp6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
type = hdr - > icmp6_type ;
2007-09-17 03:52:35 +04:00
ICMP6MSGIN_INC_STATS_BH ( idev , type ) ;
2005-04-17 02:20:36 +04:00
switch ( type ) {
case ICMPV6_ECHO_REQUEST :
icmpv6_echo_reply ( skb ) ;
break ;
case ICMPV6_ECHO_REPLY :
/* we couldn't care less */
break ;
case ICMPV6_PKT_TOOBIG :
/* BUGGG_FUTURE: if packet contains rthdr, we cannot update
standard destination cache . Seems , only " advanced "
destination cache will allow to solve this problem
- - ANK ( 980726 )
*/
if ( ! pskb_may_pull ( skb , sizeof ( struct ipv6hdr ) ) )
goto discard_it ;
2007-03-13 20:03:22 +03:00
hdr = icmp6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
orig_hdr = ( struct ipv6hdr * ) ( hdr + 1 ) ;
rt6_pmtu_discovery ( & orig_hdr - > daddr , & orig_hdr - > saddr , dev ,
ntohl ( hdr - > icmp6_mtu ) ) ;
/*
* Drop through to notify
*/
case ICMPV6_DEST_UNREACH :
case ICMPV6_TIME_EXCEED :
case ICMPV6_PARAMPROB :
icmpv6_notify ( skb , type , hdr - > icmp6_code , hdr - > icmp6_mtu ) ;
break ;
case NDISC_ROUTER_SOLICITATION :
case NDISC_ROUTER_ADVERTISEMENT :
case NDISC_NEIGHBOUR_SOLICITATION :
case NDISC_NEIGHBOUR_ADVERTISEMENT :
case NDISC_REDIRECT :
ndisc_rcv ( skb ) ;
break ;
case ICMPV6_MGM_QUERY :
igmp6_event_query ( skb ) ;
break ;
case ICMPV6_MGM_REPORT :
igmp6_event_report ( skb ) ;
break ;
case ICMPV6_MGM_REDUCTION :
case ICMPV6_NI_QUERY :
case ICMPV6_NI_REPLY :
case ICMPV6_MLD2_REPORT :
case ICMPV6_DHAAD_REQUEST :
case ICMPV6_DHAAD_REPLY :
case ICMPV6_MOBILE_PREFIX_SOL :
case ICMPV6_MOBILE_PREFIX_ADV :
break ;
default :
2005-08-10 07:50:53 +04:00
LIMIT_NETDEBUG ( KERN_DEBUG " icmpv6: msg of unknown type \n " ) ;
2005-04-17 02:20:36 +04:00
/* informational */
if ( type & ICMPV6_INFOMSG_MASK )
break ;
2007-02-09 17:24:49 +03:00
/*
* error of unknown type .
* must pass to upper level
2005-04-17 02:20:36 +04:00
*/
icmpv6_notify ( skb , type , hdr - > icmp6_code , hdr - > icmp6_mtu ) ;
2007-04-21 04:09:22 +04:00
}
2005-04-17 02:20:36 +04:00
kfree_skb ( skb ) ;
return 0 ;
discard_it :
ICMP6_INC_STATS_BH ( idev , ICMP6_MIB_INERRORS ) ;
2007-12-12 21:44:43 +03:00
drop_no_count :
2005-04-17 02:20:36 +04:00
kfree_skb ( skb ) ;
return 0 ;
}
2006-08-15 11:06:56 +04:00
/*
* Special lock - class for __icmpv6_socket :
*/
static struct lock_class_key icmpv6_socket_sk_dst_lock_key ;
2005-04-17 02:20:36 +04:00
int __init icmpv6_init ( struct net_proto_family * ops )
{
struct sock * sk ;
int err , i , j ;
2006-04-11 09:52:50 +04:00
for_each_possible_cpu ( i ) {
2005-04-17 02:20:36 +04:00
err = sock_create_kern ( PF_INET6 , SOCK_RAW , IPPROTO_ICMPV6 ,
& per_cpu ( __icmpv6_socket , i ) ) ;
if ( err < 0 ) {
printk ( KERN_ERR
" Failed to initialize the ICMP6 control socket "
" (err %d). \n " ,
err ) ;
goto fail ;
}
sk = per_cpu ( __icmpv6_socket , i ) - > sk ;
sk - > sk_allocation = GFP_ATOMIC ;
2006-08-15 11:06:56 +04:00
/*
* Split off their lock - class , because sk - > sk_dst_lock
* gets used from softirqs , which is safe for
* __icmpv6_socket ( because those never get directly used
* via userspace syscalls ) , but unsafe for normal sockets .
*/
lockdep_set_class ( & sk - > sk_dst_lock ,
& icmpv6_socket_sk_dst_lock_key ) ;
2005-04-17 02:20:36 +04:00
/* Enough space for 2 64K ICMP packets, including
* sk_buff struct overhead .
*/
sk - > sk_sndbuf =
( 2 * ( ( 64 * 1024 ) + sizeof ( struct sk_buff ) ) ) ;
sk - > sk_prot - > unhash ( sk ) ;
}
if ( inet6_add_protocol ( & icmpv6_protocol , IPPROTO_ICMPV6 ) < 0 ) {
printk ( KERN_ERR " Failed to register ICMP6 protocol \n " ) ;
err = - EAGAIN ;
goto fail ;
}
return 0 ;
fail :
for ( j = 0 ; j < i ; j + + ) {
if ( ! cpu_possible ( j ) )
continue ;
sock_release ( per_cpu ( __icmpv6_socket , j ) ) ;
}
return err ;
}
void icmpv6_cleanup ( void )
{
int i ;
2006-04-11 09:52:50 +04:00
for_each_possible_cpu ( i ) {
2005-04-17 02:20:36 +04:00
sock_release ( per_cpu ( __icmpv6_socket , i ) ) ;
}
inet6_del_protocol ( & icmpv6_protocol , IPPROTO_ICMPV6 ) ;
}
2005-11-30 03:21:38 +03:00
static const struct icmp6_err {
2005-04-17 02:20:36 +04:00
int err ;
int fatal ;
} tab_unreach [ ] = {
{ /* NOROUTE */
. err = ENETUNREACH ,
. fatal = 0 ,
} ,
{ /* ADM_PROHIBITED */
. err = EACCES ,
. fatal = 1 ,
} ,
{ /* Was NOT_NEIGHBOUR, now reserved */
. err = EHOSTUNREACH ,
. fatal = 0 ,
} ,
{ /* ADDR_UNREACH */
. err = EHOSTUNREACH ,
. fatal = 0 ,
} ,
{ /* PORT_UNREACH */
. err = ECONNREFUSED ,
. fatal = 1 ,
} ,
} ;
int icmpv6_err_convert ( int type , int code , int * err )
{
int fatal = 0 ;
* err = EPROTO ;
switch ( type ) {
case ICMPV6_DEST_UNREACH :
fatal = 1 ;
if ( code < = ICMPV6_PORT_UNREACH ) {
* err = tab_unreach [ code ] . err ;
fatal = tab_unreach [ code ] . fatal ;
}
break ;
case ICMPV6_PKT_TOOBIG :
* err = EMSGSIZE ;
break ;
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
case ICMPV6_PARAMPROB :
* err = EPROTO ;
fatal = 1 ;
break ;
case ICMPV6_TIME_EXCEED :
* err = EHOSTUNREACH ;
break ;
2007-04-21 04:09:22 +04:00
}
2005-04-17 02:20:36 +04:00
return fatal ;
}
2007-02-22 16:05:40 +03:00
EXPORT_SYMBOL ( icmpv6_err_convert ) ;
2005-04-17 02:20:36 +04:00
# ifdef CONFIG_SYSCTL
2008-01-10 13:53:43 +03:00
ctl_table ipv6_icmp_table_template [ ] = {
2005-04-17 02:20:36 +04:00
{
. ctl_name = NET_IPV6_ICMP_RATELIMIT ,
. procname = " ratelimit " ,
2008-01-10 14:02:40 +03:00
. data = & init_net . ipv6 . sysctl . icmpv6_time ,
2005-04-17 02:20:36 +04:00
. maxlen = sizeof ( int ) ,
. mode = 0644 ,
. proc_handler = & proc_dointvec
} ,
{ . ctl_name = 0 } ,
} ;
2008-01-10 13:53:43 +03:00
struct ctl_table * ipv6_icmp_sysctl_init ( struct net * net )
{
struct ctl_table * table ;
table = kmemdup ( ipv6_icmp_table_template ,
sizeof ( ipv6_icmp_table_template ) ,
GFP_KERNEL ) ;
return table ;
}
2005-04-17 02:20:36 +04:00
# endif