开始
已知:firewalld服务启动失败 且没什么日志。。。
# systemctl status firewalld
× firewalld.service - firewalld - dynamic firewall daemon
Loaded: loaded (/usr/lib/systemd/system/firewalld.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Thu 2024-11-14 18:37:59 CST; 16h ago
Docs: man:firewalld(1)
Main PID: 1485 (code=exited, status=3)
11月 14 18:37:58 localhost.localdomain systemd[1]: Starting firewalld - dynamic firewall daemon...
11月 14 18:37:59 localhost.localdomain systemd[1]: Started firewalld - dynamic firewall daemon.
11月 14 18:37:59 localhost.localdomain systemd[1]: firewalld.service: Main process exited, code=exited, status=3/NOTIMPLEMENTED
11月 14 18:37:59 localhost.localdomain systemd[1]: firewalld.service: Failed with result 'exit-code'.
# journalctl -u firewalld
11月 14 18:37:58 localhost.localdomain systemd[1]: Starting firewalld - dynamic firewall daemon...
11月 14 18:37:59 localhost.localdomain systemd[1]: Started firewalld - dynamic firewall daemon.
11月 14 18:37:59 localhost.localdomain systemd[1]: firewalld.service: Main process exited, code=exited, status=3/NOT>
11月 14 18:37:59 localhost.localdomain systemd[1]: firewalld.service: Failed with result 'exit-code'.
调试
先根据systemctl status firewalld
输出看一下服务在什么地方,看一下这个服务启动了什么命令
# cat /usr/lib/systemd/system/firewalld.service
ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS
申威架构大多追踪软件也不可使用,忽略最后一个参数,执行下试试
这个脚本是python的,先使用python的pdb试一下,如果是python代码的地方报错pdb会停在那里
# file /usr/sbin/firewalld
/usr/sbin/firewalld: Python script, ASCII text executable
# python3 -m pdb /usr/sbin/firewalld --nofork --nopid
> /usr/sbin/firewalld(27)<module>()
-> import os
(Pdb) r
mnl.c:44: Unable to initialize Netlink socket: 不支持的协议
额。。。并没有停住,看来是python调用c的时候上层封装没有抛异常
再使用gdb调试,知道会向stderr/stdout输出一条消息,所以设置断点,断点的条件是write的第一个参数是1或者2,1代表stdout,2代表stderr。设置断点在输出到stdout/stderr时候。$r4 r4寄存器是申威架构的函数传参约定的第一个参数寄存器,不同架构不一样。
firewalld会有好几次的write输出干扰,前几次的来自于pthread.so也有个write函数,没关系,多试几次就找到真正报错的地方了。
# gdb /usr/bin/python3
(gdb) set args /usr/sbin/firewalld --nofork --nopid
(gdb) b write if $r4 = 1
(gdb) b write if $r4 = 2
(gdb) r
(gdb) c
(gdb) c
(gdb) c
(gdb) c
(gdb) c
...
(gdb) bt
#0 0x00004000009a9f08 in write () from /usr/lib/libc.so.6.1
#1 0x00004000009368e8 in _IO_file_write@@GLIBC_2.1 () from /usr/lib/libc.so.6.1
#2 0x00004000009356fc in new_do_write () from /usr/lib/libc.so.6.1
#3 0x000040000093732c in __GI__IO_file_xsputn () from /usr/lib/libc.so.6.1
#4 0x00004000008ffd74 in buffered_vfprintf () from /usr/lib/libc.so.6.1
#5 0x0000400000902f5c in vfprintf@@GLIBC_2.4 () from /usr/lib/libc.so.6.1
#6 0x0000400000a1aa6c in fprintf@GLIBC_2.0 () from /usr/lib/libc.so.6.1
#7 0x0000400003d2bedc in __netlink_init_error () from /lib/libnftables.so.1.0.0
#8 0x0000400003d4475c in nft_mnl_socket_open () from /lib/libnftables.so.1.0.0
#9 0x0000400003d4c07c in nft_ctx_new () from /lib/libnftables.so.1.0.0
#10 0x0000400002076ff8 in ffi_call_osf () from /usr/lib/libffi.so.8
#11 0x000040000207680c in ffi_call_int () from /usr/lib/libffi.so.8
#12 0x00004000032db6fc in _ctypes_callproc () from /usr/lib/python3.6/lib-dynload/_ctypes.cpython-36m-sw_64-linux-gnu.so
#13 0x00004000032dd0b4 in PyCFuncPtr_call () from /usr/lib/python3.6/lib-dynload/_ctypes.cpython-36m-sw_64-linux-gnu.so
#14 0x00004000004d5784 in _PyObject_FastCallDict () from /usr/lib/libpython3.6m.so.1.0
#15 0x0000400000563928 in call_function () from /usr/lib/libpython3.6m.so.1.0
看到栈7和栈8,就是出错位置。这里执行了mnl_socket_open
没有成功
struct mnl_socket *nft_mnl_socket_open(void)
{
struct mnl_socket *nf_sock;
nf_sock = mnl_socket_open(NETLINK_NETFILTER);
if (!nf_sock)
netlink_init_error();
if (fcntl(mnl_socket_get_fd(nf_sock), F_SETFL, O_NONBLOCK))
netlink_init_error();
return nf_sock;
}
void __noreturn __netlink_init_error(const char *filename, int line,
const char *reason)
{
fprintf(stderr, "%s:%d: Unable to initialize Netlink socket: %s\n",
filename, line, reason);
exit(NFT_EXIT_NONL);
}
看到这个函数来自于外部定义,readelf -s -W /lib/libnftables.so.1.0.0
的输出中,第一个123是index,后面一个长长的0是elf文件中这个函数的地址,地址是0即代表这个函数是外部的,本elf中没有地址。
结合这个函数的名称,和readelf -d /lib/libnftables.so.1.0.0
猜,这个函数来自于ibmnl.so.0
Tips: 找函数位于哪个动态库时应该使用
readelf -d
而不要使用ldd
ldd
命令是通过环境变量设置ld
只加载不执行实现的,ld
加载过程中会加载依赖动态库的依赖动态库,也就是说,firewalld依赖libmnl.so.0
,ldd
也会显示出libmnl.so.0
的依赖,但通常我们不关心libmnl.so.0
的依赖
# readelf -s -W /lib/libnftables.so.1.0.0 |grep mnl_socket_open
123: 0000000000000000 0 FUNC GLOBAL DEFAULT UND mnl_socket_open@LIBMNL_1.0 (6)
[root@localhost nftables-master]# readelf -d /lib/libnftables.so.1.0.0
0x0000000000000001 (NEEDED) 共享库:[libmnl.so.0]
出错的mnl_socket_open(NETLINK_NETFILTER)
实际会执行socket(AF_NETLINK, SOCK_RAW | NETLINK_NETFILTER, bus);
,而很明显,socket是一个系统调用。。。
Tips: 这里说
socket
是一个系统调用,不代表c语言里直接执行socket
就是一个系统调用的执行,通常glibc会对系统调用封装,比如系统调用的返回值glibc会设置到errno中,errno是用户态每个线程独享的一个成员和内核无关,即c语言里直接执行的socket是glibc对系统调用socket的一个封装。而且,系统调用的传参约定、触发方式和用户态函数并不相同,glibc也提供裸调用syscall转化参数寄存器方法。
struct mnl_socket *mnl_socket_open(int bus)
{
return __mnl_socket_open(bus, 0);
}
static struct mnl_socket *__mnl_socket_open(int bus, int flags)
{
struct mnl_socket *nl;
nl = calloc(1, sizeof(struct mnl_socket));
if (nl == NULL)
return NULL;
nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus);
if (nl->fd == -1) {
free(nl);
return NULL;
}
return nl;
}
验证一下,确实是socket系统调用不支持网络过滤
# strace -e socket /usr/sbin/firewalld --nofork --nopid
socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER) = -1 EPROTONOSUPPORT (不支持的协议)
mnl.c:44: Unable to initialize Netlink socket: 不支持的协议
+++ exited with 3 +++
完结,内核不支持。