1:重要的结构体

  获取的报文是UDP的payload部分,结构体struct dhcpMessage描述了dhcp报文的结构。

/* packet.h */

struct dhcpMessage {
u_int8_t op; /* 1 for client,2 for server */
u_int8_t htype; /* Ethernet Type (0x01)*/
u_int8_t hlen; /* Ethernet Len(6) */
u_int8_t hops; /* 若封包需要router传输,每经过一条加1,同一网段下为0 */
u_int32_t xid; /* transaction ID 客户端产生的事务ID用来标识一次DHCP C/S交互,dhcpc一旦运行这个值就是固定了表示客户端自己*/
u_int16_t secs; /* 客户端启动耗时(一般为0) */
u_int16_t flags; /* 0-15 bit 最低bit为1则server将以广播形式发包给client,其它未使用 */
u_int32_t ciaddr; /* 若client想继续使用之前获得的IP则填充在这(一般是client 的Inform包会填写) */
u_int32_t yiaddr; /* server回复client你可使用的IP(ACK,offer报文中填写) */
u_int32_t siaddr; /* 若client需要通过网络开机,从server发出的报文这里应该填写开机程序代码
所在的server地址 */
u_int32_t giaddr; /* 若需要跨网域进行DHCP发包,这里填写server发包的目的地址
(如果没有server一般是发给租赁出去的IP地址) */
u_int8_t chaddr[]; /* client的硬件地址 */
u_int8_t sname[]; /* server 的主机名 */
u_int8_t file[]; /* 若client需要通过网络开机,这里将填写开机程序名称,让后以TFTP传输 */
u_int32_t cookie; /* should be 0x63825363 */
u_int8_t options[]; /* 312 - cookie */
};

2:udhcpd收发包主干逻辑

  2.1 获得套接字接口函数listen_socket

/* socket.c */

int listen_socket(unsigned int ip, int port, char *inf)
{
struct ifreq interface;
int fd;
struct sockaddr_in addr;
int n = ; DEBUG(LOG_INFO, "Opening listen socket on 0x%08x:%d %s\n", ip, port, inf);
/*
此套接字是IPPROTO_UDP类型,所以收到的包的内容就是UDP报文的payload数据
*/
if ((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < ) {//PF --> protocol family
DEBUG(LOG_ERR, "socket call failed: %s", strerror(errno));
return -;
} memset(&addr, , sizeof(addr));
addr.sin_family = AF_INET;//AF --> Address family
addr.sin_port = htons(port);
addr.sin_addr.s_addr = ip; /*
地址重用,服务器程序停止后想立即重启,而新套接字可以马上使用同一端口(一般一个端口释放后两分钟
之后才可以被使用)
*/
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &n, sizeof(n)) == -) {
close(fd);
return -;
} /*
允许此socket发送广播包,我的想法是,只要目的地址设成全255,这样默认就发送广播报了,这个选项作用
体现在哪里呢?这是为了防止你误发广播包,虽然你的目的IP是255.255.255.255,但你没有设置这个选项
发包时会返回EACCESS错误提醒
*/
if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, (char *) &n, sizeof(n)) == -) {
close(fd);
return -;
} /*
将套接字绑定到特定的interface,此socket只接收到此interface的报文,socket发送的
报文也只从此interface出去
*/
strncpy(interface.ifr_ifrn.ifrn_name, inf, IFNAMSIZ);
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,(char *)&interface, sizeof(interface)) < ) {
close(fd);
return -;
} /*
绑定地址结构(ip and port)到socket
*/
if (bind(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == -) {
close(fd);
return -;
} return fd;
}

  函数listen_socket返回一个UDP套接字接口,此套接字是作为dhcp服务器端套接字,绑定的端口和interface分别是SERVER_PORT(67)和server_config.interface,所以此套接字只监听来自server_config.interface接口且端口是67的报文。

  2.2 获取报文函数get_packet

/* packet.c */

