Chinaunix首页 | 论坛 | 博客
  • 博客访问: 419852
  • 博文数量: 124
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 872
  • 用 户 组: 普通用户
  • 注册时间: 2018-03-29 14:38
个人简介

默默的一块石头

文章分类

全部博文(124)

文章存档

2022年(26)

2021年(10)

2020年(28)

2019年(60)

我的朋友

分类: LINUX

2019-11-18 20:03:46

本文只分析ip选项中的宽松路由选项和严格路由选项
 
1.ip头中选项格式
由于IP首部中可以存在选项,且可以同时存在多个选项,因此IP首部的长度是可变的,IPv4允许选项最长可达40字节。选项的格式有单字节和多字节两种,单字节的即只包括一个字节的选项类型,而多字节的则除一个字节的类型之外,还包括选项长度以及选项数据等。
多字节的选项格式如下所示:
所有选项都以1字节类型(type)字段开始。在多字节选项中,类型字段后面紧接着一个长度(len)字段,其他的字节是数据(data)。许多选项数据字段的第一个字节是1字节的位移(offset)字段,指向数据字段内的某个字节。长度字节的计算覆盖了类型、长度和数据字段。类型被继续分成三个子字段: 1 bit 备份(copied )标志、2 bit 类(class)字段和5 bit 数字(number)字段。

2.linux内核存储ip选项的结构
  1. struct ip_options {
  2.     __u32        faddr;//存在宽松路由或严格路由选项时,用来记录吓一跳的IP地址
  3.     unsigned char    optlen;//标识IP首部中选项所占的字节数
  4.     unsigned char    srr;//记录宽松路由或严格路由选项在IP首部中的偏移量,即选项的第一个字节的地址
  5.                            减去IP首部的第一个字节的地址
  6.     unsigned char    rr;//用于记录记录路径选项在IP首部中的偏移量
  7.     unsigned char    ts;
  8.     unsigned char    is_setbyuser:1,
  9.             is_data:1,
  10.             is_strictroute:1,
  11.             srr_is_hit:1,
  12.             is_changed:1,
  13.             rr_needaddr:1,
  14.             ts_needtime:1,
  15.             ts_needaddr:1;
  16.     unsigned char    router_alert;
  17.     unsigned char    cipso;
  18.     unsigned char    __pad2;
  19.     unsigned char    __data[0];//若选项有数据则从该字段开始,使之紧跟在ip_options结构后面,最多不
  20.                                  超过40字节
  21. };

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

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

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

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

  1. /* IP options */
  2. #define IPOPT_COPY        0x80
  3. #define IPOPT_CLASS_MASK    0x60
  4. #define IPOPT_NUMBER_MASK    0x1f

  5. #define    IPOPT_COPIED(o)        ((o)&IPOPT_COPY)
  6. #define    IPOPT_CLASS(o)        ((o)&IPOPT_CLASS_MASK)
  7. #define    IPOPT_NUMBER(o)        ((o)&IPOPT_NUMBER_MASK)

  8. #define    IPOPT_CONTROL        0x00
  9. #define    IPOPT_RESERVED1        0x20
  10. #define    IPOPT_MEASUREMENT    0x40
  11. #define    IPOPT_RESERVED2        0x60

  12. #define IPOPT_END    (0 |IPOPT_CONTROL)
  13. #define IPOPT_NOOP    (1 |IPOPT_CONTROL)
  14. #define IPOPT_SEC    (2 |IPOPT_CONTROL|IPOPT_COPY)
  15. #define IPOPT_LSRR    (3 |IPOPT_CONTROL|IPOPT_COPY)//宽松路由的type值,即0x83
  16. #define IPOPT_TIMESTAMP    (4 |IPOPT_MEASUREMENT)
  17. #define IPOPT_CIPSO    (6 |IPOPT_CONTROL|IPOPT_COPY)
  18. #define IPOPT_RR    (7 |IPOPT_CONTROL)
  19. #define IPOPT_SID    (8 |IPOPT_CONTROL|IPOPT_COPY)
  20. #define IPOPT_SSRR    (9 |IPOPT_CONTROL|IPOPT_COPY)//严格路由的type值,即0x89
  21. #define IPOPT_RA    (20|IPOPT_CONTROL|IPOPT_COPY)

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

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

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

  1. setsockopt(sockfd,IPPROTO_IP,IP_OPTIONS,(void*)opt,optlen);
