一、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
配置install路径,首先选择Settings—>
然后选择Installation Options中的Destination path for ‘make install’选项配置install目录。
我们将其设置为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:
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配置端口。
如下图,可以看到配置表中已经多了端口配置选项。
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限制。
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;
}