中间有很长一段时间没有更新udhcp源码详解的博客,主要是源码里的函数太多,不知道要不要一个一个讲下去,要知道讲DHCP的实现理论的话一篇博文也就可以大致的讲完,但实现的源码却要关心很多的问题,比如说,理论上说从IP地址池取到一个空闲的IP,就这么一句,在源码的体现也是一大段。算啦,讲多少算多少吧,进入主题!

struct dhcpMessage报文里uint8_t options[308]字段,在整个DHCP过程中是报文的一个很重要的字段,博文的系列(二)有讲解该字段的数据组织方式,CLV(Code + Len + Value),现在来讲解下怎么把选项信息添加进该字段,以及怎么从该字段取到相应的选项信息。

options字段存储三类数据:

a).  DHCP_PADDING           填充字节, 没有任何意义,填充 0x00

b).  DHCP_END                   potions字段结束的标志              0xFF

c).  选项信息<CLV>          对于DHCP过程真正有价值的信息,承载了选项数据(V)

对于选options字段的操作主要就是read/write value:

1、根据选项信息的CODE从option字段取出选项信息

   1:  /* 
   2:   * 参数struct dhcpMessage *packet DHCP报文 
   3:   * int code需要获得什么选项信息(选项信息的标识)
   4:   *
   5:   * 返回指向选项信息的指针(去除了 OPT_CODE,OPT_LEN)
   6:   * 未找到返回NULL
   7:   */
   8:  uint8_t *get_option(struct dhcpMessage *packet, int code)
   9:  {
  10:      int i, length;
  11:      uint8_t *optionptr;
  12:      int over = 0, done = 0, curr = OPTION_FIELD;
  13:   
  14:      optionptr = packet->options;
  15:      i = 0;
  16:      length = 308;    /* 整个options字段的长度308 */
  17:   
  18:      /* 在options字段里查找code选项标识信息*/
  19:      while (!done) {
  20:          if (i >= length) {    /* 查找完所有字段都未找到code标识的信息,返回NULL */
  21:              LOG(LOG_WARNING, "bogus packet, option fields too long.");
  22:              return NULL;
  23:          }
  24:   
  25:          //CLV方式存储数据
  26:          //这里与struct option_set的data存储相似
  27:          //OPT_CODE字节上存储code标识
  28:          //OPT_LEN 字节上存储信息长度
  29:          //OPT_LEN后就是存储信息
  30:   
  31:          if (optionptr[i + OPT_CODE] == code) {    //Found
  32:              if (i + 1 + optionptr[i + OPT_LEN] >= length) {    //检查选项信息长度
  33:                  LOG(LOG_WARNING, "bogus packet, option fields too long.");
  34:                  return NULL;
  35:              }
  36:              return optionptr + i + 2;    //Found,返回选项信息的首地址
  37:          }
  38:          
  39:          switch (optionptr[i + OPT_CODE]) {
  40:          case DHCP_PADDING:    //DHCP_PADING(填充)字节,直接 i++;
  41:              i++;
  42:              break;
  43:          case DHCP_OPTION_OVER:    //选项过载DHCP_OPTION_OVER
  44:              if (i + 1 + optionptr[i + OPT_LEN] >= length) {
  45:                  LOG(LOG_WARNING, "bogus packet, option fields too long.");
  46:                  return NULL;
  47:              }
  48:   
  49:              /*
  50:                  optionptr[i + OPT_CODE] == DHCP_OPTION_OVER选项过载;
  51:                  optionptr[i + 3]存放了采用哪个字段来存储过载的选项
  52:                  可能存储过载选项的字段:
  53:                      uint8_t sname[64]; //server host name (ASCIZ) 
  54:                      uint8_t file[128];     // boot file name (ASCIZ) 
  55:  
  56:                  over = optionptr[i + 3];记录下使用那个字段存储过载选项
  57:              */
  58:   
  59:   
  60:              /*
  61:               *
  62:              The code for this option is 52, and its length is 1.  Legal values
  63:              for this option are:
  64:  
  65:                      Value    Meaning
  66:                      -----    --------
  67:                        1     the 'file' field is used to hold options
  68:                        2     the 'sname' field is used to hold options
  69:                        3     both fields are used to hold options
  70:  
  71:               Code    Len  Value
  72:              +-----+-----+-----+
  73:              |  52 |  1  |1/2/3|
  74:              +-----+-----+-----+
  75:              */
  76:   
  77:              over = optionptr[i + OPT_DATA];
  78:              i += optionptr[i + OPT_LEN] + 2;
  79:              
  80:          //    over = optionptr[i + 3];      /* Error */
  81:          //    i += optionptr[OPT_LEN] + 2;  /* Error */
  82:              break;
  83:          case DHCP_END:    //选项字段结束标志 DHCP_END 0xff
  84:   
  85:              /* 
  86:               * 当选项过载的时候(curr == OPTION_FILE允许选项过载)
  87:               *    首先用file字段,不够的话再用sname字段
  88:               *    使用file字段的时候:
  89:               *        over的右起的第0位必须为1
  90:               *    使用sname字段:
  91:               *        over的右起的第一位必须为1
  92:               */
  93:              if (curr == OPTION_FIELD && over & FILE_FIELD) {
  94:                  optionptr = packet->file;
  95:                  i = 0;
  96:                  length = 128;
  97:                  curr = FILE_FIELD;
  98:              } else if (curr == FILE_FIELD && over & SNAME_FIELD) {
  99:                  optionptr = packet->sname;
 100:                  i = 0;
 101:                  length = 64;
 102:                  curr = SNAME_FIELD;
 103:   
 104:                          //没有或不允许选项过载或over(options[i + 3])标志不允许,结束查找返回NULL
 105:              } else done = 1;
 106:              break;
 107:   
 108:   
 109:          /*
 110:           * 不是填充信息:DHCP_PADDING
 111:           * 选项过载:DHCP_OPTION_OVER
 112:           * 选项结束:DHCP_END
 113:           *
 114:           * 表明是属于选项信息,所以可以直接改变偏移量:
 115:           * i += option[OPT_LEN + i] + 2;
 116:           */
 117:          default:
 118:              i += optionptr[OPT_LEN + i] + 2;
 119:          }
 120:      }
 121:      return NULL;
 122:  }