/* read a packet from socket fd, return -1 on read error, -2 on packet error */
int get_packet(struct dhcpMessage *packet, int fd)
{
int bytes;
int i;
const char broken_vendors[][] = {
"MSFT 98",
""
};
char unsigned *vendor; memset(packet, , sizeof(struct dhcpMessage));
bytes = read(fd, packet, sizeof(struct dhcpMessage));
if (bytes < ) {
DEBUG(LOG_INFO, "couldn't read on listening socket, ignoring");
return -;
} /* packet->cookie(Default:0x63825363)字段丢掉假冒的DHCP client报文 */
if (ntohl(packet->cookie) != DHCP_MAGIC) {
LOG(LOG_ERR, "received bogus message, ignoring");
return -;
}
DEBUG(LOG_INFO, "Received a packet"); if (packet->op == BOOTREQUEST && (vendor = get_option(packet, DHCP_VENDOR))) {
for (i = ; broken_vendors[i][]; i++) {
if (vendor[OPT_LEN - ] == (unsigned char) strlen(broken_vendors[i]) &&
!strncmp(vendor, broken_vendors[i], vendor[OPT_LEN - ])) {
DEBUG(LOG_INFO, "broken client (%s), forcing broadcast",
broken_vendors[i]);
packet->flags |= htons(BROADCAST_FLAG);
}
}
} return bytes;
}

  报文获取到之后是保存在struct dhcpMessage结构体中,结构体中的options成员是一个大数组,里面保存了许多可用的信息,这些信息都是以CLV的格式保存在一段连续的内存中的,服务器后续的动作需要依赖options中的某些值,如何有效的查询这段内存的某些值是很重要的,所以options.c文件里的部分函数就是专门来处理options成员数据的。

  2.3 处理options成员的相关函数

/* options.c */

获取options成员函数get_option:

/*
get_option根据选项值(code)获得指向此选项内容的指针
options字段在dhcp报文中是可选并且大小不定,这里定义的大小是308字节.所有的options都定义在这个308字节的
数组里,如何组织各选项的结构很重要,dhcp报文的一般options字段里的内容依照CLV(code + length + value)的
格式组织,特殊的如code=DHCP_PADDING<填充字节读到此code直接跳过>,DHCP_OPTION_OVER及DHCP_END<options结
束标志>有各自不同的组织方式.
options[308] 内容大概结构: byte byte length*byte byte byte byte length*byte
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| | | | | | |
|code1 | length | value | DHCP_PADDING |code2 | length | value
| | | | | | |
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/ /* get an option with bounds checking (warning, not aligned). */
unsigned char *get_option(struct dhcpMessage *packet, int code)
{
int i, length;
unsigned char *optionptr;
int over = , done = , curr = OPTION_FIELD; optionptr = packet->options;
i = ;
length = ;
while (!done) {
if (i >= length) {
LOG(LOG_WARNING, "bogus packet, option fields too long.");
return NULL;
}
/* 检查code值是否匹配 */
if (optionptr[i + OPT_CODE] == code) {
if (i + + optionptr[i + OPT_LEN] >= length) {
LOG(LOG_WARNING, "bogus packet, option fields too long.");
return NULL;
}
return optionptr + i + ;
} /* 处理选项中特殊字段,DHCP_PADDING(跳过), DHCP_END(结束),DHCP_OPTION_OVER(自定义)*/
switch (optionptr[i + OPT_CODE]) {
case DHCP_PADDING:
i++;
break;
case DHCP_OPTION_OVER:
if (i + + optionptr[i + OPT_LEN] >= length) {
LOG(LOG_WARNING, "bogus packet, option fields too long.");
return NULL;
}
over = optionptr[i + ];
i += optionptr[OPT_LEN] + ;
break;
case DHCP_END:
if (curr == OPTION_FIELD && over & FILE_FIELD) {
optionptr = packet->file;
i = ;
length = ;
curr = FILE_FIELD;
} else if (curr == FILE_FIELD && over & SNAME_FIELD) {
optionptr = packet->sname;
i = ;
length = ;
curr = SNAME_FIELD;
} else done = ;
break;
default:
i += optionptr[OPT_LEN + i] + ;//指针指向下一个选项值的code字段
}
}
return NULL;
}

获取options中end的位置end_option:

