加载中...
busybox编译以及添加menuconfig配置选项

一、busybox介绍

Busybox是一个开源项目,遵循GPL v2协议。Busybox将众多的UNIX命令集合进了一个很小的可执行程序中,可以用来替代GNU fileutils、shellutils等工具集。Busybox中各种命令与相应的GNU工具相比,所能提供的选项比较少,但是对于一般的应用场景也足够了。Busybox主要用于嵌入式系统的开发中。

二、busybox目录结构

序号 目录名称 功能说明
1 applets 实现applets框架的文件。目录中包含了几个main()的文件
2 applets_sh 此目录包含了几个作为shell脚本实现的applet示例。在“make install”时不会被自动安装,需要使用时,手动处理
3 arch 包含用于不同体系架构的makefile文件。约束busybox在不同架构体系下的编译构建过程
4 archival 与压缩相关命令的实现源文件
5 configs busybox自带的默认配置文件
6 console-tools 与控制台相关的一些命令
7 coreutils 常用的一些核心命令。例如chgrp、rm等
8 debianutils 针对Debian的套件。
9 e2fsprogs 针对Linux Ext2 FS prog的命令。例如chattr、lsattr
10 editors 常用的编辑命令。例如diff、vi等
11 findutils 用于查找的命令
12 include busybox项目的头文件
13 init init进程的实现源码目录
14 klibc-utils klibc命令套件
15 libbb 与busybox实现相关的库文件
16 libpwdgrp libpwdgrp相关的命令
17 loginutils 与用户管理相关的命令
18 mailutils 与mail相关的命令套件
19 miscutils 该文件下是一些杂项命令,针对特定应用场景
20 modutils 与模块相关的命令
21 networking 与网络相关的命令,例如arp
22 printutils Print相关的命令
23 procps 与内存、进程相关的命令
24 runit 与Runit实现相关的命令
25 shell 与shell相关的命令
26 sysklogd 系统日志记录工具相关的命令
27 util-linux Linux下常用的命令,主要与文件系统操作相关的命令

三、编译busybox (BusyBox-1.33.1)

1、make menuconfig
make menuconfig

make menuconfig界面

配置install路径,首先选择Settings—>
Settings--->
然后选择Installation Options中的Destination path for ‘make install’选项配置install目录。
Installation Options
我们将其设置为romfs
set ./romfs
保存config后可以再目录中看到一个.config文件

3、编译和建立romfs目录
#建立romfs目录
mkdir romfs
cd rootfs
#建立一些常用目录
mkdir root home bin sbin etc dev usr lib tmp mnt sys proc  
mkdir usr/lib usr/bin

#配置交叉编译器,修改Makefile文件
ARCH ?= arm
CROSS_COMPILE ?= #指定交叉编译器路径

#编译
#make 后就会产生busybox
make
#make install后就产生busybox及每个命令的软链接。
make install

