linux 处理其他ip的包,linux ip选项处理(一)

本文只分析ip选项中的宽松路由选项和严格路由选项

1.ip头中选项格式

由于IP首部中可以存在选项,且可以同时存在多个选项,因此IP首部的长度是可变的,IPv4允许选项最长可达40字节。选项的格式有单字节和多字节两种,单字节的即只包括一个字节的选项类型,而多字节的则除一个字节的类型之外,还包括选项长度以及选项数据等。

多字节的选项格式如下所示:

234108d660fa9fa8f81d95dc1d609020.png

所有选项都以1字节类型(type)字段开始。在多字节选项中,类型字段后面紧接着一个长度(len)字段,其他的字节是数据(data)。许多选项数据字段的第一个字节是1字节的位移(offset)字段,指向数据字段内的某个字节。长度字节的计算覆盖了类型、长度和数据字段。类型被继续分成三个子字段: 1 bit 备份(copied )标志、2 bit 类(class)字段和5 bit 数字(number)字段。

2.linux内核存储ip选项的结构

struct ip_options {

__u32        faddr;//存在宽松路由或严格路由选项时,用来记录吓一跳的IP地址

unsigned char    optlen;//标识IP首部中选项所占的字节数

unsigned char    srr;//记录宽松路由或严格路由选项在IP首部中的偏移量,即选项的第一个字节的地址

减去IP首部的第一个字节的地址

unsigned char    rr;//用于记录记录路径选项在IP首部中的偏移量

unsigned char    ts;

unsigned char    is_setbyuser:1,

is_data:1,

is_strictroute:1,

srr_is_hit:1,

is_changed:1,

rr_needaddr:1,

ts_needtime:1,

ts_needaddr:1;

unsigned char    router_alert;

unsigned char    cipso;

unsigned char    __pad2;

unsigned char    __data[0];//若选项有数据则从该字段开始,使之紧跟在ip_options结构后面,最多不

超过40字节

};

3.宽松路由选项(LSRR)和严格路由选项(SSRR)

LSRR在选项的ip地址列表中并不列出一条完备而严格的路径,而是只给出路径中的某些关键点。在关键的之间可以通过路由器的自动路由选择功能进行路由,此选项在数据包分片的时候也必须被复制。

SSRR选项要求数据包必须严格按照发送方规定的路径经过每一个路由器,这些路由器应该是一一相连的,每两个指定的路由器之间不能有其他未指定的路由器,且路由器的顺序是不能改变的。如果数据包在传输时无法直接到达下一跳指定的路由器,路由器就会丢弃该数据包,然后产生一个源路由失败的目的不可达的ICMP差错报文报告给发送方。

内核定义的ip选项类型值:

/* IP options */

#define IPOPT_COPY        0x80

#define IPOPT_CLASS_MASK    0x60

#define IPOPT_NUMBER_MASK    0x1f

#define    IPOPT_COPIED(o)        ((o)&IPOPT_COPY)

#define    IPOPT_CLASS(o)        ((o)&IPOPT_CLASS_MASK)

#define    IPOPT_NUMBER(o)        ((o)&IPOPT_NUMBER_MASK)

#define    IPOPT_CONTROL        0x00

#define    IPOPT_RESERVED1        0x20

#define    IPOPT_MEASUREMENT    0x40

#define    IPOPT_RESERVED2        0x60

#define IPOPT_END    (0 |IPOPT_CONTROL)

#define IPOPT_NOOP    (1 |IPOPT_CONTROL)

#define IPOPT_SEC    (2 |IPOPT_CONTROL|IPOPT_COPY)

#define IPOPT_LSRR    (3 |IPOPT_CONTROL|IPOPT_COPY)//宽松路由的type值,即0x83

#define IPOPT_TIMESTAMP    (4 |IPOPT_MEASUREMENT)

#define IPOPT_CIPSO    (6 |IPOPT_CONTROL|IPOPT_COPY)

#define IPOPT_RR    (7 |IPOPT_CONTROL)

#define IPOPT_SID    (8 |IPOPT_CONTROL|IPOPT_COPY)

#define IPOPT_SSRR    (9 |IPOPT_CONTROL|IPOPT_COPY)//严格路由的type值,即0x89

