2005-04-17 02:20:36 +04:00
/*
* INET An implementation of the TCP / IP protocol suite for the LINUX
* operating system . INET is implemented using the BSD Socket
* interface as the means of communication with the user level .
*
* The options processing module for ip . c
*
* Version : $ Id : ip_options . c , v 1.21 2001 / 09 / 01 00 : 31 : 50 davem Exp $
*
* Authors : A . N . Kuznetsov
2007-02-09 17:24:47 +03:00
*
2005-04-17 02:20:36 +04:00
*/
2006-01-11 23:17:47 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <linux/types.h>
# include <asm/uaccess.h>
# include <linux/skbuff.h>
# include <linux/ip.h>
# include <linux/icmp.h>
# include <linux/netdevice.h>
# include <linux/rtnetlink.h>
# include <net/sock.h>
# include <net/ip.h>
# include <net/icmp.h>
2005-12-27 07:43:12 +03:00
# include <net/route.h>
2006-08-04 03:46:20 +04:00
# include <net/cipso_ipv4.h>
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:47 +03:00
/*
2005-04-17 02:20:36 +04:00
* Write options to IP header , record destination address to
* source route option , address of outgoing interface
* ( we should already know it , so that this function is allowed be
* called only after routing decision ) and timestamp ,
* if we originate this datagram .
*
* daddr is real destination address , next hop is recorded in IP header .
* saddr is address of outgoing interface .
*/
void ip_options_build ( struct sk_buff * skb , struct ip_options * opt ,
2006-09-27 09:27:05 +04:00
__be32 daddr , struct rtable * rt , int is_frag )
2005-04-17 02:20:36 +04:00
{
2007-04-11 07:50:43 +04:00
unsigned char * iph = skb_network_header ( skb ) ;
2005-04-17 02:20:36 +04:00
memcpy ( & ( IPCB ( skb ) - > opt ) , opt , sizeof ( struct ip_options ) ) ;
memcpy ( iph + sizeof ( struct iphdr ) , opt - > __data , opt - > optlen ) ;
opt = & ( IPCB ( skb ) - > opt ) ;
opt - > is_data = 0 ;
if ( opt - > srr )
memcpy ( iph + opt - > srr + iph [ opt - > srr + 1 ] - 4 , & daddr , 4 ) ;
if ( ! is_frag ) {
if ( opt - > rr_needaddr )
ip_rt_get_source ( iph + opt - > rr + iph [ opt - > rr + 2 ] - 5 , rt ) ;
if ( opt - > ts_needaddr )
ip_rt_get_source ( iph + opt - > ts + iph [ opt - > ts + 2 ] - 9 , rt ) ;
if ( opt - > ts_needtime ) {
struct timeval tv ;
2006-09-28 05:28:47 +04:00
__be32 midtime ;
2005-04-17 02:20:36 +04:00
do_gettimeofday ( & tv ) ;
midtime = htonl ( ( tv . tv_sec % 86400 ) * 1000 + tv . tv_usec / 1000 ) ;
memcpy ( iph + opt - > ts + iph [ opt - > ts + 2 ] - 5 , & midtime , 4 ) ;
}
return ;
}
if ( opt - > rr ) {
memset ( iph + opt - > rr , IPOPT_NOP , iph [ opt - > rr + 1 ] ) ;
opt - > rr = 0 ;
opt - > rr_needaddr = 0 ;
}
if ( opt - > ts ) {
memset ( iph + opt - > ts , IPOPT_NOP , iph [ opt - > ts + 1 ] ) ;
opt - > ts = 0 ;
opt - > ts_needaddr = opt - > ts_needtime = 0 ;
}
}
2007-02-09 17:24:47 +03:00
/*
2005-04-17 02:20:36 +04:00
* Provided ( sopt , skb ) points to received options ,
* build in dopt compiled option set appropriate for answering .
* i . e . invert SRR option , copy anothers ,
* and grab room in RR / TS options .
*
* NOTE : dopt cannot point to skb .
*/
2007-02-09 17:24:47 +03:00
int ip_options_echo ( struct ip_options * dopt , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
struct ip_options * sopt ;
unsigned char * sptr , * dptr ;
int soffset , doffset ;
int optlen ;
2006-09-28 05:28:47 +04:00
__be32 daddr ;
2005-04-17 02:20:36 +04:00
memset ( dopt , 0 , sizeof ( struct ip_options ) ) ;
dopt - > is_data = 1 ;
sopt = & ( IPCB ( skb ) - > opt ) ;
if ( sopt - > optlen = = 0 ) {
dopt - > optlen = 0 ;
return 0 ;
}
2007-04-11 07:50:43 +04:00
sptr = skb_network_header ( skb ) ;
2005-04-17 02:20:36 +04:00
dptr = dopt - > __data ;
if ( skb - > dst )
daddr = ( ( struct rtable * ) skb - > dst ) - > rt_spec_dst ;
else
2007-04-21 09:47:35 +04:00
daddr = ip_hdr ( skb ) - > daddr ;
2005-04-17 02:20:36 +04:00
if ( sopt - > rr ) {
optlen = sptr [ sopt - > rr + 1 ] ;
soffset = sptr [ sopt - > rr + 2 ] ;
dopt - > rr = dopt - > optlen + sizeof ( struct iphdr ) ;
memcpy ( dptr , sptr + sopt - > rr , optlen ) ;
if ( sopt - > rr_needaddr & & soffset < = optlen ) {
if ( soffset + 3 > optlen )
return - EINVAL ;
dptr [ 2 ] = soffset + 4 ;
dopt - > rr_needaddr = 1 ;
}
dptr + = optlen ;
dopt - > optlen + = optlen ;
}
if ( sopt - > ts ) {
optlen = sptr [ sopt - > ts + 1 ] ;
soffset = sptr [ sopt - > ts + 2 ] ;
dopt - > ts = dopt - > optlen + sizeof ( struct iphdr ) ;
memcpy ( dptr , sptr + sopt - > ts , optlen ) ;
if ( soffset < = optlen ) {
if ( sopt - > ts_needaddr ) {
if ( soffset + 3 > optlen )
return - EINVAL ;
dopt - > ts_needaddr = 1 ;
soffset + = 4 ;
}
if ( sopt - > ts_needtime ) {
if ( soffset + 3 > optlen )
return - EINVAL ;
if ( ( dptr [ 3 ] & 0xF ) ! = IPOPT_TS_PRESPEC ) {
dopt - > ts_needtime = 1 ;
soffset + = 4 ;
} else {
dopt - > ts_needtime = 0 ;
if ( soffset + 8 < = optlen ) {
2006-09-27 09:17:51 +04:00
__be32 addr ;
2005-04-17 02:20:36 +04:00
memcpy ( & addr , sptr + soffset - 1 , 4 ) ;
if ( inet_addr_type ( addr ) ! = RTN_LOCAL ) {
dopt - > ts_needtime = 1 ;
soffset + = 8 ;
}
}
}
}
dptr [ 2 ] = soffset ;
}
dptr + = optlen ;
dopt - > optlen + = optlen ;
}
if ( sopt - > srr ) {
unsigned char * start = sptr + sopt - > srr ;
2006-09-28 05:28:07 +04:00
__be32 faddr ;
2005-04-17 02:20:36 +04:00
optlen = start [ 1 ] ;
soffset = start [ 2 ] ;
doffset = 0 ;
if ( soffset > optlen )
soffset = optlen + 1 ;
soffset - = 4 ;
if ( soffset > 3 ) {
memcpy ( & faddr , & start [ soffset - 1 ] , 4 ) ;
for ( soffset - = 4 , doffset = 4 ; soffset > 3 ; soffset - = 4 , doffset + = 4 )
memcpy ( & dptr [ doffset - 1 ] , & start [ soffset - 1 ] , 4 ) ;
/*
* RFC1812 requires to fix illegal source routes .
*/
2007-04-21 09:47:35 +04:00
if ( memcmp ( & ip_hdr ( skb ) - > saddr ,
& start [ soffset + 3 ] , 4 ) = = 0 )
2005-04-17 02:20:36 +04:00
doffset - = 4 ;
}
if ( doffset > 3 ) {
memcpy ( & start [ doffset - 1 ] , & daddr , 4 ) ;
dopt - > faddr = faddr ;
dptr [ 0 ] = start [ 0 ] ;
dptr [ 1 ] = doffset + 3 ;
dptr [ 2 ] = 4 ;
dptr + = doffset + 3 ;
dopt - > srr = dopt - > optlen + sizeof ( struct iphdr ) ;
dopt - > optlen + = doffset + 3 ;
dopt - > is_strictroute = sopt - > is_strictroute ;
}
}
2006-08-04 03:46:20 +04:00
if ( sopt - > cipso ) {
optlen = sptr [ sopt - > cipso + 1 ] ;
dopt - > cipso = dopt - > optlen + sizeof ( struct iphdr ) ;
memcpy ( dptr , sptr + sopt - > cipso , optlen ) ;
dptr + = optlen ;
dopt - > optlen + = optlen ;
}
2005-04-17 02:20:36 +04:00
while ( dopt - > optlen & 3 ) {
* dptr + + = IPOPT_END ;
dopt - > optlen + + ;
}
return 0 ;
}
/*
* Options " fragmenting " , just fill options not
* allowed in fragments with NOOPs .
* Simple and stupid 8 ) , but the most efficient way .
*/
2007-02-09 17:24:47 +03:00
void ip_options_fragment ( struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2007-04-11 07:50:43 +04:00
unsigned char * optptr = skb_network_header ( skb ) + sizeof ( struct iphdr ) ;
2005-04-17 02:20:36 +04:00
struct ip_options * opt = & ( IPCB ( skb ) - > opt ) ;
int l = opt - > optlen ;
int optlen ;
while ( l > 0 ) {
switch ( * optptr ) {
case IPOPT_END :
return ;
case IPOPT_NOOP :
l - - ;
optptr + + ;
continue ;
}
optlen = optptr [ 1 ] ;
if ( optlen < 2 | | optlen > l )
return ;
if ( ! IPOPT_COPIED ( * optptr ) )
memset ( optptr , IPOPT_NOOP , optlen ) ;
l - = optlen ;
optptr + = optlen ;
}
opt - > ts = 0 ;
opt - > rr = 0 ;
opt - > rr_needaddr = 0 ;
opt - > ts_needaddr = 0 ;
opt - > ts_needtime = 0 ;
return ;
}
/*
* Verify options and fill pointers in struct options .
* Caller should clear * opt , and set opt - > data .
* If opt = = NULL , then skb - > data should point to IP header .
*/
int ip_options_compile ( struct ip_options * opt , struct sk_buff * skb )
{
int l ;
unsigned char * iph ;
unsigned char * optptr ;
int optlen ;
unsigned char * pp_ptr = NULL ;
struct rtable * rt = skb ? ( struct rtable * ) skb - > dst : NULL ;
if ( ! opt ) {
opt = & ( IPCB ( skb ) - > opt ) ;
2007-04-11 07:50:43 +04:00
iph = skb_network_header ( skb ) ;
2005-04-17 02:20:36 +04:00
opt - > optlen = ( ( struct iphdr * ) iph ) - > ihl * 4 - sizeof ( struct iphdr ) ;
optptr = iph + sizeof ( struct iphdr ) ;
opt - > is_data = 0 ;
} else {
2007-04-21 09:47:35 +04:00
optptr = opt - > is_data ? opt - > __data :
( unsigned char * ) & ( ip_hdr ( skb ) [ 1 ] ) ;
2005-04-17 02:20:36 +04:00
iph = optptr - sizeof ( struct iphdr ) ;
}
for ( l = opt - > optlen ; l > 0 ; ) {
switch ( * optptr ) {
case IPOPT_END :
for ( optptr + + , l - - ; l > 0 ; optptr + + , l - - ) {
if ( * optptr ! = IPOPT_END ) {
* optptr = IPOPT_END ;
opt - > is_changed = 1 ;
}
}
goto eol ;
case IPOPT_NOOP :
l - - ;
optptr + + ;
continue ;
}
optlen = optptr [ 1 ] ;
if ( optlen < 2 | | optlen > l ) {
pp_ptr = optptr ;
goto error ;
}
switch ( * optptr ) {
case IPOPT_SSRR :
case IPOPT_LSRR :
if ( optlen < 3 ) {
pp_ptr = optptr + 1 ;
goto error ;
}
if ( optptr [ 2 ] < 4 ) {
pp_ptr = optptr + 2 ;
goto error ;
}
/* NB: cf RFC-1812 5.2.4.1 */
if ( opt - > srr ) {
pp_ptr = optptr ;
goto error ;
}
if ( ! skb ) {
if ( optptr [ 2 ] ! = 4 | | optlen < 7 | | ( ( optlen - 3 ) & 3 ) ) {
pp_ptr = optptr + 1 ;
goto error ;
}
memcpy ( & opt - > faddr , & optptr [ 3 ] , 4 ) ;
if ( optlen > 7 )
memmove ( & optptr [ 3 ] , & optptr [ 7 ] , optlen - 7 ) ;
}
opt - > is_strictroute = ( optptr [ 0 ] = = IPOPT_SSRR ) ;
opt - > srr = optptr - iph ;
break ;
case IPOPT_RR :
if ( opt - > rr ) {
pp_ptr = optptr ;
goto error ;
}
if ( optlen < 3 ) {
pp_ptr = optptr + 1 ;
goto error ;
}
if ( optptr [ 2 ] < 4 ) {
pp_ptr = optptr + 2 ;
goto error ;
}
if ( optptr [ 2 ] < = optlen ) {
if ( optptr [ 2 ] + 3 > optlen ) {
pp_ptr = optptr + 2 ;
goto error ;
}
if ( skb ) {
memcpy ( & optptr [ optptr [ 2 ] - 1 ] , & rt - > rt_spec_dst , 4 ) ;
opt - > is_changed = 1 ;
}
optptr [ 2 ] + = 4 ;
opt - > rr_needaddr = 1 ;
}
opt - > rr = optptr - iph ;
break ;
case IPOPT_TIMESTAMP :
if ( opt - > ts ) {
pp_ptr = optptr ;
goto error ;
}
if ( optlen < 4 ) {
pp_ptr = optptr + 1 ;
goto error ;
}
if ( optptr [ 2 ] < 5 ) {
pp_ptr = optptr + 2 ;
goto error ;
}
if ( optptr [ 2 ] < = optlen ) {
2006-09-28 05:28:47 +04:00
__be32 * timeptr = NULL ;
2005-04-17 02:20:36 +04:00
if ( optptr [ 2 ] + 3 > optptr [ 1 ] ) {
pp_ptr = optptr + 2 ;
goto error ;
}
switch ( optptr [ 3 ] & 0xF ) {
case IPOPT_TS_TSONLY :
opt - > ts = optptr - iph ;
2007-02-09 17:24:47 +03:00
if ( skb )
2006-09-28 05:28:47 +04:00
timeptr = ( __be32 * ) & optptr [ optptr [ 2 ] - 1 ] ;
2005-04-17 02:20:36 +04:00
opt - > ts_needtime = 1 ;
optptr [ 2 ] + = 4 ;
break ;
case IPOPT_TS_TSANDADDR :
if ( optptr [ 2 ] + 7 > optptr [ 1 ] ) {
pp_ptr = optptr + 2 ;
goto error ;
}
opt - > ts = optptr - iph ;
if ( skb ) {
memcpy ( & optptr [ optptr [ 2 ] - 1 ] , & rt - > rt_spec_dst , 4 ) ;
2006-09-28 05:28:47 +04:00
timeptr = ( __be32 * ) & optptr [ optptr [ 2 ] + 3 ] ;
2005-04-17 02:20:36 +04:00
}
opt - > ts_needaddr = 1 ;
opt - > ts_needtime = 1 ;
optptr [ 2 ] + = 8 ;
break ;
case IPOPT_TS_PRESPEC :
if ( optptr [ 2 ] + 7 > optptr [ 1 ] ) {
pp_ptr = optptr + 2 ;
goto error ;
}
opt - > ts = optptr - iph ;
{
2006-09-27 09:17:51 +04:00
__be32 addr ;
2005-04-17 02:20:36 +04:00
memcpy ( & addr , & optptr [ optptr [ 2 ] - 1 ] , 4 ) ;
if ( inet_addr_type ( addr ) = = RTN_UNICAST )
break ;
if ( skb )
2006-09-28 05:28:47 +04:00
timeptr = ( __be32 * ) & optptr [ optptr [ 2 ] + 3 ] ;
2005-04-17 02:20:36 +04:00
}
opt - > ts_needtime = 1 ;
optptr [ 2 ] + = 8 ;
break ;
default :
if ( ! skb & & ! capable ( CAP_NET_RAW ) ) {
pp_ptr = optptr + 3 ;
goto error ;
}
break ;
}
if ( timeptr ) {
struct timeval tv ;
2006-09-28 05:28:47 +04:00
__be32 midtime ;
2005-04-17 02:20:36 +04:00
do_gettimeofday ( & tv ) ;
midtime = htonl ( ( tv . tv_sec % 86400 ) * 1000 + tv . tv_usec / 1000 ) ;
2006-09-28 05:28:47 +04:00
memcpy ( timeptr , & midtime , sizeof ( __be32 ) ) ;
2005-04-17 02:20:36 +04:00
opt - > is_changed = 1 ;
}
} else {
unsigned overflow = optptr [ 3 ] > > 4 ;
if ( overflow = = 15 ) {
pp_ptr = optptr + 3 ;
goto error ;
}
opt - > ts = optptr - iph ;
if ( skb ) {
optptr [ 3 ] = ( optptr [ 3 ] & 0xF ) | ( ( overflow + 1 ) < < 4 ) ;
opt - > is_changed = 1 ;
}
}
break ;
case IPOPT_RA :
if ( optlen < 4 ) {
pp_ptr = optptr + 1 ;
goto error ;
}
if ( optptr [ 2 ] = = 0 & & optptr [ 3 ] = = 0 )
opt - > router_alert = optptr - iph ;
break ;
2006-08-04 03:46:20 +04:00
case IPOPT_CIPSO :
2006-10-31 02:22:15 +03:00
if ( ( ! skb & & ! capable ( CAP_NET_RAW ) ) | | opt - > cipso ) {
2006-08-04 03:46:20 +04:00
pp_ptr = optptr ;
goto error ;
}
opt - > cipso = optptr - iph ;
2007-02-09 17:24:47 +03:00
if ( cipso_v4_validate ( & optptr ) ) {
2006-08-04 03:46:20 +04:00
pp_ptr = optptr ;
goto error ;
}
break ;
2005-04-17 02:20:36 +04:00
case IPOPT_SEC :
case IPOPT_SID :
default :
if ( ! skb & & ! capable ( CAP_NET_RAW ) ) {
pp_ptr = optptr ;
goto error ;
}
break ;
}
l - = optlen ;
optptr + = optlen ;
}
eol :
if ( ! pp_ptr )
return 0 ;
error :
if ( skb ) {
icmp_send ( skb , ICMP_PARAMETERPROB , 0 , htonl ( ( pp_ptr - iph ) < < 24 ) ) ;
}
return - EINVAL ;
}
/*
* Undo all the changes done by ip_options_compile ( ) .
*/
void ip_options_undo ( struct ip_options * opt )
{
if ( opt - > srr ) {
unsigned char * optptr = opt - > __data + opt - > srr - sizeof ( struct iphdr ) ;
memmove ( optptr + 7 , optptr + 3 , optptr [ 1 ] - 7 ) ;
memcpy ( optptr + 3 , & opt - > faddr , 4 ) ;
}
if ( opt - > rr_needaddr ) {
unsigned char * optptr = opt - > __data + opt - > rr - sizeof ( struct iphdr ) ;
optptr [ 2 ] - = 4 ;
memset ( & optptr [ optptr [ 2 ] - 1 ] , 0 , 4 ) ;
}
if ( opt - > ts ) {
unsigned char * optptr = opt - > __data + opt - > ts - sizeof ( struct iphdr ) ;
if ( opt - > ts_needtime ) {
optptr [ 2 ] - = 4 ;
memset ( & optptr [ optptr [ 2 ] - 1 ] , 0 , 4 ) ;
if ( ( optptr [ 3 ] & 0xF ) = = IPOPT_TS_PRESPEC )
optptr [ 2 ] - = 4 ;
}
if ( opt - > ts_needaddr ) {
optptr [ 2 ] - = 4 ;
memset ( & optptr [ optptr [ 2 ] - 1 ] , 0 , 4 ) ;
}
}
}
2005-08-17 02:46:48 +04:00
static struct ip_options * ip_options_get_alloc ( const int optlen )
2005-04-17 02:20:36 +04:00
{
2005-08-17 02:46:48 +04:00
struct ip_options * opt = kmalloc ( sizeof ( * opt ) + ( ( optlen + 3 ) & ~ 3 ) ,
GFP_KERNEL ) ;
if ( opt )
memset ( opt , 0 , sizeof ( * opt ) ) ;
return opt ;
}
2005-04-17 02:20:36 +04:00
2005-08-17 02:46:48 +04:00
static int ip_options_get_finish ( struct ip_options * * optp ,
struct ip_options * opt , int optlen )
{
2005-04-17 02:20:36 +04:00
while ( optlen & 3 )
opt - > __data [ optlen + + ] = IPOPT_END ;
opt - > optlen = optlen ;
opt - > is_data = 1 ;
if ( optlen & & ip_options_compile ( opt , NULL ) ) {
kfree ( opt ) ;
return - EINVAL ;
}
2005-11-08 20:41:34 +03:00
kfree ( * optp ) ;
2005-04-17 02:20:36 +04:00
* optp = opt ;
return 0 ;
}
2005-08-17 02:46:48 +04:00
int ip_options_get_from_user ( struct ip_options * * optp , unsigned char __user * data , int optlen )
{
struct ip_options * opt = ip_options_get_alloc ( optlen ) ;
if ( ! opt )
return - ENOMEM ;
if ( optlen & & copy_from_user ( opt - > __data , data , optlen ) ) {
kfree ( opt ) ;
return - EFAULT ;
}
return ip_options_get_finish ( optp , opt , optlen ) ;
}
int ip_options_get ( struct ip_options * * optp , unsigned char * data , int optlen )
{
struct ip_options * opt = ip_options_get_alloc ( optlen ) ;
if ( ! opt )
return - ENOMEM ;
if ( optlen )
memcpy ( opt - > __data , data , optlen ) ;
return ip_options_get_finish ( optp , opt , optlen ) ;
}
2005-04-17 02:20:36 +04:00
void ip_forward_options ( struct sk_buff * skb )
{
struct ip_options * opt = & ( IPCB ( skb ) - > opt ) ;
unsigned char * optptr ;
struct rtable * rt = ( struct rtable * ) skb - > dst ;
2007-04-11 07:50:43 +04:00
unsigned char * raw = skb_network_header ( skb ) ;
2005-04-17 02:20:36 +04:00
if ( opt - > rr_needaddr ) {
optptr = ( unsigned char * ) raw + opt - > rr ;
ip_rt_get_source ( & optptr [ optptr [ 2 ] - 5 ] , rt ) ;
opt - > is_changed = 1 ;
}
if ( opt - > srr_is_hit ) {
int srrptr , srrspace ;
optptr = raw + opt - > srr ;
for ( srrptr = optptr [ 2 ] , srrspace = optptr [ 1 ] ;
srrptr < = srrspace ;
srrptr + = 4
) {
if ( srrptr + 3 > srrspace )
break ;
if ( memcmp ( & rt - > rt_dst , & optptr [ srrptr - 1 ] , 4 ) = = 0 )
break ;
}
if ( srrptr + 3 < = srrspace ) {
opt - > is_changed = 1 ;
ip_rt_get_source ( & optptr [ srrptr - 1 ] , rt ) ;
2007-04-21 09:47:35 +04:00
ip_hdr ( skb ) - > daddr = rt - > rt_dst ;
2005-04-17 02:20:36 +04:00
optptr [ 2 ] = srrptr + 4 ;
} else if ( net_ratelimit ( ) )
printk ( KERN_CRIT " ip_forward(): Argh! Destination lost! \n " ) ;
if ( opt - > ts_needaddr ) {
optptr = raw + opt - > ts ;
ip_rt_get_source ( & optptr [ optptr [ 2 ] - 9 ] , rt ) ;
opt - > is_changed = 1 ;
}
}
if ( opt - > is_changed ) {
opt - > is_changed = 0 ;
2007-04-21 09:47:35 +04:00
ip_send_check ( ip_hdr ( skb ) ) ;
2005-04-17 02:20:36 +04:00
}
}
int ip_options_rcv_srr ( struct sk_buff * skb )
{
struct ip_options * opt = & ( IPCB ( skb ) - > opt ) ;
int srrspace , srrptr ;
2006-09-27 08:25:20 +04:00
__be32 nexthop ;
2007-04-21 09:47:35 +04:00
struct iphdr * iph = ip_hdr ( skb ) ;
2007-04-11 07:50:43 +04:00
unsigned char * optptr = skb_network_header ( skb ) + opt - > srr ;
2005-04-17 02:20:36 +04:00
struct rtable * rt = ( struct rtable * ) skb - > dst ;
struct rtable * rt2 ;
int err ;
if ( ! opt - > srr )
return 0 ;
if ( skb - > pkt_type ! = PACKET_HOST )
return - EINVAL ;
if ( rt - > rt_type = = RTN_UNICAST ) {
if ( ! opt - > is_strictroute )
return 0 ;
icmp_send ( skb , ICMP_PARAMETERPROB , 0 , htonl ( 16 < < 24 ) ) ;
return - EINVAL ;
}
if ( rt - > rt_type ! = RTN_LOCAL )
return - EINVAL ;
for ( srrptr = optptr [ 2 ] , srrspace = optptr [ 1 ] ; srrptr < = srrspace ; srrptr + = 4 ) {
if ( srrptr + 3 > srrspace ) {
icmp_send ( skb , ICMP_PARAMETERPROB , 0 , htonl ( ( opt - > srr + 2 ) < < 24 ) ) ;
return - EINVAL ;
}
memcpy ( & nexthop , & optptr [ srrptr - 1 ] , 4 ) ;
rt = ( struct rtable * ) skb - > dst ;
skb - > dst = NULL ;
err = ip_route_input ( skb , nexthop , iph - > saddr , iph - > tos , skb - > dev ) ;
rt2 = ( struct rtable * ) skb - > dst ;
if ( err | | ( rt2 - > rt_type ! = RTN_UNICAST & & rt2 - > rt_type ! = RTN_LOCAL ) ) {
ip_rt_put ( rt2 ) ;
skb - > dst = & rt - > u . dst ;
return - EINVAL ;
}
ip_rt_put ( rt ) ;
if ( rt2 - > rt_type ! = RTN_LOCAL )
break ;
/* Superfast 8) loopback forward */
memcpy ( & iph - > daddr , & optptr [ srrptr - 1 ] , 4 ) ;
opt - > is_changed = 1 ;
}
if ( srrptr < = srrspace ) {
opt - > srr_is_hit = 1 ;
opt - > is_changed = 1 ;
}
return 0 ;
}