Compare commits
92 Commits
65f74199de
...
v0.61.0
Author | SHA1 | Date | |
---|---|---|---|
|
4bbec09d57 | ||
|
f7a06cbe61 | ||
|
3a08c2aeb0 | ||
|
b14192a8d3 | ||
|
2466e65f43 | ||
|
2855ac71e3 | ||
|
fe4ca1b54e | ||
|
edd7cf8967 | ||
|
ccfe8c97f4 | ||
|
03c8d7bf96 | ||
|
2dcdb24cc4 | ||
|
d47e138bc9 | ||
|
f1fb2d721a | ||
|
ae73ec2fed | ||
|
e8045194cd | ||
|
69cc422edf | ||
|
243ca994e0 | ||
|
b4d5d8c756 | ||
|
c6f9d8d403 | ||
|
939c490768 | ||
|
f390e4a401 | ||
|
e649692217 | ||
|
77990c31ef | ||
|
e680acf42d | ||
|
522e2c94c1 | ||
|
301515d2e8 | ||
|
f0442d0cd5 | ||
|
9ced717d69 | ||
|
4e8e9e1dec | ||
|
92cb0b30c2 | ||
|
e81b36c5ba | ||
|
8f23733f47 | ||
|
5a6d9f60c2 | ||
|
a5b7abfc8b | ||
|
1e650ea9a7 | ||
|
d689f0fc53 | ||
|
d505ecb473 | ||
|
2b83436a97 | ||
|
051299ec25 | ||
|
44985f574d | ||
|
c9ca9353cf | ||
|
31fa3f021a | ||
|
2d3af8a108 | ||
|
466d69eae0 | ||
|
7c8cbeb250 | ||
|
4fd6301577 | ||
|
53626b370c | ||
|
4fd800bc48 | ||
|
0d6d968fe8 | ||
|
8fb99ef7a9 | ||
|
88e74ff24d | ||
|
534dc99d55 | ||
|
595aba5a9b | ||
|
a4189ba474 | ||
|
9ec84f8143 | ||
|
8ab474cc97 | ||
|
a301046f3d | ||
|
8888610d83 | ||
|
fe5fb0326b | ||
|
eb1e19a821 | ||
|
10f2620131 | ||
|
ce677820c6 | ||
|
88fcc079e8 | ||
|
2dab5d0bca | ||
|
143750901e | ||
|
997d406ec2 | ||
|
cfd1a3128a | ||
|
57577ea044 | ||
|
c5c79e4148 | ||
|
55da58eca4 | ||
|
76a1efccd9 | ||
|
980f084ad1 | ||
|
3bf1eb8565 | ||
|
b2ae433e18 | ||
|
aa0a41ee4e | ||
|
1ea1530b36 | ||
|
e0c45a1aca | ||
|
813c45f5c2 | ||
|
aa74dc4646 | ||
|
2406ecdfea | ||
|
8668fef136 | ||
|
ea62bc5a34 | ||
|
23bb76397a | ||
|
487c8d7c29 | ||
|
f480160e2d | ||
|
30c246c488 | ||
|
75f3bce04d | ||
|
adc3adc13b | ||
|
e62d9a5242 | ||
|
134a46c00b | ||
|
ae08811636 | ||
|
6451583e60 |
2
.github/FUNDING.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
# These are supported funding model platforms
|
# These are supported funding model platforms
|
||||||
|
|
||||||
github: [fatedier]
|
github: [fatedier]
|
||||||
custom: ["https://afdian.net/a/fatedier"]
|
custom: ["https://afdian.com/a/fatedier"]
|
||||||
|
4
.github/workflows/build-and-push-image.yml
vendored
@@ -2,7 +2,7 @@ name: Build Image and Publish to Dockerhub & GPR
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ created ]
|
types: [ published ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -61,7 +61,7 @@ jobs:
|
|||||||
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Build and push frpc
|
- name: Build and push frpc
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./dockerfiles/Dockerfile-for-frpc
|
file: ./dockerfiles/Dockerfile-for-frpc
|
||||||
|
4
.github/workflows/golangci-lint.yml
vendored
@@ -17,13 +17,13 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
cache: false
|
cache: false
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@v4
|
uses: golangci/golangci-lint-action@v4
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||||
version: v1.57
|
version: v1.61
|
||||||
|
|
||||||
# Optional: golangci-lint command line arguments.
|
# Optional: golangci-lint command line arguments.
|
||||||
# args: --issues-exit-code=0
|
# args: --issues-exit-code=0
|
||||||
|
2
.github/workflows/goreleaser.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
|
|
||||||
- name: Make All
|
- name: Make All
|
||||||
run: |
|
run: |
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
service:
|
service:
|
||||||
golangci-lint-version: 1.57.x # use the fixed version to not introduce new linters unexpectedly
|
golangci-lint-version: 1.61.x # use the fixed version to not introduce new linters unexpectedly
|
||||||
|
|
||||||
run:
|
run:
|
||||||
concurrency: 4
|
concurrency: 4
|
||||||
@@ -14,7 +14,7 @@ linters:
|
|||||||
enable:
|
enable:
|
||||||
- unused
|
- unused
|
||||||
- errcheck
|
- errcheck
|
||||||
- exportloopref
|
- copyloopvar
|
||||||
- gocritic
|
- gocritic
|
||||||
- gofumpt
|
- gofumpt
|
||||||
- goimports
|
- goimports
|
||||||
@@ -88,7 +88,9 @@ linters-settings:
|
|||||||
excludes:
|
excludes:
|
||||||
- G401
|
- G401
|
||||||
- G402
|
- G402
|
||||||
|
- G404
|
||||||
- G501
|
- G501
|
||||||
|
- G115 # integer overflow conversion
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
# List of regexps of issue texts to exclude, empty list by default.
|
# List of regexps of issue texts to exclude, empty list by default.
|
||||||
|
@@ -2,7 +2,7 @@ export PATH := $(PATH):`go env GOPATH`/bin
|
|||||||
export GO111MODULE=on
|
export GO111MODULE=on
|
||||||
LDFLAGS := -s -w
|
LDFLAGS := -s -w
|
||||||
|
|
||||||
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 android:arm64
|
os-archs=darwin:amd64 darwin:arm64 freebsd:amd64 linux:amd64 linux:arm:7 linux:arm:5 linux:arm64 windows:amd64 windows:arm64 linux:mips64 linux:mips64le linux:mips:softfloat linux:mipsle:softfloat linux:riscv64 linux:loong64 android:arm64
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
17
README.md
@@ -11,11 +11,17 @@
|
|||||||
<!--gold sponsors start-->
|
<!--gold sponsors start-->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
</p>
|
||||||
|
<p align="center">
|
||||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||||
<img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/beclab/terminus" target="_blank">
|
||||||
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
@@ -983,7 +989,7 @@ The HTTP request will have the `Host` header rewritten to `Host: dev.example.com
|
|||||||
|
|
||||||
### Setting other HTTP Headers
|
### Setting other HTTP Headers
|
||||||
|
|
||||||
Similar to `Host`, You can override other HTTP request headers with proxy type `http`.
|
Similar to `Host`, You can override other HTTP request and response headers with proxy type `http`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
# frpc.toml
|
# frpc.toml
|
||||||
@@ -995,9 +1001,10 @@ localPort = 80
|
|||||||
customDomains = ["test.example.com"]
|
customDomains = ["test.example.com"]
|
||||||
hostHeaderRewrite = "dev.example.com"
|
hostHeaderRewrite = "dev.example.com"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
responseHeaders.set.foo = "bar"
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, it will set header `x-from-where: frp` in the HTTP request.
|
In this example, it will set header `x-from-where: frp` in the HTTP request and `foo: bar` in the HTTP response.
|
||||||
|
|
||||||
### Get Real IP
|
### Get Real IP
|
||||||
|
|
||||||
|
14
README_zh.md
@@ -13,11 +13,17 @@ frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP
|
|||||||
<!--gold sponsors start-->
|
<!--gold sponsors start-->
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
||||||
<img width="350px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
||||||
</a>
|
</a>
|
||||||
<a> </a>
|
</p>
|
||||||
|
<p align="center">
|
||||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||||
<img width="360px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/beclab/terminus" target="_blank">
|
||||||
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_terminusos.jpeg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<!--gold sponsors end-->
|
<!--gold sponsors end-->
|
||||||
@@ -89,7 +95,7 @@ frp 是一个免费且开源的项目,我们欢迎任何人为其开发和进
|
|||||||
|
|
||||||
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
您可以通过 [GitHub Sponsors](https://github.com/sponsors/fatedier) 赞助我们。
|
||||||
|
|
||||||
国内用户可以通过 [爱发电](https://afdian.net/a/fatedier) 赞助我们。
|
国内用户可以通过 [爱发电](https://afdian.com/a/fatedier) 赞助我们。
|
||||||
|
|
||||||
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
企业赞助者可以将贵公司的 Logo 以及链接放置在项目 README 文件中。
|
||||||
|
|
||||||
|
13
Release.md
@@ -1,13 +1,4 @@
|
|||||||
### Notable Changes
|
|
||||||
|
|
||||||
We have optimized the heartbeat mechanism when tcpmux is enabled (enabled by default). The default value of `heartbeatInterval` has been adjusted to -1. This update ensures that when tcpmux is active, the client does not send additional heartbeats to the server. Since tcpmux incorporates its own heartbeat system, this change effectively reduces unnecessary data consumption, streamlining communication efficiency between client and server.
|
|
||||||
|
|
||||||
When connecting to frps versions older than v0.39.0 might encounter compatibility issues due to changes in the heartbeat mechanism. As a temporary workaround, setting the `heartbeatInterval` to 30 can help maintain stable connectivity with these older versions. We recommend updating to the latest frps version to leverage full functionality and improvements.
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Show tcpmux proxies on the frps dashboard.
|
* The frpc visitor command-line parameter adds the `--server-user` option to specify the username of the server-side proxy to connect to.
|
||||||
|
* Support multiple frpc instances with different subjects when using oidc authentication.
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* When an HTTP proxy request times out, it returns 504 instead of 404 now.
|
|
||||||
|
@@ -230,7 +230,7 @@ func (ctl *Control) registerMsgHandlers() {
|
|||||||
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
ctl.msgDispatcher.RegisterHandler(&msg.Pong{}, ctl.handlePong)
|
||||||
}
|
}
|
||||||
|
|
||||||
// headerWorker sends heartbeat to server and check heartbeat timeout.
|
// heartbeatWorker sends heartbeat to server and check heartbeat timeout.
|
||||||
func (ctl *Control) heartbeatWorker() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
|
|
||||||
|
@@ -192,7 +192,7 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
if pxy.proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connection first
|
// if plugin is set, let plugin handle connection first
|
||||||
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
pxy.proxyPlugin.Handle(remote, workConn, &extraInfo)
|
pxy.proxyPlugin.Handle(pxy.ctx, remote, workConn, &extraInfo)
|
||||||
xl.Debugf("handle by plugin finished")
|
xl.Debugf("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -137,7 +137,7 @@ func (pw *Wrapper) SetRunningStatus(remoteAddr string, respErr string) error {
|
|||||||
pw.Phase = ProxyPhaseStartErr
|
pw.Phase = ProxyPhaseStartErr
|
||||||
pw.Err = respErr
|
pw.Err = respErr
|
||||||
pw.lastStartErr = time.Now()
|
pw.lastStartErr = time.Now()
|
||||||
return fmt.Errorf(pw.Err)
|
return fmt.Errorf("%s", pw.Err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pw.pxy.Run(); err != nil {
|
if err := pw.pxy.Run(); err != nil {
|
||||||
|
@@ -169,6 +169,15 @@ func (svr *Service) Run(ctx context.Context) error {
|
|||||||
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if svr.webServer != nil {
|
||||||
|
go func() {
|
||||||
|
log.Infof("admin server listen on %s", svr.webServer.Address())
|
||||||
|
if err := svr.webServer.Run(); err != nil {
|
||||||
|
log.Warnf("admin server exit with error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
// first login to frps
|
// first login to frps
|
||||||
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
svr.loopLoginUntilSuccess(10*time.Second, lo.FromPtr(svr.common.LoginFailExit))
|
||||||
if svr.ctl == nil {
|
if svr.ctl == nil {
|
||||||
@@ -179,14 +188,6 @@ func (svr *Service) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
go svr.keepControllerWorking()
|
go svr.keepControllerWorking()
|
||||||
|
|
||||||
if svr.webServer != nil {
|
|
||||||
go func() {
|
|
||||||
log.Infof("admin server listen on %s", svr.webServer.Address())
|
|
||||||
if err := svr.webServer.Run(); err != nil {
|
|
||||||
log.Warnf("admin server exit with error: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
<-svr.ctx.Done()
|
<-svr.ctx.Done()
|
||||||
svr.stop()
|
svr.stop()
|
||||||
return nil
|
return nil
|
||||||
|
@@ -15,9 +15,11 @@
|
|||||||
package sub
|
package sub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/rodaine/table"
|
"github.com/rodaine/table"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -27,24 +29,24 @@ import (
|
|||||||
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
clientsdk "github.com/fatedier/frp/pkg/sdk/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var adminAPITimeout = 30 * time.Second
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
commands := []struct {
|
||||||
"reload",
|
name string
|
||||||
"Hot-Reload frpc configuration",
|
description string
|
||||||
ReloadHandler,
|
handler func(*v1.ClientCommonConfig) error
|
||||||
))
|
}{
|
||||||
|
{"reload", "Hot-Reload frpc configuration", ReloadHandler},
|
||||||
|
{"status", "Overview of all proxies status", StatusHandler},
|
||||||
|
{"stop", "Stop the running frpc", StopHandler},
|
||||||
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
for _, cmdConfig := range commands {
|
||||||
"status",
|
cmd := NewAdminCommand(cmdConfig.name, cmdConfig.description, cmdConfig.handler)
|
||||||
"Overview of all proxies status",
|
cmd.Flags().DurationVar(&adminAPITimeout, "api-timeout", adminAPITimeout, "Timeout for admin API calls")
|
||||||
StatusHandler,
|
rootCmd.AddCommand(cmd)
|
||||||
))
|
}
|
||||||
|
|
||||||
rootCmd.AddCommand(NewAdminCommand(
|
|
||||||
"stop",
|
|
||||||
"Stop the running frpc",
|
|
||||||
StopHandler,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) error) *cobra.Command {
|
||||||
@@ -73,7 +75,9 @@ func NewAdminCommand(name, short string, handler func(*v1.ClientCommonConfig) er
|
|||||||
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
if err := client.Reload(strictConfigMode); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Reload(ctx, strictConfigMode); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("reload success")
|
fmt.Println("reload success")
|
||||||
@@ -83,7 +87,9 @@ func ReloadHandler(clientCfg *v1.ClientCommonConfig) error {
|
|||||||
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
res, err := client.GetAllProxyStatus()
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
res, err := client.GetAllProxyStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +115,9 @@ func StatusHandler(clientCfg *v1.ClientCommonConfig) error {
|
|||||||
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
func StopHandler(clientCfg *v1.ClientCommonConfig) error {
|
||||||
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
client := clientsdk.New(clientCfg.WebServer.Addr, clientCfg.WebServer.Port)
|
||||||
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
client.SetAuth(clientCfg.WebServer.User, clientCfg.WebServer.Password)
|
||||||
if err := client.Stop(); err != nil {
|
ctx, cancel := context.WithTimeout(context.Background(), adminAPITimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := client.Stop(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Println("stop success")
|
fmt.Println("stop success")
|
||||||
|
@@ -76,7 +76,7 @@ transport.poolCount = 5
|
|||||||
|
|
||||||
# Specify keep alive interval for tcp mux.
|
# Specify keep alive interval for tcp mux.
|
||||||
# only valid if tcpMux is enabled.
|
# only valid if tcpMux is enabled.
|
||||||
# transport.tcpMuxKeepaliveInterval = 60
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
# Communication protocol used to connect to server
|
# Communication protocol used to connect to server
|
||||||
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
# supports tcp, kcp, quic, websocket and wss now, default is tcp
|
||||||
@@ -209,6 +209,7 @@ locations = ["/", "/pic"]
|
|||||||
# routeByHTTPUser = abc
|
# routeByHTTPUser = abc
|
||||||
hostHeaderRewrite = "example.com"
|
hostHeaderRewrite = "example.com"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
responseHeaders.set.foo = "bar"
|
||||||
healthCheck.type = "http"
|
healthCheck.type = "http"
|
||||||
# frpc will send a GET http request '/status' to local http service
|
# frpc will send a GET http request '/status' to local http service
|
||||||
# http service is alive when it return 2xx http response code
|
# http service is alive when it return 2xx http response code
|
||||||
@@ -314,6 +315,26 @@ localAddr = "127.0.0.1:443"
|
|||||||
hostHeaderRewrite = "127.0.0.1"
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
requestHeaders.set.x-from-where = "frp"
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_http2http"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6007
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2http"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
hostHeaderRewrite = "127.0.0.1"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "plugin_tls2raw"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = 6008
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "tls2raw"
|
||||||
|
localAddr = "127.0.0.1:80"
|
||||||
|
crtPath = "./server.crt"
|
||||||
|
keyPath = "./server.key"
|
||||||
|
|
||||||
[[proxies]]
|
[[proxies]]
|
||||||
name = "secret_tcp"
|
name = "secret_tcp"
|
||||||
# If the type is secret tcp, remotePort is useless
|
# If the type is secret tcp, remotePort is useless
|
||||||
|
@@ -34,7 +34,7 @@ transport.maxPoolCount = 5
|
|||||||
|
|
||||||
# Specify keep alive interval for tcp mux.
|
# Specify keep alive interval for tcp mux.
|
||||||
# only valid if tcpMux is true.
|
# only valid if tcpMux is true.
|
||||||
# transport.tcpMuxKeepaliveInterval = 60
|
# transport.tcpMuxKeepaliveInterval = 30
|
||||||
|
|
||||||
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
# tcpKeepalive specifies the interval between keep-alive probes for an active network connection between frpc and frps.
|
||||||
# If negative, keep-alive probes are disabled.
|
# If negative, keep-alive probes are disabled.
|
||||||
|
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB |
BIN
doc/pic/sponsor_lokal.png
Normal file
After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 12 KiB |
BIN
doc/pic/sponsor_terminusos.jpeg
Normal file
After Width: | Height: | Size: 31 KiB |
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
FROM golang:1.22 AS building
|
FROM golang:1.23 AS building
|
||||||
|
|
||||||
COPY . /building
|
COPY . /building
|
||||||
WORKDIR /building
|
WORKDIR /building
|
||||||
|
7
go.mod
@@ -23,7 +23,7 @@ require (
|
|||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/tidwall/gjson v1.17.1
|
github.com/tidwall/gjson v1.17.1
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.8
|
github.com/xtaci/kcp-go/v5 v5.6.13
|
||||||
golang.org/x/crypto v0.22.0
|
golang.org/x/crypto v0.22.0
|
||||||
golang.org/x/net v0.24.0
|
golang.org/x/net v0.24.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/oauth2 v0.16.0
|
||||||
@@ -59,9 +59,8 @@ require (
|
|||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.48.0 // indirect
|
github.com/prometheus/common v0.48.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
github.com/templexxx/cpu v0.1.1 // indirect
|
||||||
github.com/templexxx/cpu v0.1.0 // indirect
|
github.com/templexxx/xorsimd v0.4.3 // indirect
|
||||||
github.com/templexxx/xorsimd v0.4.2 // indirect
|
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
16
go.sum
@@ -116,8 +116,8 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||||
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
@@ -136,10 +136,10 @@ github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI=
|
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||||
github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI=
|
github.com/templexxx/xorsimd v0.4.3/go.mod h1:oZQcD6RFDisW2Am58dSAGwwL6rHjbzrlu25VDqfWkQg=
|
||||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
@@ -148,8 +148,8 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI=
|
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM=
|
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
@@ -18,7 +18,7 @@ rm -rf ./release/packages
|
|||||||
mkdir -p ./release/packages
|
mkdir -p ./release/packages
|
||||||
|
|
||||||
os_all='linux windows darwin freebsd android'
|
os_all='linux windows darwin freebsd android'
|
||||||
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64'
|
arch_all='386 amd64 arm arm64 mips64 mips64le mips mipsle riscv64 loong64'
|
||||||
extra_all='_ hf'
|
extra_all='_ hf'
|
||||||
|
|
||||||
cd ./release
|
cd ./release
|
||||||
|
@@ -50,7 +50,8 @@ func NewAuthVerifier(cfg v1.AuthServerConfig) (authVerifier Verifier) {
|
|||||||
case v1.AuthMethodToken:
|
case v1.AuthMethodToken:
|
||||||
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
authVerifier = NewTokenAuth(cfg.AdditionalScopes, cfg.Token)
|
||||||
case v1.AuthMethodOIDC:
|
case v1.AuthMethodOIDC:
|
||||||
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, cfg.OIDC)
|
tokenVerifier := NewTokenVerifier(cfg.OIDC)
|
||||||
|
authVerifier = NewOidcAuthVerifier(cfg.AdditionalScopes, tokenVerifier)
|
||||||
}
|
}
|
||||||
return authVerifier
|
return authVerifier
|
||||||
}
|
}
|
||||||
|
@@ -87,14 +87,18 @@ func (auth *OidcAuthProvider) SetNewWorkConn(newWorkConnMsg *msg.NewWorkConn) (e
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TokenVerifier interface {
|
||||||
|
Verify(context.Context, string) (*oidc.IDToken, error)
|
||||||
|
}
|
||||||
|
|
||||||
type OidcAuthConsumer struct {
|
type OidcAuthConsumer struct {
|
||||||
additionalAuthScopes []v1.AuthScope
|
additionalAuthScopes []v1.AuthScope
|
||||||
|
|
||||||
verifier *oidc.IDTokenVerifier
|
verifier TokenVerifier
|
||||||
subjectFromLogin string
|
subjectsFromLogin []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCServerConfig) *OidcAuthConsumer {
|
func NewTokenVerifier(cfg v1.AuthOIDCServerConfig) TokenVerifier {
|
||||||
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
provider, err := oidc.NewProvider(context.Background(), cfg.Issuer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -105,9 +109,14 @@ func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, cfg v1.AuthOIDCSer
|
|||||||
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
SkipExpiryCheck: cfg.SkipExpiryCheck,
|
||||||
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
SkipIssuerCheck: cfg.SkipIssuerCheck,
|
||||||
}
|
}
|
||||||
|
return provider.Verifier(&verifierConf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOidcAuthVerifier(additionalAuthScopes []v1.AuthScope, verifier TokenVerifier) *OidcAuthConsumer {
|
||||||
return &OidcAuthConsumer{
|
return &OidcAuthConsumer{
|
||||||
additionalAuthScopes: additionalAuthScopes,
|
additionalAuthScopes: additionalAuthScopes,
|
||||||
verifier: provider.Verifier(&verifierConf),
|
verifier: verifier,
|
||||||
|
subjectsFromLogin: []string{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,7 +125,9 @@ func (auth *OidcAuthConsumer) VerifyLogin(loginMsg *msg.Login) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
return fmt.Errorf("invalid OIDC token in login: %v", err)
|
||||||
}
|
}
|
||||||
auth.subjectFromLogin = token.Subject
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
|
auth.subjectsFromLogin = append(auth.subjectsFromLogin, token.Subject)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,11 +136,11 @@ func (auth *OidcAuthConsumer) verifyPostLoginToken(privilegeKey string) (err err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
return fmt.Errorf("invalid OIDC token in ping: %v", err)
|
||||||
}
|
}
|
||||||
if token.Subject != auth.subjectFromLogin {
|
if !slices.Contains(auth.subjectsFromLogin, token.Subject) {
|
||||||
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
return fmt.Errorf("received different OIDC subject in login and ping. "+
|
||||||
"original subject: %s, "+
|
"original subjects: %s, "+
|
||||||
"new subject: %s",
|
"new subject: %s",
|
||||||
auth.subjectFromLogin, token.Subject)
|
auth.subjectsFromLogin, token.Subject)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
64
pkg/auth/oidc_test.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package auth_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockTokenVerifier struct{}
|
||||||
|
|
||||||
|
func (m *mockTokenVerifier) Verify(ctx context.Context, subject string) (*oidc.IDToken, error) {
|
||||||
|
return &oidc.IDToken{
|
||||||
|
Subject: subject,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingWithEmptySubjectFromLoginFails(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-without-login",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.Error(err)
|
||||||
|
r.Contains(err.Error(), "received different OIDC subject in login and ping")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingAfterLoginWithNewSubjectSucceeds(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyLogin(&msg.Login{
|
||||||
|
PrivilegeKey: "ping-after-login",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-after-login",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPingAfterLoginWithDifferentSubjectFails(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
consumer := auth.NewOidcAuthVerifier([]v1.AuthScope{v1.AuthScopeHeartBeats}, &mockTokenVerifier{})
|
||||||
|
err := consumer.VerifyLogin(&msg.Login{
|
||||||
|
PrivilegeKey: "login-with-first-subject",
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = consumer.VerifyPing(&msg.Ping{
|
||||||
|
PrivilegeKey: "ping-with-different-subject",
|
||||||
|
Timestamp: time.Now().UnixMilli(),
|
||||||
|
})
|
||||||
|
r.Error(err)
|
||||||
|
r.Contains(err.Error(), "received different OIDC subject in login and ping")
|
||||||
|
}
|
@@ -140,6 +140,7 @@ func registerVisitorBaseConfigFlags(cmd *cobra.Command, c *v1.VisitorBaseConfig,
|
|||||||
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
cmd.Flags().BoolVarP(&c.Transport.UseCompression, "uc", "", false, "use compression")
|
||||||
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
cmd.Flags().StringVarP(&c.SecretKey, "sk", "", "", "secret key")
|
||||||
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
cmd.Flags().StringVarP(&c.ServerName, "server_name", "", "", "server name")
|
||||||
|
cmd.Flags().StringVarP(&c.ServerUser, "server-user", "", "", "server user")
|
||||||
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
cmd.Flags().StringVarP(&c.BindAddr, "bind_addr", "", "", "bind addr")
|
||||||
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
cmd.Flags().IntVarP(&c.BindPort, "bind_port", "", 0, "bind port")
|
||||||
}
|
}
|
||||||
|
@@ -345,35 +345,19 @@ func copySection(source, target *ini.Section) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultClientConf returns a client configuration with default values.
|
// GetDefaultClientConf returns a client configuration with default values.
|
||||||
|
// Note: Some default values here will be set to empty and will be converted to them
|
||||||
|
// new configuration through the 'Complete' function to set them as the default
|
||||||
|
// values of the new configuration.
|
||||||
func GetDefaultClientConf() ClientCommonConf {
|
func GetDefaultClientConf() ClientCommonConf {
|
||||||
return ClientCommonConf{
|
return ClientCommonConf{
|
||||||
ClientConfig: legacyauth.GetDefaultClientConf(),
|
ClientConfig: legacyauth.GetDefaultClientConf(),
|
||||||
ServerAddr: "0.0.0.0",
|
|
||||||
ServerPort: 7000,
|
|
||||||
NatHoleSTUNServer: "stun.easyvoip.com:3478",
|
|
||||||
DialServerTimeout: 10,
|
|
||||||
DialServerKeepAlive: 7200,
|
|
||||||
HTTPProxy: os.Getenv("http_proxy"),
|
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
AdminAddr: "127.0.0.1",
|
|
||||||
PoolCount: 1,
|
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
LoginFailExit: true,
|
LoginFailExit: true,
|
||||||
Start: make([]string, 0),
|
|
||||||
Protocol: "tcp",
|
Protocol: "tcp",
|
||||||
QUICKeepalivePeriod: 10,
|
Start: make([]string, 0),
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
TLSEnable: true,
|
TLSEnable: true,
|
||||||
DisableCustomTLSFirstByte: true,
|
DisableCustomTLSFirstByte: true,
|
||||||
HeartbeatInterval: 30,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
Metas: make(map[string]string),
|
Metas: make(map[string]string),
|
||||||
UDPPacketSize: 1500,
|
|
||||||
IncludeConfigFiles: make([]string, 0),
|
IncludeConfigFiles: make([]string, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -200,34 +200,20 @@ type ServerCommonConf struct {
|
|||||||
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
NatHoleAnalysisDataReserveHours int64 `ini:"nat_hole_analysis_data_reserve_hours" json:"nat_hole_analysis_data_reserve_hours"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaultServerConf returns a server configuration with reasonable
|
// GetDefaultServerConf returns a server configuration with reasonable defaults.
|
||||||
// defaults.
|
// Note: Some default values here will be set to empty and will be converted to them
|
||||||
|
// new configuration through the 'Complete' function to set them as the default
|
||||||
|
// values of the new configuration.
|
||||||
func GetDefaultServerConf() ServerCommonConf {
|
func GetDefaultServerConf() ServerCommonConf {
|
||||||
return ServerCommonConf{
|
return ServerCommonConf{
|
||||||
ServerConfig: legacyauth.GetDefaultServerConf(),
|
ServerConfig: legacyauth.GetDefaultServerConf(),
|
||||||
BindAddr: "0.0.0.0",
|
|
||||||
BindPort: 7000,
|
|
||||||
QUICKeepalivePeriod: 10,
|
|
||||||
QUICMaxIdleTimeout: 30,
|
|
||||||
QUICMaxIncomingStreams: 100000,
|
|
||||||
VhostHTTPTimeout: 60,
|
|
||||||
DashboardAddr: "0.0.0.0",
|
DashboardAddr: "0.0.0.0",
|
||||||
LogFile: "console",
|
LogFile: "console",
|
||||||
LogWay: "console",
|
LogWay: "console",
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
DetailedErrorsToClient: true,
|
DetailedErrorsToClient: true,
|
||||||
TCPMux: true,
|
TCPMux: true,
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
TCPKeepAlive: 7200,
|
|
||||||
AllowPorts: make(map[int]struct{}),
|
AllowPorts: make(map[int]struct{}),
|
||||||
MaxPoolCount: 5,
|
|
||||||
MaxPortsPerClient: 0,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
UserConnTimeout: 10,
|
|
||||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||||
UDPPacketSize: 1500,
|
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -159,18 +159,18 @@ func NewPortsRangeSliceFromString(str string) ([]PortsRange, error) {
|
|||||||
out = append(out, PortsRange{Single: int(singleNum)})
|
out = append(out, PortsRange{Single: int(singleNum)})
|
||||||
case 2:
|
case 2:
|
||||||
// range numbers
|
// range numbers
|
||||||
min, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
minNum, err := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
}
|
}
|
||||||
max, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
maxNum, err := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("range number is invalid, %v", err)
|
return nil, fmt.Errorf("range number is invalid, %v", err)
|
||||||
}
|
}
|
||||||
if max < min {
|
if maxNum < minNum {
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
out = append(out, PortsRange{Start: int(min), End: int(max)})
|
out = append(out, PortsRange{Start: int(minNum), End: int(maxNum)})
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("range number is invalid")
|
return nil, fmt.Errorf("range number is invalid")
|
||||||
}
|
}
|
||||||
|
@@ -135,7 +135,7 @@ func (c *ClientTransportConfig) Complete() {
|
|||||||
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
c.ProxyURL = util.EmptyOr(c.ProxyURL, os.Getenv("http_proxy"))
|
||||||
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
c.PoolCount = util.EmptyOr(c.PoolCount, 1)
|
||||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||||
if lo.FromPtr(c.TCPMux) {
|
if lo.FromPtr(c.TCPMux) {
|
||||||
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, -1)
|
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, -1)
|
||||||
|
@@ -17,11 +17,18 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientPluginOptions interface{}
|
type ClientPluginOptions interface {
|
||||||
|
Complete()
|
||||||
|
}
|
||||||
|
|
||||||
type TypedClientPluginOptions struct {
|
type TypedClientPluginOptions struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -42,7 +49,7 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
c.Type = typeStruct.Type
|
c.Type = typeStruct.Type
|
||||||
if c.Type == "" {
|
if c.Type == "" {
|
||||||
return nil
|
return errors.New("plugin type is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
v, ok := clientPluginOptionsTypeMap[typeStruct.Type]
|
||||||
@@ -63,14 +70,20 @@ func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.ClientPluginOptions)
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PluginHTTP2HTTPS = "http2https"
|
PluginHTTP2HTTPS = "http2https"
|
||||||
PluginHTTPProxy = "http_proxy"
|
PluginHTTPProxy = "http_proxy"
|
||||||
PluginHTTPS2HTTP = "https2http"
|
PluginHTTPS2HTTP = "https2http"
|
||||||
PluginHTTPS2HTTPS = "https2https"
|
PluginHTTPS2HTTPS = "https2https"
|
||||||
|
PluginHTTP2HTTP = "http2http"
|
||||||
PluginSocks5 = "socks5"
|
PluginSocks5 = "socks5"
|
||||||
PluginStaticFile = "static_file"
|
PluginStaticFile = "static_file"
|
||||||
PluginUnixDomainSocket = "unix_domain_socket"
|
PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
PluginTLS2Raw = "tls2raw"
|
||||||
)
|
)
|
||||||
|
|
||||||
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
@@ -78,9 +91,11 @@ var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
|||||||
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||||
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||||
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
||||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||||
|
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTP2HTTPSPluginOptions struct {
|
type HTTP2HTTPSPluginOptions struct {
|
||||||
@@ -90,36 +105,61 @@ type HTTP2HTTPSPluginOptions struct {
|
|||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTP2HTTPSPluginOptions) Complete() {}
|
||||||
|
|
||||||
type HTTPProxyPluginOptions struct {
|
type HTTPProxyPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
HTTPUser string `json:"httpUser,omitempty"`
|
HTTPUser string `json:"httpUser,omitempty"`
|
||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPProxyPluginOptions) Complete() {}
|
||||||
|
|
||||||
type HTTPS2HTTPPluginOptions struct {
|
type HTTPS2HTTPPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPS2HTTPPluginOptions) Complete() {
|
||||||
|
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||||
|
}
|
||||||
|
|
||||||
type HTTPS2HTTPSPluginOptions struct {
|
type HTTPS2HTTPSPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
EnableHTTP2 *bool `json:"enableHTTP2,omitempty"`
|
||||||
CrtPath string `json:"crtPath,omitempty"`
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
KeyPath string `json:"keyPath,omitempty"`
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *HTTPS2HTTPSPluginOptions) Complete() {
|
||||||
|
o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP2HTTPPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *HTTP2HTTPPluginOptions) Complete() {}
|
||||||
|
|
||||||
type Socks5PluginOptions struct {
|
type Socks5PluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password,omitempty"`
|
Password string `json:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Socks5PluginOptions) Complete() {}
|
||||||
|
|
||||||
type StaticFilePluginOptions struct {
|
type StaticFilePluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalPath string `json:"localPath,omitempty"`
|
LocalPath string `json:"localPath,omitempty"`
|
||||||
@@ -128,7 +168,20 @@ type StaticFilePluginOptions struct {
|
|||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *StaticFilePluginOptions) Complete() {}
|
||||||
|
|
||||||
type UnixDomainSocketPluginOptions struct {
|
type UnixDomainSocketPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
UnixPath string `json:"unixPath,omitempty"`
|
UnixPath string `json:"unixPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *UnixDomainSocketPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type TLS2RawPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
|
CrtPath string `json:"crtPath,omitempty"`
|
||||||
|
KeyPath string `json:"keyPath,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *TLS2RawPluginOptions) Complete() {}
|
||||||
|
@@ -127,6 +127,10 @@ func (c *ProxyBaseConfig) Complete(namePrefix string) {
|
|||||||
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
c.Name = lo.Ternary(namePrefix == "", "", namePrefix+".") + c.Name
|
||||||
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
c.LocalIP = util.EmptyOr(c.LocalIP, "127.0.0.1")
|
||||||
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
c.Transport.BandwidthLimitMode = util.EmptyOr(c.Transport.BandwidthLimitMode, types.BandwidthLimitModeClient)
|
||||||
|
|
||||||
|
if c.Plugin.ClientPluginOptions != nil {
|
||||||
|
c.Plugin.ClientPluginOptions.Complete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
func (c *ProxyBaseConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||||
@@ -195,6 +199,10 @@ func (c *TypedProxyConfig) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TypedProxyConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.ProxyConfigurer)
|
||||||
|
}
|
||||||
|
|
||||||
type ProxyConfigurer interface {
|
type ProxyConfigurer interface {
|
||||||
Complete(namePrefix string)
|
Complete(namePrefix string)
|
||||||
GetBaseConfig() *ProxyBaseConfig
|
GetBaseConfig() *ProxyBaseConfig
|
||||||
@@ -291,6 +299,7 @@ type HTTPProxyConfig struct {
|
|||||||
HTTPPassword string `json:"httpPassword,omitempty"`
|
HTTPPassword string `json:"httpPassword,omitempty"`
|
||||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||||
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
RequestHeaders HeaderOperations `json:"requestHeaders,omitempty"`
|
||||||
|
ResponseHeaders HeaderOperations `json:"responseHeaders,omitempty"`
|
||||||
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
RouteByHTTPUser string `json:"routeByHTTPUser,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -304,6 +313,7 @@ func (c *HTTPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
|||||||
m.HTTPUser = c.HTTPUser
|
m.HTTPUser = c.HTTPUser
|
||||||
m.HTTPPwd = c.HTTPPassword
|
m.HTTPPwd = c.HTTPPassword
|
||||||
m.Headers = c.RequestHeaders.Set
|
m.Headers = c.RequestHeaders.Set
|
||||||
|
m.ResponseHeaders = c.ResponseHeaders.Set
|
||||||
m.RouteByHTTPUser = c.RouteByHTTPUser
|
m.RouteByHTTPUser = c.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +327,7 @@ func (c *HTTPProxyConfig) UnmarshalFromMsg(m *msg.NewProxy) {
|
|||||||
c.HTTPUser = m.HTTPUser
|
c.HTTPUser = m.HTTPUser
|
||||||
c.HTTPPassword = m.HTTPPwd
|
c.HTTPPassword = m.HTTPPwd
|
||||||
c.RequestHeaders.Set = m.Headers
|
c.RequestHeaders.Set = m.Headers
|
||||||
|
c.ResponseHeaders.Set = m.ResponseHeaders
|
||||||
c.RouteByHTTPUser = m.RouteByHTTPUser
|
c.RouteByHTTPUser = m.RouteByHTTPUser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -176,7 +176,7 @@ type ServerTransportConfig struct {
|
|||||||
|
|
||||||
func (c *ServerTransportConfig) Complete() {
|
func (c *ServerTransportConfig) Complete() {
|
||||||
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
c.TCPMux = util.EmptyOr(c.TCPMux, lo.ToPtr(true))
|
||||||
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 60)
|
c.TCPMuxKeepaliveInterval = util.EmptyOr(c.TCPMuxKeepaliveInterval, 30)
|
||||||
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
c.TCPKeepAlive = util.EmptyOr(c.TCPKeepAlive, 7200)
|
||||||
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
c.MaxPoolCount = util.EmptyOr(c.MaxPoolCount, 5)
|
||||||
if lo.FromPtr(c.TCPMux) {
|
if lo.FromPtr(c.TCPMux) {
|
||||||
|
@@ -32,6 +32,8 @@ func ValidateClientPluginOptions(c v1.ClientPluginOptions) error {
|
|||||||
return validateStaticFilePluginOptions(v)
|
return validateStaticFilePluginOptions(v)
|
||||||
case *v1.UnixDomainSocketPluginOptions:
|
case *v1.UnixDomainSocketPluginOptions:
|
||||||
return validateUnixDomainSocketPluginOptions(v)
|
return validateUnixDomainSocketPluginOptions(v)
|
||||||
|
case *v1.TLS2RawPluginOptions:
|
||||||
|
return validateTLS2RawPluginOptions(v)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -70,3 +72,10 @@ func validateUnixDomainSocketPluginOptions(c *v1.UnixDomainSocketPluginOptions)
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateTLS2RawPluginOptions(c *v1.TLS2RawPluginOptions) error {
|
||||||
|
if c.LocalAddr == "" {
|
||||||
|
return errors.New("localAddr is required")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@@ -120,6 +120,10 @@ func (c *TypedVisitorConfig) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorConfig) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.VisitorConfigurer)
|
||||||
|
}
|
||||||
|
|
||||||
func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
func NewVisitorConfigurerByType(t VisitorType) VisitorConfigurer {
|
||||||
v, ok := visitorConfigTypeMap[t]
|
v, ok := visitorConfigTypeMap[t]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@@ -121,6 +121,7 @@ type NewProxy struct {
|
|||||||
HTTPPwd string `json:"http_pwd,omitempty"`
|
HTTPPwd string `json:"http_pwd,omitempty"`
|
||||||
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
|
HostHeaderRewrite string `json:"host_header_rewrite,omitempty"`
|
||||||
Headers map[string]string `json:"headers,omitempty"`
|
Headers map[string]string `json:"headers,omitempty"`
|
||||||
|
ResponseHeaders map[string]string `json:"response_headers,omitempty"`
|
||||||
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
RouteByHTTPUser string `json:"route_by_http_user,omitempty"`
|
||||||
|
|
||||||
// stcp, sudp, xtcp
|
// stcp, sudp, xtcp
|
||||||
|
@@ -78,9 +78,9 @@ func ListAllLocalIPs() ([]net.IP, error) {
|
|||||||
return ips, nil
|
return ips, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListLocalIPsForNatHole(max int) ([]string, error) {
|
func ListLocalIPsForNatHole(maxItems int) ([]string, error) {
|
||||||
if max <= 0 {
|
if maxItems <= 0 {
|
||||||
return nil, fmt.Errorf("max must be greater than 0")
|
return nil, fmt.Errorf("maxItems must be greater than 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := ListAllLocalIPs()
|
ips, err := ListAllLocalIPs()
|
||||||
@@ -88,9 +88,9 @@ func ListLocalIPsForNatHole(max int) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := make([]string, 0, max)
|
filtered := make([]string, 0, maxItems)
|
||||||
for _, ip := range ips {
|
for _, ip := range ips {
|
||||||
if len(filtered) >= max {
|
if len(filtered) >= maxItems {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
pkg/plugin/client/http2http.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// Copyright 2024 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
stdlog "log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(v1.PluginHTTP2HTTP, NewHTTP2HTTPPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type HTTP2HTTPPlugin struct {
|
||||||
|
opts *v1.HTTP2HTTPPluginOptions
|
||||||
|
|
||||||
|
l *Listener
|
||||||
|
s *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
|
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
||||||
|
|
||||||
|
listener := NewProxyListener()
|
||||||
|
|
||||||
|
p := &HTTP2HTTPPlugin{
|
||||||
|
opts: opts,
|
||||||
|
l: listener,
|
||||||
|
}
|
||||||
|
|
||||||
|
rp := &httputil.ReverseProxy{
|
||||||
|
Rewrite: func(r *httputil.ProxyRequest) {
|
||||||
|
req := r.Out
|
||||||
|
req.URL.Scheme = "http"
|
||||||
|
req.URL.Host = p.opts.LocalAddr
|
||||||
|
if p.opts.HostHeaderRewrite != "" {
|
||||||
|
req.Host = p.opts.HostHeaderRewrite
|
||||||
|
}
|
||||||
|
for k, v := range p.opts.RequestHeaders.Set {
|
||||||
|
req.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BufferPool: pool.NewBuffer(32 * 1024),
|
||||||
|
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: rp,
|
||||||
|
ReadHeaderTimeout: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
_ = p.s.Serve(listener)
|
||||||
|
}()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
_ = p.l.PutConn(wrapConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPPlugin) Name() string {
|
||||||
|
return v1.PluginHTTP2HTTP
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HTTP2HTTPPlugin) Close() error {
|
||||||
|
return p.s.Close()
|
||||||
|
}
|
@@ -17,6 +17,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
"io"
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
@@ -88,7 +89,7 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ package plugin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -68,7 +69,7 @@ func (hp *HTTPProxy) Name() string {
|
|||||||
return v1.PluginHTTPProxy
|
return v1.PluginHTTPProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HTTPProxy) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (hp *HTTPProxy) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
|
||||||
sc, rd := libnet.NewSharedConn(wrapConn)
|
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -27,9 +28,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
@@ -71,44 +74,39 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
BufferPool: pool.NewBuffer(32 * 1024),
|
BufferPool: pool.NewBuffer(32 * 1024),
|
||||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||||
}
|
}
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
p.s = &http.Server{
|
if r.TLS != nil {
|
||||||
Handler: rp,
|
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
host, _ := httppkg.CanonicalHost(r.Host)
|
||||||
|
if tlsServerName != "" && tlsServerName != host {
|
||||||
|
w.WriteHeader(http.StatusMisdirectedRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if opts.CrtPath != "" || opts.KeyPath != "" {
|
|
||||||
tlsConfig, err = p.genTLSConfig()
|
|
||||||
} else {
|
|
||||||
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
}
|
||||||
|
rp.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
}
|
}
|
||||||
ln := tls.NewListener(listener, tlsConfig)
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
ReadHeaderTimeout: 60 * time.Second,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
if !lo.FromPtr(opts.EnableHTTP2) {
|
||||||
|
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = p.s.Serve(ln)
|
_ = p.s.ServeTLS(listener, "", "")
|
||||||
}()
|
}()
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
||||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
if extra.SrcAddr != nil {
|
if extra.SrcAddr != nil {
|
||||||
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -27,9 +28,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fatedier/golib/pool"
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
httppkg "github.com/fatedier/frp/pkg/util/http"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
)
|
)
|
||||||
@@ -77,44 +80,39 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
BufferPool: pool.NewBuffer(32 * 1024),
|
BufferPool: pool.NewBuffer(32 * 1024),
|
||||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||||
}
|
}
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
p.s = &http.Server{
|
if r.TLS != nil {
|
||||||
Handler: rp,
|
tlsServerName, _ := httppkg.CanonicalHost(r.TLS.ServerName)
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
host, _ := httppkg.CanonicalHost(r.Host)
|
||||||
|
if tlsServerName != "" && tlsServerName != host {
|
||||||
|
w.WriteHeader(http.StatusMisdirectedRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
tlsConfig *tls.Config
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if opts.CrtPath != "" || opts.KeyPath != "" {
|
|
||||||
tlsConfig, err = p.genTLSConfig()
|
|
||||||
} else {
|
|
||||||
tlsConfig, err = transport.NewServerTLSConfig("", "", "")
|
|
||||||
tlsConfig.InsecureSkipVerify = true
|
|
||||||
}
|
}
|
||||||
|
rp.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||||
}
|
}
|
||||||
ln := tls.NewListener(listener, tlsConfig)
|
|
||||||
|
p.s = &http.Server{
|
||||||
|
Handler: handler,
|
||||||
|
ReadHeaderTimeout: 60 * time.Second,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
if !lo.FromPtr(opts.EnableHTTP2) {
|
||||||
|
p.s.TLSNextProto = make(map[string]func(*http.Server, *tls.Conn, http.Handler))
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = p.s.Serve(ln)
|
_ = p.s.ServeTLS(listener, "", "")
|
||||||
}()
|
}()
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPSPlugin) genTLSConfig() (*tls.Config, error) {
|
func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
||||||
cert, err := tls.LoadX509KeyPair(p.opts.CrtPath, p.opts.KeyPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HTTPS2HTTPSPlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
if extra.SrcAddr != nil {
|
if extra.SrcAddr != nil {
|
||||||
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -57,7 +58,7 @@ type ExtraInfo struct {
|
|||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
Handle(conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
|
Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -50,7 +51,7 @@ func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *Socks5Plugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = sp.Server.ServeConn(wrapConn)
|
_ = sp.Server.ServeConn(wrapConn)
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -69,7 +70,7 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *StaticFilePlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
_ = sp.l.PutConn(wrapConn)
|
_ = sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
83
pkg/plugin/client/tls2raw.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2024 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(v1.PluginTLS2Raw, NewTLS2RawPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLS2RawPlugin struct {
|
||||||
|
opts *v1.TLS2RawPluginOptions
|
||||||
|
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTLS2RawPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
|
opts := options.(*v1.TLS2RawPluginOptions)
|
||||||
|
|
||||||
|
p := &TLS2RawPlugin{
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := transport.NewServerTLSConfig(p.opts.CrtPath, p.opts.KeyPath, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.tlsConfig = tlsConfig
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLS2RawPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
|
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
||||||
|
tlsConn := tls.Server(wrapConn, p.tlsConfig)
|
||||||
|
|
||||||
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
xl.Warnf("tls handshake error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rawConn, err := net.Dial("tcp", p.opts.LocalAddr)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("dial to local addr error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
libio.Join(tlsConn, rawConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLS2RawPlugin) Name() string {
|
||||||
|
return v1.PluginTLS2Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TLS2RawPlugin) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
@@ -17,12 +17,14 @@
|
|||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -48,9 +50,11 @@ func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
|
func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
xl.Warnf("dial to uds %s error: %v", uds.UnixAddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if extra.ProxyProtocolHeader != nil {
|
if extra.ProxyProtocolHeader != nil {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -31,8 +32,8 @@ func (c *Client) SetAuth(user, pwd string) {
|
|||||||
c.authPwd = pwd
|
c.authPwd = pwd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
|
func (c *Client) GetProxyStatus(ctx context.Context, name string) (*client.ProxyStatusResp, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -54,8 +55,8 @@ func (c *Client) GetProxyStatus(name string) (*client.ProxyStatusResp, error) {
|
|||||||
return nil, fmt.Errorf("no proxy status found")
|
return nil, fmt.Errorf("no proxy status found")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
|
func (c *Client) GetAllProxyStatus(ctx context.Context) (client.StatusResp, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/status", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/status", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -70,7 +71,7 @@ func (c *Client) GetAllProxyStatus() (client.StatusResp, error) {
|
|||||||
return allStatus, nil
|
return allStatus, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Reload(strictMode bool) error {
|
func (c *Client) Reload(ctx context.Context, strictMode bool) error {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
if strictMode {
|
if strictMode {
|
||||||
v.Set("strictConfig", "true")
|
v.Set("strictConfig", "true")
|
||||||
@@ -79,7 +80,7 @@ func (c *Client) Reload(strictMode bool) error {
|
|||||||
if len(v) > 0 {
|
if len(v) > 0 {
|
||||||
queryStr = "?" + v.Encode()
|
queryStr = "?" + v.Encode()
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/reload"+queryStr, nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/reload"+queryStr, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,8 +88,8 @@ func (c *Client) Reload(strictMode bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Stop() error {
|
func (c *Client) Stop(ctx context.Context) error {
|
||||||
req, err := http.NewRequest("POST", "http://"+c.address+"/api/stop", nil)
|
req, err := http.NewRequestWithContext(ctx, "POST", "http://"+c.address+"/api/stop", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -96,16 +97,16 @@ func (c *Client) Stop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) GetConfig() (string, error) {
|
func (c *Client) GetConfig(ctx context.Context) (string, error) {
|
||||||
req, err := http.NewRequest("GET", "http://"+c.address+"/api/config", nil)
|
req, err := http.NewRequestWithContext(ctx, "GET", "http://"+c.address+"/api/config", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return c.do(req)
|
return c.do(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) UpdateConfig(content string) error {
|
func (c *Client) UpdateConfig(ctx context.Context, content string) error {
|
||||||
req, err := http.NewRequest("PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
|
req, err := http.NewRequestWithContext(ctx, "PUT", "http://"+c.address+"/api/config", strings.NewReader(content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -59,8 +59,12 @@ func fixDNSResolver() {
|
|||||||
// Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially.
|
// Note: If there are other methods to obtain the default DNS servers, the default DNS servers should be used preferentially.
|
||||||
net.DefaultResolver = &net.Resolver{
|
net.DefaultResolver = &net.Resolver{
|
||||||
PreferGo: true,
|
PreferGo: true,
|
||||||
Dial: func(ctx context.Context, network, _ string) (net.Conn, error) {
|
Dial: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return net.Dial(network, "8.8.8.8:53")
|
if addr == "127.0.0.1:53" || addr == "[::1]:53" {
|
||||||
|
addr = "8.8.8.8:53"
|
||||||
|
}
|
||||||
|
var d net.Dialer
|
||||||
|
return d.DialContext(ctx, network, addr)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -85,21 +85,21 @@ func ParseRangeNumbers(rangeStr string) (numbers []int64, err error) {
|
|||||||
numbers = append(numbers, singleNum)
|
numbers = append(numbers, singleNum)
|
||||||
case 2:
|
case 2:
|
||||||
// range numbers
|
// range numbers
|
||||||
min, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
minValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[0]), 10, 64)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = fmt.Errorf("range number is invalid, %v", errRet)
|
err = fmt.Errorf("range number is invalid, %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
max, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
maxValue, errRet := strconv.ParseInt(strings.TrimSpace(numArray[1]), 10, 64)
|
||||||
if errRet != nil {
|
if errRet != nil {
|
||||||
err = fmt.Errorf("range number is invalid, %v", errRet)
|
err = fmt.Errorf("range number is invalid, %v", errRet)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if max < min {
|
if maxValue < minValue {
|
||||||
err = fmt.Errorf("range number is invalid")
|
err = fmt.Errorf("range number is invalid")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := min; i <= max; i++ {
|
for i := minValue; i <= maxValue; i++ {
|
||||||
numbers = append(numbers, i)
|
numbers = append(numbers, i)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -118,13 +118,13 @@ func GenerateResponseErrorString(summary string, err error, detailed bool) strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
|
func RandomSleep(duration time.Duration, minRatio, maxRatio float64) time.Duration {
|
||||||
min := int64(minRatio * 1000.0)
|
minValue := int64(minRatio * 1000.0)
|
||||||
max := int64(maxRatio * 1000.0)
|
maxValue := int64(maxRatio * 1000.0)
|
||||||
var n int64
|
var n int64
|
||||||
if max <= min {
|
if maxValue <= minValue {
|
||||||
n = min
|
n = minValue
|
||||||
} else {
|
} else {
|
||||||
n = mathrand.Int64N(max-min) + min
|
n = mathrand.Int64N(maxValue-minValue) + minValue
|
||||||
}
|
}
|
||||||
d := duration * time.Duration(n) / time.Duration(1000)
|
d := duration * time.Duration(n) / time.Duration(1000)
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.57.0"
|
var version = "0.61.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
@@ -63,9 +63,9 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
req := r.Out
|
req := r.Out
|
||||||
req.URL.Scheme = "http"
|
req.URL.Scheme = "http"
|
||||||
reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo)
|
reqRouteInfo := req.Context().Value(RouteInfoKey).(*RequestRouteInfo)
|
||||||
oldHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||||
|
|
||||||
rc := rp.GetRouteConfig(oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
rc := req.Context().Value(RouteConfigKey).(*RouteConfig)
|
||||||
if rc != nil {
|
if rc != nil {
|
||||||
if rc.RewriteHost != "" {
|
if rc.RewriteHost != "" {
|
||||||
req.Host = rc.RewriteHost
|
req.Host = rc.RewriteHost
|
||||||
@@ -77,7 +77,7 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
endpoint, _ = rc.ChooseEndpointFn()
|
endpoint, _ = rc.ChooseEndpointFn()
|
||||||
reqRouteInfo.Endpoint = endpoint
|
reqRouteInfo.Endpoint = endpoint
|
||||||
log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
|
log.Tracef("choose endpoint name [%s] for http request host [%s] path [%s] httpuser [%s]",
|
||||||
endpoint, oldHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
endpoint, originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||||
}
|
}
|
||||||
// Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections.
|
// Set {domain}.{location}.{routeByHTTPUser}.{endpoint} as URL host here to let http transport reuse connections.
|
||||||
req.URL.Host = rc.Domain + "." +
|
req.URL.Host = rc.Domain + "." +
|
||||||
@@ -92,6 +92,15 @@ func NewHTTPReverseProxy(option HTTPReverseProxyOptions, vhostRouter *Routers) *
|
|||||||
req.URL.Host = req.Host
|
req.URL.Host = req.Host
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ModifyResponse: func(r *http.Response) error {
|
||||||
|
rc := r.Request.Context().Value(RouteConfigKey).(*RouteConfig)
|
||||||
|
if rc != nil {
|
||||||
|
for k, v := range rc.ResponseHeaders {
|
||||||
|
r.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
// Create a connection to one proxy routed by route policy.
|
// Create a connection to one proxy routed by route policy.
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||||
@@ -157,14 +166,6 @@ func (rp *HTTPReverseProxy) GetRouteConfig(domain, location, routeByHTTPUser str
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *HTTPReverseProxy) GetHeaders(domain, location, routeByHTTPUser string) (headers map[string]string) {
|
|
||||||
vr, ok := rp.getVhost(domain, location, routeByHTTPUser)
|
|
||||||
if ok {
|
|
||||||
headers = vr.payload.(*RouteConfig).Headers
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateConnection create a new connection by route config
|
// CreateConnection create a new connection by route config
|
||||||
func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) {
|
func (rp *HTTPReverseProxy) CreateConnection(reqRouteInfo *RequestRouteInfo, byEndpoint bool) (net.Conn, error) {
|
||||||
host, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
host, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||||
@@ -305,8 +306,13 @@ func (rp *HTTPReverseProxy) injectRequestInfoToCtx(req *http.Request) *http.Requ
|
|||||||
RemoteAddr: req.RemoteAddr,
|
RemoteAddr: req.RemoteAddr,
|
||||||
URLHost: req.URL.Host,
|
URLHost: req.URL.Host,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalHost, _ := httppkg.CanonicalHost(reqRouteInfo.Host)
|
||||||
|
rc := rp.GetRouteConfig(originalHost, reqRouteInfo.URL, reqRouteInfo.HTTPUser)
|
||||||
|
|
||||||
newctx := req.Context()
|
newctx := req.Context()
|
||||||
newctx = context.WithValue(newctx, RouteInfoKey, reqRouteInfo)
|
newctx = context.WithValue(newctx, RouteInfoKey, reqRouteInfo)
|
||||||
|
newctx = context.WithValue(newctx, RouteConfigKey, rc)
|
||||||
return req.Clone(newctx)
|
return req.Clone(newctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@ type RouteInfo string
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
RouteInfoKey RouteInfo = "routeInfo"
|
RouteInfoKey RouteInfo = "routeInfo"
|
||||||
|
RouteConfigKey RouteInfo = "routeConfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RequestRouteInfo struct {
|
type RequestRouteInfo struct {
|
||||||
@@ -113,6 +114,7 @@ type RouteConfig struct {
|
|||||||
Username string
|
Username string
|
||||||
Password string
|
Password string
|
||||||
Headers map[string]string
|
Headers map[string]string
|
||||||
|
ResponseHeaders map[string]string
|
||||||
RouteByHTTPUser string
|
RouteByHTTPUser string
|
||||||
|
|
||||||
CreateConnFn CreateConnFunc
|
CreateConnFn CreateConnFunc
|
||||||
|
@@ -58,6 +58,7 @@ func (pxy *HTTPProxy) Run() (remoteAddr string, err error) {
|
|||||||
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
RewriteHost: pxy.cfg.HostHeaderRewrite,
|
||||||
RouteByHTTPUser: pxy.cfg.RouteByHTTPUser,
|
RouteByHTTPUser: pxy.cfg.RouteByHTTPUser,
|
||||||
Headers: pxy.cfg.RequestHeaders.Set,
|
Headers: pxy.cfg.RequestHeaders.Set,
|
||||||
|
ResponseHeaders: pxy.cfg.ResponseHeaders.Set,
|
||||||
Username: pxy.cfg.HTTPUser,
|
Username: pxy.cfg.HTTPUser,
|
||||||
Password: pxy.cfg.HTTPPassword,
|
Password: pxy.cfg.HTTPPassword,
|
||||||
CreateConnFn: pxy.GetRealConn,
|
CreateConnFn: pxy.GetRealConn,
|
||||||
|
@@ -137,17 +137,17 @@ func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn net.Conn,
|
|||||||
dstAddr string
|
dstAddr string
|
||||||
srcPortStr string
|
srcPortStr string
|
||||||
dstPortStr string
|
dstPortStr string
|
||||||
srcPort int
|
srcPort uint64
|
||||||
dstPort int
|
dstPort uint64
|
||||||
)
|
)
|
||||||
|
|
||||||
if src != nil {
|
if src != nil {
|
||||||
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||||
srcPort, _ = strconv.Atoi(srcPortStr)
|
srcPort, _ = strconv.ParseUint(srcPortStr, 10, 16)
|
||||||
}
|
}
|
||||||
if dst != nil {
|
if dst != nil {
|
||||||
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||||
dstPort, _ = strconv.Atoi(dstPortStr)
|
dstPort, _ = strconv.ParseUint(dstPortStr, 10, 16)
|
||||||
}
|
}
|
||||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||||
ProxyName: pxy.GetName(),
|
ProxyName: pxy.GetName(),
|
||||||
@@ -190,8 +190,8 @@ func (pxy *BaseProxy) startCommonTCPListenersHandler() {
|
|||||||
} else {
|
} else {
|
||||||
tempDelay *= 2
|
tempDelay *= 2
|
||||||
}
|
}
|
||||||
if max := 1 * time.Second; tempDelay > max {
|
if maxTime := 1 * time.Second; tempDelay > maxTime {
|
||||||
tempDelay = max
|
tempDelay = maxTime
|
||||||
}
|
}
|
||||||
xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay)
|
xl.Infof("met temporary error: %s, sleep for %s ...", err, tempDelay)
|
||||||
time.Sleep(tempDelay)
|
time.Sleep(tempDelay)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -54,7 +55,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
conf, err := client.GetConfig()
|
conf, err := client.GetConfig(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
newP2Port := f.AllocPort()
|
newP2Port := f.AllocPort()
|
||||||
@@ -65,10 +66,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
newClientConf = newClientConf[:p3Index]
|
newClientConf = newClientConf[:p3Index]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.UpdateConfig(newClientConf)
|
err = client.UpdateConfig(context.Background(), newClientConf)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
err = client.Reload(true)
|
err = client.Reload(context.Background(), true)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
err := client.Stop()
|
err := client.Stop(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -101,7 +102,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
|
|
||||||
// tcp random port
|
// tcp random port
|
||||||
status, err := client.GetProxyStatus("tcp")
|
status, err := client.GetProxyStatus(context.Background(), "tcp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
||||||
@@ -112,7 +113,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(port).Ensure()
|
framework.NewRequestExpect(f).Port(port).Ensure()
|
||||||
|
|
||||||
// udp random port
|
// udp random port
|
||||||
status, err = client.GetProxyStatus("udp")
|
status, err = client.GetProxyStatus(context.Background(), "udp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -57,7 +58,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
framework.NewRequestExpect(f).Port(p3Port).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
conf, err := client.GetConfig()
|
conf, err := client.GetConfig(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
newP2Port := f.AllocPort()
|
newP2Port := f.AllocPort()
|
||||||
@@ -68,10 +69,10 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
newClientConf = newClientConf[:p3Index]
|
newClientConf = newClientConf[:p3Index]
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.UpdateConfig(newClientConf)
|
err = client.UpdateConfig(context.Background(), newClientConf)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
err = client.Reload(true)
|
err = client.Reload(context.Background(), true)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
|
|
||||||
@@ -124,7 +125,7 @@ var _ = ginkgo.Describe("[Feature: ClientManage]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
framework.NewRequestExpect(f).Port(testPort).Ensure()
|
||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
err := client.Stop()
|
err := client.Stop(context.Background())
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
@@ -72,7 +73,7 @@ var _ = ginkgo.Describe("[Feature: Config]", func() {
|
|||||||
|
|
||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
checkProxyFn := func(name string, localPort, remotePort int) {
|
checkProxyFn := func(name string, localPort, remotePort int) {
|
||||||
status, err := client.GetProxyStatus(name)
|
status, err := client.GetProxyStatus(context.Background(), name)
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
framework.ExpectContainSubstring(status.LocalAddr, fmt.Sprintf(":%d", localPort))
|
framework.ExpectContainSubstring(status.LocalAddr, fmt.Sprintf(":%d", localPort))
|
||||||
|
@@ -267,7 +267,7 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
|||||||
Ensure()
|
Ensure()
|
||||||
})
|
})
|
||||||
|
|
||||||
ginkgo.It("Modify headers", func() {
|
ginkgo.It("Modify request headers", func() {
|
||||||
vhostHTTPPort := f.AllocPort()
|
vhostHTTPPort := f.AllocPort()
|
||||||
serverConf := getDefaultServerConf(vhostHTTPPort)
|
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||||
|
|
||||||
@@ -292,7 +292,6 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
|||||||
|
|
||||||
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
// not set auth header
|
|
||||||
framework.NewRequestExpect(f).Port(vhostHTTPPort).
|
framework.NewRequestExpect(f).Port(vhostHTTPPort).
|
||||||
RequestModify(func(r *request.Request) {
|
RequestModify(func(r *request.Request) {
|
||||||
r.HTTP().HTTPHost("normal.example.com")
|
r.HTTP().HTTPHost("normal.example.com")
|
||||||
@@ -301,6 +300,40 @@ var _ = ginkgo.Describe("[Feature: HTTP]", func() {
|
|||||||
Ensure()
|
Ensure()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.It("Modify response headers", func() {
|
||||||
|
vhostHTTPPort := f.AllocPort()
|
||||||
|
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
clientConf := consts.DefaultClientConfig
|
||||||
|
clientConf += fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "test"
|
||||||
|
type = "http"
|
||||||
|
localPort = %d
|
||||||
|
customDomains = ["normal.example.com"]
|
||||||
|
responseHeaders.set.x-from-where = "frp"
|
||||||
|
`, localPort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).Port(vhostHTTPPort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPHost("normal.example.com")
|
||||||
|
}).
|
||||||
|
Ensure(func(res *request.Response) bool {
|
||||||
|
return res.Header.Get("X-From-Where") == "frp"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
ginkgo.It("Host Header Rewrite", func() {
|
ginkgo.It("Host Header Rewrite", func() {
|
||||||
vhostHTTPPort := f.AllocPort()
|
vhostHTTPPort := f.AllocPort()
|
||||||
serverConf := getDefaultServerConf(vhostHTTPPort)
|
serverConf := getDefaultServerConf(vhostHTTPPort)
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -112,7 +113,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
client := f.APIClientForFrpc(adminPort)
|
client := f.APIClientForFrpc(adminPort)
|
||||||
|
|
||||||
// tcp random port
|
// tcp random port
|
||||||
status, err := client.GetProxyStatus("tcp")
|
status, err := client.GetProxyStatus(context.Background(), "tcp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err := net.SplitHostPort(status.RemoteAddr)
|
||||||
@@ -123,7 +124,7 @@ var _ = ginkgo.Describe("[Feature: Server Manager]", func() {
|
|||||||
framework.NewRequestExpect(f).Port(port).Ensure()
|
framework.NewRequestExpect(f).Port(port).Ensure()
|
||||||
|
|
||||||
// udp random port
|
// udp random port
|
||||||
status, err = client.GetProxyStatus("udp")
|
status, err = client.GetProxyStatus(context.Background(), "udp")
|
||||||
framework.ExpectNoError(err)
|
framework.ExpectNoError(err)
|
||||||
|
|
||||||
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
_, portStr, err = net.SplitHostPort(status.RemoteAddr)
|
||||||
|
@@ -3,6 +3,7 @@ package plugin
|
|||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/v2"
|
"github.com/onsi/ginkgo/v2"
|
||||||
@@ -329,4 +330,122 @@ var _ = ginkgo.Describe("[Feature: Client-Plugins]", func() {
|
|||||||
ExpectResp([]byte("test")).
|
ExpectResp([]byte("test")).
|
||||||
Ensure()
|
Ensure()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.Describe("http2http", func() {
|
||||||
|
ginkgo.It("host header rewrite", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "http2http"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = %d
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2http"
|
||||||
|
localAddr = "127.0.0.1:%d"
|
||||||
|
hostHeaderRewrite = "rewrite.test.com"
|
||||||
|
`, remotePort, localPort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
_, _ = w.Write([]byte(req.Host))
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(remotePort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPHost("example.com")
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("rewrite.test.com")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("set request header", func() {
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
remotePort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "http2http"
|
||||||
|
type = "tcp"
|
||||||
|
remotePort = %d
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "http2http"
|
||||||
|
localAddr = "127.0.0.1:%d"
|
||||||
|
requestHeaders.set.x-from-where = "frp"
|
||||||
|
`, remotePort, localPort)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithHandler(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
_, _ = w.Write([]byte(req.Header.Get("x-from-where")))
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(remotePort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTP().HTTPHost("example.com")
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("frp")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
ginkgo.It("tls2raw", func() {
|
||||||
|
generator := &cert.SelfSignedCertGenerator{}
|
||||||
|
artifacts, err := generator.Generate("example.com")
|
||||||
|
framework.ExpectNoError(err)
|
||||||
|
crtPath := f.WriteTempFile("tls2raw_server.crt", string(artifacts.Cert))
|
||||||
|
keyPath := f.WriteTempFile("tls2raw_server.key", string(artifacts.Key))
|
||||||
|
|
||||||
|
serverConf := consts.DefaultServerConfig
|
||||||
|
vhostHTTPSPort := f.AllocPort()
|
||||||
|
serverConf += fmt.Sprintf(`
|
||||||
|
vhostHTTPSPort = %d
|
||||||
|
`, vhostHTTPSPort)
|
||||||
|
|
||||||
|
localPort := f.AllocPort()
|
||||||
|
clientConf := consts.DefaultClientConfig + fmt.Sprintf(`
|
||||||
|
[[proxies]]
|
||||||
|
name = "tls2raw-test"
|
||||||
|
type = "https"
|
||||||
|
customDomains = ["example.com"]
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "tls2raw"
|
||||||
|
localAddr = "127.0.0.1:%d"
|
||||||
|
crtPath = "%s"
|
||||||
|
keyPath = "%s"
|
||||||
|
`, localPort, crtPath, keyPath)
|
||||||
|
|
||||||
|
f.RunProcesses([]string{serverConf}, []string{clientConf})
|
||||||
|
|
||||||
|
localServer := httpserver.New(
|
||||||
|
httpserver.WithBindPort(localPort),
|
||||||
|
httpserver.WithResponse([]byte("test")),
|
||||||
|
)
|
||||||
|
f.RunServer("", localServer)
|
||||||
|
|
||||||
|
framework.NewRequestExpect(f).
|
||||||
|
Port(vhostHTTPSPort).
|
||||||
|
RequestModify(func(r *request.Request) {
|
||||||
|
r.HTTPS().HTTPHost("example.com").TLSConfig(&tls.Config{
|
||||||
|
ServerName: "example.com",
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
})
|
||||||
|
}).
|
||||||
|
ExpectResp([]byte("test")).
|
||||||
|
Ensure()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|