-------本文旨在全面了解busybox中的udhcp文件夹下的DHCP中继工作原理
1、 主文件:dhcprelay.c。dhcprelay功能的实现基本全靠这个文件实现,从解析命令到封装数据包发送数据包等等。
2、 辅助文件common.c与common.h。这两个文件定义了一些通用的函数,比如获得option选项、添加option、发送数据包等等。
Dhcprelay涉及的结构体不多,在主文件定义的只有下面这个,其他的数据包结构已经在服务器中提及过了。记录客户端信息的结构体:
/* This list holds information about clients. The xid_* functions manipulate this list. */
struct xid_item {
unsigned timestamp; //客户端的时间戳
int client; //客户端的编号
uint32_t xid; //客户端发送的discover包的xid字段,是一个随机值
struct sockaddr_in ip; //客户端的IP
struct xid_item *next; //下一个客户端的结构,是一个链表结构
} FIX_ALIASING;
我们将主函数分为一下几个步骤来执行:
int dhcprelay_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int dhcprelay_main(int argc, char **argv)
{
struct sockaddr_in server_addr;
。。。。。。
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
server_addr.sin_port = htons(SERVER_PORT);
/* dhcprelay CLIENT_IFACE1[,CLIENT_IFACE2...] SERVER_IFACE [SERVER_IP] */
if (argc == 4) {
if (!inet_aton(argv[3], &server_addr.sin_addr))
bb_perror_msg_and_die("bad server IP");
} else if (argc != 3) {
bb_show_usage();
}
iface_list = make_iface_list(argv + 1, &num_sockets);
。。。。。。。。。。。
}
首先给服务器地址的结构体初始化,sockaddr_in结构体的定义在linux内核中include/linux/in.h文件中。初始化了该结构体的三个成员变量(本来有四个的)。
然后进入第一个重要的函数make_iface_list;在进入这个之前我们需要先判断参数的个数,从涉及的初衷来说,参数的个数必须为4个或是3个,这边的服务器地址可以省略,所以我们才需要判断是不是有四个参数,如果是有4个参数也就是有指定了服务器的地址,就将刚才初始化的服务器广播地址替换为命令行参数指定的地址。
接下的make_iface_list函数是创建接口链表。
/*
*函数名: make_iface_list
*形参:char **client_and_server_ifaces:指向客户端以及服务器接口的字符串指针的指针
* int *client_number:调用函数需要知道客户端的数目,所以需要该形参返回确切的****** *值。
*/返回值:return iface_list;( char **iface_list;)
static char **make_iface_list(char **client_and_server_ifaces, int *client_number)
{
char *s, **iface_list;
int i, cn;
/* get number of items */
//先初始化cn,值为2,这是最小的值
cn = 2; /* 1 server iface + at least 1 client one */
//根据命令行参数我们假设这样:dhcprelay eth1.2,eth1.3,eth1,4 eth1.5
//那么*argv[1]=” eth1.2,eth1.3,eth1,4”即*client_and_server_ifaces[0] =” eth1.2,eth1.3,eth1.4”。
//所以我们利用中间的逗号就可以计算出有多少个客户端了!但是这边我感觉它的名字取得//不对,按照这样编码的话是连服务器接口都算进去的,所以client_number应改为//server_client_number才对!
s = client_and_server_ifaces[0]; /* list of client ifaces */
while (*s) {
if (*s == ',')
cn++;
s++;
}
//将获得的接口数目传给调用函数
*client_number = cn;
/* create vector of pointers */
//为接口列表创建空间
iface_list = xzalloc(cn * sizeof(iface_list[0]));
//列表的第一个元素填写的是服务器的接口名称,之后才是客户端接口
iface_list[0] = client_and_server_ifaces[1]; /* server iface */
i = 1;
//复制一份客户端的接口字符串,然后这里有一个比较巧的办法直接利用goto语句跳到while
//循环里,将客户端的第一个接口名称赋给刚才创建的列表
//不断循环知道i=cn时才退出,这样就将接口列表存储在iface_list中了。
s = xstrdup(client_and_server_ifaces[0]); /* list of client ifaces */
goto store_client_iface_name;
while (i < cn) {
if (*s++ == ',') {
s[-1] = '\0';
store_client_iface_name:
iface_list[i++] = s;
}
}
return iface_list;
}
//为每一个套接描述符创建相应的空间
fds = xmalloc(num_sockets * sizeof(fds[0]));
/* Create sockets and bind one to every iface */
//进入init_socket函数是为每一个接口创建套接字并绑定起来
max_socket = init_sockets(iface_list, num_sockets, fds);
//主要这个读取接口参数的函数udhcp_read_interface,该函数利用定义在linux内核中的
Include/linux/if.h的结构体struct ifreq来获得相应接口的ip、mac地址等参数,成功的话返回0!这里我们要获取的是服务器接口的IP地址。
/* Get our IP on server_iface */
if (udhcp_read_interface(argv[2], NULL, &our_nip, NULL))
return 1;
/* Main loop */
while (1) {
// reinit stuff from time to time? go back to make_iface_list
// every N minutes?
fd_set rfds;
struct timeval tv;
int i;
FD_ZERO(&rfds);
for (i = 0; i < num_sockets; i++)
FD_SET(fds[i], &rfds);
tv.tv_sec = SELECT_TIMEOUT;
tv.tv_usec = 0;
//利用select函数来监听各个接口的情况,看看是否有dhcp包的到达
if (select(max_socket + 1, &rfds, NULL, NULL, &tv) > 0) {
int packlen;
struct dhcp_packet dhcp_msg;
/* server */
//如果服务器接口有包到达的时候说明是从服务器端那边有reply包到达,//udhcp_recv_kernel_packet函数定义在packet.c函数中,该函数是从socket中读取数据,并//先做一些必要的检查比如包的magic和包的类型。然后将包利用pass_to_client函数传递给//客户端。具体过程看pass_to_client函数的解析。
if (FD_ISSET(fds[0], &rfds)) {
packlen = udhcp_recv_kernel_packet(&dhcp_msg, fds[0]);
if (packlen > 0) {
pass_to_client(&dhcp_msg, packlen, fds);
}
}
//如果不是从服务器接口获得的话那就是客户端接口了,循环监听所有的客户端接口
/* clients */
for (i = 1; i < num_sockets; i++) {
struct sockaddr_in client_addr;
socklen_t addr_size;
//如果这个接口没有数据到达直接continue,检查下一个接口。
if (!FD_ISSET(fds[i], &rfds))
continue;
//从套接字读取数据,用socket编程的常用函数recvfrom
addr_size = sizeof(client_addr);
packlen = recvfrom(fds[i], &dhcp_msg, sizeof(dhcp_msg), 0,
(struct sockaddr *)(&client_addr), &addr_size);
if (packlen <= 0)
continue;
/* Get our IP on corresponding client_iface */
if (udhcp_read_interface(iface_list[i], NULL, &dhcp_msg.gateway_nip, NULL)) {
/* Fall back to our IP on server iface */
// this makes more sense!
//这里当udhcp_read_interface返回失败时为什么要将服务器接口的地址赋给该字段呢?不是很懂!!!!!!!!!!!!!!
dhcp_msg.gateway_nip = our_nip;
}
// maybe dhcp_msg.hops++? drop packets with too many hops (RFC 1542 says 4 or 16)?
//这边作者也想到了跳数,可以自加1,但是作者为什么不写上去呢?可能不是很需要吧
pass_to_server(&dhcp_msg, packlen, i, fds, &client_addr, &server_addr);
}
}
xid_expire();
} /* while (1) */
/* return 0; - not reached */
}
/**
* pass_to_client() - forwards dhcp packets from server to client
* p - packet to send
*/
static void pass_to_client(struct dhcp_packet *p, int packet_len, int *fds)
{
int type;
struct xid_item *item;
//这时传递给客户端的函数,我们先要检查传给客户端的这个包是不是刚才从本地设备传递//过去的,因为每一个客户端发送的包都会记录下它的xid值的。如果不是的话直接丢包。
/* check xid */
item = xid_find(p->xid);
if (!item) {
return;
}
//然后再检查包的类型,看是不是却是时服务器的回应包
/* check packet type */
type = get_dhcp_packet_type(p);
if (type != DHCPOFFER && type != DHCPACK && type != DHCPNAK) {
return;
}
//TODO: also do it if (p->flags & htons(BROADCAST_FLAG)) is set!
//首先宏定义INADDR_ANY 定义在include/linux/in.h中
/* Address to accept any incoming messages. */
#defineINADDR_ANY((unsigned long int) 0x00000000)
//所以判断客户端之前的地址是不是全为0,若是的话则利用广播给客户端,因为此时客户端没有IP地址,但有时我们也可以用单播给客户端,这个没有做限制的!
if (item->ip.sin_addr.s_addr == htonl(INADDR_ANY))
item->ip.sin_addr.s_addr = htonl(INADDR_BROADCAST);
if (sendto_ip4(fds[item->client], p, packet_len, &item->ip) != 0) {
return; /* send error occurred */
}
//发送完这个消息时我们就可以删掉这个客户端的信息,因为消息都是从客户端发起服务器回应的,所以只要服务器回应一个包说明刚才客户端发送的已经得到相应了不再需要了。
/* remove xid entry */
xid_del(p->xid);
}
static void pass_to_server(struct dhcp_packet *p, int packet_len, int client, int *fds,
struct sockaddr_in *client_addr, struct sockaddr_in *server_addr)
{
int type;
//首先就检查包的类型。
/* check packet_type */
type = get_dhcp_packet_type(p);
if (type != DHCPDISCOVER && type != DHCPREQUEST
&& type != DHCPDECLINE && type != DHCPRELEASE
&& type != DHCPINFORM
) {
return;
}
//前面没问题的话就先创建该客户端的一个链表,往之前定义的xid_item结构体赋初值
item->ip = *ip; /*ip为数据来源的ip地址,在主函数中recvfrom接收数据包的过程中它会获取数据包的源地址保存在client_addr字段中并作为形参传进来,再赋给这边的ip变量*/
item->xid = xid; /*将数据包的xid字段提取出来赋给item->xid */
item->client = client; /*将第几个客户端接口的值赋给client,比如是从第一个接口接收到数据的,则client=1*/
item->timestamp = monotonic_sec();
item->next = dhcprelay_xid_list.next;
dhcprelay_xid_list.next = item;
/* create new xid entry */
xid_add(p->xid, client_addr, client);
/* forward request to server */
/* note that we send from fds[0] which is bound to SERVER_PORT (67).
* IOW: we send _from_ SERVER_PORT! Although this may look strange,
* RFC 1542 not only allows, but prescribes this for BOOTP relays.
*/
sendto_ip4(fds[0], p, packet_len, server_addr);
}
其他关于xid_item链表的添加删除操作都与一般的单链表一样,不赘述。
//该函数是判断已经存在的客户端是否已经超时,若超时则释放掉该客户端结构体的空间也就是删除掉!
static void xid_expire(void)
{
struct xid_item *item = dhcprelay_xid_list.next;
struct xid_item *last = &dhcprelay_xid_list;
unsigned current_time = monotonic_sec();
while (item != NULL) {
if ((current_time - item->timestamp) > MAX_LIFETIME) {
last->next = item->next;
free(item);
item = last->next;
} else {
last = item;
item = item->next;
}
}
}
Dhcprelay的实现在udhcp源码中是比较简单的一个功能,实现的不是很全面,很多细节它都没有处理到。像option82的添加啦,判断接口地址是不是本地所有的接口啦等等,不过这些都是可以在此基础上添加的。后来我把这个源码丰富了一下,添加了支持option82选项并在一些细节的处理上完善了一番!