Назад к обновлениям
New releaseAug 30, 2026

frp v0.71.0

Быстрый обратный прокси-сервер, помогающий открыть доступ к локальному серверу за NAT или файрволом из интернета.

Поделиться

frp

Build Status GitHub release GitHub Releases Stats

README | 中文文档

Спонсоры

frp — это проект с открытым исходным кодом, дальнейшее развитие которого стало возможным исключительно благодаря поддержке наших замечательных спонсоров. Если вы хотите присоединиться к ним, пожалуйста, рассмотрите возможность спонсирования разработки frp.

Золотые спонсоры


Полноценная IDE, созданная для профессиональных Go-разработчиков


Суверенное облако, которое даёт вам полный контроль
Открытая альтернатива публичным облакам с самостоятельным хостингом, созданная для владения данными и конфиденциальности

Recall.ai — API для записи встреч

Если вы ищете API для записи встреч, обратите внимание на Recall.ai,

API, которое записывает Zoom, Google Meet, Microsoft Teams, очные встречи и многое другое.

Что такое frp?

frp — это быстрый обратный прокси-сервер, который позволяет вам предоставить доступ к локальному серверу, находящемуся за NAT или брандмауэром, через Интернет. В настоящее время он поддерживает протоколы TCP и UDP, а также HTTP и HTTPS, что позволяет пересылать запросы на внутренние сервисы через доменное имя.

frp также предлагает режим P2P-подключения.

Содержание

Статус разработки

frp в настоящее время находится в стадии разработки. Вы можете попробовать последнюю версию релиза в ветке master или использовать ветку dev для доступа к версии, находящейся в разработке.

В настоящее время мы работаем над версией 2 и пытаемся выполнить некоторый рефакторинг кода и улучшения. Однако обратите внимание, что она не будет совместима с версией 1.

Мы перейдём от версии 0 к версии 1 в подходящий момент и будем принимать только исправления ошибок и улучшения, а не крупные запросы на новые функции.

О V2

Сложность и трудность версии v2 намного выше, чем ожидалось. Я могу работать над её разработкой только в разрозненные промежутки времени, а постоянные перерывы значительно снижают продуктивность. Учитывая эту ситуацию, мы продолжим оптимизировать и развивать текущую версию, пока у нас не появится больше свободного времени для проведения капитального обновления версии.

Концепция v2 основана на моих многолетних опыте и размышлениях в области cloud-native, особенно в K8s и ServiceMesh. Её ядро — это современный прокси четвёртого и седьмого уровней, похожий на envoy. Этот прокси сам по себе высокомасштабируем и способен не только реализовать функциональность проникновения в интранет, но и применяться в различных других областях. На основе этого высокомасштабируемого ядра мы стремимся реализовать все возможности frp v1, а также решить задачи, которые ранее было невозможно или сложно реализовать элегантным способом. Кроме того, мы будем поддерживать эффективную разработку и итерации.

Кроме того, я вижу frp как высокорасширяемую систему и платформу, аналогично тому, как мы можем предоставлять ряд расширений на основе K8s. В K8s мы можем настраивать разработку в соответствии с потребностями предприятия, используя такие функции, как CRD, режим контроллера, webhook, CSI и CNI. В frp v1 мы ввели концепцию серверных плагинов, которая реализовала некоторую базовую расширяемость. Однако она опирается на простой HTTP-протокол и требует от пользователей запуска независимых процессов и самостоятельного управления ими. Такой подход далёк от гибкости и удобства, а реальные потребности сильно различаются. Нереалистично ожидать, что некоммерческий проект с открытым исходным кодом, поддерживаемый несколькими людьми, удовлетворит потребности каждого.

Наконец, мы признаём, что текущий дизайн таких модулей, как управление конфигурацией, проверка разрешений, управление сертификатами и управление API, недостаточно современен. Хотя мы можем провести некоторые оптимизации в версии v1, обеспечение совместимости остаётся сложной задачей, требующей значительных усилий для решения.

Мы искренне ценим вашу поддержку frp.

Архитектура

architecture

Примеры использования

Для начала загрузите последнюю программу для вашей операционной системы и архитектуры со страницы Release.

Затем разместите бинарный файл frps и файл конфигурации сервера на сервере A, у которого есть публичный IP-адрес.

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

Некоторые антивирусы ошибочно помечают frpc как вредоносное ПО и удаляют его. Это связано с тем, что frp — это сетевой инструмент, способный создавать обратные прокси. Антивирусы иногда помечают обратные прокси из-за их способности обходить ограничения портов брандмауэра. Если вы используете антивирус, вам может потребоваться добавить frpc в белый список/исключения в настройках антивируса, чтобы избежать случайной изоляции/удаления. Подробнее см. issue 3637.

Доступ к вашему компьютеру в локальной сети через SSH

  1. Измените frps.toml на сервере A, установив bindPort для подключения клиентов frp: ```toml

frps.toml

bindPort = 7000

2. Запустите `frps` на сервере A:

`./frps -c ./frps.toml`

3. Измените `frpc.toml` на сервере B и установите поле `serverAddr` на публичный IP-адрес вашего сервера frps:  ```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000

Обратите внимание, что localPort (прослушивается на клиенте) и remotePort (открывается на сервере) используются для трафика, входящего и исходящего из системы frp, в то время как serverPort используется для связи между frps и frpc.

  1. Запустите frpc на сервере B:

./frpc -c ./frpc.toml

  1. Чтобы получить доступ к серверу B с другой машины через сервер A по SSH (предполагая, что имя пользователя — test), используйте следующую команду:

ssh -oPort=6000 [email protected]

Несколько SSH-сервисов, использующих один и тот же порт

В этом примере реализовано несколько SSH-сервисов, открытых через один и тот же порт, с использованием прокси типа tcpmux. Аналогично, если клиент поддерживает метод прокси-соединения HTTP Connect, таким образом можно добиться повторного использования порта.

  1. Разверните frps на машине с публичным IP-адресом и измените файл frps.toml. Вот упрощённая конфигурация: ```toml bindPort = 7000 tcpmuxHTTPConnectPort = 5002
2. Разверните frpc на внутренней машине A со следующей конфигурацией:  ```toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "ssh1"
type = "tcpmux"
multiplexer = "httpconnect"
customDomains = ["machine-a.example.com"]
localIP = "127.0.0.1"
localPort = 22
  1. Разверните ещё один frpc на внутренней машине B со следующей конфигурацией: ```toml serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "ssh2" type = "tcpmux" multiplexer = "httpconnect" customDomains = ["machine-b.example.com"] localIP = "127.0.0.1" localPort = 22

4. Для доступа к внутренней машине A через SSH ProxyCommand, предполагая, что имя пользователя — «test»:

`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' [email protected]`

5. Для доступа к внутренней машине B единственное отличие — доменное имя, предполагая, что имя пользователя — «test»:

`ssh -o 'proxycommand socat - PROXY:x.x.x.x:%h:%p,proxyport=5002' [email protected]`

### Доступ к внутренним веб-сервисам с пользовательскими доменами в локальной сети

Иногда нам нужно предоставить доступ к локальному веб-сервису, находящемуся за NAT-сетью, другим пользователям для тестирования с использованием собственного доменного имени.

К сожалению, мы не можем сопоставить доменное имя с локальным IP-адресом. Однако мы можем использовать frp для публикации HTTP(S)-сервиса.

1. Измените `frps.toml` и установите HTTP-порт для vhost на 8080:  ```toml
# frps.toml
bindPort = 7000
vhostHTTPPort = 8080

Если вы хотите настроить HTTPS-прокси, вам необходимо задать vhostHTTPSPort.

  1. Запустите frps:

./frps -c ./frps.toml

  1. Измените frpc.toml и укажите serverAddr как IP-адрес удалённого сервера frps. Задайте localPort вашего веб-сервиса: ```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "web" type = "http" localPort = 80 customDomains = ["www.example.com"]

4. Запустите `frpc`:

`./frpc -c ./frpc.toml`

5. Привяжите A-запись `www.example.com` к публичному IP-адресу удалённого сервера frps или создайте CNAME-запись, указывающую на ваш исходный домен.

6. Откройте ваш локальный веб-сервис по адресу `http://www.example.com:8080`.

### Пересылка DNS-запросов

1. Измените `frps.toml`:  ```toml
# frps.toml
bindPort = 7000
  1. Запустите frps:

./frps -c ./frps.toml

  1. Измените frpc.toml и укажите serverAddr как IP-адрес удалённого сервера frps. Перенаправьте запросы DNS-запросов на общедоступный DNS-сервер Google 8.8.8.8:53: ```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "dns" type = "udp" localIP = "8.8.8.8" localPort = 53 remotePort = 6000

4. Запустите frpc:

`./frpc -c ./frpc.toml`

5. Проверьте разрешение DNS с помощью команды `dig`:

`dig @x.x.x.x -p 6000 www.google.com`

