Перейти к содержимому

socat: проброс Unix-сокетов через TCP

Иногда нужно обратиться к Unix-сокету с хоста, где этот сокет физически не существует. SSH-туннель не поможет — он работает с TCP-портами. socat решает эту задачу: поднимает TCP- listener и пробрасывает соединения в Unix-сокет, а клиенту достаточно подключиться по сети.

Установка

Пакет есть в любом крупном дистрибутиве. На Debian/Ubuntu:

apt install socat

На RHEL/CentOS:

yum install socat
# или
dnf install socat

Alpine:

apk add socat

Проверка:

socat -V
# socat version 1.7.4.4

Базовый проброс: TCP-LISTEN + UNIX-CONNECT

Серверная сторона. Слушаем TCP-порт и при подключении перенаправляем трафик в Unix-сокет:

socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock

Флаги:

  • TCP-LISTEN:2375 — открываем порт 2375
  • fork — порождаем дочерний процесс на каждое подключение; без него socat примет одно соединение и выйдет

На клиенте работаем как обычно — например, curl к Docker API:

curl http://localhost:2375/version

Если клиент на удалённом хосте, укажите IP сервера:

curl http://192.168.1.100:2375/version
Предупреждение

Docker по умолчанию слушает только локальный сокет. Открывать TCP-LISTEN наружу без TLS или firewall — риск. Ограничьте бинд интерфейсом: TCP-LISTEN:2375,bind=127.0.0.1.

Остановить проброс — Ctrl+C или kill по PID.

Клиентский тест через STDIO

Если нужно быстро проверить доступность сокета или отправить ручную команду, используйте STDIO на стороне клиента:

socat STDIO UNIX-CONNECT:/var/run/docker.sock

После запуска вводите сырые HTTP-запросы. Пример сессии:

socat STDIO UNIX-CONNECT:/var/run/docker.sock
GET /version HTTP/1.0

HTTP/1.1 200 OK
Content-Type: application/json
{"ApiVersion":"1.45","Version":"24.0.7"...}

Выход — Ctrl+D или Ctrl+C. Это удобно для отладки API без curl и без настройки переменных окружения.

Для TCP-соединения по сети клиент запускается симметрично:

socat STDIO TCP:192.168.1.100:2375

Abstract vs Filesystem сокеты

Unix-сокеты бывают двух типов. Разница принципиальна для работы socat.

Filesystem sockets — привязаны к файловой системе. Путь начинается с /:

/var/run/docker.sock
/run/user/1000/pulse/runtime/native
/tmp/mysql.sock

Abstract sockets — живут в памяти ядра, не имеют файлового представления. Путь начинается с \0 или @ (ASCII zero и at-символ). Docker в rootless-режиме использует именно их:

# Отображение абстрактного сокета в ls
ls -la /run/user/1000/docker.sock
# srwxr-xr-x 1 user user 0 Jan 15 10:00 /run/user/1000/docker.sock

# Реальный путь в ядре начинается с \0
# В socat пишите:
socat TCP-LISTEN:2375,fork UNIX-CONNECT:@/docker.sock

Проверить тип сокета:

ss -x | grep docker
# u_str  LISTEN  0  4096  /run/user/1000/docker.sock  12345  * 0

# Если путь начинается с @ — абстрактный

В socat синтаксис:

  • @/path/to/socket — абстрактный сокет
  • /path/to/socket — файловый сокет
Примечание

Абстрактные сокеты невидимы для процессов без доступа к namespace. Это плюс для изоляции, но осложняет проброс между контейнерами.

Timeout-флаги

По умолчанию socat ждёт вечно. Для автоматизации и скриптов нужны таймауты.

ФлагОписание
readtimeout=SECONDSТаймаут на чтение
writetimeout=SECONDSТаймаут на запись
timeout=SECONDSТаймаут на обе операции

Пример с общим таймаутом:

socat TCP-LISTEN:2375,fork,timeout=30 UNIX-CONNECT:/var/run/docker.sock

Соединение закроется через 30 секунд неактивности.

Раздельные таймауты для клиента и сервера:

# Сервер ждёт 10 сек на запись, клиент — 5 сек на чтение
socat TCP-LISTEN:2375,forever,writewait=10 UNIX-CONNECT:/var/run/docker.sock,readtimeout=5

В cron-скриптах или systemd-юнитах ставьте таймаут, иначе процесс повиснет при обрыве сети:

socat TCP-LISTEN:2375,fork,timeout=60 UNIX-CONNECT:/var/run/docker.sock

Для бесконечного ожидания без fork подойдёт forever, но в продакшене лучше комбинировать с system limits:

socat TCP-LISTEN:2375,reuseaddr,timeout=0 UNIX-CONNECT:/var/run/docker.sock
Подсказка

reuseaddr позволяет быстро перезапустить socat без ошибки «Address already in use».

Типичные ошибки

Permission denied при доступе к сокету

# Проверьте права
ls -la /var/run/docker.sock
# srw-rw---- 1 root docker

# Добавьте пользователя в группу
usermod -aG docker username

Connection refused

Проверьте, что socat запущен и слушает порт:

ss -tlnp | grep 2375
# LISTEN 0 5 *:2375 *:*  users:(("socat",pid=1234))

Фаерволл:

iptables -L -n | grep 2375
# ACCEPT  tcp  --  0.0.0.0/0  0.0.0.0/0  tcp dpt:2375

Один запрос — и socat умирает

Отсутствует fork. Каждый socat обрабатывает одно соединение и выходит. Добавьте флаг:

socat TCP-LISTEN:2375,fork UNIX-CONNECT:/var/run/docker.sock

Абстрактный сокет не найден

Убедитесь в правильном префиксе. Docker в rootless использует @, но это ASCII 0:

# Покажет реальный путь в ядре
cat /proc/$(pgrep dockerd)/net/unix | grep docker

В socat пишите @/docker.sock, не @@/docker.sock.

Systemd-сервис для постоянного проброса

Если нужен постоянный проброс, оберните в systemd-юнит:

[Unit]
Description=socat Docker socket forwarder
After=network.target

[Service]
ExecStart=/usr/bin/socat TCP-LISTEN:2375,fork,reuseaddr,timeout=60 UNIX-CONNECT:/var/run/docker.sock
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable socat-docker-forward
systemctl start socat-docker-forward

Не забудьте ограничить бинд интерфейсом, если не хотите открывать порт наружу.

Вместо заключения

socat — это unix way для прозрачного проброса чего угодно куда угодно. Для Unix-сокетов через TCP достаточно двух процессов и минуты настройки. Держите таймауты, не забывайте fork, и не открывайте порты без авторизации в public network.