Предположим, у меня есть исполняемый файл foo
и я имею bar
. Оба жестко запрограммированы на прослушивание порта 80. Предположим, у нас есть root.
Как я могу заставить оба работать на разных портах? Или один из них? Вы можете предположить, что у меня нет исходного кода.
Очевидно, я могу думать о следующих способах:
bind
системный вызов с ptrace
или что-то вроде того?80
в двоичном формате и измените параметр, входящий в привязкуМожете ли вы сделать это с помощью сетевых пространств имен а-ля Docker?
Благодаря функции сетевого пространства имен Linux, начиная с ядра 2.6.29, можно запускать программу в ее собственном сетевом пространстве, которое может быть построено со своими собственными ссылками, адресами и маршрутизацией, а также может быть подключено к основному сетевому пространству хоста. через маршрутизацию. Это можно сделать с помощью программ пользовательского пространства без докеров или других контейнерных фреймворков.
Поэтому, если вы хотите запустить программу, которая прослушивает на всех интерфейсах порт, который также используется другой программой, вы можете запустить ее одновременно на собственном интерфейсе.
Например, настройте сетевое пространство имен с именем island
. Добавьте интерфейс veth-island и поместите его в сеть 10.1.0.0. Подключите его виртуальный интерфейс Ethernet к одноранговому виртуальному интерфейсу veth-host в основном сетевом пространстве.
ip netns add island
ip link add veth-island type veth peer name veth-host
ip link set veth-island netns island
ip netns exec island ip link set veth-island up
ip netns exec island ip link set lo up
ip link set veth-host up
ip netns exec island ip address add 10.1.0.2/16 dev veth-island
ip addr add 10.1.0.1/16 dev veth-host
Теперь пространство имен island имеет собственный интерфейс обратной связи и интерфейс Ethernet, называемый veth-island с адресом 10.1.0.2, который может достигать veth-host на 10.1.0.1.
Вы можете проверить:
ip netns exec island ip address show
С главного хоста вы можете получить доступ к 10.1.0.2 и всему, что на нем слушает.
Итак, если у вас есть программа, которая прослушивает порт 8080 на всех интерфейсах, вы можете запустить ее с помощью:
ip netns exec island program arguments
, а на главном хосте вы можете подключиться к нему через порт 10.1.0.2 8080 и больше нигде.
Ты можешь сделать ip netns exec island netstat -plnt
и вы увидите, как программа прослушивает все интерфейсы на острове пространства имен.
Без использования iptables это все, что вам нужно. Основное ограничение заключается в том, что программа в остров пространство имен не может выполнять исходящие соединения с внешним миром и разрешать имена. Возможно, вы не захотите изменить это на производственной машине, потому что маршрутизация вносит незначительные изменения в то, какие функции IP принимаются или нет.
Итак, в основном для настройки исходящих функций (замените eth0
с вашим внешним интерфейсом):
#Activate router functions
echo 1 > /proc/sys/net/ipv4/ip_forward
#Set a gateway for the island namespace
ip netns exec island ip route add default via 10.1.0.1
#Masquerade outgoing connections (you can limit to tcp with `-p tcp`)
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j MASQUERADE
#Let packets move from the outward interface to the virtual ethernet pair and vice versa
iptables -A FORWARD -i eth0 -o veth-host -j ACCEPT
iptables -A FORWARD -o eth0 -i veth-host -j ACCEPT
#Setup a resolver (replace with your own DNS, does not work with a loopback resolver)
mkdir -p /etc/netns/island
echo nameserver dns-ip > /etc/netns/island/resolv.conf
#Maybe give it its own hosts file, to do edits
cp /etc/hosts /etc/netns/island/hosts
Теперь вы можете протестировать ip netns exec island ping example.com
Вы можете использовать что-то вроде socat
для сопоставления сервера с выбранным портом в основном пространстве имен.
socat tcp-listen:8080,reuseaddr,fork tcp-connect:10.1.0.2:80
Или вы можете использовать iptables как своего рода TCP-прокси. Это также включает в себя настройку sysctl.
# Proxy connections on localhost at a given port of main namespace to a chosen port on other namespace
# You have to set a sysctl and SNAT if you want to connect to loopback in main namespace and get packets returned from island namespace
# This sets up DNAT for packets output from a locally executed program in the main namespace
iptables -t nat -A OUTPUT -o lo -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.1.0.2:80
# You can omit SNAT if you connect to any interface different from loopback
iptables -t nat -A POSTROUTING -d 10.1.0.2/32 -p tcp -m tcp --dport 80 -j SNAT --to-source 10.1.0.1
# Allow routing of localhost output packets to veth-host (only needed for connections on loopback)
sysctl -w net.ipv4.conf.veth-host.route_localnet=1
Решение iptables сложно, потому что netstat не покажет вам ничего, прослушивающего в основном пространстве имен для порта DNAT. Вы можете запустить программу, которая привязывается к тому же порту в основном пространстве имен. С указанными командами это будет означать, например, что подключения извне будут достигать программы, прослушивающей порт в основном пространстве имен, но локально выполняемая программа в основном пространстве имен будет подключаться к порту DNAT в пространстве имен острова. Если вы хотите подключиться извне, вам нужно будет добавить правило PREROUTING DNAT. Просто скажу, что если на этой машине будет работать больше людей, может возникнуть путаница.
Предостережение. Для некоторых приложений виртуальные интерфейсы Ethernet могут быть ненадежными. Однажды я столкнулся с этим с помощью приложения VPN. В каком-то смысле это сработало, но невероятно медленно. Может быть ipsec, может быть другой транспортный уровень.
Программы Linux редко используют системные вызовы напрямую. Обычно бывает libc функция с таким же именем. Есть стандартный способ переопределить libc (и любой другой динамической библиотеки) функции:
dlsym(RLTD_NEXT, "function_name")
.LD_PRELOAD
переменная окружения.Для вашей конкретной проблемы вы можете создать файл libaltbind.c
:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
typedef int bindfn_type(int fd, const struct sockaddr *addr, socklen_t addrlen);
static bindfn_type* real_bind = find_bind;
int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
bindfn_type* found;
found = (bindfn_type*) dlsym(RTLD_NEXT, "bind");
if (found == NULL) {
fprintf(stderr, "Didn't find the real bind().\n");
return -1;
}
real_bind = found;
return real_bind(fd,addr,addrlen);
}
int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
if (addr->sa_family == AF_INET) {
struct sockaddr_in *addr_in = (struct sockaddr_in*) addr;
if (ntohs(addr_in->sin_port) == 80) {
char* env = getenv("ALT_PORT");
if (env != NULL) {
int new_port = atoi(env);
if (new_port != -1) {
addr_in->sin_port = htons(new_port);
}
}
}
}
return real_bind(fd, addr, addrlen);
}
и превратить его в динамическую библиотеку с помощью:
gcc -c -fPIC -Wall -Werror -o libaltbind.o libaltbind.c
gcc -shared -ldl -o libaltbind.so libaltbind.o
Теперь все, что вам нужно сделать, чтобы привязать вашу программу к порту 8080
вместо того 80
это запустить:
ALT_PORT=8080 LD_PRELOAD=/full/path/to/libaltbind.so program