frp server plugin is aimed to extend frp's ability without modifying the Golang code.
An external server should run in a different process receiving RPC calls from frps. Before frps is doing some operations, it will send RPC requests to notify the external RPC server and act according to its response.
RPC requests are based on JSON over HTTP.
When a server plugin accepts an operation request, it can respond with three different responses:
HTTP path can be configured for each manage plugin in frps. We'll assume for this example that it's /handler
.
A request to the RPC server will look like:
POST /handler?version=0.1.0&op=Login
{
"version": "0.1.0",
"op": "Login",
"content": {
... // Operation info
}
}
Request Header:
X-Frp-Reqid: for tracing
The response can look like any of the following:
Non-200 HTTP response status code (this will automatically tell frps that the request should fail)
Reject operation:
{
"reject": true,
"reject_reason": "invalid user"
}
Allow operation and keep original content:
{
"reject": false,
"unchange": true
}
Allow operation and modify content
{
"unchange": "false",
"content": {
... // Replaced content
}
}
Currently Login
, NewProxy
, CloseProxy
, Ping
, NewWorkConn
and NewUserConn
operations are supported.
Client login operation
{
"content": {
"version": <string>,
"hostname": <string>,
"os": <string>,
"arch": <string>,
"user": <string>,
"timestamp": <int64>,
"privilege_key": <string>,
"run_id": <string>,
"pool_count": <int>,
"metas": map<string>string,
"client_address": <string>
}
}
Create new proxy
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>,
"proxy_type": <string>,
"use_encryption": <bool>,
"use_compression": <bool>,
"bandwidth_limit": <string>,
"bandwidth_limit_mode": <string>,
"group": <string>,
"group_key": <string>,
// tcp and udp only
"remote_port": <int>,
// http and https only
"custom_domains": []<string>,
"subdomain": <string>,
"locations": <string>,
"http_user": <string>,
"http_pwd": <string>,
"host_header_rewrite": <string>,
"headers": map<string>string,
// stcp only
"sk": <string>,
// tcpmux only
"multiplexer": <string>
"metas": map<string>string
}
}
A previously created proxy is closed.
Please note that one request will be sent for every proxy that is closed, do NOT use this if you have too many proxies bound to a single client, as this may exhaust the server's resources.
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>
}
}
Heartbeat from frpc
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"timestamp": <int64>,
"privilege_key": <string>
}
}
New work connection received from frpc (RPC sent after run_id
is matched with an existing frp connection)
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"run_id": <string>
"timestamp": <int64>,
"privilege_key": <string>
}
}
New user connection received from proxy (support tcp
, stcp
, https
and tcpmux
) .
{
"content": {
"user": {
"user": <string>,
"metas": map<string>string
"run_id": <string>
},
"proxy_name": <string>,
"proxy_type": <string>,
"remote_addr": <string>
}
}
# frps.toml
bindPort = 7000
[[httpPlugins]]
name = "user-manager"
addr = "127.0.0.1:9000"
path = "/handler"
ops = ["Login"]
[[httpPlugins]]
name = "port-manager"
addr = "127.0.0.1:9001"
path = "/handler"
ops = ["NewProxy"]
addr = "https://127.0.0.1:9001"
.Metadata will be sent to the server plugin in each RPC request.
There are 2 types of metadata entries - global one and the other under each proxy configuration.
Global metadata entries will be sent in Login
under the key metas
, and in any other RPC request under user.metas
.
Metadata entries under each proxy configuration will be sent in NewProxy
op only, under metas
.
This is an example of metadata entries:
# frpc.toml
serverAddr = "127.0.0.1"
serverPort = 7000
user = "fake"
metadatas.token = "fake"
metadatas.version = "1.0.0"
[[proxies]]
name = "ssh"
type = "tcp"
localPort = 22
remotePort = 6000
metadatas.id = "123"