#define IPOPT_RA    (20|IPOPT_CONTROL|IPOPT_COPY)

4.分析一个完整的ip选项处理流程(从setsockopt设置选项到发送syn、路由节点接收syn到转发syn及目的端接收syn到发送synack的ip选项的处理过程)

(1)从setsockopt设置选项到发送syn

用户层socket编程可以通过setsockopt来设置ip选项,在connect前设置选项值

setsockopt(sockfd,IPPROTO_IP,IP_OPTIONS,(void*)opt,optlen);setsockopt系统调用在内核中的实现,之前已经介绍过了。现在我们直接跳到函数

int ip_setsockopt(struct sock *sk, int level,

int optname, char __user *optval, int optlen)

{

......

err = do_ip_setsockopt(sk, level, optname, optval, optlen);//IPPPROTO_IP的处理函数

......

}

static int do_ip_setsockopt(struct sock *sk, int level,

int optname, char __user *optval, int optlen)

{

struct inet_sock *inet = inet_sk(sk);

......

lock_sock(sk);

switch (optname) {

case IP_OPTIONS://ip_options选项处理

{

struct ip_options * opt = NULL;

if (optlen > 40 || optlen < 0)

goto e_inval;

err = ip_options_get_from_user(&opt, optval, optlen);

if (err)

break;

if (inet->is_icsk) {

struct inet_connection_sock *icsk = inet_csk(sk);

......

if (inet->opt)

icsk->icsk_ext_hdr_len -= inet->opt->optlen;

if (opt)

icsk->icsk_ext_hdr_len += opt->optlen;

icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);

......

}

opt = xchg(&inet->opt, opt);//将opt与inet->opt交换;现在inet->opt中存储了我们解

析过的选项的值,inet->opt->faddr为下一跳的地址

kfree(opt);

break;

}

......

}

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)) {// 应用层数据拷贝到opt->__da

ta指向的内存处

kfree(opt);

return -EFAULT;

}

return ip_options_get_finish(optp, opt, optlen);//解析opt信息并用optp返回

}

static int ip_options_get_finish(struct ip_options **optp,

struct ip_options *opt, int optlen)

{

while (optlen & 3)

opt->__data[optlen++] = IPOPT_END;//如IP选项内容不是以四字节对齐的,则将未对齐部分用选

项列表结束符填充,使之对齐

opt->optlen = optlen;

opt->is_data = 1;

opt->is_setbyuser = 1;

if (optlen && ip_options_compile(opt, NULL)) {//解析IP选项信息块各字段值

kfree(opt);

return -EINVAL;

}

kfree(*optp);

*optp = opt;

return 0;

}

int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)

{

......

switch (*optptr) {

case IPOPT_SSRR:

case IPOPT_LSRR:

if (optlen < 3) {//1字节type,1字节len,1字节offset

pp_ptr = optptr + 1;

goto error;

}

if (optptr[2] < 4) {//offset至少为4,指向第一个ip

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)) {//至少能容下一个ip

pp_ptr = optptr + 1;

goto error;

}

memcpy(&opt->faddr, &optptr[3], 4);//取出第一个地址作为下一跳地址,

opt->faddr为下一跳的地址

if (optlen > 7)

memmove(&optptr[3], &optptr[7], optlen-7);//在路径列表中多于一个地址

时,将剩余的所有地址往前移动4个字节

}

opt->is_strictroute = (optptr[0] == IPOPT_SSRR);//记录是否为严格路由选项

opt->srr = optptr - iph;//记录源路由选项在IP首部的偏移量

break;

......

}

options的offset项没有修改,它指向了源地址路由的下一个节点,因为此时第一个路由节点已经从选项中删除,其他路由节点向前移动了4个字节

应用层socket编程connect函数将执行系统调用sys_connect

asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)

{

struct socket *sock;

......

err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,

sock->file->f_flags);

......

}sock->ops指向注册的proto_ops钩子,此时为tcp_prot

struct proto tcp_prot = {

.name            = "TCP",

......

.connect        = tcp_v4_connect,

......

};

int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)