### Проброс Unix domain socket

Откройте доступ к Unix domain socket (например, сокету демона Docker) как к TCP.

Настройте `frps`, как указано выше.

1. Запустите `frpc` со следующей конфигурацией:  ```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "unix_domain_socket"
type = "tcp"
remotePort = 6000
[proxies.plugin]
type = "unix_domain_socket"
unixPath = "/var/run/docker.sock"
  1. Проверьте конфигурацию, получив версию Docker с помощью curl:

curl http://x.x.x.x:6000/version

Откройте простой HTTP-файловый сервер

Откройте простой HTTP-файловый сервер, чтобы получить доступ к файлам, хранящимся в локальной сети, из публичного интернета.

Настройте frps, как описано выше, затем:

  1. Запустите frpc со следующей конфигурацией: ```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "test_static_file" type = "tcp" remotePort = 6000 [proxies.plugin] type = "static_file" localPath = "/tmp/files" stripPrefix = "static" httpUser = "abc" httpPassword = "abc"

2. Откройте `http://x.x.x.x:6000/static/` в браузере и укажите правильные имя пользователя и пароль, чтобы просмотреть файлы в `/tmp/files` на машине `frpc`.

### Включение HTTPS для локального HTTP(S)-сервиса

Вы можете заменить плагин на `https2https` и указать `localAddr` на HTTPS-конечную точку.

1. Запустите `frpc` со следующей конфигурацией:  ```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[proxies]]
name = "test_https2http"
type = "https"
customDomains = ["test.example.com"]

[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:80"
crtPath = "./server.crt"
keyPath = "./server.key"
hostHeaderRewrite = "127.0.0.1"
requestHeaders.set.x-from-where = "frp"
  1. Посетите https://test.example.com.

Приватное предоставление доступа к вашему сервису

Чтобы снизить риски, связанные с предоставлением доступа к определенным сервисам напрямую в публичную сеть, режим STCP (Secret TCP) требует использования предварительно общего ключа для доступа к сервису со стороны других клиентов.

Настройте frps так же, как указано выше.

  1. Запустите frpc на машине B со следующей конфигурацией. В этом примере предоставляется доступ к SSH-сервису (порт 22), обратите внимание на поле secretKey для предварительно общего ключа, а также на то, что поле remotePort здесь удалено: ```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "secret_ssh" type = "stcp" secretKey = "abcdefg" localIP = "127.0.0.1" localPort = 22

2. Запустите ещё один `frpc` (обычно на другой машине C) со следующей конфигурацией для доступа к SSH-сервису с использованием ключа безопасности (поле `secretKey`):  ```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000

[[visitors]]
name = "secret_ssh_visitor"
type = "stcp"
serverName = "secret_ssh"
secretKey = "abcdefg"
bindAddr = "127.0.0.1"
bindPort = 6000
  1. На машине C подключитесь к SSH на машине B, используя эту команду:

ssh -oPort=6000 127.0.0.1

Режим P2P

xtcp предназначен для передачи больших объёмов данных напрямую между клиентами. Сервер frps по-прежнему необходим, поскольку P2P здесь относится только к фактической передаче данных.

Обратите внимание, что он может работать не со всеми типами NAT-устройств. Если xtcp не работает, возможно, стоит переключиться на stcp.

  1. Запустите frpc на машине B и откройте SSH-порт. Обратите внимание, что поле remotePort удалено: ```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

set up a new stun server if the default one is not available.

natHoleStunServer = "xxx"

[[proxies]] name = "p2p_ssh" type = "xtcp" secretKey = "abcdefg" localIP = "127.0.0.1" localPort = 22

2. Запустите ещё один `frpc` (обычно на другой машине C) с конфигурацией для подключения к SSH в режиме P2P:  ```toml
# frpc.toml
serverAddr = "x.x.x.x"
serverPort = 7000
# set up a new stun server if the default one is not available.
# natHoleStunServer = "xxx"

[[visitors]]
name = "p2p_ssh_visitor"
type = "xtcp"
serverName = "p2p_ssh"
secretKey = "abcdefg"
bindAddr = "127.0.0.1"
bindPort = 6000
# when automatic tunnel persistence is required, set it to true
keepTunnelOpen = false
  1. На машине C подключитесь по SSH к машине B, используя эту команду:

ssh -oPort=6000 127.0.0.1

Возможности

Файлы конфигурации

Начиная с v0.52.0, мы поддерживаем TOML, YAML и JSON для конфигурации. Обратите внимание, что INI устарел и будет удалён в будущих выпусках. Новые функции будут доступны только в TOML, YAML или JSON. Пользователям, желающим получить эти новые функции, следует соответствующим образом переключить формат своей конфигурации.

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

В примерах используется формат TOML, но вы по-прежнему можете использовать YAML или JSON.

Эти файлы конфигурации предназначены только для справки. Пожалуйста, не используйте эту конфигурацию напрямую для запуска программы, так как она может вызывать различные проблемы.

Полный файл конфигурации для frps (Сервер)

Полный файл конфигурации для frpc (Клиент)

Использование переменных окружения

На переменные окружения можно ссылаться в файле конфигурации, используя стандартный формат Go:```toml

frpc.toml

serverAddr = "{{ .Envs.FRP_SERVER_ADDR }}" serverPort = 7000

[[proxies]] name = "ssh" type = "tcp" localIP = "127.0.0.1" localPort = 22 remotePort = {{ .Envs.FRP_SSH_REMOTE_PORT }}

С приведённой выше конфигурацией переменные можно передавать в программу `frpc` следующим образом:```
export FRP_SERVER_ADDR=x.x.x.x
export FRP_SSH_REMOTE_PORT=6000
./frpc -c ./frpc.toml

frpc будет рендерить шаблон файла конфигурации, используя переменные окружения ОС. Не забудьте добавить префикс .Envs к вашей ссылке.

Разделение конфигураций на разные файлы

Вы можете разделить несколько конфигураций прокси на разные файлы и включить их в основной файл.```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000 includes = ["./confd/*.toml"]

I need the actual content of chunk 39 to translate it. Please provide the Markdown text you'd like translated.```toml
# ./confd/test.toml