在源码busybox 1.2的udhcp源码中,对于从options字段取出选项信息,在对选项过载的处理是存在错误的,

   1:  case DHCP_OPTION_OVER:    //选项过载DHCP_OPTION_OVER
   2:      if (i + 1 + optionptr[i + OPT_LEN] >= length) {
   3:          LOG(LOG_WARNING, "bogus packet, option fields too long.");
   4:          return NULL;
   5:      }
   6:          /*            
   7:       * Code    Len  Value
   8:       * +-----+-----+-----+
   9:       * |  52 |  1  |1/2/3|
  10:       * +-----+-----+-----+
  11:       */
  12:      over = optionptr[i + OPT_DATA];
  13:      i += optionptr[i + OPT_LEN] + 2;
  14:          
  15:  //    over = optionptr[i + 3];       /* 未改动源码 */
  16:  //    i += optionptr[OPT_LEN] + 2;   /* 未改动源码 */
  17:      break;

2、向options字段写入选项信息

a).  写入是添加在options字段中最后的选项后面,即DHCP_END标志之前

查找DHCP_END标志字段:

   1:  /* return the position of the 'end' option (no bounds checking) */
   2:  int end_option(uint8_t *optionptr)
   3:  {
   4:      int i = 0;
   5:   
   6:      /* 在选项字段里找到DHCP_END的偏移 */
   7:      /* 在选项字段里面里三类信息:
   8:          1.DHCP_PADDING 填充字节
   9:          2.DHCP_END    选项结束字节
  10:          3.选项信息<CLV> code + length + value
  11:          */
  12:      while (optionptr[i] != DHCP_END) {
  13:          if (optionptr[i] == DHCP_PADDING) i++;    //填充字节DHCP_PADDING
  14:          else i += optionptr[i + OPT_LEN] + 2;    //选项信息
  15:      }
  16:      return i;
  17:  }