setsockopt系统调用在内核中的实现,之前已经介绍过了。现在我们直接跳到函数
  1. int ip_setsockopt(struct sock *sk, int level,
  2.         int optname, char __user *optval, int optlen)
  3. {
  4.     ......
  5.     err = do_ip_setsockopt(sk, level, optname, optval, optlen);//IPPPROTO_IP的处理函数
  6.     ......
  7. }
  1. static int do_ip_setsockopt(struct sock *sk, int level,
  2.         int optname, char __user *optval, int optlen)
  3. {
  4.     struct inet_sock *inet = inet_sk(sk);
  5.     ......
  6.     lock_sock(sk);

  7.     switch (optname) {
  8.         case IP_OPTIONS://ip_options选项处理
  9.         {
  10.             struct ip_options * opt = NULL;
  11.             if (optlen > 40 || optlen < 0)
  12.                 goto e_inval;
  13.             err = ip_options_get_from_user(&opt, optval, optlen);
  14.             if (err)
  15.                 break;
  16.             if (inet->is_icsk) {
  17.                 struct inet_connection_sock *icsk = inet_csk(sk);
  18.                 ......
  19.                     if (inet->opt)
  20.                         icsk->icsk_ext_hdr_len -= inet->opt->optlen;
  21.                     if (opt)
  22.                         icsk->icsk_ext_hdr_len += opt->optlen;
  23.                     icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
  24.                 ......
  25.             }
  26.             opt = xchg(&inet->opt, opt);//将opt与inet->opt交换;现在inet->opt中存储了我们解
  27.                                           析过的选项的值,inet->opt->faddr为下一跳的地址
  28.             kfree(opt);
  29.             break;
  30.         }
  31.         ......
  32. }
  1. int ip_options_get_from_user(struct ip_options **optp, unsigned char __user *data, int optlen)
  2. {
  3.     struct ip_options *opt = ip_options_get_alloc(optlen);

  4.     if (!opt)
  5.         return -ENOMEM;
  6.     if (optlen && copy_from_user(opt->__data, data, optlen)) {// 应用层数据拷贝到opt->__da
  7.                                                                  ta指向的内存处
  8.         kfree(opt);
  9.         return -EFAULT;
  10.     }
  11.     return ip_options_get_finish(optp, opt, optlen);//解析opt信息并用optp返回
  12. }
  1. static int ip_options_get_finish(struct ip_options **optp,
  2.                  struct ip_options *opt, int optlen)
  3. {
  4.     while (optlen & 3)
  5.         opt->__data[optlen++] = IPOPT_END;//如IP选项内容不是以四字节对齐的,则将未对齐部分用选
  6.                                             项列表结束符填充,使之对齐
  7.     opt->optlen = optlen;
  8.     opt->is_data = 1;
  9.     opt->is_setbyuser = 1;
  10.     if (optlen && ip_options_compile(opt, NULL)) {//解析IP选项信息块各字段值
  11.         kfree(opt);
  12.         return -EINVAL;
  13.     }
  14.     kfree(*optp);
  15.     *optp = opt;
  16.     return 0;
  17. }
  1. int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)
  2. {
  3.         ......
  4.         switch (*optptr) {
  5.          case IPOPT_SSRR:
  6.          case IPOPT_LSRR:
  7.             if (optlen < 3) {//1字节type,1字节len,1字节offset
  8.                 pp_ptr = optptr + 1;
  9.                 goto error;
  10.             }
  11.             if (optptr[2] < 4) {//offset至少为4,指向第一个ip
  12.                 pp_ptr = optptr + 2;
  13.                 goto error;
  14.             }
  15.             /* NB: cf RFC-1812 5.2.4.1 */
  16.             if (opt->srr) {
  17.                 pp_ptr = optptr;
  18.                 goto error;
  19.             }
  20.             if (!skb) {
  21.                 if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {//至少能容下一个ip
  22.                     pp_ptr = optptr + 1;
  23.                     goto error;
  24.                 }
  25.                 memcpy(&opt->faddr, &optptr[3], 4);//取出第一个地址作为下一跳地址,
  26.                                                      opt->faddr为下一跳的地址
  27.                 if (optlen > 7)
  28.                     memmove(&optptr[3], &optptr[7], optlen-7);//在路径列表中多于一个地址
  29.                                                        时,将剩余的所有地址往前移动4个字节
  30.             }
  31.             opt->is_strictroute = (optptr[0] == IPOPT_SSRR);//记录是否为严格路由选项
  32.             opt->srr = optptr - iph;//记录源路由选项在IP首部的偏移量
  33.             break;
  34.             ......
  35. }
    options的offset项没有修改,它指向了源地址路由的下一个节点,因为此时第一个路由节点已经从选项中删除,其他路由节点向前移动了4个字节

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

  1. asmlinkage long sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
  2. {
  3.     struct socket *sock;
  4.     ......
  5.     err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
  6.                  sock->file->f_flags);
  7.     ......
  8. }
