分析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首部中的偏移量,即选项的第一个字节的地址减去IP首部的第一个字节的地址
  5.      unsigned char rr;//用于记录记录路径选项在IP首部中的偏移量
  6.      unsigned char ts;
  7.      unsigned char is_setbyuser:1,
  8.      is_data:1,
  9.      is_strictroute:1,
  10.      srr_is_hit:1,
  11.      is_changed:1,
  12.      rr_needaddr:1,
  13.      ts_needtime:1,
  14.      ts_needaddr:1;
  15.      unsigned char router_alert;
  16.      unsigned char cipso;
  17.      unsigned char __pad2;
  18.      unsigned char __data[0];//若选项有数据则从该字段开始,使之紧跟在ip_options结构后面,最多不超过40字节
  19. };

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)   break;
  15.       if (inet->is_icsk) {
  16.              struct inet_connection_sock *icsk = inet_csk(sk);
  17.              ......
  18.              if (inet->opt)
  19.                    icsk->icsk_ext_hdr_len -= inet->opt->optlen;
  20.              if (opt)
  21.                  icsk->icsk_ext_hdr_len += opt->optlen;
  22.              icsk->icsk_sync_mss(sk, icsk->icsk_pmtu_cookie);
  23.               ......
  24.      }
  25.      opt = xchg(&inet->opt, opt);//将opt与inet->opt交换;现在inet->opt中存储了我们解
  26.                                                   析过的选项的值,inet->opt->faddr为下一跳的地址
  27.       kfree(opt);
  28.      break;
  29. }
  30. ......
  31. }
  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)     return -ENOMEM;
  5.         if (optlen && copy_from_user(opt->__data, data, optlen)) {// 应用层数据拷贝到opt->__data指向的内存处
  6.                 kfree(opt);
  7.                 return -EFAULT;
  8.        }
  9.        return ip_options_get_finish(optp, opt, optlen);//解析opt信息并用optp返回
  10. }
  1. static int ip_options_get_finish(struct ip_options **optp,struct ip_options *opt, int optlen)
  2. {
  3.         while (optlen & 3)
  4.                 opt->__data[optlen++] = IPOPT_END;//如IP选项内容不是以四字节对齐的,则将未对齐部分用选项列表结束符填充,使之对齐
  5.         opt->optlen = optlen;
  6.         opt->is_data = 1;
  7.         opt->is_setbyuser = 1;
  8.         if (optlen && ip_options_compile(opt, NULL)) {//解析IP选项信息块各字段值
  9.               kfree(opt);
  10.               return -EINVAL;
  11.         }
  12.         kfree(*optp);
  13.        *optp = opt;
  14.        return 0;
  15. }
  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);//取出第一个地址作为下一跳地址,opt->faddr为下一跳的地址
  26.                     if (optlen > 7)
  27.                     memmove(&optptr[3], &optptr[7], optlen-7);//在路径列表中多于一个地址时,将剩余的所有地址往前移动4个字节
  28.              }
  29.              opt->is_strictroute = (optptr[0] == IPOPT_SSRR);//记录是否为严格路由选项
  30.              opt->srr = optptr - iph;//记录源路由选项在IP首部的偏移量
  31.              break;
  32.              ......
  33. }
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参数中的地址,如果使用源地址路由,则将下一跳地址设置为IP  选项中的faddr
  9.          if (!daddr)     return -EINVAL;
  10.          nexthop = inet->opt->faddr;
  11. }

  12. tmp = ip_route_connect(&rt, nexthop, inet->saddr,//查找路由缓存项
  13.                                       RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
  14.                                        IPPROTO_TCP,
  15.                                        inet->sport, usin->sin_port, sk, 1);//
  16. if (tmp < 0) {
  17.         if (tmp == -ENETUNREACH)
  18.         IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
  19.         return tmp;
  20. }
  21. ......
  22. err = tcp_connect(sk);//发送syn包
  23. rt = NULL;
  24. if (err)    goto failure;
  25. ......
  26. }
  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函数中分析过了

本文来自:http://blog.chinaunix.net/uid-23207633-id-289708.html

posted @ 2012-04-26 08:20  血马雄风  阅读(3751)  评论(0)    收藏  举报