cd etc
#拷贝examples/bootfloppy/etc文件到etc
cp -rf ../../examples/bootfloppy/etc/* ./
#passwd、shadow、group等文件也需要准备放到etc下

拷贝examples/inittab 到 etc下,做一些必要的修改

# /etc/inittab init(8) configuration for BusyBox
#
# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
#
#
# Note, BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use sysvinit.
#
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# <id>: WARNING: This field has a non-traditional meaning for BusyBox init!
#
#	The id field is used by BusyBox init to specify the controlling tty for
#	the specified process to run on.  The contents of this field are
#	appended to "/dev/" and used as-is.  There is no need for this field to
#	be unique, although if it isn't you may have strange results.  If this
#	field is left blank, then the init's stdin/out will be used.
#
# <runlevels>: The runlevels field is completely ignored.
#
# <action>: Valid actions include: sysinit, wait, once, respawn, askfirst,
#                                  shutdown, restart and ctrlaltdel.
#
#	sysinit actions are started first, and init waits for them to complete.
#	wait actions are started next, and init waits for them to complete.
#	once actions are started next (and not waited for).
#
#	askfirst and respawn are started next.
#	For askfirst, before running the specified process, init displays
#	the line "Please press Enter to activate this console"
#	and then waits for the user to press enter before starting it.
#
#	shutdown actions are run on halt/reboot/poweroff, or on SIGQUIT.
#	Then the machine is halted/rebooted/powered off, or for SIGQUIT,
#	restart action is exec'ed (init process is replaced by that process).
#	If no restart action specified, SIGQUIT has no effect.
#
#	ctrlaltdel actions are run when SIGINT is received
#	(this might be initiated by Ctrl-Alt-Del key combination).
#	After they complete, normal processing of askfirst / respawn resumes.
#
#	Note: unrecognized actions (like initdefault) will cause init to emit
#	an error message, and then go along with its business.
#
# <process>: Specifies the process to be executed and it's command line.
#
# Note: BusyBox init works just fine without an inittab. If no inittab is
# found, it has the following default behavior:
#	::sysinit:/etc/init.d/rcS
#	::askfirst:/bin/sh
#	::ctrlaltdel:/sbin/reboot
#	::shutdown:/sbin/swapoff -a
#	::shutdown:/bin/umount -a -r
#	::restart:/sbin/init
#	tty2::askfirst:/bin/sh
#	tty3::askfirst:/bin/sh
#	tty4::askfirst:/bin/sh
#
# Boot-time system configuration/initialization script.
# This is run first except when booting in single-user mode.
#
::sysinit:/etc/init.d/rcS

# /bin/sh invocations on selected ttys
#
# Note below that we prefix the shell commands with a "-" to indicate to the
# shell that it is supposed to be a login shell.  Normally this is handled by
# login, but since we are bypassing login in this case, BusyBox lets you do
# this yourself...
#
# Start an "askfirst" shell on the console (whatever that may be)
#askfirst类似respawn,主要用途是减少系统上执行的终端应用程序的数量。它将会促使init在控制台上显示“Please press Enter to active this console”的信息,并在重新启动进程之前等待用户按下“enter”键
#::askfirst:-/bin/sh
# Start an "askfirst" shell on /dev/tty2-4
#
#tty2::askfirst:-/bin/sh
#tty3::askfirst:-/bin/sh
#tty4::askfirst:-/bin/sh

# /sbin/getty invocations for selected ttys
#屏蔽
#tty4::respawn:/sbin/getty 38400 tty5
#tty5::respawn:/sbin/getty 38400 tty6

# Example of how to put a getty on a serial line (for a terminal)
::respawn:/sbin/getty -L ttyS000 115200 vt100 -n root -I "Auto login as root ..."
#::respawn:/sbin/getty -L ttyS0 9600 vt100
#::respawn:/sbin/getty -L ttyS1 9600 vt100
#
# Example how to put a getty on a modem line.
#::respawn:/sbin/getty 57600 ttyS2

# Stuff to do when restarting the init process
::restart:/sbin/init

# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

拷贝交叉编译环境下的lib库到romfs/lib目录下

四、busybox的menuconfig添加自定义编译选项

以telnetd端口和udhcpd mac限制和hostname限制为例

1、Config.src 和 Config.in

Config.src文件中保存着make menuconfig配置界面上的配置
Config.in是通过Config.src和.c源码中的一些config配置结合产生的。
下面以networking/Config.src为例大致说明配置的含义

#
# For a description of the syntax of this configuration file,
# see docs/Kconfig-language.txt.
#

#菜单选项
menu "Networking Utilities" 

config FEATURE_IPV6
	bool "Enable IPv6 support"
	default y
	help
	Enable IPv6 support in busybox.
	This adds IPv6 support in the networking applets.

config FEATURE_UNIX_LOCAL
	bool "Enable Unix domain socket support (usually not needed)"
	default n
	help
	Enable Unix domain socket support in all busybox networking
	applets.  Address of the form local:/path/to/unix/socket
	will be recognized.

	This extension is almost never used in real world usage.
	You most likely want to say N.

config FEATURE_PREFER_IPV4_ADDRESS
	bool "Prefer IPv4 addresses from DNS queries"
	default y
	depends on FEATURE_IPV6
	help
	Use IPv4 address of network host if it has one.

	If this option is off, the first returned address will be used.
	This may cause problems when your DNS server is IPv6-capable and
	is returning IPv6 host addresses too. If IPv6 address
	precedes IPv4 one in DNS reply, busybox network applets
	(e.g. wget) will use IPv6 address. On an IPv6-incapable host
	or network applets will fail to connect to the host
	using IPv6 address.

config VERBOSE_RESOLUTION_ERRORS
	bool "Verbose resolution errors"
	default n
	help
	Enable if you are not satisfied with simplistic
	"can't resolve 'hostname.com'" and want to know more.
	This may increase size of your executable a bit.

config FEATURE_TLS_SHA1 #config配置名
	bool "In TLS code, support ciphers which use deprecated SHA1" #数据类型是bool型所以参数是 y和n
	depends on TLS  #依赖TLS开启该配置才可以生效配置。
	default n       #默认配置为n,不开启
	help   #help介绍该配置
	Selecting this option increases interoperability with very old
	servers, but slightly increases code size.

	Most TLS servers support SHA256 today (2018), since SHA1 is
	considered possibly insecure (although not yet definitely broken).
#INSERT
#source networking/udhcp/Config.in
#是将里面一级的目录中的配置插入进本文件,所有配置都是如此一层一层目录中插入过来。也能将c源文件中注释语句中的config插入
INSERT

source networking/udhcp/Config.in

config IFUPDOWN_UDHCPC_CMD_OPTIONS
	string "ifup udhcpc command line options"
	default "-R -n"
	depends on IFUP || IFDOWN
	help
	Command line options to pass to udhcpc from ifup.
	Intended to alter options not available in /etc/network/interfaces.
	(IE: --syslog --background etc...)

endmenu
2、Kbuild.src和Kbuild

Kbuild.src是更具配置决定某个源文件是否要编译的配置文件,比如添加新的源文件要通过配置来控制就需要再这边添加
Kbuild是由Kbuild.src和递归当前源文件下的配置而成
以networking/udhcp/Kbuild.src为例

# Makefile for busybox
#
# Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
#
# Licensed under GPLv2 or later, see file LICENSE in this source tree.
#

lib-y:=

#INSERT 可以插入.C源文件中注释中的Kbuild配置生成Kbuild,一层一层的生成后,递归后就得到所有要编译进去的文件
INSERT
#$(CONFIG_UDHCPC)就是再makeconfig所配置的,为y或者n
lib-$(CONFIG_UDHCPC)     += common.o packet.o signalpipe.o socket.o
lib-$(CONFIG_UDHCPD)     += common.o packet.o signalpipe.o socket.o

lib-$(CONFIG_UDHCPC)     += dhcpc.o
lib-$(CONFIG_UDHCPD)     += dhcpd.o arpping.o
lib-$(CONFIG_DUMPLEASES) += dumpleases.o
lib-$(CONFIG_DHCPRELAY)  += dhcprelay.o common.o socket.o packet.o

lib-$(CONFIG_FEATURE_UDHCPC_ARPING) += arpping.o
lib-$(CONFIG_FEATURE_UDHCP_RFC3397) += domain_codec.o
3、添加telnetd 端口自定义配置

打开telnetd相关源文件,再其源文件中添加配置修改代码

telnetd.c

//config:config IS_NOLANCHECK
//config:	bool "Support not check lan"
//config:	default n
//config:	depends on TELNETD
//config:	help
//config:	Support check lan.
//config:
//config:config TELNETD_PORT
//config:	int "Support set telnetd port"
//config:	default 25565
//config:	range 0 65535
//config:	depends on TELNETD
//config:	help
//config:	set telnetd port.
//config:

telnetdConfig
telnetdPort
CONFIG_TELNETD_PORT宏定义在include/autoconf.h头文件中是由make menuconfig生成的头文件
添加IS_NOLANCHECK和TELNETD_PORT两个配置。
IS_NOLANCHECK的数据类型是bool所以是y和n,TELNETD_PORT数据类型是int所以是数字。
default字段配置该选项的默认配置,端口默认配置为50119做了修改和常规的端口不同,range 0 65535语句限制端口设置的范围再0到65535之间。两个配置都有depends on TELNETD,依赖该前置配置,只有TELNETD配置开启后才能生效。源码中把写死的端口修改为CONFIG_TELNETD_PORT。就实现了再menuconfig配置端口。
如下图,可以看到配置表中已经多了端口配置选项。
menuconfig_telnetdPort

4、dhcpd添加自定义hostname限制和自定义mac地址限制

networking/udhcp/Config.src文件中添加相关menuconfig配置选项如下
Config.src

#
# For a description of the syntax of this configuration file,
# see docs/Kconfig-language.txt.
#

config UDHCPD
	bool "udhcpd (21 kb)"
	default y
	help
	udhcpd is a DHCP server geared primarily toward embedded systems,
	while striving to be fully functional and RFC compliant.
#######################添加开始###############################
#是否开启honstname设置功能
config UDHCPD_SET_HOSTNAME
	bool "udhcpd set hostname restrictions"
	default n
	depends on UDHCPD
	help
	udhcpd set hostname restrictions.
#honstname设置选项,数据类型为string,默认为空字符串即不限制
config UDHCPD_HOSTNAME
	string "udhcpd set hostname restrictions"
	default ""
	depends on UDHCPD_SET_HOSTNAME
	help
	A hostname is "A"
	B hostname is "B"
#是否开启mac限制功能
config UDHCPD_SET_HOSTMAC
	bool "udhcpd set  mac restrictions"
	default n
	depends on UDHCPD
	help
	udhcpd set  mac restrictions.
#配置mac限制,再对应mac字段配置mac即可限制该字段。可以实现自定义任意位mac限制
config UDHCPD_HOSTMAC
	string "udhcpd set hostmac restrictions"
	default "51:1B:17:::"
	depends on UDHCPD_SET_HOSTMAC
	help
	Example  Restrict MAC 51:1B:17:::
	Restrict the first 3 MAC addresses is 0x51 0x1B 0x17,
	can allocate IP.
	Example  Restrict MAC ::::2B:
	Restrict the fifth MAC addresses is 0x2B,can allocate IP.
################################添加结束#############################

下图是udhcpd相关menuconfig添加后的选项,选上udhpcd选项后就会出现udhcpd set hostname restrictions和udhcpd set mac restrictions选项,将这两个选项选中后即可自定义hostname限制和mac限制。
menuconfig_udhcpd

udhcpd.c源码有修改的函数如下

static NOINLINE void send_offer(struct dhcp_packet *oldpacket,
		uint32_t static_lease_nip,
		struct dyn_lease *lease,
		uint32_t requested_nip,
		unsigned arpping_ms)
{
	struct dhcp_packet packet;
	uint32_t lease_time_sec;

	init_packet(&packet, oldpacket, DHCPOFFER);

	/* If it is a static lease, use its IP */
	packet.yiaddr = static_lease_nip;
	/* Else: */
	if (!static_lease_nip) {
		/* We have no static lease for client's chaddr */
		const char *p_host_name;

		if (lease) {
			/* We have a dynamic lease for client's chaddr.
			 * Reuse its IP (even if lease is expired).
			 * Note that we ignore requested IP in this case.
			 */
			packet.yiaddr = lease->lease_nip;
		}
		/* Or: if client has requested an IP */
		else if (requested_nip != 0
		 /* and the IP is in the lease range */
		 && ntohl(requested_nip) >= server_data.start_ip
		 && ntohl(requested_nip) <= server_data.end_ip
		 /* and */
		 && (  !(lease = find_lease_by_nip(requested_nip)) /* is not already taken */
		    || is_expired_lease(lease) /* or is taken, but expired */
		    )
		) {
			packet.yiaddr = requested_nip;
		}
		else {
			/* Otherwise, find a free IP */
			packet.yiaddr = find_free_or_expired_nip(oldpacket->chaddr, arpping_ms);
		}

		if (!packet.yiaddr) {
			bb_simple_error_msg("no free IP addresses. OFFER abandoned");
			return;
		}
		/* Reserve the IP for a short time hoping to get DHCPREQUEST soon */
		p_host_name = (const char*) udhcp_get_option(oldpacket, DHCP_HOST_NAME);

/*********hostname限制 *********/
/*ENABLE_UDHCPD_SET_HOSTNAME宏定义在include/autoconf.h头文件中,
该头文件是make menuconfig配置选项保存后生成的*/
#if ENABLE_UDHCPD_SET_HOSTNAME 
		if(p_host_name != NULL && strstr(p_host_name, CONFIG_UDHCPD_HOSTNAME))
		{
			lease = add_lease(packet.chaddr, packet.yiaddr,
					server_data.offer_time,
					p_host_name,
					p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
			);
		}
		else
		{
			bb_info_msg("Not Found --> %s\n",CONFIG_UDHCPD_HOSTNAME);
			return;
		}
#else
		lease = add_lease(packet.chaddr, packet.yiaddr,
				server_data.offer_time,
				p_host_name,
				p_host_name ? (unsigned char)p_host_name[OPT_LEN - OPT_DATA] : 0
		);
#endif
/********* hostname限制 *********/
		if (!lease) {
			bb_simple_error_msg("no free IP addresses. OFFER abandoned");
			return;
		}
	}

	lease_time_sec = select_lease_time(oldpacket);
	udhcp_add_simple_option(&packet, DHCP_LEASE_TIME, htonl(lease_time_sec));
	add_server_options(&packet);

	/* send_packet emits error message itself if it detects failure */
	send_packet_verbose(&packet, "sending OFFER to %s");
}