sock->ops指向注册的proto_ops钩子,此时为tcp_prot
  1. struct proto tcp_prot = {
  2.     .name            = "TCP",
  3.     ......
  4.     .connect        = tcp_v4_connect,
  5.     ......
  6. };
  1. int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
  2. {
  3.     struct inet_sock *inet = inet_sk(sk);
  4.     struct tcp_sock *tp = tcp_sk(sk);
  5.     struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
  6.     ......

  7.     nexthop = daddr = usin->sin_addr.s_addr;
  8.     if (inet->opt && inet->opt->srr) {//将临时变量下一跳地址和目的地址值都暂时设置为connect参
  9.                             数中的地址,如果使用源地址路由,则将下一跳地址设置为IP选项中的faddr
  10.         if (!daddr)
  11.             return -EINVAL;
  12.         nexthop = inet->opt->faddr;
  13.     }

  14.     tmp = ip_route_connect(&rt, nexthop, inet->saddr,//查找路由缓存项
  15.              RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
  16.              IPPROTO_TCP,
  17.              inet->sport, usin->sin_port, sk, 1);//
  18.     if (tmp < 0) {
  19.         if (tmp == -ENETUNREACH)
  20.             IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
  21.         return tmp;
  22.     }
  23.     ......
  24.     err = tcp_connect(sk);//发送syn包
  25.     rt = NULL;
  26.     if (err)
  27.         goto failure;
  28.     ......
  29. }
  1. int tcp_connect(struct sock *sk)
  2. {
  3.     ......
  4.     buff = alloc_skb_fclone(MAX_TCP_HEADER + 15, sk->sk_allocation);//分配skbuff
  5.     ......
  6.     tcp_transmit_skb(sk, buff, 1, GFP_KERNEL);//构造tcp头和ip头并发送
  7.     ......
  8. }
  1. static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask)
  2. {
  3.     ......
  4.     err = icsk->icsk_af_ops->queue_xmit(skb, 0);//构造ip头并发送,queue_xmit注册为
  5.                                                   ip_queue_xmit函数
  6.     ......
  7. }
  1. struct inet_connection_sock_af_ops ipv4_specific = {
  2.     .queue_xmit     = ip_queue_xmit,
  3.     ......
  4. };
  1. int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
  2. {
  3.     struct sock *sk = skb->sk;
  4.     struct inet_sock *inet = inet_sk(sk);
  5.     struct ip_options *opt = inet->opt;
  6.     struct rtable *rt;
  7.     struct iphdr *iph;
  8.     ......

  9.         /* Use correct destination address if we have options. */
  10.         daddr = inet->daddr;
  11.         if(opt && opt->srr)//如果使用源地址路由,下一跳为inet->opt->faddr,即选项中的第一个地址
  12.             daddr = opt->faddr;

  13.         {
  14.             struct flowi fl = { .oif = sk->sk_bound_dev_if,
  15.                      .nl_u = { .ip4_u =
  16.                          { .daddr = daddr,
  17.                             .saddr = inet->saddr,
  18.                             .tos = RT_CONN_FLAGS(sk) } },
  19.                      .proto = sk->sk_protocol,
  20.                      .uli_u = { .ports =
  21.                          { .sport = inet->sport,
  22.                              .dport = inet->dport } } };

  23.             /* If this fails, retransmit mechanism of transport layer will
  24.              * keep trying until route appears or the connection times
  25.              * itself out.
  26.              */
  27.             security_sk_classify_flow(sk, &fl);
  28.             if (ip_route_output_flow(&rt, &fl, sk, 0))
  29.                 goto no_route;
  30.         }
  31.         sk_setup_caps(sk, &rt->u.dst);
  32.     }
  33.     skb->dst = dst_clone(&rt->u.dst);

  34. packet_routed:
  35.     if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)//如果是严格路由选项,并且网关地址和路由的下一跳不一致则中断处理流程,这也是严格路由所要求的:下一跳必须是选项中的顺序的地址
  36.         goto no_route;
  37.     ......
  38.     iph->saddr = rt->rt_src;
  39.     iph->daddr = rt->rt_dst;//目的地址已经是路由的下一跳地址,即为选项中的第一个地址,即为
  40.                               inet->opt->faddr
  41.     skb->nh.iph = iph;
  42.     /* Transport layer set skb->h.foo itself. */

  43.     if (opt && opt->optlen) {
  44.         iph->ihl += opt->optlen >> 2;
  45.         ip_options_build(skb, opt, inet->daddr, rt, 0);//将目的地址到options中最后一个ip地
  46.                                                          址后面
  47.     }
  48.     ......
  49. }

发送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包)结果如下:

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

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

实验二(SSRR):

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

参考资料:

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

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

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

阅读(1844) | 评论(0) | 转发(0) |
0

上一篇:发送tcp/ip数据包

下一篇:socket学习

给主人留下些什么吧!~~