
Быстрый обратный прокси-сервер, помогающий открыть доступ к локальному серверу за NAT или файрволом из интернета.
frp — это проект с открытым исходным кодом, дальнейшее развитие которого стало возможным исключительно благодаря поддержке наших замечательных спонсоров. Если вы хотите присоединиться к ним, пожалуйста, рассмотрите возможность спонсирования разработки frp.
Полноценная IDE, созданная для профессиональных Go-разработчиков
Если вы ищете API для записи встреч, обратите внимание на Recall.ai,
API, которое записывает Zoom, Google Meet, Microsoft Teams, очные встречи и многое другое.
frp — это быстрый обратный прокси-сервер, который позволяет вам предоставить доступ к локальному серверу, находящемуся за NAT или брандмауэром, через Интернет. В настоящее время он поддерживает протоколы TCP и UDP, а также HTTP и HTTPS, что позволяет пересылать запросы на внутренние сервисы через доменное имя.
frp также предлагает режим P2P-подключения.
frp в настоящее время находится в стадии разработки. Вы можете попробовать последнюю версию релиза в ветке master или использовать ветку dev для доступа к версии, находящейся в разработке.
В настоящее время мы работаем над версией 2 и пытаемся выполнить некоторый рефакторинг кода и улучшения. Однако обратите внимание, что она не будет совместима с версией 1.
Мы перейдём от версии 0 к версии 1 в подходящий момент и будем принимать только исправления ошибок и улучшения, а не крупные запросы на новые функции.
Сложность и трудность версии v2 намного выше, чем ожидалось. Я могу работать над её разработкой только в разрозненные промежутки времени, а постоянные перерывы значительно снижают продуктивность. Учитывая эту ситуацию, мы продолжим оптимизировать и развивать текущую версию, пока у нас не появится больше свободного времени для проведения капитального обновления версии.
Концепция v2 основана на моих многолетних опыте и размышлениях в области cloud-native, особенно в K8s и ServiceMesh. Её ядро — это современный прокси четвёртого и седьмого уровней, похожий на envoy. Этот прокси сам по себе высокомасштабируем и способен не только реализовать функциональность проникновения в интранет, но и применяться в различных других областях. На основе этого высокомасштабируемого ядра мы стремимся реализовать все возможности frp v1, а также решить задачи, которые ранее было невозможно или сложно реализовать элегантным способом. Кроме того, мы будем поддерживать эффективную разработку и итерации.
Кроме того, я вижу frp как высокорасширяемую систему и платформу, аналогично тому, как мы можем предоставлять ряд расширений на основе K8s. В K8s мы можем настраивать разработку в соответствии с потребностями предприятия, используя такие функции, как CRD, режим контроллера, webhook, CSI и CNI. В frp v1 мы ввели концепцию серверных плагинов, которая реализовала некоторую базовую расширяемость. Однако она опирается на простой HTTP-протокол и требует от пользователей запуска независимых процессов и самостоятельного управления ими. Такой подход далёк от гибкости и удобства, а реальные потребности сильно различаются. Нереалистично ожидать, что некоммерческий проект с открытым исходным кодом, поддерживаемый несколькими людьми, удовлетворит потребности каждого.
Наконец, мы признаём, что текущий дизайн таких модулей, как управление конфигурацией, проверка разрешений, управление сертификатами и управление API, недостаточно современен. Хотя мы можем провести некоторые оптимизации в версии v1, обеспечение совместимости остаётся сложной задачей, требующей значительных усилий для решения.
Мы искренне ценим вашу поддержку frp.
Для начала загрузите последнюю программу для вашей операционной системы и архитектуры со страницы Release.
Затем разместите бинарный файл frps и файл конфигурации сервера на сервере A, у которого есть публичный IP-адрес.
Наконец, разместите бинарный файл frpc и файл конфигурации клиента на сервере B, который находится в локальной сети и не может быть напрямую доступен из публичного интернета.
Некоторые антивирусы ошибочно помечают frpc как вредоносное ПО и удаляют его. Это связано с тем, что frp — это сетевой инструмент, способный создавать обратные прокси. Антивирусы иногда помечают обратные прокси из-за их способности обходить ограничения портов брандмауэра. Если вы используете антивирус, вам может потребоваться добавить frpc в белый список/исключения в настройках антивируса, чтобы избежать случайной изоляции/удаления. Подробнее см. issue 3637.
frps.toml на сервере A, установив bindPort для подключения клиентов frp: ```tomlbindPort = 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.
frpc на сервере B:./frpc -c ./frpc.toml
test), используйте следующую команду:ssh -oPort=6000 [email protected]
В этом примере реализовано несколько SSH-сервисов, открытых через один и тот же порт, с использованием прокси типа tcpmux. Аналогично, если клиент поддерживает метод прокси-соединения HTTP Connect, таким образом можно добиться повторного использования порта.
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
[[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.
frps:./frps -c ./frps.toml
frpc.toml и укажите serverAddr как IP-адрес удалённого сервера frps. Задайте localPort вашего веб-сервиса: ```tomlserverAddr = "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
frps:./frps -c ./frps.toml
frpc.toml и укажите serverAddr как IP-адрес удалённого сервера frps. Перенаправьте запросы DNS-запросов на общедоступный DNS-сервер Google 8.8.8.8:53: ```tomlserverAddr = "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"
curl:curl http://x.x.x.x:6000/version
Откройте простой HTTP-файловый сервер, чтобы получить доступ к файлам, хранящимся в локальной сети, из публичного интернета.
Настройте frps, как описано выше, затем:
frpc со следующей конфигурацией: ```tomlserverAddr = "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"
https://test.example.com.Чтобы снизить риски, связанные с предоставлением доступа к определенным сервисам напрямую в публичную сеть, режим STCP (Secret TCP) требует использования предварительно общего ключа для доступа к сервису со стороны других клиентов.
Настройте frps так же, как указано выше.
frpc на машине B со следующей конфигурацией. В этом примере предоставляется доступ к SSH-сервису (порт 22), обратите внимание на поле secretKey для предварительно общего ключа, а также на то, что поле remotePort здесь удалено: ```tomlserverAddr = "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
ssh -oPort=6000 127.0.0.1
xtcp предназначен для передачи больших объёмов данных напрямую между клиентами. Сервер frps по-прежнему необходим, поскольку P2P здесь относится только к фактической передаче данных.
Обратите внимание, что он может работать не со всеми типами NAT-устройств. Если xtcp не работает, возможно, стоит переключиться на stcp.
frpc на машине B и откройте SSH-порт. Обратите внимание, что поле remotePort удалено: ```tomlserverAddr = "x.x.x.x" serverPort = 7000
[[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
ssh -oPort=6000 127.0.0.1
Начиная с v0.52.0, мы поддерживаем TOML, YAML и JSON для конфигурации. Обратите внимание, что INI устарел и будет удалён в будущих выпусках. Новые функции будут доступны только в TOML, YAML или JSON. Пользователям, желающим получить эти новые функции, следует соответствующим образом переключить формат своей конфигурации.
Прочитайте полные примеры файлов конфигурации, чтобы узнать о дополнительных функциях, не описанных здесь.
В примерах используется формат TOML, но вы по-прежнему можете использовать YAML или JSON.
Эти файлы конфигурации предназначены только для справки. Пожалуйста, не используйте эту конфигурацию напрямую для запуска программы, так как она может вызывать различные проблемы.
На переменные окружения можно ссылаться в файле конфигурации, используя стандартный формат Go:```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
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
Проверяйте статус frp и статистическую информацию о прокси через Dashboard.
Настройте порт для dashboard, чтобы включить эту функцию:```toml
webServer.addr = "0.0.0.0" webServer.port = 7500
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.

Клиентский административный интерфейс помогает проверять и управлять конфигурацией и прокси 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 имеют приоритет при совпадении имён.
Когда веб-сервер включён, frps сохраняет данные мониторинга в кэше в течение 7 дней. Они будут очищены после перезапуска процесса.
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
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
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
[[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
* сборка сертификатов 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
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
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.
frp поддерживает мультиплексирование TCP-потоков начиная с версии v0.10.0, аналогично мультиплексированию HTTP2, при котором все логические соединения с одним и тем же frpc мультиплексируются в одно и то же TCP-соединение.
Вы можете отключить эту функцию, изменив frps.toml и frpc.toml:```toml
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.
frpc.toml to use KCP to connect to frps: ```tomlserverAddr = "x.x.x.x"
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.
frpc.toml to use QUIC to connect to frps: ```tomlserverAddr = "x.x.x.x"
serverPort = 7000 transport.protocol = "quic"
### Пул соединений
По умолчанию frps создаёт новое соединение frpc с внутренним сервисом при каждом запросе пользователя. С помощью пула соединений frps поддерживает определённое количество заранее установленных соединений, сокращая время, необходимое для установления соединения.
Эта функция подходит для большого количества коротких соединений.
1. Настройте лимит размера пула, который может использовать каждый прокси, в `frps.toml`: ```toml
# frps.toml
transport.maxPoolCount = 5
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
[[proxies]] name = "test1" type = "tcp" localPort = 22 remotePort = 6000
healthCheck.type = "tcp"
healthCheck.timeoutSeconds = 3
healthCheck.maxFailed = 3
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
По умолчанию frp вообще не изменяет туннелируемые HTTP-запросы, поскольку это побайтовая копия.
Однако, говоря о веб-серверах и HTTP-запросах, ваш веб-сервер может полагаться на HTTP-заголовок Host для определения запрашиваемого веб-сайта. frp может перезаписывать заголовок Host при пересылке HTTP-запросов с помощью поля hostHeaderRewrite:```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-ответе.
Эта функция предназначена для прокси http или прокси с включёнными плагинами https2http и https2https.
Вы можете получить реальный IP-адрес пользователя из заголовков HTTP-запроса X-Forwarded-For.
frp поддерживает Proxy Protocol для передачи реального IP-адреса пользователя локальным сервисам.
Вот пример для https-сервиса:```toml
[[proxies]] name = "web" type = "https" localPort = 443 customDomains = ["test.example.com"]
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
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.
frp поддерживает пересылку HTTP-запросов к разным внутренним веб-сервисам с помощью маршрутизации URL.
locations задаёт префикс URL, используемый для маршрутизации. frps сначала ищет наиболее конкретный префиксный location, заданный строковыми литералами, независимо от порядка их перечисления.```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
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.
frpc может подключаться к frps через прокси, если задать переменную окружения ОС HTTP_PROXY или если в файле frpc.toml установлен параметр transport.proxyURL.
Это работает только при использовании протокола tcp.```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
[[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 }
Функции обычно проходят три стадии:
Заинтересованы в участии? Мы будем рады помочь вам!
Примечание: Мы предпочитаем, чтобы вы оставляли свои предложения в issues, чтобы другие пользователи с аналогичным вопросом могли быстро найти их, и нам не приходилось отвечать на них повторно.
Если frp вам очень помог, вы можете поддержать нас:
Поддержите нас через Github Sponsors.
Вы можете разместить логотип своей компании в файле README этого проекта.
Переведите деньги через PayPal на мой счёт [email protected].