Это воспроизводимое с помощью socat
( 1.7.3.2-2
не 1.7.3.1-2+deb9u1
который игнорирует reuseport
) на Debian (так похожий на Ubuntu):
term1$ socat -d -d TCP4-LISTEN:5555,reuseaddr,fork - 2018/08/03 08:20:43 socat[25084] N listening on AF=2 0.0.0.0:5555 term2$ socat -d -d TCP4-LISTEN:5555,bind=127.0.0.2,reuseaddr,fork - 2018/08/03 08:21:00 socat[25085] E bind(5,, 16): Address already in use 2018/08/03 08:21:00 socat[25085] N exit(1)
Теперь, если вы добавите reuseport
оба :
term1$ socat -d -d TCP4-LISTEN:5555,reuseaddr,reuseport,fork - 2018/08/03 08:21:28 socat[25086] N listening on AF=2 0.0.0.0:5555 term2$ socat -d -d TCP4-LISTEN:5555,bind=127.0.0.2,reuseaddr,reuseport,fork - 2018/08/03 08:21:56 socat[25092] N listening on AF=2 127.0.0.2:5555 otherterm$ netstat -tnlp 2>/dev/null|grep :5555 tcp 0 0 127.0.0.2:5555 0.0.0.0:* LISTEN 25092/socat tcp 0 0 0.0.0.0:5555 0.0.0.0:* LISTEN 25086/socat
Использование другого socat
или netcat для подключения покажет, что он правильно направлен на правильное прослушивание в socat
зависимости от IP-адреса.
Таким образом, разница в поведении, безусловно, о том, как bind
и setsockopt
звонки делаются на MacOS и Ubuntu для SSH (возможно, запуск программ имеет роль тоже?). Используя strace
, это добавляется в рабочем socat
случае:
setsockopt(5, SOL_SOCKET, SO_REUSEPORT, [1], 4) = 0
ОБНОВЛЕНИЕ : в
то время как я обращусь к письму вопроса OP: заставьте это работать, я также думаю, что вопрос не говорит о более широкой цели, и эта цель могла бы быть достигнута с совершенно другим решением.
Таким образом, для изменения sshd
и ssh
поведения, когда они связывают сокет, можно использовать dlsym()
оболочку, которая изменит bind(3)
поведение функции перед реальным bind(2)
системным вызовом.
скомпилируйте файл reuseport-wrapper.c
ниже с помощью:
gcc -shared -fPIC -o reuseport-wrapper.so reuseport-wrapper.c -ldl
#define _GNU_SOURCE #include <dlfcn.h> #include <sys/socket.h> #include <stddef.h> /* NULL */ int bind(int socket, const struct sockaddr *address, socklen_t address_len) { static const int optval=1; static int (*orig_bind)(int, const struct sockaddr *,socklen_t)=NULL; if (orig_bind == NULL && (orig_bind=dlsym(RTLD_NEXT,"bind")) == NULL) return -1; if (address != NULL && (address->sa_family == AF_INET || address->sa_family == AF_INET6)) { if (setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval) != 0) { return -1; } } return orig_bind(socket, address, address_len); }
Это установит обертку, bind(3)
чтобы всегда устанавливать SO_REUSEPORT
перед привязкой inet
или inet6
сокета.
Поместите его куда-нибудь (например, как, /usr/local/lib/reuseport-wrapper.so
но на самом деле это не имеет значения, поскольку он явно загружен как плагин).
Отредактируйте /etc/default/ssh
и добавьте:
LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so
перезапустите сервер ssh (например service ssh restart
)
И запустите любой другой инструмент (включая ssh
) с правами суперпользователя (см. Ниже, почему) с помощью LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so
export. Например:
$ sudo su - [...] # LD_PRELOAD=/usr/local/lib/reuseport-wrapper.so ssh -NfL 127.0.0.2:22:localhost:22 userB@BoxB
Эту команду нужно запускать с правами root по двум причинам: порт 22 требует привязки к нему root, и даже с некоторыми дополнительными возможностями есть дополнительное ограничение, описанное в socket(7)
:
SO_REUSEPORT (начиная с Linux 3.9)
Разрешает привязку нескольких сокетов AF_INET или AF_INET6 к одному и тому же адресу сокета. Эта опция должна быть установлена на каждом сокете (включая первый сокет) до вызова bind (2) на сокете. Чтобы предотвратить перехват порта, все процессы, привязанные к одному и тому же адресу, должны иметь один и тот же эффективный UID . Эта опция может использоваться с сокетами TCP и UDP.
Как только это будет сделано:
# netstat -tnlp |grep -w 22 tcp 0 0 127.0.0.2:22 0.0.0.0:* LISTEN 157/ssh tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 143/sshd tcp6 0 0 :::22 :::* LISTEN 143/sshd
И туннель будет работать как положено.