b).   选项信息已经在一个字符串里以CLV方式组织好,直接copy到DHCP_END标志位置,DHCP_END向后移动:

   1:  /* add an option string to the options (an option string contains an option code,
   2:   * length, then data) */
   3:  int add_option_string(uint8_t *optionptr, uint8_t *string)
   4:  {
   5:      int end = end_option(optionptr);//找到DHCP_END在选项字段里偏移
   6:   
   7:      /* end position + string length + option code/length + end option */
   8:          //检查需要添加的选项信息后的长度是否大于选项字段的最大长度
   9:      if (end + string[OPT_LEN] + 2 + 1 >= 308) {
  10:          LOG(LOG_ERR,"Option 0x%02x did not fit into packet!",string[OPT_CODE]);
  11:          return 0;
  12:      }
  13:      DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]);
  14:      memcpy(optionptr + end, string, string[OPT_LEN] + 2);
  15:      optionptr[end + string[OPT_LEN] + 2] = DHCP_END;//在<CLV>的最后添加上DHCP_END
  16:      return string[OPT_LEN] + 2;    //返回<CLV>长度
  17:  }

c).  把选项信息按CLV的方式组织好存放到一个字符串里,最后调用add_option_string把在字符串内组织好的选项信息添加进options字段:

   1:  /* add a one to four byte option to a packet */
   2:  /* add_simple_option函数只能想选项字段添加OPT_LEN = 4 bytes的选项 */
   3:  /*     optionptr: 报文选项字段的首地址
   4:      code:    选项code
   5:      data:    选项value
   6:  
   7:      返回值是 <CLV>的长度
   8:      返回0 表示添加失败
   9:      */
  10:  int add_simple_option(uint8_t *optionptr, uint8_t code, uint32_t data)
  11:  {
  12:      struct dhcp_option *dh;
  13:   
  14:      /* 检查需要添加的选项是否符合标准的选项 */
  15:      /* 在dhcp_options数组里查找 */
  16:      for (dh=dhcp_options; dh->code; dh++) {
  17:          if (dh->code == code) {        //Found
  18:              uint8_t option[6], len;
  19:   
  20:              option[OPT_CODE] = code;    //添加code
  21:              len = option_lengths[dh->flags & TYPE_MASK];//计算length
  22:              option[OPT_LEN] = len;        //添加length
  23:   
  24:                          /* 
  25:                           * 假设data长度是一个字节,但在大端字节序的机器里
  26:                               * 但存放在uint32_t里的放在最高地址的地方,
  27:                               * 所以data << 8 * (4 - len) 把她移到低位
  28:                               */
  29:              if (BB_BIG_ENDIAN) data <<= 8 * (4 - len);
  30:   
  31:              /* This memcpy is for broken processors which can't
  32:               * handle a simple unaligned 32-bit assignment */
  33:              memcpy(&option[OPT_DATA], &data, 4);
  34:              return add_option_string(optionptr, option);
  35:          }
  36:      }
  37:   
  38:      DEBUG(LOG_ERR, "Could not add option 0x%02x", code);
  39:      return 0;
  40:  }