/********* MAC限制 *********/
/*这两个函数主要功能是解析配置下来的mac限制
CONFIG_UDHCPD_HOSTMAC和ENABLE_UDHCPD_SET_HOSTMAC宏定义在include/autoconf.h头文件中,
该头文件make menuconfig配置后生成的头文件
*/
#if ENABLE_UDHCPD_SET_HOSTMAC
static int strToChar(char *pStr, int j, unsigned char *pMacCh)
{
	int i;
	if(j > 2) return -1;  //mac err
	for(i = j;i > 0;i-- )
	{
		if( pStr[i - 1] >= 'a' && pStr[i - 1] <= 'f')
		{
			*pMacCh += (pStr[i - 1] - 'a' + 0xa) * (0x1 << 4*(j-i));
		}
		else if( pStr[i - 1] >= 'A' && pStr[i - 1] <= 'F')
		{
			*pMacCh += (pStr[i - 1] - 'A' + 0xa) * (0x1 << 4*(j-i));
		}
		else if( pStr[i - 1] >= '0' && pStr[i - 1] <= '9')
		{
			*pMacCh += (pStr[i - 1] - '0') * (0x1 << 4*(j-i));
		}
	}
	return 0;
}

static int parseHostMac(unsigned char *pMac, int *pMask)
{
	char arrStr[4] = {};
	char *pPreMac = CONFIG_UDHCPD_HOSTMAC;
	int strLen = strlen(CONFIG_UDHCPD_HOSTMAC);
	char *pTemp;
	char *pTempMac = pPreMac;
	int i,j;
	
	*pMask = 0;
	for(i = 0; i < 5; i++)
	{
		pTemp = strstr(pTempMac, ":");
		if(NULL == pTemp)
		{
			return -1;
		}

		if(pTemp - pTempMac > 2) return -1;

		j = 0;
		memset(arrStr,0,4);
		while(pTemp - pTempMac )
		{
			arrStr[j] = pTempMac[0];
			pTempMac += 1;
			j++;
		}

		pTempMac += 1;
		if(j)
		{
			if(strToChar(arrStr, j, &pMac[i])) return -1;
			*pMask |= 0x1 << i;
		}
		

		if(i == 4)
		{
			j = 0;
			memset(arrStr,0,4);
			while(pTempMac - pPreMac < strLen)
			{
				arrStr[j] = pTempMac[0];
				pTempMac += 1;
				j++;
			}
			if(j)
			{
				if(strToChar(arrStr, j, &pMac[i+1])) return -1;
				*pMask |= 0x1 << (i + 1);
			}
		}
	}

	return 0;
}
#endif
/********* MAC限制*********/