[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 6000

Server Dashboard

Проверяйте статус frp и статистическую информацию о прокси через Dashboard.

Настройте порт для dashboard, чтобы включить эту функцию:```toml

The default value is 127.0.0.1. Change it to 0.0.0.0 when you want to access it from a public network.

webServer.addr = "0.0.0.0" webServer.port = 7500

dashboard's username and password are both optional

webServer.user = "admin" webServer.password = "admin"

Затем посетите `http://[serverAddr]:7500`, чтобы увидеть панель управления, где имя пользователя и пароль — оба `admin`.

Кроме того, вы можете использовать HTTPS-порт, применив wildcard-сертификат для вашего домена или обычный SSL-сертификат:```toml
webServer.port = 7500
# dashboard's username and password are both optional
webServer.user = "admin"
webServer.password = "admin"
webServer.tls.certFile = "server.crt"
webServer.tls.keyFile = "server.key"

Затем посетите https://[serverAddr]:7500, чтобы увидеть панель управления через защищённое HTTPS-соединение, где имя пользователя и пароль — оба admin.

dashboard

Клиентский административный интерфейс

Клиентский административный интерфейс помогает проверять и управлять конфигурацией и прокси frpc.

Настройте адрес для административного интерфейса, чтобы включить эту функцию:```toml webServer.addr = "127.0.0.1" webServer.port = 7400 webServer.user = "admin" webServer.password = "admin"

Затем откройте `http://127.0.0.1:7400`, чтобы увидеть веб-интерфейс администратора, где имя пользователя и пароль — оба `admin`.

#### Динамическое управление прокси (Store)

Вы можете динамически создавать, обновлять и удалять прокси и посетителей во время выполнения через веб-интерфейс или API, без перезапуска frpc.

Чтобы включить эту функцию, настройте `store.path`, указав файл для сохранения конфигураций:```toml
[store]
path = "./db.json"

Прокси и посетители, управляемые через Store, сохраняются на диск и автоматически восстанавливаются при перезапуске frpc. Они работают вместе с прокси, определёнными в файле конфигурации — записи Store имеют приоритет при совпадении имён.

Monitor

Когда веб-сервер включён, frps сохраняет данные мониторинга в кэше в течение 7 дней. Они будут очищены после перезапуска процесса.

Prometheus также поддерживается.

Prometheus

Сначала включите dashboard, затем настройте enablePrometheus = true в frps.toml.

http://{dashboard_addr}/metrics будет предоставлять данные мониторинга Prometheus.

Аутентификация клиента

Существует 2 метода аутентификации для проверки frpc с помощью frps.

Вы можете решить, какой из них использовать, настроив auth.method в frpc.toml и frps.toml; по умолчанию используется token.

Настройка auth.additionalScopes = ["HeartBeats"] будет использовать настроенный метод аутентификации для добавления и проверки аутентификации при каждом heartbeat между frpc и frps.

Настройка auth.additionalScopes = ["NewWorkConns"] сделает то же самое для каждого нового рабочего соединения между frpc и frps.

Аутентификация по токену

При указании auth.method = "token" в frpc.toml и frps.toml будет использоваться аутентификация на основе токена.

Убедитесь, что указан одинаковый auth.token в frps.toml и frpc.toml, чтобы frpc прошёл проверку frps.

Источник токена

frp поддерживает чтение токенов аутентификации из внешних источников с помощью конфигурации tokenSource. В настоящее время поддерживается файловый источник токена.

Файловый источник токена:```toml

frpc.toml

auth.method = "token" auth.tokenSource.type = "file" auth.tokenSource.file.path = "/path/to/token/file"

Токен будет прочитан из указанного файла при запуске. Это полезно для сценариев, где токенами управляют внешние системы или их необходимо хранить отдельно от файлов конфигурации по соображениям безопасности.

#### OIDC-аутентификация

При указании `auth.method = "oidc"` в `frpc.toml` и `frps.toml` будет использоваться аутентификация на основе OIDC.

OIDC расшифровывается как OpenID Connect, и используемый поток называется [Client Credentials Grant](https://tools.ietf.org/html/rfc6749#section-4.4).

Чтобы использовать этот тип аутентификации, настройте `frpc.toml` и `frps.toml` следующим образом:```toml
# frps.toml
auth.method = "oidc"
auth.oidc.issuer = "https://example-oidc-issuer.com/"
auth.oidc.audience = "https://oidc-audience.com/.default"
$ python3 -m pip install --upgrade pip
$ python3 -m pip install --upgrade git+https://github.com/KoreanThinker/baduk.git

Usage

from baduk import Game, Move, Player

game = Game()
game.play(Move(Player.black, (15, 15)))
game.play(Move(Player.white, (3, 3)))
game.play(Move(Player.black, (16, 16)))
game.play(Move(Player.white, (4, 4)))
game.play(Move(Player.black, (17, 17)))
game.play(Move(Player.white, (5, 5)))
game.play(Move(Player.black, (18, 18)))
game.play(Move(Player.white, (6, 6)))
game.play(Move(Player.black, (19, 19)))
game.play(Move(Player.white, (7, 7)))
game.play(Move(Player.black, (20, 20)))
game.play(Move(Player.white, (8, 8)))
game.play(Move(Player.black, (21, 21)))
game.play(Move(Player.white, (9, 9)))
game.play(Move(Player.black, (22, 22)))
game.play(Move(Player.white, (10, 10)))
game.play(Move(Player.black, (23, 23)))
game.play(Move(Player.white, (11, 11)))
game.play(Move(Player.black, (24, 24)))
game.play(Move(Player.white, (12, 12)))
game.play(Move(Player.black, (25, 25)))
game.play(Move(Player.white, (13, 13)))
game.play(Move(Player.black, (26, 26)))
game.play(Move(Player.white, (14, 14)))
game.play(Move(Player.black, (27, 27)))
game.play(Move(Player.white, (15, 15)))
game.play(Move(Player.black, (28, 28)))
game.play(Move(Player.white, (16, 16)))
game.play(Move(Player.black, (29, 29)))
game.play(Move(Player.white, (17, 17)))
game.play(Move(Player.black, (30, 30)))
game.play(Move(Player.white, (18, 18)))
game.play(Move(Player.black, (31, 31)))
game.play(Move(Player.white, (19, 19)))
game.play(Move(Player.black, (32, 32)))
game.play(Move(Player.white, (20, 20)))
game.play(Move(Player.black, (33, 33)))
game.play(Move(Player.white, (21, 21)))
game.play(Move(Player.black, (34, 34)))
game.play(Move(Player.white, (22, 22)))
game.play(Move(Player.black, (35, 35)))
game.play(Move(Player.white, (23, 23)))
game.play(Move(Player.black, (36, 36)))
game.play(Move(Player.white, (24, 24)))
game.play(Move(Player.black, (37, 37)))
game.play(Move(Player.white, (25, 25)))
game.play(Move(Player.black, (38, 38)))
game.play(Move(Player.white, (26, 26)))
game.play(Move(Player.black, (39, 39)))
game.play(Move(Player.white, (27, 27)))
game.play(Move(Player.black, (40, 40)))
game.play(Move(Player.white, (28, 28)))
game.play(Move(Player.black, (41, 41)))
game.play(Move(Player.white, (29, 29)))
game.play(Move(Player.black, (42, 42)))
game.play(Move(Player.white, (30, 30)))
game.play(Move(Player.black, (43, 43)))
game.play(Move(Player.white, (31, 31)))
game.play(Move(Player.black, (44, 44)))
game.play(Move(Player.white, (32, 32)))
game.play(Move(Player.black, (45, 45)))
game.play(Move(Player.white, (33, 33)))
game.play(Move(Player.black, (46, 46)))
game.play(Move(Player.white, (34, 34)))
game.play(Move(Player.black, (47, 47)))
game.play(Move(Player.white, (35, 35)))
game.play(Move(Player.black, (48, 48)))
game.play(Move(Player.white, (36, 36)))
game.play(Move(Player.black, (49, 49)))
game.play(Move(Player.white, (37, 37)))
game.play(Move(Player.black, (50, 50)))
game.play(Move(Player.white, (38, 38)))
game.play(Move(Player.black, (51, 51)))
game.play(Move(Player.white, (39, 39)))
game.play(Move(Player.black, (52, 52)))
game.play(Move(Player.white, (40, 40)))
game.play(Move(Player.black, (53, 53)))
game.play(Move(Player.white, (41, 41)))
game.play(Move(Player.black, (54, 54)))
game.play(Move(Player.white, (42, 42)))
game.play(Move(Player.black, (55, 55)))
game.play(Move(Player.white, (43, 43)))
game.play(Move(Player.black, (56, 56)))
game.play(Move(Player.white, (44, 44)))
game.play(Move(Player.black, (57, 57)))
game.play(Move(Player.white, (45, 45)))
game.play(Move(Player.black, (58, 58)))
game.play(Move(Player.white, (46, 46)))
game.play(Move(Player.black, (59, 59)))
game.play(Move(Player.white, (47, 47)))
game.play(Move(Player.black, (60, 60)))
game.play(Move(Player.white, (48, 48)))
game.play(Move(Player.black, (61, 61)))
game.play(Move(Player.white, (49, 49)))
game.play(Move(Player.black, (62, 62)))
game.play(Move(Player.white, (50, 50)))
game.play(Move(Player.black, (63, 63)))
game.play(Move(Player.white, (51, 51)))
game.play(Move(Player.black, (64, 64)))
game.play(Move(Player.white, (52, 52)))
game.play(Move(Player.black, (65, 65)))
game.play(Move(Player.white, (53, 53)))
game.play(Move(Player.black, (66, 66)))
game.play(Move(Player.white, (54, 54)))
game.play(Move(Player.black, (67, 67)))
game.play(Move(Player.white, (55, 55)))
game.play(Move(Player.black, (68, 68)))
game.play(Move(Player.white, (56, 56)))
game.play(Move(Player.black, (69, 69)))
game.play(Move(Player.white, (57, 57)))
game.play(Move(Player.black, (70, 70)))
game.play(Move(Player.white, (58, 58)))
game.play(Move(Player.black, (71, 71)))
game.play(Move(Player.white, (59, 59)))
game.play(Move(Player.black, (72, 72)))
game.play(Move(Player.white, (60, 60)))
game.play(Move(Player.black, (73, 73)))
game.play(Move(Player.white, (61, 61)))
game.play(Move(Player.black, (74, 74)))
game.play(Move(Player.white, (62, 62)))
game.play(Move(Player.black, (75, 75)))
game.play(Move(Player.white, (63, 63)))
game.play(Move(Player.black, (76, 76)))
game.play(Move(Player.white, (64, 64)))
game.play(Move(Player.black, (77, 77)))
game.play(Move(Player.white, (65, 65)))
game.play(Move(Player.black, (78, 78)))
game.play(Move(Player.white, (66, 66)))
game.play(Move(Player.black, (79, 79)))
game.play(Move(Player.white, (67, 67)))
game.play(Move(Player.black, (80, 80)))
game.play(Move(Player.white, (68, 68)))
game.play(Move(Player.black, (81, 81)))
game.play(Move(Player.white, (69, 69)))
game.play(Move(Player.black, (82, 82)))
game.play(Move(Player.white, (70, 70)))
game.play(Move(Player.black, (83, 83)))
game.play(Move(Player.white, (71, 71)))
game.play(Move(Player.black, (84, 84)))
game.play(Move(Player.white, (72, 72)))
game.play(Move(Player.black, (85, 85)))
game.play(Move(Player.white, (73, 73)))
game.play(Move(Player.black, (86, 86)))
game.play(Move(Player.white, (74, 74)))
game.play(Move(Player.black, (87, 87)))
game.play(Move(Player.white, (75, 75)))
game.play(Move(Player.black, (88, 88)))
game.play(Move(Player.white, (76, 76)))
game.play(Move(Player.black, (89, 89)))
game.play(Move(Player.white, (77, 77)))
game.play(Move(Player.black, (90, 90)))
game.play(Move(Player.white, (78, 78)))
game.play(Move(Player.black, (91, 91)))
game.play(Move(Player.white, (79, 79)))
game.play(Move(Player.black, (92, 92)))
game.play(Move(Player.white, (80, 80)))
game.play(Move(Player.black, (93, 93)))
game.play(Move(Player.white, (81, 81)))
game.play(Move(Player.black, (94, 94)))
game.play(Move(Player.white, (82, 82)))
game.play(Move(Player.black, (95, 95)))
game.play(Move(Player.white, (83, 83)))
game.play(Move(Player.black, (96, 96)))
game.play(Move(Player.white, (84, 84)))
game.play(Move(Player.black, (97, 97)))
game.play(Move(Player.white, (85, 85)))
game.play(Move(Player.black, (98, 98)))
game.play(Move(Player.white, (86, 86)))
game.play(Move(Player.black, (99, 99)))
game.play(Move(Player.white, (87, 87)))
game.play(Move(Player.black, (100, 100)))
game.play(Move(Player.white, (88, 88)))
game.play(Move(Player.black, (101, 101)))
game.play(Move(Player.white, (89, 89)))
game.play(Move(Player.black, (102, 102)))
game.play(Move(Player.white, (90, 90)))
game.play(Move(Player.black, (103, 103)))
game.play(Move(Player.white, (91, 91)))
game.play(Move(Player.black, (104, 104)))
game.play(Move(Player.white, (92, 92)))
game.play(Move(Player.black, (105, 105)))
game.play(Move(Player.white, (93, 93)))
game.play(Move(Player.black, (106, 106)))
game.play(Move(Player.white, (94, 94)))
game.play(Move(Player.black, (107, 107)))
game.play(Move(Player.white, (95, 95)))
game.play(Move(Player.black, (108, 108)))
game.play(Move(Player.white, (96, 96)))
game.play(Move(Player.black, (109, 109)))
game.play(Move(Player.white, (97, 97)))
game.play(Move(Player.black, (110, 110)))
game.play(Move(Player.white, (98, 98)))
game.play(Move(Player.black, (111, 111)))
game.play(Move(Player.white, (99, 99)))
game.play(Move(Player.black, (112, 112)))
game.play(Move(Player.white, (100, 100)))
game.play(Move(Player.black, (113, 113)))
game.play(Move(Player.white, (101, 101)))
game.play(Move(Player.black, (114, 114)))
game.play(Move(Player.white, (102, 102)))
game.play(Move(Player.black, (115, 115)))
game.play(Move(Player.white, (103, 103)))
game.play(Move(Player.black, (116, 116)))
game.play(Move(Player.white, (104, 104)))
game.play(Move(Player.black, (117, 117)))
game.play(Move(Player.white, (105, 105)))
game.play(Move(Player.black, (118, 118)))
game.play(Move(Player.white, (106, 106)))
game.play(Move(Player.black, (119, 119)))
game.play(Move(Player.white, (107, 107)))
game.play(Move(Player.black, (120, 120)))
game.play(Move(Player.white, (108, 108)))
game.play(Move(Player.black, (121, 121)))
game.play(Move(Player.white, (109, 109)))
game.play(Move(Player.black, (122, 122)))
game.play(Move(Player.white, (110, 110)))
game.play(Move(Player.black, (123, 123)))
game.play(Move(Player.white, (111, 111)))
game.play(Move(Player.black, (124, 124)))
game.play(Move(Player.white, (112, 112)))
game.play(Move(Player.black, (125, 125)))
game.play(Move(Player.white, (113, 113)))
game.play(Move(Player.black, (126, 126)))
game.play(Move(Player.white, (114, 114)))
game.play(Move(Player.black, (127, 127)))
game.play(Move(Player.white, (115, 115)))
game.play(Move(Player.black, (128, 128)))
game.play(Move(Player.white, (116, 116)))
game.play(Move(Player.black, (129, 129)))
game.play(Move(Player.white, (117, 117)))
game.play(Move(Player.black, (130, 130)))
game.play(Move(Player.white, (118, 118)))
game.play(Move(Player.black, (131, 131)))
game.play(Move(Player.white, (119, 119)))
game.play(Move(Player.black, (132, 132)))
game.play(Move(Player.white, (120, 120)))
game.play(Move(Player.black, (133, 133)))
game.play(Move(Player.white, (121, 121)))
game.play(Move(Player.black, (134, 134)))
game.play(Move(Player.white, (122, 122)))
game.play(Move(Player.black, (135, 135)))
game.play(Move(Player.white, (123, 123)))
game.play(Move(Player.black, (136, 136)))
game.play(Move(Player.white, (124, 124)))
game.play(Move(Player.black, (137, 137)))
game.play(Move(Player.white, (125, 125)))
game.play(Move(Player.black, (138, 138)))
game.play(Move(Player.white, (126, 126)))
game.play(Move(Player.black, (139, 139)))
game.play(Move(Player.white, (127, 127)))
game.play(Move(Player.black, (140, 140)))
game.play(Move(Player.white, (128, 128)))
game.play(Move(Player.black, (141, 141)))
game.play(Move(Player.white, (129, 129)))
game.play(Move(Player.black, (142, 142)))
game.play(Move(Player.white, (130, 130)))
game.play(Move(Player.black, (143, 143)))
game.play(Move(Player.white, (131, 131)))
game.play(Move(Player.black, (144, 144)))
game.play(Move(Player.white, (132, 132)))
game.play(Move(Player.black, (145, 145)))
game.play(Move(Player.white, (133, 133)))
game.play(Move(Player.black, (146, 146)))
game.play(Move(Player.white, (134, 134)))
game.play(Move(Player.black, (147, 147)))
game.play(Move(Player.white, (135, 135)))
game.play(Move(Player.black, (148, 148)))
game.play(Move(Player.white, (136, 136)))
game.play(Move(Player.black, (149, 149)))
game.play(Move(Player.white, (137, 137)))
game.play(Move(Player.black, (150, 150)))
game.play(Move(Player.white, (138, 138)))
game.play(Move(Player.black, (151, 151)))
game.play(Move(Player.white, (139, 139)))
game.play(Move(Player.black, (152, 152)))
game.play(Move(Player.white, (140, 140)))
game.play(Move(Player.black, (153, 153)))
game.play(Move(Player.white, (141, 141)))
game.play(Move(Player.black, (154, 154)))
game.play(Move(Player.white, (142, 142)))
game.play(Move(Player.black, (155, 155)))
game.play(Move(Player.white, (143, 143)))
game.play(Move(Player.black, (156, 156)))
game.play(Move(Player.white, (144, 144)))
game.play(Move(Player.black, (157, 157)))
game.play(Move(Player.white, (145, 145)))
game.play(Move(Player.black, (158, 158)))
game.play(Move(Player.white, (146, 146)))
game.play(Move(Player.black, (159, 159)))
game.play(Move(Player.white, (147, 147)))
game.play(Move(Player.black, (160, 160)))
game.play(Move(Player.white, (148, 148)))
game.play(Move(Player.black, (161, 161)))
game.play(Move(Player.white, (149, 149)))
game.play(Move(Player.black, (162, 162)))
game.play(Move(Player.white, (150, 150)))
game.play(Move(Player.black, (163, 163)))
game.play(Move(Player.white, (151, 151)))
game.play(Move(Player.black, (164, 164)))
game.play(Move(Player.white, (152, 152)))
game.play(Move(Player.black, (165, 165)))
game.play(Move(Player.white, (153, 153)))
game.play(Move(Player.black, (166, 166)))
game.play(Move(Player.white, (154, 154)))
game.play(Move(Player.black, (167, 167)))
game.play(Move(Player.white, (155, 155)))
game.play(Move(Player.black, (168, 168)))
game.play(Move(Player.white, (156, 156)))
game.play(Move(Player.black, (169, 169)))
game.play(Move(Player.white, (157, 157)))
game.play(Move(Player.black, (170, 170)))
game.play(Move(Player.white, (158, 158)))
game.play(Move(Player.black, (171, 171)))
game.play(Move(Player.white, (159, 159)))
game.play(Move(Player.black, (172, 172)))
game.play(Move(Player.white, (160, 160)))
game.play(Move(Player.black, (173, 173)))
game.play(Move(Player.white, (161, 161)))
game.play(Move(Player.black, (174, 174)))
game.play(Move(Player.white, (162, 162)))
game.play(Move(Player.black, (175, 175)))
game.play(Move(Player.white, (163, 163)))
game.play(Move(Player.black, (176, 176)))
game.play(Move(Player.white, (164, 164)))
game.play(Move(Player.black, (177, 177)))
game.play(Move(Player.white, (165, 165)))
game.play(Move(Player.black, (178, 178)))
game.play(Move(Player.white, (166, 166)))
game.play(Move(Player.black, (179, 179)))
game.play(Move(Player.white, (167, 167)))
game.play(Move(Player.black, (180, 180)))
game.play(Move(Player.white, (168, 168)))
game.play(Move(Player.black, (181, 181)))
game.play(Move(Player.white, (169, 169)))
game.play(Move(Player.black, (182, 182)))
game.play(Move(Player.white, (170, 170)))
game.play(Move(Player.black, (183, 183)))
game.play(Move(Player.white, (171, 171)))
game.play(Move(Player.black, (184, 184)))
game.play(Move(Player.white, (172, 172)))
game.play(Move(Player.black, (185, 185)))
game.play(Move(Player.white, (173, 173)))
game.play(Move(Player.black, (186, 186)))
game.play(Move(Player.white, (174, 174)))
game.play(Move(Player.black, (187, 187)))
game.play(Move(Player.white, (175, 175)))
game.play(Move(Player.black, (188, 188)))
game.play(Move(Player.white, (176, 176)))
game.play(Move(Player.black, (189, 189)))
game.play(Move(Player.white, (177, 177)))
game.play(Move(Player.black, (190, 190)))
game.play(Move(Player.white, (178, 178)))
game.play(Move(Player.black, (191, 191)))
game.play(Move(Player.white, (179, 179)))
game.play(Move(Player.black, (192, 192)))
game.play(Move(Player.white, (180, 180)))
game.play(Move(Player.black, (193, 193)))
game.play(Move(Player.white, (181, 181)))
game.play(Move(Player.black, (194, 194)))
game.play(Move(Player.white, (182, 182)))
game.play(Move(Player.black, (195, 195)))
game.play(Move(Player.white, (183, 183)))
game.play(Move(Player.black, (196, 196)))
game.play(Move(Player.white, (184, 184)))
game.play(Move(Player.black, (197, 197)))
game.play(Move(Player.white, (185, 185)))
game.play(Move(Player.black, (198, 198)))
game.play(Move(Player.white, (186, 186)))
game.play(Move(Player.black, (199, 199)))
game.play(Move(Player.white, (187, 187)))
game.play(Move(Player.black, (200, 200)))
game.play(Move(Player.white, (188, 188)))
game.play(Move(Player.black, (201, 201)))
game.play(Move(Player.white, (189, 189)))
game.play(Move(Player.black, (202, 202)))
game.play(Move(Player.white, (190, 190)))
game.play(Move(Player.black, (203, 203)))
game.play(Move(Player.white, (191, 191)))
game.play(Move(Player.black, (204, 204)))
game.play(Move(Player.white, (192, 192)))
game.play(Move(Player.black, (205, 205)))
game.play(Move(Player.white, (193, 193)))
game.play(Move(Player.black, (206, 206)))
game.play(Move(Player.white, (194, 194)))
game.play(Move(Player.black, (207, 207)))
game.play(Move(Player.white, (195, 195)))
game.play(Move(Player.black, (208, 208)))
game.play(Move(Player.white, (196, 196)))
game.play(Move(Player.black, (209, 209)))
game.play(Move(Player.white, (197, 197)))
game.play(Move(Player.black, (210, 210)))
game.play(Move(Player.white, (198, 198)))
game.play(Move(Player.black, (211, 211)))
game.play(Move(Player.white, (199, 199)))
game.play(Move(Player.black, (212, 212)))
game.play(Move(Player.white, (200, 200)))
game.play(Move(Player.black, (213, 213)))
game.play(Move(Player.white, (201, 201)))
game.play(Move(Player.black, (214, 214)))
game.play(Move(Player.white, (202, 202)))
game.play(Move(Player.black, (215, 215)))
game.play(Move(Player.white, (203, 203)))
game.play(Move(Player.black, (216, 216)))
game.play(Move(Player.white, (204, 204)))
game.play(Move(Player.black, (217, 217)))
game.play(Move(Player.white, (205, 205)))
game.play(Move(Player.black, (218, 218)))
game.play(Move(Player.white, (206, 206)))
game.play(Move(Player.black, (219, 219)))
game.play(Move(Player.white, (207, 207)))
game.play(Move(Player.black, (220, 220)))
game.play(Move(Player.white, (208, 208)))
game.play(Move(Player.black, (221, 221)))
game.play(Move(Player.white, (209, 209)))
game.play(Move(Player.black, (222, 222)))
game.play(Move(Player.white, (210, 210)))
game.play(Move(Player.black, (223, 223)))
game.play(Move(Player.white, (211, 211)))
game.play(Move(Player.black, (224, 224)))
game.play(Move(Player.white, (212, 212)))
game.play(Move(Player.black, (225, 225)))
game.play(Move(Player.white, (213, 213)))
game.play(Move(Player.black, (226, 226)))
game.play(Move(Player.white, (214, 214)))
game.play(Move(Player.black, (227, 227)))
game.play(Move(Player.white, (215, 215)))
game.play(Move(Player.black, (228, 228)))
game.play(Move(Player.white, (216, 216)))
game.play(Move(Player.black, (229, 229)))
game.play(Move(Player.white, (217, 217)))
game.play(Move(Player.black, (230, 230)))
game.play(Move(Player.white, (218, 218)))
game.play(Move(Player.black, (231, 231)))
game.play(Move(Player.white, (219, 219)))
game.play(Move(Player.black, (232, 232)))
game.play(Move(Player.white, (220, 220)))
game.play(Move(Player.black, (233, 233)))
game.play(Move(Player.white, (221, 221)))
game.play(Move(Player.black, (234, 234)))
game.play(Move(Player.white, (222, 222)))
game.play(Move(Player.black, (235, 235)))
game.play(Move(Player.white, (223, 223)))
game.play(Move(Player.black, (236, 236)))
game.play(Move(Player.white, (224, 224)))
game.play(Move(Player.black, (237, 237)))
game.play(Move(Player.white, (225, 225)))
game.play(Move(Player.black, (238, 238)))
game.play(Move(Player.white, (226, 226)))
game.play(Move(Player.black, (239, 239)))
game.play(Move(Player.white, (227, 227)))
game.play(Move(Player.black, (240, 240)))
game.play(Move(Player.white, (228, 228)))
game.play(Move(Player.black, (241, 241)))
game.play(Move(Player.white, (229, 229)))
game.play(Move(Player.black, (242, 242)))
game.play(Move(Player.white, (230, 230)))
game.play(Move(Player.black, (243, 243)))
game.play(Move(Player.white, (231, 231)))
game.play(Move(Player.black, (244, 244)))
game.play(Move(Player.white, (232, 232)))
game.play(Move(Player.black, (245, 245)))
game.play(Move(Player.white, (233, 233)))
game.play(Move(Player.black, (246, 246)))
game.play(Move(Player.white, (234, 234)))
game.play(Move(Player.black, (247, 247)))
game.play(Move(Player.white, (235, 235)))
game.play(Move(Player.black, (248, 248)))
game.play(Move(Player.white, (236, 236)))
game.play(Move(Player.black, (249, 249)))
game.play(Move(Player.white, (237, 237)))
game.play(Move(Player.black, (250, 250)))
game.play(Move(Player.white, (238, 238)))
game.play(Move(Player.black, (251, 251)))
game.play(Move(Player.white, (239, 239)))
game.play(Move(Player.black, (252, 252)))
game.play(Move(Player.white, (240, 240)))
game.play(Move(Player.black, (253, 253)))
game.play(Move(Player.white, (241, 241)))
game.play(Move(Player.black, (254, 254)))
game.play(Move(Player.white, (242, 242)))
game.play(Move(Player.black, (255, 255)))
game.play(Move(Player.white, (243, 243)))
game.play(Move(Player.black, (256, 256)))
game.play(Move(Player.white, (244, 244)))
game.play(Move(Player.black, (257, 257)))
game.play(Move(Player.white, (245, 245)))
game.play(Move(Player.black, (258, 258)))
game.play(Move(Player.white, (246, 246)))
game.play(Move(Player.black, (259, 259)))
game.play(Move(Player.white, (247, 247)))
game.play(Move(Player.black, (260, 260)))
game.play(Move(Player.white, (248, 248)))
game.play(Move(Player.black, (261, 261)))
game.play(Move(Player.white, (249, 249)))
game.play(Move(Player.black, (262, 262)))
game.play(Move(Player.white, (250, 250)))
game.play(Move(Player.black, (263, 263)))
game.play(Move(Player.white, (251, 251)))
game.play(Move(Player.black, (264, 264)))
game.play(Move(Player.white, (252, 252)))
game.play(Move(Player.black, (265, 265)))
game.play(Move(Player.white, (253, 253)))
game.play(Move(Player.black, (266, 266)))
game.play(Move(Player.white, (254, 254)))
game.play(Move(Player.black, (267, 267)))
game.play(Move(Player.white, (255, 255)))
game.play(Move(Player.black, (268, 268)))
game.play(Move(Player.white, (256, 256)))
game.play(Move(Player.black, (269, 269)))
game.play(Move(Player.white, (257, 257)))
game.play(Move(Player.black, (270, 270)))
game.play(Move(Player.white, (258, 258)))
game.play(Move(Player.black, (271, 271)))
game.play(Move(Player.white, (259, 259)))
game.play(Move(Player.black, (272, 272)))
game.play(Move(Player.white, (260, 260)))
game.play(Move(Player.black, (273, 273)))
game.play(Move(Player.white, (261, 261)))
game.play(Move(Player.black, (274, 274)))
game.play(Move(Player.white, (262, 262)))
game.play(Move(Player.black, (275, 275)))
game.play(Move(Player.white, (263, 263)))
game.play(Move(Player.black, (276, 276)))
game.play(Move(Player.white, (264, 264)))
game.play(Move(Player.black, (277, 277)))
game.play(Move(Player.white, (265, 265)))
game.play(Move(Player.black, (278, 278)))
game.play(Move(Player.white, (266, 266)))
game.play(Move(Player.black, (279, 279)))
game.play(Move(Player.white, (267, 267)))
game.play(Move(Player.black, (280, 280)))
game.play(Move(Player.white, (268, 268)))
game.play(Move(Player.black, (281, 281)))
game.play(Move(Player.white, (269, 269)))
game.play(Move(Player.black, (282, 282)))
game.play(Move(Player.white, (270, 270)))
game.play(Move(Player.black, (283, 283)))
game.play(Move(Player.white, (271, 271)))
game.play(Move(Player.black, (284, 284)))
game.play(Move(Player.white, (272, 272)))
game.play(Move(Player.black, (285, 285)))
game.play(Move(Player.white, (273, 273)))
game.play(Move(Player.black, (286, 286)))
game.play(Move(Player.white, (274, 274)))
game.play(Move(Player.black, (287, 287)))
game.play(Move(Player.white, (275, 275)))
game.play(Move(Player.black, (288, 288)))
game.play(Move(Player.white, (276, 276)))
game.play(Move(Player.black, (289, 289)))
game.play(Move(Player.white, (277, 277)))
game.play(Move(Player.black, (290, 290)))
game.play(Move(Player.white, (278, 278)))
game.play(Move(Player.black, (291, 291)))
game.play(Move(Player.white, (279, 279)))
game.play(Move(Player.black, (292, 292)))
game.play(Move(Player.white, (280, 280)))
game.play(Move(Player.black, (293, 293)))
game.play(Move(Player.white, (281, 281)))
game.play(Move(Player.black, (294, 294)))
game.play(Move(Player.white, (282, 282)))
game.play(Move(Player.black, (295, 295)))
game.play(Move(Player.white, (283, 283)))
game.play(Move(Player.black, (296, 296)))
game.play(Move(Player.white, (284, 284)))
game.play(Move(Player.black, (297, 297)))
game.play(Move(Player.white, (285, 285)))
game.play(Move(Player.black, (298, 298)))
game.play(Move(Player.white, (286, 286)))
game.play(Move(Player.black, (299, 299)))
game.play(Move(Player.white, (287, 287)))
game.play(Move(Player.black, (300, 300)))
game.play(Move(Player.white, (288, 288)))
game.play(Move(Player.black, (301, 301)))
game.play(Move(Player.white, (289, 289)))
game.play(Move(Player.black, (302, 302)))
game.play(Move(Player.white, (290, 290)))
game.play(Move(Player.black, (303, 303)))
game.play(Move(Player.white, (291, 291)))
game.play(Move(Player.black, (304, 304)))
game.play(Move(Player.white, (292, 292)))
game.play(Move(Player.black, (305, 305```toml
# frpc.toml
auth.method = "oidc"
auth.oidc.clientID = "98692467-37de-409a-9fac-bb2585826f18" # Replace with OIDC client ID
auth.oidc.clientSecret = "oidc_secret"
auth.oidc.audience = "https://oidc-audience.com/.default"
auth.oidc.tokenEndpointURL = "https://example-oidc-endpoint.com/oauth2/v2.0/token"

Шифрование и сжатие

Эти функции отключены по умолчанию. Вы можете включить шифрование и/или сжатие:```toml

frpc.toml

[[proxies]] name = "ssh" type = "tcp" localPort = 22 remotePort = 6000 transport.useEncryption = true transport.useCompression = true

#### TLS

Начиная с v0.50.0, значения по умолчанию для `transport.tls.enable` и `transport.tls.disableCustomTLSFirstByte` были изменены на true, и TLS включён по умолчанию.

Для мультиплексирования портов frp отправляет первый байт `0x17` для установления TLS-соединения. Это действует только в том случае, если вы установите `transport.tls.disableCustomTLSFirstByte` в false.

Чтобы **принудительно** заставить `frps` принимать только TLS-соединения — укажите `transport.tls.force = true` в `frps.toml`. **Это необязательно.**

**Настройки TLS для `frpc`:**```toml
transport.tls.enable = true
transport.tls.certFile = "certificate.crt"
transport.tls.keyFile = "certificate.key"
transport.tls.trustedCaFile = "ca.crt"

frps параметры TLS:```toml transport.tls.force = true transport.tls.certFile = "certificate.crt" transport.tls.keyFile = "certificate.key" transport.tls.trustedCaFile = "ca.crt"

Вам понадобится **корневой сертификат CA** и **как минимум один SSL/TLS-сертификат**. Он **может** быть самоподписанным или обычным (например, от Let's Encrypt или другого поставщика SSL/TLS-сертификатов).

Если вы используете `frp` через IP-адрес, а не имя хоста, обязательно укажите соответствующий IP-адрес в поле Subject Alternative Name (SAN) при генерации SSL/TLS-сертификатов.

Пример:

* Подготовьте файл конфигурации openssl. Он находится по пути `/etc/pki/tls/openssl.cnf` в Linux и `/System/Library/OpenSSL/openssl.cnf` в MacOS, и вы можете скопировать его в текущий каталог, например: `cp /etc/pki/tls/openssl.cnf ./my-openssl.cnf`. Если его нет, вы можете создать его самостоятельно, например:```
cat > my-openssl.cnf << EOF
[ ca ]
default_ca = CA_default
[ CA_default ]
x509_extensions = usr_cert
[ req ]
default_bits        = 2048
default_md          = sha256
default_keyfile     = privkey.pem
distinguished_name  = req_distinguished_name
attributes          = req_attributes
x509_extensions     = v3_ca
string_mask         = utf8only
[ req_distinguished_name ]
[ req_attributes ]
[ usr_cert ]
basicConstraints       = CA:FALSE
nsComment              = "OpenSSL Generated Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = CA:true
EOF
  • собрать сертификаты CA:``` openssl genrsa -out ca.key 2048 openssl req -x509 -new -nodes -key ca.key -subj "/CN=example.ca.com" -days 5000 -out ca.crt
* сборка сертификатов frps:```
openssl genrsa -out server.key 2048

openssl req -new -sha256 -key server.key \
    -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=server.com" \
    -reqexts SAN \
    -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com")) \
    -out server.csr