/* return the position of the 'end' option (no bounds checking) */
/* 返回从optionptr到'end'之间的步长 optionptr必须是packet->options(就是指向options数组的头部) */
int end_option(unsigned char *optionptr)
{
int i = ; while (optionptr[i] != DHCP_END) {
if (optionptr[i] == DHCP_PADDING) i++;
else i += optionptr[i + OPT_LEN] + ;
}
return i;
}

添加一个选项内容string(string已经组织为CLV的结构)到options数组中add_option_string 

/* add an option string to the options (an option string contains an option code,
* length, then data) */
/* 添加一个选项到optionptr指向的options数组中,optionptr必须指向此数组的头部!*/
int add_option_string(unsigned char *optionptr, unsigned char *string)
{
int end = end_option(optionptr); /* end position + string length + option code/length + end option */
if (end + string[OPT_LEN] + + >= ) {
LOG(LOG_ERR, "Option 0x%02x did not fit into the packet!", string[OPT_CODE]);
return ;
}
DEBUG(LOG_INFO, "adding option 0x%02x", string[OPT_CODE]);
memcpy(optionptr + end, string, string[OPT_LEN] + );
optionptr[end + string[OPT_LEN] + ] = DHCP_END;//补充END选项结尾
return string[OPT_LEN] + ;//返回所添加选项的整体长度
}

 将一个4字节的数据作为选项添加到options数组中 add_simple_option

/* add a one to four byte option to a packet */
/*
将一个4字节的数据和code值组织为CLV格式存储起来
得到的CLV结构数据交给add_option_string函数添加到options数组中
*/ int add_simple_option(unsigned char *optionptr, unsigned char code, u_int32_t data)
{
char length = ;
int i;
unsigned char option[ + ];
unsigned char *u8;
u_int16_t *u16;
u_int32_t *u32;
u_int32_t aligned;
u8 = (unsigned char *) &aligned;
u16 = (u_int16_t *) &aligned;
u32 = &aligned; for (i = ; options[i].code; i++)
if (options[i].code == code) {
length = option_lengths[options[i].flags & TYPE_MASK];
} if (!length) {
DEBUG(LOG_ERR, "Could not add option 0x%02x", code);
return ;
} option[OPT_CODE] = code;
option[OPT_LEN] = length; switch (length) {
case : *u8 = data; break;
case : *u16 = data; break;
case : *u32 = data; break;
}
memcpy(option + , &aligned, length);
return add_option_string(optionptr, option);
}

注意:在options.c文件中还有两个函数分别是find_option和attach_option,这两个函数和上面的函数用处不一样,上面的这些函数是用于操作struct dhcpMessage报文结构中options[308]这个数组的,而这两个函数是在读取配置文件时操作struct server_config_t结构体中struct option_set *options成员的,这个成员将会保存配置文件中设置的opt选项(根据code值的升序链表)

  到这里,获取到dhcp报文和如何维护dhcp报文中的数据已经记录完了,下面就是根据获取到的报文决定dhcpd改如何动作,这部分'动作'是一定要按照dhcp协议规范来实现的。

 2.4 遵循协议规范的报文交互动作

  dhcp有几种报文类型,在客户端与服务器交互中这几种报文按照协议规定交互,可参考获得更多细节:

  参考网站:http://blog.csdn.net/u013485792/article/details/50731538

  下图是一个典型的客户端请求IP地址的报文交互,1234这4个报文是服务器的动作:

   结合参考网站可以很清晰的理解dhcp协议的运作流程。

  因为dhcpd是被动的,它等待客户端的连接,下图是服务器端收到报文之后动作的流程图:

  其实画完这个图我就后悔了,原因是我觉得把这个流程复杂化了,服务器收包后的处理过程看源代码应该更容易理解.

总结:

  服务器使用struct dhcpMessage结构体来接收收到的报文数据,get_packet函数是处理的开始,收到的报文根据报文的htype成员决定回复的动作是什么。options.c中的部分函数就是定义来方便访问struct dhcpMessage结构中options[308]成员。  

