mirror of
https://github.com/fatedier/frp.git
synced 2025-08-02 12:07:20 +00:00
Compare commits
73 Commits
8115f7b410
...
v0.58.1
Author | SHA1 | Date | |
---|---|---|---|
|
e649692217 | ||
|
77990c31ef | ||
|
e680acf42d | ||
|
522e2c94c1 | ||
|
301515d2e8 | ||
|
f0442d0cd5 | ||
|
9ced717d69 | ||
|
4e8e9e1dec | ||
|
92cb0b30c2 | ||
|
e81b36c5ba | ||
|
d0d396becb | ||
|
ee3892798d | ||
|
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 |
4
.github/workflows/build-and-push-image.yml
vendored
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
|
||||||
|
@@ -804,7 +804,7 @@ You can disable this feature by modify `frps.toml` and `frpc.toml`:
|
|||||||
|
|
||||||
```toml
|
```toml
|
||||||
# frps.toml and frpc.toml, must be same
|
# frps.toml and frpc.toml, must be same
|
||||||
tcpMux = false
|
transport.tcpMux = false
|
||||||
```
|
```
|
||||||
|
|
||||||
### Support KCP Protocol
|
### Support KCP Protocol
|
||||||
@@ -983,7 +983,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 +995,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
|
||||||
|
|
||||||
|
12
Release.md
12
Release.md
@@ -1,7 +1,9 @@
|
|||||||
### Features
|
|
||||||
|
|
||||||
* Show tcpmux proxies on the frps dashboard.
|
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
* When an HTTP proxy request times out, it returns 504 instead of 404 now.
|
* Fixed an issue where HTTP/2 was not enabled for https2http and https2https plugins.
|
||||||
|
* Fixed the issue where the default values of INI configuration parameters are inconsistent with other configuration formats.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
* Updated the default value of `transport.tcpMuxKeepaliveInterval` from 60 to 30.
|
||||||
|
* On the Android platform, the Google DNS server is used only when the default DNS server cannot be obtained.
|
||||||
|
@@ -20,8 +20,6 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
"github.com/fatedier/frp/client/proxy"
|
"github.com/fatedier/frp/client/proxy"
|
||||||
"github.com/fatedier/frp/client/visitor"
|
"github.com/fatedier/frp/client/visitor"
|
||||||
"github.com/fatedier/frp/pkg/auth"
|
"github.com/fatedier/frp/pkg/auth"
|
||||||
@@ -236,10 +234,8 @@ func (ctl *Control) registerMsgHandlers() {
|
|||||||
func (ctl *Control) heartbeatWorker() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
xl := ctl.xl
|
||||||
|
|
||||||
// TODO(fatedier): Change default value of HeartbeatInterval to -1 if tcpmux is enabled.
|
|
||||||
// Users can still enable heartbeat feature by setting HeartbeatInterval to a positive value.
|
|
||||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 {
|
||||||
// send heartbeat to server
|
// Send heartbeat to server.
|
||||||
sendHeartBeat := func() (bool, error) {
|
sendHeartBeat := func() (bool, error) {
|
||||||
xl.Debugf("send heartbeat to server")
|
xl.Debugf("send heartbeat to server")
|
||||||
pingMsg := &msg.Ping{}
|
pingMsg := &msg.Ping{}
|
||||||
@@ -263,10 +259,8 @@ func (ctl *Control) heartbeatWorker() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check heartbeat timeout only if TCPMux is not enabled and users don't disable heartbeat feature.
|
// Check heartbeat timeout.
|
||||||
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 &&
|
if ctl.sessionCtx.Common.Transport.HeartbeatInterval > 0 && ctl.sessionCtx.Common.Transport.HeartbeatTimeout > 0 {
|
||||||
!lo.FromPtr(ctl.sessionCtx.Common.Transport.TCPMux) {
|
|
||||||
|
|
||||||
go wait.Until(func() {
|
go wait.Until(func() {
|
||||||
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
if time.Since(ctl.lastPong.Load().(time.Time)) > time.Duration(ctl.sessionCtx.Common.Transport.HeartbeatTimeout)*time.Second {
|
||||||
xl.Warnf("heartbeat timeout")
|
xl.Warnf("heartbeat timeout")
|
||||||
|
@@ -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
|
||||||
|
@@ -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.
|
||||||
|
@@ -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",
|
DashboardAddr: "0.0.0.0",
|
||||||
BindPort: 7000,
|
LogFile: "console",
|
||||||
QUICKeepalivePeriod: 10,
|
LogWay: "console",
|
||||||
QUICMaxIdleTimeout: 30,
|
DetailedErrorsToClient: true,
|
||||||
QUICMaxIncomingStreams: 100000,
|
TCPMux: true,
|
||||||
VhostHTTPTimeout: 60,
|
AllowPorts: make(map[int]struct{}),
|
||||||
DashboardAddr: "0.0.0.0",
|
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
||||||
LogFile: "console",
|
|
||||||
LogWay: "console",
|
|
||||||
LogLevel: "info",
|
|
||||||
LogMaxDays: 3,
|
|
||||||
DetailedErrorsToClient: true,
|
|
||||||
TCPMux: true,
|
|
||||||
TCPMuxKeepaliveInterval: 60,
|
|
||||||
TCPKeepAlive: 7200,
|
|
||||||
AllowPorts: make(map[int]struct{}),
|
|
||||||
MaxPoolCount: 5,
|
|
||||||
MaxPortsPerClient: 0,
|
|
||||||
HeartbeatTimeout: 90,
|
|
||||||
UserConnTimeout: 10,
|
|
||||||
HTTPPlugins: make(map[string]HTTPPluginOptions),
|
|
||||||
UDPPacketSize: 1500,
|
|
||||||
NatHoleAnalysisDataReserveHours: 7 * 24,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -135,9 +135,15 @@ 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)
|
||||||
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
if lo.FromPtr(c.TCPMux) {
|
||||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
// 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.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||||
|
} else {
|
||||||
|
c.HeartbeatInterval = util.EmptyOr(c.HeartbeatInterval, 30)
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
}
|
||||||
c.QUIC.Complete()
|
c.QUIC.Complete()
|
||||||
c.TLS.Complete()
|
c.TLS.Complete()
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
@@ -42,7 +43,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,6 +64,10 @@ 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"
|
||||||
|
@@ -195,6 +195,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 +295,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 +309,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 +323,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,10 +176,15 @@ 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)
|
||||||
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
if lo.FromPtr(c.TCPMux) {
|
||||||
|
// If TCPMux is enabled, heartbeat of application layer is unnecessary because we can rely on heartbeat in tcpmux.
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, -1)
|
||||||
|
} else {
|
||||||
|
c.HeartbeatTimeout = util.EmptyOr(c.HeartbeatTimeout, 90)
|
||||||
|
}
|
||||||
c.QUIC.Complete()
|
c.QUIC.Complete()
|
||||||
if c.TLS.TrustedCaFile != "" {
|
if c.TLS.TrustedCaFile != "" {
|
||||||
c.TLS.Force = true
|
c.TLS.Force = true
|
||||||
|
@@ -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
|
||||||
|
@@ -72,11 +72,6 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.s = &http.Server{
|
|
||||||
Handler: rp,
|
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
err error
|
err error
|
||||||
@@ -90,10 +85,15 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
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: rp,
|
||||||
|
ReadHeaderTimeout: 60 * time.Second,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = p.s.Serve(ln)
|
_ = p.s.ServeTLS(listener, "", "")
|
||||||
}()
|
}()
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
@@ -78,11 +78,6 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
ErrorLog: stdlog.New(log.NewWriteLogger(log.WarnLevel, 2), "", 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.s = &http.Server{
|
|
||||||
Handler: rp,
|
|
||||||
ReadHeaderTimeout: 60 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
err error
|
err error
|
||||||
@@ -96,10 +91,15 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
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: rp,
|
||||||
|
ReadHeaderTimeout: 60 * time.Second,
|
||||||
|
TLSConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
_ = p.s.Serve(ln)
|
_ = p.s.ServeTLS(listener, "", "")
|
||||||
}()
|
}()
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.57.0"
|
var version = "0.58.1"
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -29,7 +29,8 @@ import (
|
|||||||
type RouteInfo string
|
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
|
||||||
|
@@ -297,20 +297,18 @@ func (ctl *Control) GetWorkConn() (workConn net.Conn, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ctl *Control) heartbeatWorker() {
|
func (ctl *Control) heartbeatWorker() {
|
||||||
xl := ctl.xl
|
if ctl.serverCfg.Transport.HeartbeatTimeout <= 0 {
|
||||||
|
return
|
||||||
// Don't need application heartbeat if TCPMux is enabled,
|
|
||||||
// yamux will do same thing.
|
|
||||||
// TODO(fatedier): let default HeartbeatTimeout to -1 if TCPMux is enabled. Users can still set it to positive value to enable it.
|
|
||||||
if !lo.FromPtr(ctl.serverCfg.Transport.TCPMux) && ctl.serverCfg.Transport.HeartbeatTimeout > 0 {
|
|
||||||
go wait.Until(func() {
|
|
||||||
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
|
||||||
xl.Warnf("heartbeat timeout")
|
|
||||||
ctl.conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}, time.Second, ctl.doneCh)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xl := ctl.xl
|
||||||
|
go wait.Until(func() {
|
||||||
|
if time.Since(ctl.lastPing.Load().(time.Time)) > time.Duration(ctl.serverCfg.Transport.HeartbeatTimeout)*time.Second {
|
||||||
|
xl.Warnf("heartbeat timeout")
|
||||||
|
ctl.conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}, time.Second, ctl.doneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until Control closed
|
// block until Control closed
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user