openssl x509 -req -days 365 -sha256 \
	-in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
	-extfile <(printf "subjectAltName=DNS:localhost,IP:127.0.0.1,DNS:example.server.com") \
	-out server.crt
  • сборка сертификатов frpc:``` openssl genrsa -out client.key 2048 openssl req -new -sha256 -key client.key
    -subj "/C=XX/ST=DEFAULT/L=DEFAULT/O=DEFAULT/CN=client.com"
    -reqexts SAN
    -config <(cat my-openssl.cnf <(printf "\n[SAN]\nsubjectAltName=DNS:client.com,DNS:example.client.com"))
    -out client.csr

openssl x509 -req -days 365 -sha256
-in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial
-extfile <(printf "subjectAltName=DNS:client.com,DNS:example.client.com")
-out client.crt

### Горячая перезагрузка конфигурации frpc

Поля `webServer` обязательны для включения HTTP API:```toml
# frpc.toml
webServer.addr = "127.0.0.1"
webServer.port = 7400

Затем выполните команду frpc reload -c ./frpc.toml и подождите около 10 секунд, чтобы frpc создал, обновил или удалил прокси.

Обратите внимание, что глобальные параметры клиента не будут изменены, кроме 'start'.

start — это глобальный белый список, который оценивается после объединения всех источников (файл конфигурации/include/store). Если start не пуст, любой прокси или посетитель, не указанный в нём, не будет запущен, включая записи, созданные через Store API.