{

struct inet_sock *inet = inet_sk(sk);

struct tcp_sock *tp = tcp_sk(sk);

struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;

......

nexthop = daddr = usin->sin_addr.s_addr;

if (inet->opt && inet->opt->srr) {//将临时变量下一跳地址和目的地址值都暂时设置为connect参

数中的地址,如果使用源地址路由,则将下一跳地址设置为IP选项中的faddr

if (!daddr)

return -EINVAL;

nexthop = inet->opt->faddr;

}

tmp = ip_route_connect(&rt, nexthop, inet->saddr,//查找路由缓存项

RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,

IPPROTO_TCP,

inet->sport, usin->sin_port, sk, 1);//

if (tmp < 0) {

if (tmp == -ENETUNREACH)

IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);

return tmp;

}

......

err = tcp_connect(sk);//发送syn包

rt = NULL;

if (err)

goto failure;

......

}

int tcp_connect(struct sock *sk)

{

......

buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);//分配skbuff

......

tcp_transmit_skb(sk, buff, 1, GFP_KERNEL);//构造tcp头和ip头并发送

......

}

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)

{

......

err = icsk->icsk_af_ops->queue_xmit(skb, 0);//构造ip头并发送,queue_xmit注册为

ip_queue_xmit函数

......

}

struct inet_connection_sock_af_ops ipv4_specific = {

.queue_xmit     = ip_queue_xmit,

......

};

int ip_queue_xmit(struct sk_buff *skb, int ipfragok)

{

struct sock *sk = skb->sk;

struct inet_sock *inet = inet_sk(sk);

struct ip_options *opt = inet->opt;

struct rtable *rt;

struct iphdr *iph;

......

/* Use correct destination address if we have options. */

daddr = inet->daddr;

if(opt && opt->srr)//如果使用源地址路由,下一跳为inet->opt->faddr,即选项中的第一个地址

daddr = opt->faddr;

{

struct flowi fl = { .oif = sk->sk_bound_dev_if,

.nl_u = { .ip4_u =

{ .daddr = daddr,

.saddr = inet->saddr,

.tos = RT_CONN_FLAGS(sk) } },

.proto = sk->sk_protocol,

.uli_u = { .ports =

{ .sport = inet->sport,

.dport = inet->dport } } };

/* If this fails, retransmit mechanism of transport layer will

* keep trying until route appears or the connection times

* itself out.

*/

security_sk_classify_flow(sk, &fl);

if (ip_route_output_flow(&rt, &fl, sk, 0))

goto no_route;

}

sk_setup_caps(sk, &rt->u.dst);

}

skb->dst = dst_clone(&rt->u.dst);

packet_routed:

if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)//如果是严格路由选项,并且网关地址和路由的下一跳不一致则中断处理流程,这也是严格路由所要求的:下一跳必须是选项中的顺序的地址

goto no_route;

......

iph->saddr = rt->rt_src;

iph->daddr = rt->rt_dst;//目的地址已经是路由的下一跳地址,即为选项中的第一个地址,即为

inet->opt->faddr

skb->nh.iph = iph;

/* Transport layer set skb->h.foo itself. */

if (opt && opt->optlen) {

iph->ihl += opt->optlen >> 2;

ip_options_build(skb, opt, inet->daddr, rt, 0);//将目的地址到options中最后一个ip地

址后面

}

......

}

发送syn的处理告一段落,将通过一个实例来体现这一流程:

实验一(LSRR):

客户端:192.168.18.73 路由节点1:192.168.18.71 路由节点2:192.168.18.72 服务器端:192.168.18.76 18.71上18.0网段有个网关192.168.18.79

18.73抓包(发送syn包)结果如下:bbc4ac22f1ce207073705b6b32f9e759.png

一个NOP是为了对齐而设置的。从图中可以看出目的地址改为了options中首个ip地址,偏移量指向了下一个路由节点地址,绝对目的地址被加入了options末尾。len为1个字节的type+1个字节len+1个字节的offset+2个四个字节的ip地址

如果在73上设定网关18.79后,syn包发送不受影响

实验二(SSRR):

c299cc9816aa514b2e63da4c890f1512.png

如果设定18.79网关,syn包就发不出去了,原因在ip_queue_xmit函数中分析过了

参考资料:

tcp/ip详解:第9章 ip选项处理

linux内核源码剖析-tcp/ip实现:第12章 IP选项处理 作者:樊东东 莫澜

UNIX网络编程第一卷:27.3节 IP源路径选项

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值