Compare commits

...

10 Commits

Author SHA1 Message Date
fatedier
aa0a41ee4e Merge pull request #2088 from fatedier/dev
bump version to v0.34.3
2020-11-20 17:04:55 +08:00
fatedier
8a779eb88c add goreleaser action (#2087)
* add goreleaser action

* change version
2020-11-20 17:00:14 +08:00
蓝云Reyes
0138dbd352 update ISSUE_TEMPLATE (#2061) 2020-11-09 19:39:56 +08:00
yuyulei
9b45c93c14 Fix set-env in github actions of frp (#2060) 2020-11-09 16:56:53 +08:00
fatedier
191da54980 .travis.yml add go1.15 and remove go1.13 2020-11-06 15:49:03 +08:00
Dan Ordille
c3b7575453 Add enable_prometheus option as command line flag (#2057) 2020-11-06 15:33:59 +08:00
fatedier
1ea1530b36 Merge pull request #2058 from fatedier/dev
bump version to v0.34.2
2020-11-06 14:50:50 +08:00
fatedier
7f7305fa03 update version 2020-11-06 14:39:04 +08:00
yuyulei
644a0cfdb6 Update ReverseProxy code from official golang repo (#2051)
Fix #1192
2020-11-05 16:34:17 +08:00
Chirag Sukhala
3c2e2bcea5 Update frpc_full.ini (#2023)
Added authenticate_heartbeats and authenticate_new_work_conns
2020-10-08 14:25:03 +08:00
17 changed files with 260 additions and 97 deletions

View File

@@ -1,29 +0,0 @@
Issue is only used for submiting bug report and documents typo. If there are same issues or answers can be found in documents, we will close it directly.
Use the commands below to provide key information from your environment:
You do NOT have to include this information if this is a FEATURE REQUEST
**What version of frp are you using (./frpc -v or ./frps -v)?**
**What operating system and processor architecture are you using (`go env`)?**
**Configures you used:**
**Steps to reproduce the issue:**
1.
2.
3.
**Describe the results you received:**
**Describe the results you expected:**
**Additional information you deem important (e.g. issue happens only occasionally):**
**Can you point out what caused this issue (optional)**

44
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,44 @@
---
name: Bug Report
about: Bug Report for FRP
title: ''
labels: Requires Testing
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
<!-- ⚠️⚠️ Incomplete reports will be marked as invalid, and closed, with few exceptions ⚠️⚠️ -->
<!-- in addition, please use search well so that the same solution can be found in the feedback, we will close it directly -->
<!-- for convenience of differentiation, use FRPS or FRPC to refer to the FRP server or client -->
**[REQUIRED] hat version of frp are you using**
<!-- Use ./frpc -v or ./frps -v -->
Version:
**[REQUIRED] What operating system and processor architecture are you using**
OS:
CPU architecture:
**[REQUIRED] description of errors**
**confile**
<!-- Please pay attention to hiding the token, server_addr and other privacy information -->
**log file**
<!-- If the file is too large, use Pastebin, for example https://pastebin.ubuntu.com/ -->
**Steps to reproduce the issue**
1.
2.
3.
**Supplementary information**
**Can you guess what caused this issue**
**Checklist**:
<!--- Make sure you've completed the following steps (put an "X" between of brackets): -->
- [] I included all information required in the sections above
- [] I made sure there are no duplicates of this report [(Use Search)](https://github.com/fatedier/frp/issues?q=is%3Aissue)

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: DOCS
url: https://github.com/fatedier/frp
about: Here you can find out how to configure frp.

View File

@@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: "[+] Enhancement"
assignees: ''
---
<!-- From Chinese to English by machine translation, welcome to revise and polish. -->
**The solution you want**
<!--A clear and concise description of the solution you want. -->
**Alternatives considered**
<!--A clear and concise description of any alternative solutions or features you have considered. -->
**How to implement this function**
<!--Implementation steps for the solution you want. -->
**Application scenarios of this function**
<!--Make a clear and concise description of the application scenario of the solution you want. -->

View File

@@ -75,41 +75,41 @@ jobs:
name: Get Image Tag Name name: Get Image Tag Name
run: | run: |
if [ x${{ github.event.inputs.tag }} == x"" ]; then if [ x${{ github.event.inputs.tag }} == x"" ]; then
echo ::set-env name=TAG_NAME::${GITHUB_REF#refs/*/} echo "TAG_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
else else
echo ::set-env name=TAG_NAME::${{ github.event.inputs.tag }} echo "TAG_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
fi fi
# prepare image tags # prepare image tags
- -
name: Prepare Image Tags name: Prepare Image Tags
run: | run: |
echo ::set-env name=DOCKERFILE_FRPC_PATH::dockerfiles/Dockerfile-for-frpc echo "DOCKERFILE_FRPC_PATH=dockerfiles/Dockerfile-for-frpc" >> $GITHUB_ENV
echo ::set-env name=DOCKERFILE_FRPS_PATH::dockerfiles/Dockerfile-for-frps echo "DOCKERFILE_FRPS_PATH=dockerfiles/Dockerfile-for-frps" >> $GITHUB_ENV
echo ::set-env name=TAG_FRPC::fatedier/frpc:$TAG_NAME echo "TAG_FRPC=fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
echo ::set-env name=TAG_FRPS::fatedier/frps:$TAG_NAME echo "TAG_FRPS=fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
echo ::set-env name=TAG_FRPC_GPR::ghcr.io/fatedier/frpc:$TAG_NAME echo "TAG_FRPC_GPR=ghcr.io/fatedier/frpc:${{ env.TAG_NAME }}" >> $GITHUB_ENV
echo ::set-env name=TAG_FRPS_GPR::ghcr.io/fatedier/frps:$TAG_NAME echo "TAG_FRPS_GPR=ghcr.io/fatedier/frps:${{ env.TAG_NAME }}" >> $GITHUB_ENV
# build images # build images
- -
name: Build Images name: Build Images
run: | run: |
# for Docker hub # for Docker hub
docker build --file $DOCKERFILE_FRPC_PATH --tag $TAG_FRPC . docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC }} .
docker build --file $DOCKERFILE_FRPS_PATH --tag $TAG_FRPS . docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS }} .
# for GPR # for GPR
docker build --file $DOCKERFILE_FRPC_PATH --tag $TAG_FRPC_GPR . docker build --file ${{ env.DOCKERFILE_FRPC_PATH }} --tag ${{ env.TAG_FRPC_GPR }} .
docker build --file $DOCKERFILE_FRPS_PATH --tag $TAG_FRPS_GPR . docker build --file ${{ env.DOCKERFILE_FRPS_PATH }} --tag ${{ env.TAG_FRPS_GPR }} .
# push to dockerhub # push to dockerhub
- -
name: Publish to Dockerhub name: Publish to Dockerhub
run: | run: |
echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login --username ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin
docker push $TAG_FRPC docker push ${{ env.TAG_FRPC }}
docker push $TAG_FRPS docker push ${{ env.TAG_FRPS }}
# push to gpr # push to gpr
- -
name: Publish to GPR name: Publish to GPR
run: | run: |
echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin echo ${{ secrets.GPR_TOKEN }} | docker login ghcr.io --username ${{ github.repository_owner }} --password-stdin
docker push $TAG_FRPC_GPR docker push ${{ env.TAG_FRPC_GPR }}
docker push $TAG_FRPS_GPR docker push ${{ env.TAG_FRPS_GPR }}

30
.github/workflows/goreleaser.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: goreleaser
on:
workflow_dispatch:
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.15
- name: Make All
run: |
./package.sh
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
version: latest
args: release --rm-dist --release-notes=./Release.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ packages/
release/ release/
test/bin/ test/bin/
vendor/ vendor/
dist/
# Cache # Cache
*.swp *.swp

19
.goreleaser.yml Normal file
View File

@@ -0,0 +1,19 @@
builds:
- skip: true
checksum:
name_template: 'checksums.txt'
release:
# Same as for github
# Note: it can only be one: either github, gitlab or gitea
github:
owner: fatedier
name: frp
draft: false
# You can add extra pre-existing files to the release.
# The filename on the release will be the last part of the path (base). If
# another file with the same name exists, the latest one found will be used.
# Defaults to empty.
extra_files:
- glob: ./release/packages/*

View File

@@ -2,8 +2,8 @@ sudo: false
language: go language: go
go: go:
- 1.13.x
- 1.14.x - 1.14.x
- 1.15.x
install: install:
- make - make

View File

@@ -20,10 +20,10 @@ fmt:
go fmt ./... go fmt ./...
frps: frps:
env CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frps ./cmd/frps
frpc: frpc:
env CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc env CGO_ENABLED=0 go build -trimpath -ldflags "$(LDFLAGS)" -o bin/frpc ./cmd/frpc
test: gotest test: gotest

View File

@@ -7,32 +7,29 @@ all: build
build: app build: app
app: app:
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_darwin_amd64 ./cmd/frpc env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_darwin_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_darwin_amd64 ./cmd/frps env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_darwin_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_386 ./cmd/frpc env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_386 ./cmd/frps env CGO_ENABLED=0 GOOS=freebsd GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_amd64 ./cmd/frpc env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_freebsd_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_amd64 ./cmd/frps env CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_freebsd_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_386 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_386 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_386 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_386 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_amd64 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_amd64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_amd64 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_amd64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm64 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_arm64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm64 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_arm64 ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_386.exe ./cmd/frpc env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_386.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o ./release/frps_windows_386.exe ./cmd/frps env CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_386.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_amd64.exe ./cmd/frpc env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_windows_amd64.exe ./cmd/frpc
env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_windows_amd64.exe ./cmd/frps env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_windows_amd64.exe ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64 ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64 ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64 ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips64 go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64 ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64le ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips64le ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64le ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips64le go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips64le ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mips ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mips GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mips ./cmd/frps
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mipsle ./cmd/frpc env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frpc_linux_mipsle ./cmd/frpc
env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mipsle ./cmd/frps env CGO_ENABLED=0 GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -trimpath -ldflags "$(LDFLAGS)" -o ./release/frps_linux_mipsle ./cmd/frps
temp:
env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o ./frps_linux_amd64 ./cmd/frps

3
Release.md Normal file
View File

@@ -0,0 +1,3 @@
### New
* Command line parameters support `enable_prometheus`.

View File

@@ -49,6 +49,7 @@ var (
dashboardPort int dashboardPort int
dashboardUser string dashboardUser string
dashboardPwd string dashboardPwd string
enablePrometheus bool
assetsDir string assetsDir string
logFile string logFile string
logLevel string logLevel string
@@ -79,6 +80,7 @@ func init() {
rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port") rootCmd.PersistentFlags().IntVarP(&dashboardPort, "dashboard_port", "", 0, "dashboard port")
rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user") rootCmd.PersistentFlags().StringVarP(&dashboardUser, "dashboard_user", "", "admin", "dashboard user")
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password") rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
rootCmd.PersistentFlags().BoolVarP(&enablePrometheus, "enable_prometheus", "", false, "enable prometheus dashboard")
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file") rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level") rootCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days") rootCmd.PersistentFlags().Int64VarP(&logMaxDays, "log_max_days", "", 3, "log max days")
@@ -171,6 +173,7 @@ func parseServerCommonCfgFromCmd() (cfg config.ServerCommonConf, err error) {
cfg.DashboardPort = dashboardPort cfg.DashboardPort = dashboardPort
cfg.DashboardUser = dashboardUser cfg.DashboardUser = dashboardUser
cfg.DashboardPwd = dashboardPwd cfg.DashboardPwd = dashboardPwd
cfg.EnablePrometheus = enablePrometheus
cfg.LogFile = logFile cfg.LogFile = logFile
cfg.LogLevel = logLevel cfg.LogLevel = logLevel
cfg.LogMaxDays = logMaxDays cfg.LogMaxDays = logMaxDays

View File

@@ -22,7 +22,14 @@ log_max_days = 3
# disable log colors when log_file is console, default is false # disable log colors when log_file is console, default is false
disable_log_color = false disable_log_color = false
# for authentication # for authentication, should be same as your frps.ini
# AuthenticateHeartBeats specifies whether to include authentication token in heartbeats sent to frps. By default, this value is false.
authenticate_heartbeats = false
# AuthenticateNewWorkConns specifies whether to include authentication token in new work connections sent to frps. By default, this value is false.
authenticate_new_work_conns = false
# auth token
token = 12345678 token = 12345678
# set admin address for control frpc's action by http api such as reload # set admin address for control frpc's action by http api such as reload

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/fatedier/frp module github.com/fatedier/frp
go 1.12 go 1.15
require ( require (
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5

View File

@@ -19,7 +19,7 @@ import (
"strings" "strings"
) )
var version string = "0.34.1" var version string = "0.34.3"
func Full() string { func Full() string {
return version return version

View File

@@ -13,6 +13,7 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/textproto"
"net/url" "net/url"
"strings" "strings"
"sync" "sync"
@@ -24,6 +25,19 @@ import (
// ReverseProxy is an HTTP Handler that takes an incoming request and // ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the // sends it to another server, proxying the response back to the
// client. // client.
//
// ReverseProxy by default sets the client IP as the value of the
// X-Forwarded-For header.
//
// If an X-Forwarded-For header already exists, the client IP is
// appended to the existing values. As a special case, if the header
// exists in the Request.Header map but has a nil value (such as when
// set by the Director func), the X-Forwarded-For header is
// not modified.
//
// To prevent IP spoofing, be sure to delete any pre-existing
// X-Forwarded-For header coming from the client or
// an untrusted proxy.
type ReverseProxy struct { type ReverseProxy struct {
// Director must be a function which modifies // Director must be a function which modifies
// the request into a new request to be sent // the request into a new request to be sent
@@ -44,9 +58,9 @@ type ReverseProxy struct {
// A negative value means to flush immediately // A negative value means to flush immediately
// after each write to the client. // after each write to the client.
// The FlushInterval is ignored when ReverseProxy // The FlushInterval is ignored when ReverseProxy
// recognizes a response as a streaming response; // recognizes a response as a streaming response, or
// for such responses, writes are flushed to the client // if its ContentLength is -1; for such responses, writes
// immediately. // are flushed to the client immediately.
FlushInterval time.Duration FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors // ErrorLog specifies an optional logger for errors
@@ -97,6 +111,27 @@ func singleJoiningSlash(a, b string) string {
return a + b return a + b
} }
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
// NewSingleHostReverseProxy returns a new ReverseProxy that routes // NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the // URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir", // target's path is "/base" and the incoming request was for "/dir",
@@ -109,7 +144,7 @@ func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
director := func(req *http.Request) { director := func(req *http.Request) {
req.URL.Scheme = target.Scheme req.URL.Scheme = target.Scheme
req.URL.Host = target.Host req.URL.Host = target.Host
req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" { if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else { } else {
@@ -195,16 +230,19 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
}() }()
} }
outreq := req.WithContext(ctx) outreq := req.Clone(ctx)
if req.ContentLength == 0 { if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
} }
if outreq.Header == nil {
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
}
// ============================= // =============================
// Modified for frp // Modified for frp
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoURL, req.URL.Path)) outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoURL, req.URL.Path))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoHost, req.Host)) outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoHost, req.Host))
outreq = outreq.WithContext(context.WithValue(outreq.Context(), RouteInfoRemote, req.RemoteAddr)) outreq = outreq.Clone(context.WithValue(outreq.Context(), RouteInfoRemote, req.RemoteAddr))
// ============================= // =============================
p.Director(outreq) p.Director(outreq)
@@ -244,10 +282,14 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// If we aren't the first proxy retain prior // If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space // X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one. // separated list and fold multiple headers into one.
if prior, ok := outreq.Header["X-Forwarded-For"]; ok { prior, ok := outreq.Header["X-Forwarded-For"]
omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP clientIP = strings.Join(prior, ", ") + ", " + clientIP
} }
outreq.Header.Set("X-Forwarded-For", clientIP) if !omit {
outreq.Header.Set("X-Forwarded-For", clientIP)
}
} }
res, err := transport.RoundTrip(outreq) res, err := transport.RoundTrip(outreq)
@@ -290,7 +332,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(res.StatusCode) rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(req, res)) err = p.copyResponse(rw, res.Body, p.flushInterval(res))
if err != nil { if err != nil {
defer res.Body.Close() defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do // Since we're streaming the response, if we run into an error all we can do
@@ -353,7 +395,7 @@ func shouldPanicOnCopyError(req *http.Request) bool {
func removeConnectionHeaders(h http.Header) { func removeConnectionHeaders(h http.Header) {
for _, f := range h["Connection"] { for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") { for _, sf := range strings.Split(f, ",") {
if sf = strings.TrimSpace(sf); sf != "" { if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf) h.Del(sf)
} }
} }
@@ -362,7 +404,7 @@ func removeConnectionHeaders(h http.Header) {
// flushInterval returns the p.FlushInterval value, conditionally // flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response. // overriding its value for a specific request/response.
func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time.Duration { func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration {
resCT := res.Header.Get("Content-Type") resCT := res.Header.Get("Content-Type")
// For Server-Sent Events responses, flush immediately. // For Server-Sent Events responses, flush immediately.
@@ -371,7 +413,11 @@ func (p *ReverseProxy) flushInterval(req *http.Request, res *http.Response) time
return -1 // negative means immediately return -1 // negative means immediately
} }
// TODO: more specific cases? e.g. res.ContentLength == -1? // We might have the case of streaming for which Content-Length might be unset.
if res.ContentLength == -1 {
return -1
}
return p.FlushInterval return p.FlushInterval
} }
@@ -510,8 +556,6 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
return return
} }
copyHeader(res.Header, rw.Header())
hj, ok := rw.(http.Hijacker) hj, ok := rw.(http.Hijacker)
if !ok { if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw)) p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
@@ -522,13 +566,30 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body")) p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return return
} }
defer backConn.Close()
backConnCloseCh := make(chan bool)
go func() {
// Ensure that the cancelation of a request closes the backend.
// See issue https://golang.org/issue/35559.
select {
case <-req.Context().Done():
case <-backConnCloseCh:
}
backConn.Close()
}()
defer close(backConnCloseCh)
conn, brw, err := hj.Hijack() conn, brw, err := hj.Hijack()
if err != nil { if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err)) p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return return
} }
defer conn.Close() defer conn.Close()
copyHeader(rw.Header(), res.Header)
res.Header = rw.Header()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil { if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err)) p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))