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

Индекс LLMS: [llms.txt](/llms.txt)

---

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

## Установка

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

```bash
apt install socat
```

На RHEL/CentOS:

```bash
yum install socat
# или
dnf install socat
```

Alpine:

```bash
apk add socat
```

Проверка:

```bash
socat -V
# socat version 1.7.4.4
```

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

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

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

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

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

```bash
curl http://localhost:2375/version
```

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

```bash
curl http://192.168.1.100:2375/version
```

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

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

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

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

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

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

```bash
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-соединения по сети клиент запускается симметрично:

```bash
socat STDIO TCP:192.168.1.100:2375
```

## Abstract vs Filesystem сокеты

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

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

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

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

```bash
# Отображение абстрактного сокета в 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
```

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

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

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

В socat синтаксис:
- `@/path/to/socket` — абстрактный сокет
- `/path/to/socket` — файловый сокет

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

## Timeout-флаги

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

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

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

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

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

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

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

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

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

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

```bash
socat TCP-LISTEN:2375,reuseaddr,timeout=0 UNIX-CONNECT:/var/run/docker.sock
```

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

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

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

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

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

**Connection refused**

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

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

Фаерволл:

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

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

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

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

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

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

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

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

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

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

```ini
[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
```

```bash
systemctl enable socat-docker-forward
systemctl start socat-docker-forward
```

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

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

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