start сохраняется в основном для совместимости и в целом не рекомендуется для новых конфигураций. Предпочитайте enabled для отдельных прокси/посетителей и оставляйте start пустым, если вы явно не хотите такого поведения глобального белого списка.

Вы можете выполнить команду frpc verify -c ./frpc.toml перед перезагрузкой, чтобы проверить наличие ошибок конфигурации.

Получение статуса прокси от клиента

Используйте frpc status -c ./frpc.toml, чтобы получить статус всех прокси. Поля webServer обязательны для включения HTTP API.

Разрешение только определённых портов на сервере

allowPorts в frps.toml используется для предотвращения злоупотребления портами:```toml

frps.toml

allowPorts = [ { start = 2000, end = 3000 }, { single = 3001 }, { single = 3003 }, { start = 4000, end = 50000 } ]

### Повторное использование портов

`vhostHTTPPort` и `vhostHTTPSPort` во frps могут использовать тот же порт, что и `bindPort`. frps определит протокол соединения и обработает его соответствующим образом.

Следует обратить внимание на то, что если вы хотите настроить `vhostHTTPSPort` и `bindPort` на один и тот же порт, вам сначала нужно установить `transport.tls.disableCustomTLSFirstByte` в значение false.

В будущем мы хотели бы попробовать разрешить нескольким прокси привязываться к одному и тому же удалённому порту с разными протоколами.

### Ограничение пропускной способности

#### Для каждого прокси```toml
# frpc.toml