int udhcpd_main(int argc UNUSED_PARAM, char **argv)
{
	int server_socket = -1, retval;
	uint8_t *state;
	unsigned timeout_end;
	unsigned num_ips;
	unsigned opt;
	struct option_set *option;
	char *str_I = str_I;
	const char *str_a = "2000";
	unsigned arpping_ms;
	IF_FEATURE_UDHCP_PORT(char *str_P;)

	setup_common_bufsiz();

	IF_FEATURE_UDHCP_PORT(SERVER_PORT = 67;)
	IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 68;)

	/* Make sure fd 0,1,2 are open */
	/* Setup the signal pipe on fds 3,4 - must be before openlog() */
	udhcp_sp_setup();

	opt = getopt32(argv, "^"
		"fSI:va:"IF_FEATURE_UDHCP_PORT("P:")
		"\0"
#if defined CONFIG_UDHCP_DEBUG && CONFIG_UDHCP_DEBUG >= 1
		"vv"
#endif
		, &str_I
		, &str_a
		IF_FEATURE_UDHCP_PORT(, &str_P)
		IF_UDHCP_VERBOSE(, &dhcp_verbose)
		);
	if (!(opt & 1)) { /* no -f */
		bb_daemonize_or_rexec(0, argv);
		logmode = LOGMODE_NONE;
	}
	/* update argv after the possible vfork+exec in daemonize */
	argv += optind;
	if (opt & 2) { /* -S */
		openlog(applet_name, LOG_PID, LOG_DAEMON);
		logmode |= LOGMODE_SYSLOG;
	}
	if (opt & 4) { /* -I */
		len_and_sockaddr *lsa = xhost_and_af2sockaddr(str_I, 0, AF_INET);
		server_data.server_nip = lsa->u.sin.sin_addr.s_addr;
		free(lsa);
	}
