2019-05-19 15:08:55 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/*
* IPv6 library code , needed by static components when full IPv6 support is
* not configured or static .
*/
2011-07-15 19:47:34 +04:00
# include <linux/export.h>
2005-04-17 02:20:36 +04:00
# include <net/ipv6.h>
2007-02-09 17:24:49 +03:00
/*
2005-04-17 02:20:36 +04:00
* find out if nexthdr is a well - known extension header or a protocol
*/
2012-05-18 22:57:34 +04:00
bool ipv6_ext_hdr ( u8 nexthdr )
2005-04-17 02:20:36 +04:00
{
2007-02-09 17:24:49 +03:00
/*
2005-04-17 02:20:36 +04:00
* find out if nexthdr is an extension header or a protocol
*/
2010-09-23 00:43:57 +04:00
return ( nexthdr = = NEXTHDR_HOP ) | |
2005-04-17 02:20:36 +04:00
( nexthdr = = NEXTHDR_ROUTING ) | |
( nexthdr = = NEXTHDR_FRAGMENT ) | |
( nexthdr = = NEXTHDR_AUTH ) | |
( nexthdr = = NEXTHDR_NONE ) | |
2010-09-23 00:43:57 +04:00
( nexthdr = = NEXTHDR_DEST ) ;
2005-04-17 02:20:36 +04:00
}
2012-04-01 11:49:05 +04:00
EXPORT_SYMBOL ( ipv6_ext_hdr ) ;
2005-04-17 02:20:36 +04:00
/*
* Skip any extension headers . This is used by the ICMP module .
*
* Note that strictly speaking this conflicts with RFC 2460 4.0 :
2007-02-09 17:24:49 +03:00
* . . . The contents and semantics of each extension header determine whether
2005-04-17 02:20:36 +04:00
* or not to proceed to the next header . Therefore , extension headers must
* be processed strictly in the order they appear in the packet ; a
* receiver must not , for example , scan through a packet looking for a
* particular kind of extension header and process that header prior to
* processing all preceding ones .
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* We do exactly this . This is a protocol bug . We can ' t decide after a
2007-02-09 17:24:49 +03:00
* seeing an unknown discard - with - error flavour TLV option if it ' s a
2005-04-17 02:20:36 +04:00
* ICMP error message or not ( errors should never be send in reply to
* ICMP error messages ) .
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* But I see no other way to do this . This might need to be reexamined
* when Linux implements ESP ( and maybe AUTH ) headers .
* - - AK
*
2005-04-25 07:16:19 +04:00
* This function parses ( probably truncated ) exthdr set " hdr " .
* " nexthdrp " initially points to some place ,
2005-04-17 02:20:36 +04:00
* where type of the first header can be found .
*
* It skips all well - known exthdrs , and returns pointer to the start
* of unparsable area i . e . the first header with unknown type .
* If it is not NULL * nexthdr is updated by type / protocol of this header .
*
* NOTES : - if packet terminated with NEXTHDR_NONE it returns NULL .
* - it may return pointer pointing beyond end of packet ,
* if the last recognized header is truncated in the middle .
* - if packet is truncated , so that all parsed headers are skipped ,
* it returns NULL .
* - First fragment header is skipped , not - first ones
* are considered as unparsable .
2011-12-01 05:05:51 +04:00
* - Reports the offset field of the final fragment header so it is
* possible to tell whether this is a first fragment , later fragment ,
* or not fragmented .
2005-04-17 02:20:36 +04:00
* - ESP is unparsable for now and considered like
* normal payload protocol .
* - Note also special handling of AUTH header . Thanks to IPsec wizards .
*
* - - ANK ( 980726 )
*/
2011-12-01 05:05:51 +04:00
int ipv6_skip_exthdr ( const struct sk_buff * skb , int start , u8 * nexthdrp ,
__be16 * frag_offp )
2005-04-17 02:20:36 +04:00
{
u8 nexthdr = * nexthdrp ;
2011-12-01 05:05:51 +04:00
* frag_offp = 0 ;
2005-04-17 02:20:36 +04:00
while ( ipv6_ext_hdr ( nexthdr ) ) {
struct ipv6_opt_hdr _hdr , * hp ;
int hdrlen ;
if ( nexthdr = = NEXTHDR_NONE )
return - 1 ;
hp = skb_header_pointer ( skb , start , sizeof ( _hdr ) , & _hdr ) ;
2015-03-29 16:00:04 +03:00
if ( ! hp )
2005-04-25 07:16:19 +04:00
return - 1 ;
2005-04-17 02:20:36 +04:00
if ( nexthdr = = NEXTHDR_FRAGMENT ) {
2006-11-15 07:56:00 +03:00
__be16 _frag_off , * fp ;
2005-04-17 02:20:36 +04:00
fp = skb_header_pointer ( skb ,
start + offsetof ( struct frag_hdr ,
frag_off ) ,
sizeof ( _frag_off ) ,
& _frag_off ) ;
2015-03-29 16:00:04 +03:00
if ( ! fp )
2005-04-17 02:20:36 +04:00
return - 1 ;
2011-12-01 05:05:51 +04:00
* frag_offp = * fp ;
if ( ntohs ( * frag_offp ) & ~ 0x7 )
2005-04-17 02:20:36 +04:00
break ;
hdrlen = 8 ;
} else if ( nexthdr = = NEXTHDR_AUTH )
2017-09-20 19:18:17 +03:00
hdrlen = ipv6_authlen ( hp ) ;
2005-04-17 02:20:36 +04:00
else
2007-02-09 17:24:49 +03:00
hdrlen = ipv6_optlen ( hp ) ;
2005-04-17 02:20:36 +04:00
nexthdr = hp - > nexthdr ;
start + = hdrlen ;
}
* nexthdrp = nexthdr ;
return start ;
}
EXPORT_SYMBOL ( ipv6_skip_exthdr ) ;
2012-11-15 12:49:20 +04:00
2016-06-27 22:06:15 +03:00
int ipv6_find_tlv ( const struct sk_buff * skb , int offset , int type )
2012-11-15 12:49:20 +04:00
{
const unsigned char * nh = skb_network_header ( skb ) ;
2013-05-29 00:34:26 +04:00
int packet_len = skb_tail_pointer ( skb ) - skb_network_header ( skb ) ;
2012-11-15 12:49:20 +04:00
struct ipv6_opt_hdr * hdr ;
int len ;
if ( offset + 2 > packet_len )
goto bad ;
hdr = ( struct ipv6_opt_hdr * ) ( nh + offset ) ;
len = ( ( hdr - > hdrlen + 1 ) < < 3 ) ;
if ( offset + len > packet_len )
goto bad ;
offset + = 2 ;
len - = 2 ;
while ( len > 0 ) {
int opttype = nh [ offset ] ;
int optlen ;
if ( opttype = = type )
return offset ;
switch ( opttype ) {
case IPV6_TLV_PAD1 :
optlen = 1 ;
break ;
default :
optlen = nh [ offset + 1 ] + 2 ;
if ( optlen > len )
goto bad ;
break ;
}
offset + = optlen ;
len - = optlen ;
}
/* not_found */
bad :
return - 1 ;
}
EXPORT_SYMBOL_GPL ( ipv6_find_tlv ) ;
2012-11-30 21:01:30 +04:00
2012-11-10 05:05:07 +04:00
/*
* find the offset to specified header or the protocol number of last header
* if target < 0. " last header " is transport protocol header , ESP , or
* " No next header " .
*
2018-05-07 10:45:26 +03:00
* Note that * offset is used as input / output parameter , and if it is not zero ,
2012-11-10 05:05:07 +04:00
* then it must be a valid offset to an inner IPv6 header . This can be used
* to explore inner IPv6 header , eg . ICMPv6 error messages .
*
* If target header is found , its offset is set in * offset and return protocol
* number . Otherwise , return - 1.
*
* If the first fragment doesn ' t contain the final protocol header or
* NEXTHDR_NONE it is considered invalid .
*
* Note that non - 1 st fragment is special case that " the protocol number
* of last header " is " next header " field in Fragment header. In this case,
* * offset is meaningless and fragment offset is stored in * fragoff if fragoff
* isn ' t NULL .
*
2012-11-10 05:11:31 +04:00
* if flags is not NULL and it ' s a fragment , then the frag flag
* IP6_FH_F_FRAG will be set . If it ' s an AH header , the
* IP6_FH_F_AUTH flag is set and target < 0 , then this function will
* stop at the AH header . If IP6_FH_F_SKIP_RH flag was passed , then this
* function will skip all those routing headers , where segements_left was 0.
2012-11-10 05:05:07 +04:00
*/
int ipv6_find_hdr ( const struct sk_buff * skb , unsigned int * offset ,
int target , unsigned short * fragoff , int * flags )
{
unsigned int start = skb_network_offset ( skb ) + sizeof ( struct ipv6hdr ) ;
u8 nexthdr = ipv6_hdr ( skb ) - > nexthdr ;
2012-11-10 05:11:31 +04:00
bool found ;
2012-11-10 05:05:07 +04:00
if ( fragoff )
* fragoff = 0 ;
if ( * offset ) {
struct ipv6hdr _ip6 , * ip6 ;
ip6 = skb_header_pointer ( skb , * offset , sizeof ( _ip6 ) , & _ip6 ) ;
if ( ! ip6 | | ( ip6 - > version ! = 6 ) ) {
printk ( KERN_ERR " IPv6 header not found \n " ) ;
return - EBADMSG ;
}
start = * offset + sizeof ( struct ipv6hdr ) ;
nexthdr = ip6 - > nexthdr ;
}
2012-11-10 05:11:31 +04:00
do {
2012-11-10 05:05:07 +04:00
struct ipv6_opt_hdr _hdr , * hp ;
unsigned int hdrlen ;
2012-11-10 05:11:31 +04:00
found = ( nexthdr = = target ) ;
2012-11-10 05:05:07 +04:00
if ( ( ! ipv6_ext_hdr ( nexthdr ) ) | | nexthdr = = NEXTHDR_NONE ) {
2014-02-27 15:57:58 +04:00
if ( target < 0 | | found )
2012-11-10 05:05:07 +04:00
break ;
return - ENOENT ;
}
hp = skb_header_pointer ( skb , start , sizeof ( _hdr ) , & _hdr ) ;
2015-03-29 16:00:04 +03:00
if ( ! hp )
2012-11-10 05:05:07 +04:00
return - EBADMSG ;
2012-11-10 05:11:31 +04:00
if ( nexthdr = = NEXTHDR_ROUTING ) {
struct ipv6_rt_hdr _rh , * rh ;
rh = skb_header_pointer ( skb , start , sizeof ( _rh ) ,
& _rh ) ;
2015-03-29 16:00:04 +03:00
if ( ! rh )
2012-11-10 05:11:31 +04:00
return - EBADMSG ;
if ( flags & & ( * flags & IP6_FH_F_SKIP_RH ) & &
rh - > segments_left = = 0 )
found = false ;
}
2012-11-10 05:05:07 +04:00
if ( nexthdr = = NEXTHDR_FRAGMENT ) {
unsigned short _frag_off ;
__be16 * fp ;
if ( flags ) /* Indicate that this is a fragment */
* flags | = IP6_FH_F_FRAG ;
fp = skb_header_pointer ( skb ,
start + offsetof ( struct frag_hdr ,
frag_off ) ,
sizeof ( _frag_off ) ,
& _frag_off ) ;
2015-03-29 16:00:04 +03:00
if ( ! fp )
2012-11-10 05:05:07 +04:00
return - EBADMSG ;
_frag_off = ntohs ( * fp ) & ~ 0x7 ;
if ( _frag_off ) {
if ( target < 0 & &
( ( ! ipv6_ext_hdr ( hp - > nexthdr ) ) | |
hp - > nexthdr = = NEXTHDR_NONE ) ) {
if ( fragoff )
* fragoff = _frag_off ;
return hp - > nexthdr ;
}
2016-03-01 18:15:16 +03:00
if ( ! found )
return - ENOENT ;
if ( fragoff )
* fragoff = _frag_off ;
break ;
2012-11-10 05:05:07 +04:00
}
hdrlen = 8 ;
} else if ( nexthdr = = NEXTHDR_AUTH ) {
if ( flags & & ( * flags & IP6_FH_F_AUTH ) & & ( target < 0 ) )
break ;
2019-07-10 16:14:10 +03:00
hdrlen = ipv6_authlen ( hp ) ;
2012-11-10 05:05:07 +04:00
} else
hdrlen = ipv6_optlen ( hp ) ;
2012-11-10 05:11:31 +04:00
if ( ! found ) {
nexthdr = hp - > nexthdr ;
start + = hdrlen ;
}
} while ( ! found ) ;
2012-11-10 05:05:07 +04:00
* offset = start ;
return nexthdr ;
}
EXPORT_SYMBOL ( ipv6_find_hdr ) ;