[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
transport.bandwidthLimit = "1MB"

Установите transport.bandwidthLimit в конфигурации каждого прокси, чтобы включить эту функцию. Поддерживаемые единицы измерения: MB и KB.

Установите transport.bandwidthLimitMode в значение client или server, чтобы ограничить пропускную способность на стороне клиента или сервера. По умолчанию используется client.

Мультиплексирование TCP-потоков

frp поддерживает мультиплексирование TCP-потоков начиная с версии v0.10.0, аналогично мультиплексированию HTTP2, при котором все логические соединения с одним и тем же frpc мультиплексируются в одно и то же TCP-соединение.

Вы можете отключить эту функцию, изменив frps.toml и frpc.toml:```toml

frps.toml and frpc.toml, must be same

transport.tcpMux = false

### Поддержка протокола KCP

KCP — это быстрый и надёжный протокол, который позволяет добиться снижения средней задержки на 30–40% и уменьшения максимальной задержки в три раза, ценой потери 10–20% дополнительной пропускной способности по сравнению с TCP.

Режим KCP использует UDP в качестве нижележащего транспорта. Использование KCP во frp:

1. Включите KCP во frps:  ```toml
  # frps.toml
  bindPort = 7000
  # Specify a UDP port for KCP.
  kcpBindPort = 7000

The kcpBindPort number can be the same number as bindPort, since the bindPort field specifies a TCP port.

  1. Configure frpc.toml to use KCP to connect to frps: ```toml

frpc.toml

serverAddr = "x.x.x.x"

Same as the 'kcpBindPort' in frps.toml

serverPort = 7000 transport.protocol = "kcp"

### Поддержка протокола QUIC

QUIC — это новый мультиплексированный транспорт, построенный поверх UDP.

Использование QUIC во frp:

1. Включите QUIC во frps:  ```toml
# frps.toml
bindPort = 7000
# Specify a UDP port for QUIC.
quicBindPort = 7000

The quicBindPort number can be the same number as bindPort, since the bindPort field specifies a TCP port.

  1. Configure frpc.toml to use QUIC to connect to frps: ```toml

frpc.toml

serverAddr = "x.x.x.x"

Same as the 'quicBindPort' in frps.toml

serverPort = 7000 transport.protocol = "quic"

### Пул соединений

По умолчанию frps создаёт новое соединение frpc с внутренним сервисом при каждом запросе пользователя. С помощью пула соединений frps поддерживает определённое количество заранее установленных соединений, сокращая время, необходимое для установления соединения.

Эта функция подходит для большого количества коротких соединений.

1. Настройте лимит размера пула, который может использовать каждый прокси, в `frps.toml`:  ```toml
# frps.toml
transport.maxPoolCount = 5
  1. Включите и укажите количество пула соединений: ```toml

frpc.toml

transport.poolCount = 1

### Балансировка нагрузки

Балансировка нагрузки поддерживается через `group`.

Эта функция сейчас доступна только для типов `tcp`, `http`, `tcpmux`.```toml
# frpc.toml

[[proxies]]
name = "test1"
type = "tcp"
localPort = 8080
remotePort = 80
loadBalancer.group = "web"
loadBalancer.groupKey = "123"

[[proxies]]
name = "test2"
type = "tcp"
localPort = 8081
remotePort = 80
loadBalancer.group = "web"
loadBalancer.groupKey = "123"

loadBalancer.groupKey используется для аутентификации.

Подключения к порту 80 будут случайным образом распределяться между прокси в той же группе.

Для типа tcp значение remotePort в одной группе должно быть одинаковым.

Для типа http значения customDomains, subdomain, locations должны быть одинаковыми.

Проверка работоспособности службы

Функция проверки работоспособности поможет вам добиться высокой доступности при балансировке нагрузки.

Добавьте healthCheck.type = "tcp" или healthCheck.type = "http", чтобы включить проверку работоспособности.

При типе проверки tcp порт службы будет проверяться (TCPing):```toml

frpc.toml

[[proxies]] name = "test1" type = "tcp" localPort = 22 remotePort = 6000

Enable TCP health check

healthCheck.type = "tcp"

TCPing timeout seconds

healthCheck.timeoutSeconds = 3

If health check failed 3 times in a row, the proxy will be removed from frps

healthCheck.maxFailed = 3

A health check every 10 seconds

healthCheck.intervalSeconds = 10

С типом проверки работоспособности **http** к сервису будет отправлен HTTP-запрос, и ожидается ответ HTTP 2xx OK:```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localIP = "127.0.0.1"
localPort = 80
customDomains = ["test.example.com"]
# Enable HTTP health check
healthCheck.type = "http"
# frpc will send a GET request to '/status'
# and expect an HTTP 2xx OK response
healthCheck.path = "/status"
healthCheck.timeoutSeconds = 3
healthCheck.maxFailed = 3
healthCheck.intervalSeconds = 10

Перезапись HTTP-заголовка Host

По умолчанию frp вообще не изменяет туннелируемые HTTP-запросы, поскольку это побайтовая копия.

Однако, говоря о веб-серверах и HTTP-запросах, ваш веб-сервер может полагаться на HTTP-заголовок Host для определения запрашиваемого веб-сайта. frp может перезаписывать заголовок Host при пересылке HTTP-запросов с помощью поля hostHeaderRewrite:```toml

frpc.toml

[[proxies]] name = "web" type = "http" localPort = 80 customDomains = ["test.example.com"] hostHeaderRewrite = "dev.example.com"

The HTTP-запрос будет иметь заголовок `Host`, переписанный на `Host: dev.example.com`, когда он достигнет фактического веб-сервера, хотя запрос от браузера, вероятно, имеет `Host: test.example.com`.

### Установка других HTTP-заголовков

Аналогично `Host`, вы можете переопределить другие HTTP-заголовки запроса и ответа с помощью типа прокси `http`.```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
customDomains = ["test.example.com"]
hostHeaderRewrite = "dev.example.com"
requestHeaders.set.x-from-where = "frp"
responseHeaders.set.foo = "bar"

В этом примере будет установлен заголовок x-from-where: frp в HTTP-запросе и foo: bar в HTTP-ответе.

Получение реального IP-адреса

HTTP X-Forwarded-For

Эта функция предназначена для прокси http или прокси с включёнными плагинами https2http и https2https.

Вы можете получить реальный IP-адрес пользователя из заголовков HTTP-запроса X-Forwarded-For.

Proxy Protocol

frp поддерживает Proxy Protocol для передачи реального IP-адреса пользователя локальным сервисам.

Вот пример для https-сервиса:```toml

frpc.toml

[[proxies]] name = "web" type = "https" localPort = 443 customDomains = ["test.example.com"]

now v1 and v2 are supported

transport.proxyProtocolVersion = "v2"

Вы можете включить поддержку Proxy Protocol в nginx, чтобы передавать реальный IP-адрес пользователя в HTTP-заголовке `X-Real-IP`, а затем считывать заголовок `X-Real-IP` в вашем веб-сервисе для получения реального IP-адреса.

### Требовать HTTP Basic Auth (Пароль) для веб-сервисов

Любой, кто сможет угадать URL вашего туннеля, сможет получить доступ к вашему локальному веб-серверу, если вы не защитите его паролем.

Это принудительно включает HTTP Basic Auth для всех запросов с именем пользователя и паролем, указанными в файле конфигурации frpc.

Его можно включить только в том случае, если тип прокси — http.```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
customDomains = ["test.example.com"]
httpUser = "abc"
httpPassword = "abc"

Посетите http://test.example.com в браузере, и теперь вам будет предложено ввести имя пользователя и пароль.

Пользовательские имена поддоменов

Удобно использовать настройку subdomain для типов http и https, когда много людей используют один сервер frps.```toml

frps.toml

subDomainHost = "frps.com"

Разрешите `*.frps.com` на IP-адрес сервера frps. Обычно это называется записью Wildcard DNS.```toml
# frpc.toml

[[proxies]]
name = "web"
type = "http"
localPort = 80
subdomain = "test"

Теперь вы можете посетить ваш веб-сервис по адресу test.frps.com.

Обратите внимание, что если subdomainHost не пуст, customDomains не должен быть поддоменом subdomainHost.

Маршрутизация URL

frp поддерживает пересылку HTTP-запросов к разным внутренним веб-сервисам с помощью маршрутизации URL.

locations задаёт префикс URL, используемый для маршрутизации. frps сначала ищет наиболее конкретный префиксный location, заданный строковыми литералами, независимо от порядка их перечисления.```toml

frpc.toml

[[proxies]] name = "web01" type = "http" localPort = 80 customDomains = ["web.example.com"] locations = ["/"]

[[proxies]] name = "web02" type = "http" localPort = 81 customDomains = ["web.example.com"] locations = ["/news", "/about"]

HTTP-запросы с префиксом URL `/news` или `/about` будут перенаправляться на **web02**, а остальные запросы — на **web01**.

### Мультиплексирование TCP-портов

frp поддерживает приём TCP-сокетов, направляемых на разные прокси, через один порт на frps, аналогично `vhostHTTPPort` и `vhostHTTPSPort`.

Единственный поддерживаемый на данный момент метод мультиплексирования TCP-портов — `httpconnect` — туннель HTTP CONNECT.

Если в frps задать `tcpmuxHTTPConnectPort` любое значение, отличное от 0, frps будет прослушивать этот порт для HTTP CONNECT-запросов.

Хост HTTP CONNECT-запроса будет использоваться для сопоставления с прокси в frps. Хосты прокси можно настроить в frpc, указав `customDomains` и/или `subdomain` для прокси `tcpmux`, когда задано `multiplexer = "httpconnect"`.

Например:```toml
# frps.toml
bindPort = 7000
tcpmuxHTTPConnectPort = 1337

I need the input content to translate. Please provide the chunk of Markdown content you'd like me to translate from English to Russian.```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000

[[proxies]] name = "proxy1" type = "tcpmux" multiplexer = "httpconnect" customDomains = ["test1"] localPort = 80

[[proxies]] name = "proxy2" type = "tcpmux" multiplexer = "httpconnect" customDomains = ["test2"] localPort = 8080

В приведённой выше конфигурации — frps можно связаться на порту 1337 с HTTP CONNECT заголовком, например:```
CONNECT test1 HTTP/1.1\r\n\r\n

и соединение будет направлено через proxy1.

Подключение к frps через PROXY

frpc может подключаться к frps через прокси, если задать переменную окружения ОС HTTP_PROXY или если в файле frpc.toml установлен параметр transport.proxyURL.

Это работает только при использовании протокола tcp.```toml

frpc.toml

serverAddr = "x.x.x.x" serverPort = 7000 transport.proxyURL = "http://user:[email protected]:8080"

### Сопоставление диапазона портов

*Добавлено в v0.56.0*

Мы можем использовать синтаксис диапазона шаблона Go в сочетании со встроенной функцией `parseNumberRangePair` для достижения сопоставления диапазона портов.

Следующий пример при запуске создаст 8 прокси с именами `test-6000, test-6001 ... test-6007`, каждый из которых сопоставляет удалённый порт с локальным портом.```
{{- range $_, $v := parseNumberRangePair "6000-6006,6007" "6000-6006,6007" }}
[[proxies]]
name = "tcp-{{ $v.First }}"
type = "tcp"
localPort = {{ $v.First }}
remotePort = {{ $v.Second }}
{{- end }}

Клиентские плагины

По умолчанию frpc только перенаправляет запросы на локальные TCP- или UDP-порты.

Плагины используются для предоставления расширенных возможностей. Существуют встроенные плагины, такие как unix_domain_socket, http_proxy, socks5, static_file, http2https, https2http, https2https, и вы можете ознакомиться с примером использования.

Использование плагина http_proxy:```toml

frpc.toml

[[proxies]] name = "http_proxy" type = "tcp" remotePort = 6000 [proxies.plugin] type = "http_proxy" httpUser = "abc" httpPassword = "abc"

`httpUser` и `httpPassword` — это параметры конфигурации, используемые в плагине `http_proxy`.

### Плагины управления сервером

Прочитайте [документацию](https://github.com/fatedier/frp/blob/HEAD/doc/server_plugin.md).

Больше плагинов можно найти в [gofrp/plugin](https://github.com/gofrp/plugin).

### SSH-туннельный шлюз

*добавлено в v0.53.0*

frp поддерживает прослушивание SSH-порта на стороне frps и обеспечивает проксирование TCP-протокола через протокол SSH -R, не полагаясь на frpc.```toml
# frps.toml
sshTunnelGateway.bindPort = 2200

Когда выполняется ./frps -c frps.toml, в текущем рабочем каталоге автоматически создаётся файл закрытого ключа с именем .autogen_ssh_key. Этот сгенерированный файл закрытого ключа будет использоваться SSH-сервером в frps.

Выполнение команды```bash ssh -R :80:127.0.0.1:8080 v0@{frp address} -p 2200 tcp --proxy_name "test-tcp" --remote_port 9090

настраивает прокси на frps, который перенаправляет локальный сервис на порту 8080 на порт 9090.```bash
frp (via SSH) (Ctrl+C to quit)

User:
ProxyName: test-tcp
Type: tcp
RemoteAddress: :9090

Это эквивалентно:```bash frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote_port 9090

Пожалуйста, обратитесь к этому [документу](https://github.com/fatedier/frp/blob/HEAD/doc/ssh_tunnel_gateway.md) для получения дополнительной информации.

### Виртуальная сеть (VirtualNet)

*Альфа-функция, добавленная в v0.62.0*

Функция VirtualNet позволяет frp создавать и управлять виртуальными сетевыми соединениями между клиентами и посетителями через интерфейс TUN. Это обеспечивает маршрутизацию на уровне IP между машинами, расширяя возможности frp за пределы простого перенаправления портов для поддержки полной сетевой связности.

Подробную информацию о настройке и использовании см. в [документации VirtualNet](https://github.com/fatedier/frp/blob/HEAD/doc/virtual_net.md).

## Флаги функций

frp поддерживает флаги функций для включения или отключения экспериментальных функций. Это позволяет пользователям опробовать новые функции до того, как они будут признаны стабильными.

### Доступные флаги функций

| Имя | Этап | По умолчанию | Описание |
|------|-------|---------|-------------|
| VirtualNet | ALPHA | false | Возможности виртуальной сети для frp |

### Включение флагов функций

Чтобы включить экспериментальную функцию, добавьте флаг функции в вашу конфигурацию:```toml
featureGates = { VirtualNet = true }

Жизненный цикл функций

Функции обычно проходят три стадии:

  1. ALPHA: Отключена по умолчанию, может быть нестабильной
  2. BETA: Может быть включена по умолчанию, более стабильна, но всё ещё развивается
  3. GA (Generally Available): Включена по умолчанию, готова к использованию в производстве

Связанные проекты

  • gofrp/plugin — репозиторий для плагинов frp, содержащий множество плагинов, реализованных на основе механизма расширения frp, удовлетворяющих потребности в настройке для различных сценариев.
  • gofrp/tiny-frpc — облегчённая версия клиента frp (минимальный размер около 3,5 МБ), реализованная с использованием протокола ssh, поддерживающая некоторые из наиболее часто используемых функций, подходящая для устройств с ограниченными ресурсами.

Участие в разработке

Заинтересованы в участии? Мы будем рады помочь вам!

  • Ознакомьтесь с нашим списком issues и рассмотрите возможность отправки Pull Request в ветку dev.
  • Если вы хотите добавить новую функцию, сначала создайте issue с описанием новой функции и подхода к её реализации. После принятия предложения создайте реализацию новых функций и отправьте её как pull request.
  • Извините за мой плохой английский. Приветствуются улучшения этого документа, даже исправления опечаток.
  • Если у вас есть отличные идеи, отправьте письмо на [email protected].

Примечание: Мы предпочитаем, чтобы вы оставляли свои предложения в issues, чтобы другие пользователи с аналогичным вопросом могли быстро найти их, и нам не приходилось отвечать на них повторно.

Пожертвования

Если frp вам очень помог, вы можете поддержать нас:

GitHub Sponsors

Поддержите нас через Github Sponsors.

Вы можете разместить логотип своей компании в файле README этого проекта.

PayPal

Переведите деньги через PayPal на мой счёт [email protected].

Категории