#if ENABLE_FEATURE_UDHCP_PORT
	if (opt & 32) { /* -P */
		SERVER_PORT = xatou16(str_P);
		CLIENT_PORT = SERVER_PORT + 1;
	}
#endif
	arpping_ms = xatou(str_a);

	/* Would rather not do read_config before daemonization -
	 * otherwise NOMMU machines will parse config twice */
	read_config(argv[0] ? argv[0] : DHCPD_CONF_FILE);
	/* prevent poll timeout overflow */
	if (server_data.auto_time > INT_MAX / 1000)
		server_data.auto_time = INT_MAX / 1000;

	/* Create pidfile */
	write_pidfile(server_data.pidfile);
	/* if (!..) bb_perror_msg("can't create pidfile %s", pidfile); */

	bb_simple_info_msg("started, v"BB_VER);

	option = udhcp_find_option(server_data.options, DHCP_LEASE_TIME);
	server_data.max_lease_sec = DEFAULT_LEASE_TIME;
	if (option) {
		move_from_unaligned32(server_data.max_lease_sec, option->data + OPT_DATA);
		server_data.max_lease_sec = ntohl(server_data.max_lease_sec);
	}

	/* Sanity check */
	num_ips = server_data.end_ip - server_data.start_ip + 1;
	if (server_data.max_leases > num_ips) {
		bb_error_msg("max_leases=%u is too big, setting to %u",
			(unsigned)server_data.max_leases, num_ips);
		server_data.max_leases = num_ips;
	}

	/* this sets g_leases */
	SET_PTR_TO_GLOBALS(xzalloc(server_data.max_leases * sizeof(g_leases[0])));

	read_leases(server_data.lease_file);

	if (udhcp_read_interface(server_data.interface,
			&server_data.ifindex,
			(server_data.server_nip == 0 ? &server_data.server_nip : NULL),
			server_data.server_mac)
	) {
		retval = 1;
		goto ret;
	}
