本文同步自(最佳显示效果请点击):https://zohead.com/archives/libc-bind-wrapper/
最近在使用一个第三方程序的时候发现程序绑定的 UDP 端口和现有 Linux 系统中的程序有冲突,系统自带的程序又不好修改端口,而第三方程序更没有源码或者配置文件来指定端口。
这种情况下想到可以用 libc 的封装库自己实现 bind 之类的函数来修改端口号,而网上也找到了 Daniel Ryde 类似的实现:
http://www.ryde.net/code/bind.c.txt
编译这个 bind 库可以通过环境变量指定绑定的本地 IP 地址,但不支持端口号修改,而且是直接修改程序所有绑定主机地址不好过滤定制。因此,我对这个 bind 的封装库做了以下改进:
- 支持 socket 类型过滤,可以指定是 TCP、UDP socket 等;
- 支持指定是本地监听请求还是连接远程的请求;
- 支持修改本地使用的 IP 地址及端口号;
- 支持通过环境变量过滤掉某些端口
支持上面这些新增的功能之后,我们就可以根据实际情况只修改特定请求的绑定地址或者绑定端口以满足需求。
我修改的 bind 封装库代码如下,也比较简单:
/* Copyright (C) 2000 Daniel Ryde Modified by Uranus Zhou (2014) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <dlfcn.h> #include <errno.h> #include <string.h> int (*real_bind)(int, const struct sockaddr *, socklen_t); int (*real_connect)(int, const struct sockaddr *, socklen_t); char *bind_addr_env, *connect_bind_addr_env, *bind_port_env, *bind_type_env, *exclude_ports_env, *connect_port_env, *connect_type_env, *connect_bind_port_env; unsigned long int bind_addr_saddr; struct sockaddr_in local_sockaddr_in[] = { 0 }; int bind_port_ns = -1, connect_port_ns = -1; int bind_type = -1, connect_type = -1; static int get_sock_type(const char *str) { if (strcasecmp(str, "TCP") == 0) return SOCK_STREAM; else if (strcasecmp(str, "UDP") == 0) return SOCK_DGRAM; else if (strcasecmp(str, "RAW") == 0) return SOCK_RAW; else if (strcasecmp(str, "PACKET") == 0) return SOCK_PACKET; else return -1; } void _init (void) { const char *err; real_bind = dlsym (RTLD_NEXT, "bind"); if ((err = dlerror ()) != NULL) { fprintf (stderr, "dlsym (bind): %s\n", err); } real_connect = dlsym (RTLD_NEXT, "connect"); if ((err = dlerror ()) != NULL) { fprintf (stderr, "dlsym (connect): %s\n", err); } if (bind_addr_env = getenv ("BIND_ADDR")) bind_addr_saddr = inet_addr (bind_addr_env); if (bind_port_env = getenv ("BIND_PORT")) bind_port_ns = htons (atoi(bind_port_env)); if (bind_type_env = getenv ("BIND_TYPE")) bind_type = get_sock_type(bind_type_env); exclude_ports_env = getenv ("EXCLUDE_PORTS"); if (connect_port_env = getenv ("CONNECT_PORT")) connect_port_ns = htons (atoi(connect_port_env)); if (connect_type_env = getenv ("CONNECT_TYPE")) connect_type = get_sock_type(connect_type_env); if (connect_bind_addr_env = getenv ("CONNECT_BIND_ADDR")) { local_sockaddr_in->sin_family = AF_INET; local_sockaddr_in->sin_addr.s_addr = inet_addr (connect_bind_addr_env); local_sockaddr_in->sin_port = htons (0); } if (connect_bind_port_env = getenv ("CONNECT_BIND_PORT")) local_sockaddr_in->sin_port = htons (atoi(connect_bind_port_env)); } static int check_port(int port) { char *tmp = exclude_ports_env, *str = NULL; if (tmp == NULL) return 0; while (1) { char szPort[50] = {0}; str = strchr(tmp, ','); if (str == NULL) strncpy(szPort, tmp, sizeof(szPort)); else strncpy(szPort, tmp, str - tmp); if (atoi(szPort) == port) return 1; if (str == NULL) break; tmp = str + 1; } return 0; } int bind (int fd, const struct sockaddr *sk, socklen_t sl) { static struct sockaddr_in *lsk_in; lsk_in = (struct sockaddr_in *)sk; if (lsk_in->sin_family == AF_INET || lsk_in->sin_family == AF_INET6) { int type, length = sizeof(int); getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &length); if (check_port(ntohs(lsk_in->sin_port)) == 0 && (bind_type_env == NULL || bind_type == type)) { // change bind address if (lsk_in->sin_addr.s_addr == htonl (INADDR_ANY) && bind_addr_env) lsk_in->sin_addr.s_addr = bind_addr_saddr; // change bind port if (bind_port_env) lsk_in->sin_port = bind_port_ns; } } return real_bind (fd, sk, sl); } int connect (int fd, const struct sockaddr *sk, socklen_t sl) { static struct sockaddr_in *rsk_in; rsk_in = (struct sockaddr_in *)sk; if (rsk_in->sin_family == AF_INET || rsk_in->sin_family == AF_INET6) { int type, length = sizeof(int); getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &length); if ((connect_port_env == NULL || connect_port_ns == rsk_in->sin_port) && (connect_type_env == NULL || connect_type == type)) { // change connect bind address or bind port if (connect_bind_addr_env) real_bind (fd, (struct sockaddr *)local_sockaddr_in, sizeof (struct sockaddr)); } } return real_connect (fd, sk, sl); }
编译方法:
gcc -nostartfiles -fpic -shared bind.c -o bind.so -ldl -D_GNU_SOURCE
运行指定程序时需要通过 LD_PRELOAD 预先加载 bind.so 库,并通过环境变量指定如何过滤修改网络参数:
- BIND_ADDR 指定本地监听的 IP 地址;
- BIND_PORT 指定本地监听的端口;
- BIND_TYPE 指定本地监听的 socket 类型(TCP、UDP、RAW、PACKET);
- CONNECT_PORT 指定连接远程主机的端口;
- CONNECT_TYPE 指定远程主机 socket 连接类型(与 BIND_TYPE 类似);
- CONNECT_BIND_ADDR 指定连接远程主机时本地使用的 IP 地址;
- CONNECT_BIND_PORT 指定连接远程主机时本地使用的端口;
- EXCLUDE_PORTS 需要排除的端口列表(以逗号隔开)
这里举个例子说明如何使用 bind.so:
假设第三方程序为 testapp,testapp 启动时会本地监听 UDP 775 端口,另外还会在本地 eth0 网卡上监听一个随机的 UDP 端口。现在我们需要将后面这个随机的 UDP 端口固定为 666,而且需要指定使用 eth2 网卡,就可以这样运行 testapp 程序:
BIND_ADDR="eth2-ip" BIND_PORT=666 BIND_TYPE=UDP EXCLUDE_PORTS=775 LD_PRELOAD=bind.so testapp
上面的命令中指定了本地 IP 地址(eth2-ip)、绑定端口(666)、socket 类型(UDP),并且使用 EXCLUDE_PORTS 环境变量过滤了固定监听的 775 端口,这样 testapp 程序运行使用的随机 UDP 端口就固定在 666 上了。
我修改的 bind.c 源代码可以从我的 Gist 下载:
https://gist.github.com/zohead/9950663ca01952c940eb
实际使用如果有任何问题欢迎提出指正哦,玩的开心 ^_^
在树莓派上试
BIND_ADDR=”192.168.137.18″ BIND_PORT=8080 BIND_TYPE=TCP EXCLUDE_PORTS=80 LD_PRELOAD=bind.so /usr/local/nginx/sbin/nginx
ERROR: ld.so: object ‘bind.so’ from LD_PRELOAD cannot be preloaded: ignored.
是不是 bind.so 的路径不对?
可以把 bind.so 换成绝对路径看看,不行也可以 strace 调试看看有没有确实加载库文件