udhcpd源码分析4--获取client报文及发包动作的更多相关文章

  1. Eureka 源码分析之 Eureka Client

    文章首发于微信公众号<程序员果果> 地址:https://mp.weixin.qq.com/s/47TUd96NMz67_PCDyvyInQ 简介 Eureka是一种基于REST(Repr ...

  2. UiAutomator源码分析之获取控件信息

    根据上一篇文章<UiAutomator源码分析之注入事件>开始时提到的计划,这一篇文章我们要分析的是第二点: 如何获取控件信息 我们在测试脚本中初始化一个UiObject的时候通常是像以下 ...

  3. lodash源码分析之获取数据类型

    所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...

  4. springMVC源码分析--AbstractHandlerMethodMapping获取url和HandlerMethod对应关系(十)

    在之前的博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMethodMapping的父类AbstractHandlerMa ...

  5. udhcpd源码分析2--读取配置文件

    1:重要的结构体 读取配置文件信息到全局的结构体struct server_config_t server_config中,这个结构在很多文件中都有引用到很重要. /* dhcpd.h */ stru ...

  6. HDFS源码分析四-HDFS Client

    4. HDFS Client ( 未完待续 ) 目录: 4.1 认识 DFSClient ( 未完待续 ) 4.2 输入流 ( 未完待续 ) 4.3 输出流 ( 未完待续 ) 4.4 Distribu ...

  7. udhcpd源码分析3--IP租赁管理

    1:重要的结构体 全局链表的成员struct dhcpOfferedAddr *leases 记录了当前租赁出去的IP信息 /* leases.h */ struct dhcpOfferedAddr ...

  8. go源码分析(五) 获取函数名和调用者的函数名

    参考资料 实现代码保存在我的github // input flag 1:FunName 2:CallerFunName func GetFuncName(flag int) string {     ...

  9. Shiro源码分析之SecurityManager对象获取

    目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @   上篇文章Shiro源码分析之获 ...

随机推荐

  1. LeetCode 700——二叉搜索树中的搜索

    1. 题目 2. 解答 如果根节点为空,直接返回 NULL.如果根节点非空,从根节点开始循环查找,直到节点为空. 如果待查找的值大于当前节点值,节点指向右孩子: 如果待查找的值小于当前节点值,节点指向 ...

  2. CodeForces 908C. New Year and Curling 解题报告 Java

    1. 思路 这题实际上是个几何问题——两个外相切的圆,由勾股定理,他们的纵坐标有以下的规律: 则有$$y_{n+1} = y_{n} + \sqrt{(2r)^2 - (x_{n} - x_{n+1} ...

  3. Mishka and Contest(模拟水题)

    Mishka started participating in a programming contest. There are nn problems in the contest. Mishka' ...

  4. Internet History

    Alan Turing and Bletchley Park Top secret breaking effort(二战破译希特勒密码) 10,000 people at the peak(team ...

  5. mysql入门 — (1)

    使用cd进入到mysql/bin文件夹下面,或者配置完环境之后,直接在cmd中使用mysql,然后回车开启mysql. 登录 为了安全考虑,在这里只设置了本地root用户可以连接上数据库.使用的指令是 ...

  6. lintcode-156-合并区间

    156-合并区间 给出若干闭合区间,合并所有重叠的部分. 样例 给出的区间列表 => 合并后的区间列表: [ [ [1, 3], [1, 6], [2, 6], => [8, 10], [ ...

  7. 转 【.NET平台下使用MongoDB入门教程】

    目录 一.了解MongoDB 二.MongoDB特点 三.安装及常用命令 3.1 下载安装 3.2 启动服务器 3.3 常用操作 3.4 其他命令 3.5 做成windows服务 四.批处理程序开启M ...

  8. 父类与子类的转换as,is

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  9. OSI参考模型和TCP/IP参考模型

  10. BZOJ 2241 打地鼠(特技暴力)

    果然暴力出奇迹.. O(n^2m^2)=1e8 536ms能过. 枚举锤子的长和宽,再验证是否可以满足条件并更新答案. 我们先从左上角为(1,1)的先锤,显然锤的次数是a[1][1]. 锤(i,j)的 ...