/*********  MAC限制 *********/
/*解析mac限制
ENABLE_UDHCPD_SET_HOSTMAC宏定义在include/autoconf.h头文件中,
该头文件make menuconfig配置后生成的头文件
*/
#if ENABLE_UDHCPD_SET_HOSTMAC
	int mask =0;
	unsigned char mac[6] = {};
	int err = parseHostMac(mac, &mask);
	bb_info_msg("mask_mac %02x %02x %02x %02x %02x %02x  mask = %x \n",mac[0],mac[1],mac[2],mac[3],mac[4],mac[5],mask);
	if(err) 
	{
		bb_info_msg("SET_HOSTMAC is error!\n");
		goto ret;
	}
#endif
/********* MAC限制 *********/
 continue_with_autotime:
	timeout_end = monotonic_sec() + server_data.auto_time;
	while (1) { /* loop until universe collapses */
		struct pollfd pfds[2];
		struct dhcp_packet packet;
		int bytes;
		int tv;
		uint8_t *server_id_opt;
		uint8_t *requested_ip_opt;
		uint32_t requested_nip;
		uint32_t static_lease_nip;
		struct dyn_lease *lease, fake_lease;

		if (server_socket < 0) {
			server_socket = udhcp_listen_socket(/*INADDR_ANY,*/ SERVER_PORT,
					server_data.interface);
		}

		udhcp_sp_fd_set(pfds, server_socket);

 new_tv:
		tv = -1;
		if (server_data.auto_time) {
			tv = timeout_end - monotonic_sec();
			if (tv <= 0) {
 write_leases:
				write_leases();
				goto continue_with_autotime;
			}
			tv *= 1000;
		}

		/* Block here waiting for either signal or packet */
		retval = poll(pfds, 2, tv);
		if (retval <= 0) {
			if (retval == 0)
				goto write_leases;
			if (errno == EINTR)
				goto new_tv;
			/* < 0 and not EINTR: should not happen */
			bb_simple_perror_msg_and_die("poll");
		}

		if (pfds[0].revents) switch (udhcp_sp_read()) {
		case SIGUSR1:
			bb_info_msg("received %s", "SIGUSR1");
			write_leases();
			/* why not just reset the timeout, eh */
			goto continue_with_autotime;
		case SIGTERM:
			bb_info_msg("received %s", "SIGTERM");
			write_leases();
			goto ret0;
		}

		/* Is it a packet? */
		if (!pfds[1].revents)
			continue; /* no */

		/* Note: we do not block here, we block on poll() instead.
		 * Blocking here would prevent SIGTERM from working:
		 * socket read inside this call is restarted on caught signals.
		 */
		bytes = udhcp_recv_kernel_packet(&packet, server_socket);
		if (bytes < 0) {
			/* bytes can also be -2 ("bad packet data") */
			if (bytes == -1 && errno != EINTR) {
				log1("read error: "STRERROR_FMT", reopening socket" STRERROR_ERRNO);
				close(server_socket);
				server_socket = -1;
			}
			continue;
		}
		if (packet.hlen != 6) {
			bb_info_msg("MAC length != 6%s", ", ignoring packet");
			continue;
		}
		if (packet.op != BOOTREQUEST) {
			bb_info_msg("not a REQUEST%s", ", ignoring packet");
			continue;
		}
		state = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
		if (state == NULL || state[0] < DHCP_MINTYPE || state[0] > DHCP_MAXTYPE) {
			bb_info_msg("no or bad message type option%s", ", ignoring packet");
			continue;
		}

		/* Get SERVER_ID if present */
		server_id_opt = udhcp_get_option32(&packet, DHCP_SERVER_ID);
		if (server_id_opt) {
			uint32_t server_id_network_order;
			move_from_unaligned32(server_id_network_order, server_id_opt);
			if (server_id_network_order != server_data.server_nip) {
				/* client talks to somebody else */
				log1("server ID doesn't match%s", ", ignoring");
				continue;
			}
		}

		/* Look for a static/dynamic lease */
		static_lease_nip = get_static_nip_by_mac(&packet.chaddr);
		if (static_lease_nip) {
			bb_info_msg("found static lease: %x", static_lease_nip);
			memcpy(&fake_lease.lease_mac, &packet.chaddr, 6);
			fake_lease.lease_nip = static_lease_nip;
			fake_lease.expires = 0;
			lease = &fake_lease;
		} else {
/********* MAC限制 *********/
/*校验mac限制
ENABLE_UDHCPD_SET_HOSTMAC宏定义在include/autoconf.h头文件中,
该头文件make menuconfig配置后生成的头文件
*/
#if ENABLE_UDHCPD_SET_HOSTMAC
			int i = 0;
			for(i = 0; i < 6; i++)
			{
				if(mask & (0x1 << i)) 
				{
					if(packet.chaddr[i] != mac[i])
					{
						break;
					}
				}
			}
			
			if(i != 6)
			{
				continue;
			}
#endif
/********* MAC限制 *********/
			lease = find_lease_by_mac(packet.chaddr);
			bb_info_msg("mac:%02x:%02x:%02x:%02x:%02x:%02x \n", packet.chaddr[0], packet.chaddr[1], packet.chaddr[2], packet.chaddr[3], packet.chaddr[4], packet.chaddr[5]);
		}

		/* Get REQUESTED_IP if present */
		requested_nip = 0;
		requested_ip_opt = udhcp_get_option32(&packet, DHCP_REQUESTED_IP);
		if (requested_ip_opt) {
			move_from_unaligned32(requested_nip, requested_ip_opt);
		}

		switch (state[0]) {

		case DHCPDISCOVER:
			log1("received %s", "DISCOVER");

			send_offer(&packet, static_lease_nip, lease, requested_nip, arpping_ms);
			break;

		case DHCPREQUEST:
			log1("received %s", "REQUEST");
/* RFC 2131:

o DHCPREQUEST generated during SELECTING state:

   Client inserts the address of the selected server in 'server
   identifier', 'ciaddr' MUST be zero, 'requested IP address' MUST be
   filled in with the yiaddr value from the chosen DHCPOFFER.

   Note that the client may choose to collect several DHCPOFFER
   messages and select the "best" offer.  The client indicates its
   selection by identifying the offering server in the DHCPREQUEST
   message.  If the client receives no acceptable offers, the client
   may choose to try another DHCPDISCOVER message.  Therefore, the
   servers may not receive a specific DHCPREQUEST from which they can
   decide whether or not the client has accepted the offer.

o DHCPREQUEST generated during INIT-REBOOT state:

   'server identifier' MUST NOT be filled in, 'requested IP address'
   option MUST be filled in with client's notion of its previously
   assigned address. 'ciaddr' MUST be zero. The client is seeking to
   verify a previously allocated, cached configuration. Server SHOULD
   send a DHCPNAK message to the client if the 'requested IP address'
   is incorrect, or is on the wrong network.

   Determining whether a client in the INIT-REBOOT state is on the
   correct network is done by examining the contents of 'giaddr', the
   'requested IP address' option, and a database lookup. If the DHCP
   server detects that the client is on the wrong net (i.e., the
   result of applying the local subnet mask or remote subnet mask (if
   'giaddr' is not zero) to 'requested IP address' option value
   doesn't match reality), then the server SHOULD send a DHCPNAK
   message to the client.

   If the network is correct, then the DHCP server should check if
   the client's notion of its IP address is correct. If not, then the
   server SHOULD send a DHCPNAK message to the client. If the DHCP
   server has no record of this client, then it MUST remain silent,
   and MAY output a warning to the network administrator. This
   behavior is necessary for peaceful coexistence of non-
   communicating DHCP servers on the same wire.

   If 'giaddr' is 0x0 in the DHCPREQUEST message, the client is on
   the same subnet as the server.  The server MUST broadcast the
   DHCPNAK message to the 0xffffffff broadcast address because the
   client may not have a correct network address or subnet mask, and
   the client may not be answering ARP requests.

   If 'giaddr' is set in the DHCPREQUEST message, the client is on a
   different subnet.  The server MUST set the broadcast bit in the
   DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
   client, because the client may not have a correct network address
   or subnet mask, and the client may not be answering ARP requests.

o DHCPREQUEST generated during RENEWING state:

   'server identifier' MUST NOT be filled in, 'requested IP address'
   option MUST NOT be filled in, 'ciaddr' MUST be filled in with
   client's IP address. In this situation, the client is completely
   configured, and is trying to extend its lease. This message will
   be unicast, so no relay agents will be involved in its
   transmission.  Because 'giaddr' is therefore not filled in, the
   DHCP server will trust the value in 'ciaddr', and use it when
   replying to the client.

   A client MAY choose to renew or extend its lease prior to T1.  The
   server may choose not to extend the lease (as a policy decision by
   the network administrator), but should return a DHCPACK message
   regardless.

o DHCPREQUEST generated during REBINDING state:

   'server identifier' MUST NOT be filled in, 'requested IP address'
   option MUST NOT be filled in, 'ciaddr' MUST be filled in with
   client's IP address. In this situation, the client is completely
   configured, and is trying to extend its lease. This message MUST
   be broadcast to the 0xffffffff IP broadcast address.  The DHCP
   server SHOULD check 'ciaddr' for correctness before replying to
   the DHCPREQUEST.

   The DHCPREQUEST from a REBINDING client is intended to accommodate
   sites that have multiple DHCP servers and a mechanism for
   maintaining consistency among leases managed by multiple servers.
   A DHCP server MAY extend a client's lease only if it has local
   administrative authority to do so.
*/
			if (!requested_ip_opt) {
				requested_nip = packet.ciaddr;
				if (requested_nip == 0) {
					log1("no requested IP and no ciaddr%s", ", ignoring");
					break;
				}
			}
			if (lease && requested_nip == lease->lease_nip) {
				/* client requested or configured IP matches the lease.
				 * ACK it, and bump lease expiration time. */
				send_ACK(&packet, lease->lease_nip);
				break;
			}
			/* No lease for this MAC, or lease IP != requested IP */

			if (server_id_opt    /* client is in SELECTING state */
			 || requested_ip_opt /* client is in INIT-REBOOT state */
			) {
				/* "No, we don't have this IP for you" */
				send_NAK(&packet);
			} /* else: client is in RENEWING or REBINDING, do not answer */

			break;

		case DHCPDECLINE:
			/* RFC 2131:
			 * "If the server receives a DHCPDECLINE message,
			 * the client has discovered through some other means
			 * that the suggested network address is already
			 * in use. The server MUST mark the network address
			 * as not available and SHOULD notify the local
			 * sysadmin of a possible configuration problem."
			 *
			 * SERVER_ID must be present,
			 * REQUESTED_IP must be present,
			 * chaddr must be filled in,
			 * ciaddr must be 0 (we do not check this)
			 */
			log1("received %s", "DECLINE");
			if (server_id_opt
			 && requested_ip_opt
			 && lease  /* chaddr matches this lease */
			 && requested_nip == lease->lease_nip
			) {
				memset(lease->lease_mac, 0, sizeof(lease->lease_mac));
				lease->expires = time(NULL) + server_data.decline_time;
			}
			break;

		case DHCPRELEASE:
			/* "Upon receipt of a DHCPRELEASE message, the server
			 * marks the network address as not allocated."
			 *
			 * SERVER_ID must be present,
			 * REQUESTED_IP must not be present (we do not check this),
			 * chaddr must be filled in,
			 * ciaddr must be filled in
			 */
			log1("received %s", "RELEASE");
			if (server_id_opt
			 && lease  /* chaddr matches this lease */
			 && packet.ciaddr == lease->lease_nip
			) {
				lease->expires = time(NULL);
			}
			break;

		case DHCPINFORM:
			log1("received %s", "INFORM");
			send_inform(&packet);
			break;
		}
	}
 ret0:
	retval = 0;
 ret:
	/*if (server_data.pidfile) - server_data.pidfile is never NULL */
		remove_pidfile(server_data.pidfile);
	return retval;
}
下一篇:
udhcpc6嵌入式获取IPV6
本文目录
本文目录