udhcp源码详解(五) 之DHCP包--options字段的更多相关文章

  1. udhcp源码详解(四) 之租赁IP的管理

    Server端对于租赁出去的IP的管理是基于结构体dhcpOfferedAddr的,该结构体的定义是在leases.c文件里:(结构体的成员介绍说明见详解之数据结构) 1: struct dhcpOf ...

  2. OkHttp3源码详解(五) okhttp连接池复用机制

    1.概述 提高网络性能优化,很重要的一点就是降低延迟和提升响应速度. 通常我们在浏览器中发起请求的时候header部分往往是这样的 keep-alive 就是浏览器和服务端之间保持长连接,这个连接是可 ...

  3. udhcp源码详解(三)上 之配置信息的读取

    上节介绍了存储管理配置信息的结构体struct server_config_t,该结构体贯穿整个server端程序的运行. 在dhcpd.c里的用该结构体定义个一个全局的变量: struct serv ...

  4. udhcp源码详解(二)--转

    定义的数据结构对于C程序的重要性,不言而喻.面向对象设计的程序是一个个对象的集合,而面向过程语言设计的程序则是数据结构与算法的集合. 下面来分析的是dhcp server中的定义结构体: 1).在pa ...

  5. udhcp源码详解(一)之文件组织结构(dhcp server) --转

    udhcp目录下有十几个源文件,一个源文件相对应一个模块,完成一系列相关的功能,例如在static_leases.c主要针对static_lease链表增删查找等操作. dhcpd.c——   整个d ...

  6. udhcp源码详解(三) 下 之配置信息的读取

    上节讲解了read_config函数,读取配置信息到server_config的相应成员变量里,但read_config函数只负责把配置信息重文件里读出来,具体怎么把信息填写到指定的地址内,是调用ke ...

  7. spring事务详解(三)源码详解

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  8. [转]Linux内核源码详解--iostat

    Linux内核源码详解——命令篇之iostat 转自:http://www.cnblogs.com/york-hust/p/4846497.html 本文主要分析了Linux的iostat命令的源码, ...

  9. Shiro 登录认证源码详解

    Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...

随机推荐

  1. 枚举(enum)的使用

    在开发中我们经常会遇到一些特殊的字段,比如订单状态.支付状态.类型等,这些特殊字段在编码开发的时候,可以写成枚举类型.接下来还是看Demo吧! public enum AuditState { Wai ...

  2. C语言之static用法

    1,static修饰全局变量 限定变量的作用域.被static修饰的全局变量存储域不变,依然存储在静态存储区,即bss段或data段.但作用域发生改变,被static修饰全局变量只能被本文件的函数访问 ...

  3. 在不是modelAttribute的情况下,如何保存页面输入值的方法(多行遍历)

    <c:forEach var="prdRelInfo" items="${goodRelPrdList}" varStatus="s" ...

  4. spring的IOC入门案例

    步骤: 一,导入jar 二,创建类,在类里创建方法 三,创建Spring配置文件,配置创建类 四,写代码测试对象创建

  5. div添加滚动条

  6. 谢孟军:The State of Go | ECUG Con 精粹系列

    本月 17 日,Go 1.8 版本火热发布.相较于以往的版本,Go 1.8 具体有哪些新的特性呢?想必这是不少 Gopher 们热切关注和讨论的问题.作为著名的Golang 布道者,Gopher Ch ...

  7. 洛谷P1504 积木城堡

    题目描述 XC的儿子小XC最喜欢玩的游戏用积木垒漂亮的城堡.城堡是用一些立方体的积木垒成的,城堡的每一层是一块积木.小XC是一个比他爸爸XC还聪明的孩子,他发现垒城堡的时候,如果下面的积木比上面的积木 ...

  8. java web 项目常用框架

    java框架实在是太多了,网上一搜索一大箩筐,根本就了解不到什么. 我还是以我的经验来说一下j2ee的框架. 1.首先力推struts2框架,这是最经典的框架(可以说没有“之一”).可以帮你快速搭建出 ...

  9. ie6 js报错汇总

    假如ie6是一个非美女的人,那不知道为多少人乱刀砍多少次,假如不幸它是一个美女,那不知道要被多少人轮奸完之后还是乱刀砍死. ie6的js的错误说明总是很隐晦的,它喜欢和捉迷藏.报的行数和错误,常常不知 ...

  10. hdu5608:function

    $n^2-3n+2=\sum_{d|i}f(i)$,问$f(i)$前$n$项和. 方法一:直接切入! $S(n)=\sum_{i=1}^{n}f(i)=\sum_{i=1}^{n}(i^2-3i+2- ...