udhcpd源码分析4--获取client报文及发包动作
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报文及发包动作的更多相关文章
- Eureka 源码分析之 Eureka Client
文章首发于微信公众号<程序员果果> 地址:https://mp.weixin.qq.com/s/47TUd96NMz67_PCDyvyInQ 简介 Eureka是一种基于REST(Repr ...
- UiAutomator源码分析之获取控件信息
根据上一篇文章<UiAutomator源码分析之注入事件>开始时提到的计划,这一篇文章我们要分析的是第二点: 如何获取控件信息 我们在测试脚本中初始化一个UiObject的时候通常是像以下 ...
- lodash源码分析之获取数据类型
所有的悲伤,总会留下一丝欢乐的线索,所有的遗憾,总会留下一处完美的角落,我在冰峰的深海,寻找希望的缺口,却在惊醒时,瞥见绝美的阳光! --几米 本文为读 lodash 源码的第十八篇,后续文章会更新到 ...
- springMVC源码分析--AbstractHandlerMethodMapping获取url和HandlerMethod对应关系(十)
在之前的博客springMVC源码分析--AbstractHandlerMapping(二)中我们介绍了AbstractHandlerMethodMapping的父类AbstractHandlerMa ...
- udhcpd源码分析2--读取配置文件
1:重要的结构体 读取配置文件信息到全局的结构体struct server_config_t server_config中,这个结构在很多文件中都有引用到很重要. /* dhcpd.h */ stru ...
- HDFS源码分析四-HDFS Client
4. HDFS Client ( 未完待续 ) 目录: 4.1 认识 DFSClient ( 未完待续 ) 4.2 输入流 ( 未完待续 ) 4.3 输出流 ( 未完待续 ) 4.4 Distribu ...
- udhcpd源码分析3--IP租赁管理
1:重要的结构体 全局链表的成员struct dhcpOfferedAddr *leases 记录了当前租赁出去的IP信息 /* leases.h */ struct dhcpOfferedAddr ...
- go源码分析(五) 获取函数名和调用者的函数名
参考资料 实现代码保存在我的github // input flag 1:FunName 2:CallerFunName func GetFuncName(flag int) string { ...
- Shiro源码分析之SecurityManager对象获取
目录 SecurityManager获取过程 1.SecurityManager接口介绍 2.SecurityManager实例化时序图 3.源码分析 4.总结 @ 上篇文章Shiro源码分析之获 ...
随机推荐
- beego 笔记
1.开发文档 https://beego.me/docs/intro/ 2.bee run projectname demo controller package autoscaler import ...
- 论文笔记:Attentional Correlation Filter Network for Adaptive Visual Tracking
Attentional Correlation Filter Network for Adaptive Visual Tracking CVPR2017 摘要:本文提出一种新的带有注意机制的跟踪框架, ...
- java鼠标操控小程序
最近在做一个软工的屏幕监控软件,已经实现了屏幕图片的传输,但是没有鼠标,才发现键盘上的PtrScSysRq键所截到图是没有鼠标信息的.== 暂时只需实现鼠标的移动事件,用robot.mouseMove ...
- C++ 学习笔记之 STL 队列
一. 引言 在算法以及数据结构的实现中,很多地方我们都需要队列(遵循FIFO,先进先出原则). 为了使用队列,我们可以自己用数组来实现队列,但自己写太麻烦不说,并且还很容易出错. 好在C++的STL ...
- .mat转成.npy文件+Python(Pytorch)压缩裁剪图片
需求:现有数据文件V1.mat,里面包含多个数据集,现需将里面的images数据集提取出来,然后进行压缩裁剪成指定大小 V1.mat数据集目录: 1.从mat文件中提取数据(使用Python) V1. ...
- 往Matlab中添加工具包
使用Matlab过程中,常常会缺少一些函数包导致无法运行,会显示未定义函数. 假如我要用sigshift( ) 这个移位函数,但Matlab中没有,就会提示错误:未定义函数或变量 'sigshift' ...
- 第一、二章——Python简介与Python基础
前言:<Data Wrangling with Python>这本书主要是讲使用Pyhon来处理各种类型保存的数据的. 第一章:Python简介 1.版本选择 本书选择的Python版本是 ...
- TCP系列18—重传—8、FACK及SACK reneging下的重传
一.介绍 FACK的全称是forward acknowledgement,FACK通过记录SACK块中系列号最大(forward-most)的SACK块来推测丢包信息,在linux中使用fackets ...
- bootstrap控件点击之后没有反应的原因
引用的jquery.js文件要放到bootstrap.js的前面 jquery.js文件版本太低. 这些问题可以通过firebug或者谷歌调试器发现. 问题很简单,简单记录下,以免以后遗忘.
- phpcms v9 thumb(缩略图) 函数说明
打开phcmsc/libs/functions/global.func.php文件,找到如下代码:/** * 生成缩略图函数 * @param $imgurl 图片路径 * @param $wid ...