forked from Mxmilu666/frp
Compare commits
85 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e611c44dea | ||
|
|
8e36e2bb67 | ||
|
|
541ad8d899 | ||
|
|
17cc0735d1 | ||
|
|
fd336a5503 | ||
|
|
802d1c1861 | ||
|
|
65fe0a1179 | ||
|
|
2d24879fa3 | ||
|
|
75383a95b3 | ||
|
|
95444ea46b | ||
|
|
9f9c01b520 | ||
|
|
285d1eba0d | ||
|
|
0dfd3a421c | ||
|
|
6a1f15b25e | ||
|
|
9f47c324b7 | ||
|
|
f0df6084af | ||
|
|
879ca47590 | ||
|
|
6a7efc81c9 | ||
|
|
12c5c553c3 | ||
|
|
988e9b1de3 | ||
|
|
db6bbc5187 | ||
|
|
c67b4e7b94 | ||
|
|
b7a73d3469 | ||
|
|
7f9d88c10a | ||
|
|
79237d2b94 | ||
|
|
9c4ec56491 | ||
|
|
74a8752570 | ||
|
|
a8ab4c5003 | ||
|
|
9cee263c91 | ||
|
|
c6bf6f59e6 | ||
|
|
4b7aef2196 | ||
|
|
f6d0046b5a | ||
|
|
84363266d2 | ||
|
|
9ac8f2a047 | ||
|
|
b2b55533b8 | ||
|
|
a4cfab689a | ||
|
|
c7df39074c | ||
|
|
fdcdccb0c2 | ||
|
|
e945c1667a | ||
|
|
87a4de4370 | ||
|
|
e1e2913b77 | ||
|
|
9be24db410 | ||
|
|
6b61cb3742 | ||
|
|
90b7f2080f | ||
|
|
d1f1c72a55 | ||
|
|
1925847ef8 | ||
|
|
8b216b0ca9 | ||
|
|
dbfeea99f3 | ||
|
|
5e64bbfa7c | ||
|
|
e691a40260 | ||
|
|
d812488767 | ||
|
|
3c03690ab7 | ||
|
|
3df27b9c04 | ||
|
|
ba45d29b7c | ||
|
|
3cf83f57a8 | ||
|
|
03e4318d79 | ||
|
|
178d134f46 | ||
|
|
cbf9c731a0 | ||
|
|
de4bfcc43c | ||
|
|
9737978f28 | ||
|
|
5bc7fe2cea | ||
|
|
65d8fe37c5 | ||
|
|
1723d7b651 | ||
|
|
2481dfab64 | ||
|
|
95a881a7d3 | ||
|
|
fe403ab328 | ||
|
|
66555dbb00 | ||
|
|
7f9ea48405 | ||
|
|
96d7e2da6f | ||
|
|
d879b8208b | ||
|
|
3585e456d4 | ||
|
|
1de8c3fc87 | ||
|
|
bbab3fe9ca | ||
|
|
48990da22e | ||
|
|
5543fc2a9a | ||
|
|
c41de6fd28 | ||
|
|
8c8fd9790e | ||
|
|
5a7ef3be74 | ||
|
|
d9b5e0bde0 | ||
|
|
05ca72dbf0 | ||
|
|
ef6f8bbf6c | ||
|
|
70ac7d3d11 | ||
|
|
385c4d3dd5 | ||
|
|
5e1983f7ed | ||
|
|
516cdbddb0 |
2
.github/ISSUE_TEMPLATE
vendored
2
.github/ISSUE_TEMPLATE
vendored
@@ -1,5 +1,7 @@
|
||||
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.
|
||||
(为了节约时间,提高处理问题的效率,不按照格式填写的 issue 将会直接关闭。)
|
||||
(请不要在 issue 评论中出现无意义的 **加1**,**我也是** 等内容,将会被直接删除。)
|
||||
(由于个人精力有限,和系统环境,网络环境等相关的求助问题请转至其他论坛或社交平台。)
|
||||
|
||||
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
|
||||
|
||||
@@ -2,8 +2,8 @@ sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
install:
|
||||
- make
|
||||
|
||||
251
Gopkg.lock
generated
251
Gopkg.lock
generated
@@ -1,251 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5a91fc342af1f94bce8b760a80d5b709fe53ea10c870a5daf1dc7e9fada8525f"
|
||||
name = "github.com/armon/go-socks5"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "UT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0f8ca5fa815e8058bfbf5d0e4ad0c2f8334d68cac86e3bfee94b4e3031e9f69f"
|
||||
name = "github.com/fatedier/beego"
|
||||
packages = ["logs"]
|
||||
pruneopts = "UT"
|
||||
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:edb90bd03be19aa95d375ed6eb5d681538e0a3f7d2a057b69bc2ca6e5217477a"
|
||||
name = "github.com/fatedier/golib"
|
||||
packages = [
|
||||
"control/shutdown",
|
||||
"crypto",
|
||||
"errors",
|
||||
"io",
|
||||
"msg/json",
|
||||
"net",
|
||||
"net/mux",
|
||||
"pool",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "ff8cd814b04901d617b7fffaca6fedb81067821d"
|
||||
|
||||
[[projects]]
|
||||
branch = "frp"
|
||||
digest = "1:6621826f49b587c0d6f868e1c56d2bbbc1d75597347d97419b3b027e8a753bdb"
|
||||
name = "github.com/fatedier/kcp-go"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "cd167d2f15f451b0f33780ce862fca97adc0331e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:29a5ab9fa9e845fd8e8726f31b187d710afd271ef1eb32085fe3d604b7e06382"
|
||||
name = "github.com/golang/snappy"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "553a641470496b2327abcac10b36396bd98e45c9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
|
||||
name = "github.com/gorilla/context"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
|
||||
name = "github.com/gorilla/mux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
||||
version = "v1.6.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e"
|
||||
name = "github.com/gorilla/websocket"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:6074024c54115955afc83ee5064367523bbc55e4eb0e9cf145e43c9c0371918c"
|
||||
name = "github.com/hashicorp/yamux"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
|
||||
name = "github.com/inconshreveable/mousetrap"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
|
||||
version = "v1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "UT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bc91590d3e20673d5e33267fc140e7dadddde0b84f2e9030547ba86859d2d13e"
|
||||
name = "github.com/rakyll/statik"
|
||||
packages = ["fs"]
|
||||
pruneopts = "UT"
|
||||
revision = "fd36b3595eb2ec8da4b8153b107f7ea08504899d"
|
||||
version = "v0.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:4c01929c0b1665523b469482fc8241a04519bd5bfc97a1c113367cfadebab07b"
|
||||
name = "github.com/rodaine/table"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "212a2ad1c462ed4d5b5511ea2b480a573281dbbd"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
|
||||
name = "github.com/spf13/cobra"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
|
||||
version = "v1.0.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:f85e109eda8f6080877185d1c39e98dd8795e1780c08beca28304b87fd855a1c"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "UT"
|
||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||
version = "v1.2.1"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:710ccf83337a9ca27abe968c3e58fdf16bd69d76b9870dadafc511e94fc33d7f"
|
||||
name = "github.com/templexxx/cpufeat"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "3794dfbfb04749f896b521032f69383f24c3687e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7bf0e709c5dd92c937e6f59a76056fe0a89cfe2f52ce25493c6337d23781af0a"
|
||||
name = "github.com/templexxx/reedsolomon"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "5e06b81a1c7628d9c8d4fb7c3c4e401e37db39b4"
|
||||
version = "0.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a0a269bea865974fc4d583373c984a5aa60cf98b5aa4f3e1b5de527891d37845"
|
||||
name = "github.com/templexxx/xor"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "0af8e873c554da75f37f2049cdffda804533d44c"
|
||||
version = "0.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:97293f3bd0b9f81484da18dba66a20de340307b43835a91157aaaee484c80e9b"
|
||||
name = "github.com/tjfoc/gmsm"
|
||||
packages = ["sm4"]
|
||||
pruneopts = "UT"
|
||||
revision = "98aa888b79d8de04afe0fccf45ed10594efc858b"
|
||||
version = "v1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8f70510b21fd07eba5bd4e0f84d49d932ea74c8b0ea20a5807e9492cc819928c"
|
||||
name = "github.com/vaughan0/go-ini"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a14b2b0fb9cc2d9ed073aac9834ff93ded673b94fedee4eead3cd9a65e80a40b"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"blowfish",
|
||||
"cast5",
|
||||
"pbkdf2",
|
||||
"salsa20",
|
||||
"salsa20/salsa",
|
||||
"tea",
|
||||
"twofish",
|
||||
"xtea",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4781de952463c8e97ab707c03c73f5f53296be672d1bceac9323393a7b6e7e0a"
|
||||
name = "golang.org/x/net"
|
||||
packages = [
|
||||
"bpf",
|
||||
"context",
|
||||
"internal/iana",
|
||||
"internal/socket",
|
||||
"internal/socks",
|
||||
"ipv4",
|
||||
"proxy",
|
||||
"websocket",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "dfa909b99c79129e1100513e5cd36307665e5723"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/armon/go-socks5",
|
||||
"github.com/fatedier/beego/logs",
|
||||
"github.com/fatedier/golib/control/shutdown",
|
||||
"github.com/fatedier/golib/crypto",
|
||||
"github.com/fatedier/golib/errors",
|
||||
"github.com/fatedier/golib/io",
|
||||
"github.com/fatedier/golib/msg/json",
|
||||
"github.com/fatedier/golib/net",
|
||||
"github.com/fatedier/golib/net/mux",
|
||||
"github.com/fatedier/golib/pool",
|
||||
"github.com/fatedier/kcp-go",
|
||||
"github.com/gorilla/mux",
|
||||
"github.com/gorilla/websocket",
|
||||
"github.com/hashicorp/yamux",
|
||||
"github.com/rakyll/statik/fs",
|
||||
"github.com/rodaine/table",
|
||||
"github.com/spf13/cobra",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/vaughan0/go-ini",
|
||||
"golang.org/x/net/ipv4",
|
||||
"golang.org/x/net/websocket",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
78
Gopkg.toml
78
Gopkg.toml
@@ -1,78 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
# [prune]
|
||||
# non-go = false
|
||||
# go-tests = true
|
||||
# unused-packages = true
|
||||
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/armon/go-socks5"
|
||||
revision = "e75332964ef517daa070d7c38a9466a0d687e0a5"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatedier/beego"
|
||||
revision = "6c6a4f5bd5eb5a39f7e289b8f345b55f75e7e3e8"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatedier/golib"
|
||||
revision = "ff8cd814b04901d617b7fffaca6fedb81067821d"
|
||||
|
||||
[[constraint]]
|
||||
branch = "frp"
|
||||
name = "github.com/fatedier/kcp-go"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/websocket"
|
||||
version = "1.2.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/hashicorp/yamux"
|
||||
revision = "2658be15c5f05e76244154714161f17e3e77de2e"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/gorilla/mux"
|
||||
version = "1.6.2"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rakyll/statik"
|
||||
version = "0.1.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/rodaine/table"
|
||||
version = "1.0.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/spf13/cobra"
|
||||
version = "0.0.3"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/vaughan0/go-ini"
|
||||
revision = "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
|
||||
|
||||
[[override]]
|
||||
name = "github.com/templexxx/reedsolomon"
|
||||
version = "0.1.1"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
12
Makefile
12
Makefile
@@ -6,11 +6,12 @@ build: frps frpc
|
||||
|
||||
# compile assets into binary file
|
||||
file:
|
||||
rm -rf ./assets/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/static
|
||||
go get -d github.com/rakyll/statik
|
||||
go install github.com/rakyll/statik
|
||||
rm -rf ./assets/statik
|
||||
rm -rf ./assets/frps/static/*
|
||||
rm -rf ./assets/frpc/static/*
|
||||
cp -rf ./web/frps/dist/* ./assets/frps/static
|
||||
cp -rf ./web/frpc/dist/* ./assets/frpc/static
|
||||
rm -rf ./assets/frps/statik
|
||||
rm -rf ./assets/frpc/statik
|
||||
go generate ./assets/...
|
||||
|
||||
fmt:
|
||||
@@ -18,7 +19,6 @@ fmt:
|
||||
|
||||
frps:
|
||||
go build -o bin/frps ./cmd/frps
|
||||
@cp -rf ./assets/static ./bin
|
||||
|
||||
frpc:
|
||||
go build -o bin/frpc ./cmd/frpc
|
||||
|
||||
85
README.md
85
README.md
@@ -22,14 +22,17 @@ Now it also try to support p2p connect.
|
||||
* [Forward DNS query request](#forward-dns-query-request)
|
||||
* [Forward unix domain socket](#forward-unix-domain-socket)
|
||||
* [Expose a simple http file server](#expose-a-simple-http-file-server)
|
||||
* [Enable HTTPS for local HTTP service](#enable-https-for-local-http-service)
|
||||
* [Expose your service in security](#expose-your-service-in-security)
|
||||
* [P2P Mode](#p2p-mode)
|
||||
* [Features](#features)
|
||||
* [Configuration File](#configuration-file)
|
||||
* [Configuration file template](#configuration-file-template)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [Authentication](#authentication)
|
||||
* [Encryption and Compression](#encryption-and-compression)
|
||||
* [TLS](#tls)
|
||||
* [Hot-Reload frpc configuration](#hot-reload-frpc-configuration)
|
||||
* [Get proxy status from client](#get-proxy-status-from-client)
|
||||
* [Port White List](#port-white-list)
|
||||
@@ -42,6 +45,8 @@ Now it also try to support p2p connect.
|
||||
* [Rewriting the Host Header](#rewriting-the-host-header)
|
||||
* [Set Headers In HTTP Request](#set-headers-in-http-request)
|
||||
* [Get Real IP](#get-real-ip)
|
||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [Password protecting your web service](#password-protecting-your-web-service)
|
||||
* [Custom subdomain names](#custom-subdomain-names)
|
||||
* [URL routing](#url-routing)
|
||||
@@ -241,11 +246,34 @@ Configure frps same as above.
|
||||
|
||||
2. Visit `http://x.x.x.x:6000/static/` by your browser, set correct user and password, so you can see files in `/tmp/file`.
|
||||
|
||||
### Enable HTTPS for local HTTP service
|
||||
|
||||
1. Start frpc with configurations:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_htts2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
```
|
||||
|
||||
2. Visit `https://test.yourdomain.com`.
|
||||
|
||||
### Expose your service in security
|
||||
|
||||
For some services, if expose them to the public network directly will be a security risk.
|
||||
|
||||
**stcp(secret tcp)** help you create a proxy avoiding any one can access it.
|
||||
**stcp(secret tcp)** helps you create a proxy avoiding any one can access it.
|
||||
|
||||
Configure frps same as above.
|
||||
|
||||
@@ -389,6 +417,22 @@ Then visit `http://[server_addr]:7500` to see dashboard, default username and pa
|
||||
|
||||

|
||||
|
||||
### Admin UI
|
||||
|
||||
Admin UI help you check and manage frpc's configure.
|
||||
|
||||
Configure a address for admin UI to enable this feature:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
```
|
||||
|
||||
Then visit `http://127.0.0.1:7400` to see admin UI, default username and password are both `admin`.
|
||||
|
||||
### Authentication
|
||||
|
||||
`token` in frps.ini and frpc.ini should be same.
|
||||
@@ -407,6 +451,14 @@ use_encryption = true
|
||||
use_compression = true
|
||||
```
|
||||
|
||||
#### TLS
|
||||
|
||||
frp support TLS protocol between frpc and frps since v0.25.0.
|
||||
|
||||
Config `tls_enable = true` in `common` section to frpc.ini to enable this feature.
|
||||
|
||||
For port multiplexing, frp send a first byte 0x17 to dial a TLS connection.
|
||||
|
||||
### Hot-Reload frpc configuration
|
||||
|
||||
First you need to set admin port in frpc's configure file to let it provide HTTP API for more features.
|
||||
@@ -458,8 +510,6 @@ tcp_mux = false
|
||||
|
||||
### Support KCP Protocol
|
||||
|
||||
frp support kcp protocol since v0.12.0.
|
||||
|
||||
KCP is a fast and reliable protocol that can achieve the transmission effect of a reduction of the average latency by 30% to 40% and reduction of the maximum delay by a factor of three, at the cost of 10% to 20% more bandwidth wasted than TCP.
|
||||
|
||||
Using kcp in frp:
|
||||
@@ -592,7 +642,7 @@ custom_domains = test.yourdomain.com
|
||||
host_header_rewrite = dev.yourdomain.com
|
||||
```
|
||||
|
||||
If `host_header_rewrite` is specified, the host header will be rewritten to match the hostname portion of the forwarding address.
|
||||
The `Host` request header will be rewritten to `Host: dev.yourdomain.com` before it reach your local http server.
|
||||
|
||||
### Set Headers In HTTP Request
|
||||
|
||||
@@ -613,9 +663,32 @@ In this example, it will set header `X-From-Where: frp` to http request.
|
||||
|
||||
### Get Real IP
|
||||
|
||||
#### HTTP X-Forwarded-For
|
||||
|
||||
Features for http proxy only.
|
||||
|
||||
You can get user's real IP from http request header `X-Forwarded-For` and `X-Real-IP`.
|
||||
You can get user's real IP from HTTP request header `X-Forwarded-For` and `X-Real-IP`.
|
||||
|
||||
#### Proxy Protocol
|
||||
|
||||
frp support Proxy Protocol to send user's real IP to local service. It support all types without UDP.
|
||||
|
||||
Here is an example for https service:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = https
|
||||
local_port = 443
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
# now v1 and v2 is supported
|
||||
proxy_protocol_version = v2
|
||||
```
|
||||
|
||||
You can enable Proxy Protocol support in nginx to parse user's real IP to http header `X-Real-IP`.
|
||||
|
||||
Then you can get it from HTTP request header in your local service.
|
||||
|
||||
### Password protecting your web service
|
||||
|
||||
@@ -736,8 +809,6 @@ plugin_http_passwd = abc
|
||||
## Development Plan
|
||||
|
||||
* Log http request information in frps.
|
||||
* Direct reverse proxy, like haproxy.
|
||||
* kubernetes ingress support.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
91
README_zh.md
91
README_zh.md
@@ -18,14 +18,17 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [转发 DNS 查询请求](#转发-dns-查询请求)
|
||||
* [转发 Unix域套接字](#转发-unix域套接字)
|
||||
* [对外提供简单的文件访问服务](#对外提供简单的文件访问服务)
|
||||
* [为本地 HTTP 服务启用 HTTPS](#为本地-http-服务启用-https)
|
||||
* [安全地暴露内网服务](#安全地暴露内网服务)
|
||||
* [点对点内网穿透](#点对点内网穿透)
|
||||
* [功能说明](#功能说明)
|
||||
* [配置文件](#配置文件)
|
||||
* [配置文件模版渲染](#配置文件模版渲染)
|
||||
* [Dashboard](#dashboard)
|
||||
* [Admin UI](#admin-ui)
|
||||
* [身份验证](#身份验证)
|
||||
* [加密与压缩](#加密与压缩)
|
||||
* [TLS](#tls)
|
||||
* [客户端热加载配置文件](#客户端热加载配置文件)
|
||||
* [客户端查看代理状态](#客户端查看代理状态)
|
||||
* [端口白名单](#端口白名单)
|
||||
@@ -38,6 +41,8 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [修改 Host Header](#修改-host-header)
|
||||
* [设置 HTTP 请求的 header](#设置-http-请求的-header)
|
||||
* [获取用户真实 IP](#获取用户真实-ip)
|
||||
* [HTTP X-Forwarded-For](#http-x-forwarded-for)
|
||||
* [Proxy Protocol](#proxy-protocol)
|
||||
* [通过密码保护你的 web 服务](#通过密码保护你的-web-服务)
|
||||
* [自定义二级域名](#自定义二级域名)
|
||||
* [URL 路由](#url-路由)
|
||||
@@ -47,6 +52,7 @@ frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp
|
||||
* [开发计划](#开发计划)
|
||||
* [为 frp 做贡献](#为-frp-做贡献)
|
||||
* [捐助](#捐助)
|
||||
* [知识星球](#知识星球)
|
||||
* [支付宝扫码捐赠](#支付宝扫码捐赠)
|
||||
* [微信支付捐赠](#微信支付捐赠)
|
||||
* [Paypal 捐赠](#paypal-捐赠)
|
||||
@@ -241,6 +247,33 @@ frps 的部署步骤同上。
|
||||
|
||||
2. 通过浏览器访问 `http://x.x.x.x:6000/static/` 来查看位于 `/tmp/file` 目录下的文件,会要求输入已设置好的用户名和密码。
|
||||
|
||||
### 为本地 HTTP 服务启用 HTTPS
|
||||
|
||||
通过 `https2http` 插件可以让本地 HTTP 服务转换成 HTTPS 服务对外提供。
|
||||
|
||||
1. 启用 frpc,启用 `https2http` 插件,配置如下:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[common]
|
||||
server_addr = x.x.x.x
|
||||
server_port = 7000
|
||||
|
||||
[test_htts2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
|
||||
# HTTPS 证书相关的配置
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
```
|
||||
|
||||
2. 通过浏览器访问 `https://test.yourdomain.com` 即可。
|
||||
|
||||
### 安全地暴露内网服务
|
||||
|
||||
对于某些服务来说如果直接暴露于公网上将会存在安全隐患。
|
||||
@@ -404,6 +437,24 @@ dashboard_pwd = admin
|
||||
|
||||

|
||||
|
||||
### Admin UI
|
||||
|
||||
Admin UI 可以帮助用户通过浏览器来查询和管理客户端的 proxy 状态和配置。
|
||||
|
||||
需要在 frpc.ini 中指定 admin 服务使用的端口,即可开启此功能:
|
||||
|
||||
```ini
|
||||
[common]
|
||||
admin_addr = 127.0.0.1
|
||||
admin_port = 7400
|
||||
admin_user = admin
|
||||
admin_pwd = admin
|
||||
```
|
||||
|
||||
打开浏览器通过 `http://127.0.0.1:7400` 访问 Admin UI,用户名密码默认为 `admin`。
|
||||
|
||||
如果想要在外网环境访问 Admin UI,将 7400 端口映射出去即可,但需要重视安全风险。
|
||||
|
||||
### 身份验证
|
||||
|
||||
服务端和客户端的 common 配置中的 `token` 参数一致则身份验证通过。
|
||||
@@ -426,6 +477,14 @@ use_compression = true
|
||||
|
||||
如果传输的报文长度较长,通过设置 `use_compression = true` 对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 cpu 资源。
|
||||
|
||||
#### TLS
|
||||
|
||||
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输。通过在 `frpc.ini` 的 `common` 中配置 `tls_enable = true` 来启用此功能,安全性更高。
|
||||
|
||||
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17。
|
||||
|
||||
**注意: 启用此功能后除 xtcp 外,不需要再设置 use_encryption。**
|
||||
|
||||
### 客户端热加载配置文件
|
||||
|
||||
当修改了 frpc 中的代理配置,可以通过 `frpc reload` 命令来动态加载配置文件,通常会在 10 秒内完成代理的更新。
|
||||
@@ -485,7 +544,7 @@ tcp_mux = false
|
||||
|
||||
### 底层通信可选 kcp 协议
|
||||
|
||||
从 v0.12.0 版本开始,底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
底层通信协议支持选择 kcp 协议,在弱网环境下传输效率提升明显,但是会有一些额外的流量消耗。
|
||||
|
||||
开启 kcp 协议支持:
|
||||
|
||||
@@ -537,6 +596,7 @@ tcp_mux = false
|
||||
### 负载均衡
|
||||
|
||||
可以将多个相同类型的 proxy 加入到同一个 group 中,从而实现负载均衡的功能。
|
||||
|
||||
目前只支持 tcp 类型的 proxy。
|
||||
|
||||
```ini
|
||||
@@ -639,7 +699,34 @@ header_X-From-Where = frp
|
||||
|
||||
### 获取用户真实 IP
|
||||
|
||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 和 `X-Real-IP` 来获取用户真实 IP。
|
||||
#### HTTP X-Forwarded-For
|
||||
|
||||
目前只有 **http** 类型的代理支持这一功能,可以通过用户请求的 header 中的 `X-Forwarded-For` 来获取用户真实 IP,默认启用。
|
||||
|
||||
#### Proxy Protocol
|
||||
|
||||
frp 支持通过 **Proxy Protocol** 协议来传递经过 frp 代理的请求的真实 IP,此功能支持所有以 TCP 为底层协议的类型,不支持 UDP。
|
||||
|
||||
**Proxy Protocol** 功能启用后,frpc 在和本地服务建立连接后,会先发送一段 **Proxy Protocol** 的协议内容给本地服务,本地服务通过解析这一内容可以获得访问用户的真实 IP。所以不仅仅是 HTTP 服务,任何的 TCP 服务,只要支持这一协议,都可以获得用户的真实 IP 地址。
|
||||
|
||||
需要注意的是,在代理配置中如果要启用此功能,需要本地的服务能够支持 **Proxy Protocol** 这一协议,目前 nginx 和 haproxy 都能够很好的支持。
|
||||
|
||||
这里以 https 类型为例:
|
||||
|
||||
```ini
|
||||
# frpc.ini
|
||||
[web]
|
||||
type = https
|
||||
local_port = 443
|
||||
custom_domains = test.yourdomain.com
|
||||
|
||||
# 目前支持 v1 和 v2 两个版本的 proxy protocol 协议。
|
||||
proxy_protocol_version = v2
|
||||
```
|
||||
|
||||
只需要在代理配置中增加一行 `proxy_protocol_version = v2` 即可开启此功能。
|
||||
|
||||
本地的 https 服务可以通过在 nginx 的配置中启用 **Proxy Protocol** 的解析并将结果设置在 `X-Real-IP` 这个 Header 中就可以在自己的 Web 服务中通过 `X-Real-IP` 获取到用户的真实 IP。
|
||||
|
||||
### 通过密码保护你的 web 服务
|
||||
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
|
||||
package assets
|
||||
|
||||
//go:generate statik -src=./static
|
||||
//go:generate go fmt statik/statik.go
|
||||
//go:generate statik -src=./frps/static -dest=./frps
|
||||
//go:generate statik -src=./frpc/static -dest=./frpc
|
||||
//go:generate go fmt ./frps/statik/statik.go
|
||||
//go:generate go fmt ./frpc/statik/statik.go
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@@ -24,8 +26,6 @@ import (
|
||||
"path"
|
||||
|
||||
"github.com/rakyll/statik/fs"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/statik"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
1
assets/frpc/static/index.html
Normal file
1
assets/frpc/static/index.html
Normal file
@@ -0,0 +1 @@
|
||||
<!doctype html> <html lang=en> <head> <meta charset=utf-8> <title>frp client admin UI</title> <link rel="shortcut icon" href="favicon.ico"></head> <body> <div id=app></div> <script type="text/javascript" src="manifest.js?d2cd6337d30c7b22e836"></script><script type="text/javascript" src="vendor.js?edb271e1d9c81f857840"></script></body> </html>
|
||||
1
assets/frpc/static/manifest.js
Normal file
1
assets/frpc/static/manifest.js
Normal file
@@ -0,0 +1 @@
|
||||
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,u){for(var i,a,f,l=0,s=[];l<t.length;l++)a=t[l],o[a]&&s.push(o[a][0]),o[a]=0;for(i in c)Object.prototype.hasOwnProperty.call(c,i)&&(e[i]=c[i]);for(r&&r(t,c,u);s.length;)s.shift()();if(u)for(l=0;l<u.length;l++)f=n(n.s=u[l]);return f};var t={},o={1:0};n.e=function(e){function r(){i.onerror=i.onload=null,clearTimeout(a);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var u=document.getElementsByTagName("head")[0],i=document.createElement("script");i.type="text/javascript",i.charset="utf-8",i.async=!0,i.timeout=12e4,n.nc&&i.setAttribute("nonce",n.nc),i.src=n.p+""+e+".js?"+{0:"edb271e1d9c81f857840"}[e];var a=setTimeout(r,12e4);return i.onerror=i.onload=r,u.appendChild(i),c},n.m=e,n.c=t,n.i=function(e){return e},n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n.oe=function(e){throw console.error(e),e}}([]);
|
||||
1
assets/frpc/static/vendor.js
Normal file
1
assets/frpc/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
10
assets/frpc/statik/statik.go
Normal file
10
assets/frpc/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
BIN
assets/frps/static/6f0a76321d30f3c8120915e57f7bd77e.ttf
Normal file
Binary file not shown.
BIN
assets/frps/static/favicon.ico
Normal file
BIN
assets/frps/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
10
assets/frps/statik/statik.go
Normal file
10
assets/frps/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -20,6 +20,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
|
||||
@@ -41,6 +42,15 @@ func (svr *Service) RunAdminServer(addr string, port int) (err error) {
|
||||
// api, see dashboard_api.go
|
||||
router.HandleFunc("/api/reload", svr.apiReload).Methods("GET")
|
||||
router.HandleFunc("/api/status", svr.apiStatus).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiGetConfig).Methods("GET")
|
||||
router.HandleFunc("/api/config", svr.apiPutConfig).Methods("PUT")
|
||||
|
||||
// view
|
||||
router.Handle("/favicon.ico", http.FileServer(assets.FileSystem)).Methods("GET")
|
||||
router.PathPrefix("/static/").Handler(frpNet.MakeHttpGzipHandler(http.StripPrefix("/static/", http.FileServer(assets.FileSystem)))).Methods("GET")
|
||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "/static/", http.StatusMovedPermanently)
|
||||
})
|
||||
|
||||
address := fmt.Sprintf("%s:%d", addr, port)
|
||||
server := &http.Server{
|
||||
|
||||
@@ -17,6 +17,7 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -28,57 +29,53 @@ import (
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
// api/reload
|
||||
type ReloadResp struct {
|
||||
GeneralResponse
|
||||
}
|
||||
// GET api/reload
|
||||
|
||||
func (svr *Service) apiReload(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res ReloadResp
|
||||
)
|
||||
defer func() {
|
||||
log.Info("Http response [/api/reload]: code [%d]", res.Code)
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}()
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http request: [/api/reload]")
|
||||
log.Info("Http request [/api/reload]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/reload], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
if err != nil {
|
||||
res.Code = 1
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc config file error: %v", err)
|
||||
log.Warn("reload frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
newCommonCfg, err := config.UnmarshalClientConfFromIni(nil, content)
|
||||
if err != nil {
|
||||
res.Code = 2
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc common section error: %v", err)
|
||||
log.Warn("reload frpc common section error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
pxyCfgs, visitorCfgs, err := config.LoadAllConfFromIni(g.GlbClientCfg.User, content, newCommonCfg.Start)
|
||||
if err != nil {
|
||||
res.Code = 3
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc proxy config error: %v", err)
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
err = svr.ctl.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
err = svr.ReloadConf(pxyCfgs, visitorCfgs)
|
||||
if err != nil {
|
||||
res.Code = 4
|
||||
res.Code = 500
|
||||
res.Msg = err.Error()
|
||||
log.Error("reload frpc proxy config error: %v", err)
|
||||
log.Warn("reload frpc proxy config error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
log.Info("success reload conf")
|
||||
@@ -163,7 +160,7 @@ func NewProxyStatusResp(status *proxy.ProxyStatus) ProxyStatusResp {
|
||||
return psr
|
||||
}
|
||||
|
||||
// api/status
|
||||
// GET api/status
|
||||
func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
@@ -175,14 +172,14 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
res.Https = make([]ProxyStatusResp, 0)
|
||||
res.Stcp = make([]ProxyStatusResp, 0)
|
||||
res.Xtcp = make([]ProxyStatusResp, 0)
|
||||
|
||||
log.Info("Http request [/api/status]")
|
||||
defer func() {
|
||||
log.Info("Http response [/api/status]")
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
}()
|
||||
|
||||
log.Info("Http request: [/api/status]")
|
||||
|
||||
ps := svr.ctl.pm.GetAllProxyStatus()
|
||||
for _, status := range ps {
|
||||
switch status.Type {
|
||||
@@ -208,3 +205,122 @@ func (svr *Service) apiStatus(w http.ResponseWriter, r *http.Request) {
|
||||
sort.Sort(ByProxyStatusResp(res.Xtcp))
|
||||
return
|
||||
}
|
||||
|
||||
// GET api/config
|
||||
func (svr *Service) apiGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http get request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http get response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
if g.GlbClientCfg.CfgFile == "" {
|
||||
res.Code = 400
|
||||
res.Msg = "frpc has no config file path"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := config.GetRenderedConfFromFile(g.GlbClientCfg.CfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
rows := strings.Split(content, "\n")
|
||||
newRows := make([]string, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
continue
|
||||
}
|
||||
newRows = append(newRows, row)
|
||||
}
|
||||
res.Msg = strings.Join(newRows, "\n")
|
||||
}
|
||||
|
||||
// PUT api/config
|
||||
func (svr *Service) apiPutConfig(w http.ResponseWriter, r *http.Request) {
|
||||
res := GeneralResponse{Code: 200}
|
||||
|
||||
log.Info("Http put request [/api/config]")
|
||||
defer func() {
|
||||
log.Info("Http put response [/api/config], code [%d]", res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
// get new config content
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = fmt.Sprintf("read request body error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
res.Code = 400
|
||||
res.Msg = "body can't be empty"
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
// get token from origin content
|
||||
token := ""
|
||||
b, err := ioutil.ReadFile(g.GlbClientCfg.CfgFile)
|
||||
if err != nil {
|
||||
res.Code = 400
|
||||
res.Msg = err.Error()
|
||||
log.Warn("load frpc config file error: %s", res.Msg)
|
||||
return
|
||||
}
|
||||
content := string(b)
|
||||
|
||||
for _, row := range strings.Split(content, "\n") {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
token = row
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tmpRows := make([]string, 0)
|
||||
for _, row := range strings.Split(string(body), "\n") {
|
||||
row = strings.TrimSpace(row)
|
||||
if strings.HasPrefix(row, "token") {
|
||||
continue
|
||||
}
|
||||
tmpRows = append(tmpRows, row)
|
||||
}
|
||||
|
||||
newRows := make([]string, 0)
|
||||
if token != "" {
|
||||
for _, row := range tmpRows {
|
||||
newRows = append(newRows, row)
|
||||
if strings.HasPrefix(row, "[common]") {
|
||||
newRows = append(newRows, token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newRows = tmpRows
|
||||
}
|
||||
content = strings.Join(newRows, "\n")
|
||||
|
||||
err = ioutil.WriteFile(g.GlbClientCfg.CfgFile, []byte(content), 0644)
|
||||
if err != nil {
|
||||
res.Code = 500
|
||||
res.Msg = fmt.Sprintf("write content to frpc config file error: %v", err)
|
||||
log.Warn("%s", res.Msg)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
@@ -130,7 +131,7 @@ func (ctl *Control) HandleReqWorkConn(inMsg *msg.ReqWorkConn) {
|
||||
workConn.AddLogPrefix(startMsg.ProxyName)
|
||||
|
||||
// dispatch this work connection to related proxy
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn)
|
||||
ctl.pm.HandleWorkConn(startMsg.ProxyName, workConn, &startMsg)
|
||||
}
|
||||
|
||||
func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
@@ -147,6 +148,9 @@ func (ctl *Control) HandleNewProxyResp(inMsg *msg.NewProxyResp) {
|
||||
func (ctl *Control) Close() error {
|
||||
ctl.pm.Close()
|
||||
ctl.conn.Close()
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -166,8 +170,14 @@ func (ctl *Control) connectServer() (conn frpNet.Conn, err error) {
|
||||
}
|
||||
conn = frpNet.WrapConn(stream)
|
||||
} else {
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
var tlsConfig *tls.Config
|
||||
if g.GlbClientCfg.TLSEnable {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
ctl.Warn("start new connection to server error: %v", err)
|
||||
return
|
||||
@@ -195,6 +205,7 @@ func (ctl *Control) reader() {
|
||||
return
|
||||
} else {
|
||||
ctl.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -293,6 +304,9 @@ func (ctl *Control) worker() {
|
||||
ctl.vm.Close()
|
||||
|
||||
close(ctl.closedDoneCh)
|
||||
if ctl.session != nil {
|
||||
ctl.session.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -170,6 +172,8 @@ func (monitor *HealthCheckMonitor) doHttpCheck(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode/100 != 2 {
|
||||
return fmt.Errorf("do http health check, StatusCode is [%d] not 2xx", resp.StatusCode)
|
||||
|
||||
@@ -18,7 +18,10 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -33,6 +36,8 @@ import (
|
||||
"github.com/fatedier/golib/errors"
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
pp "github.com/pires/go-proxyproto"
|
||||
)
|
||||
|
||||
// Proxy defines how to handle work connections for different proxy type.
|
||||
@@ -40,7 +45,7 @@ type Proxy interface {
|
||||
Run() error
|
||||
|
||||
// InWorkConn accept work connections registered to server.
|
||||
InWorkConn(conn frpNet.Conn)
|
||||
InWorkConn(frpNet.Conn, *msg.StartWorkConn)
|
||||
|
||||
Close()
|
||||
log.Logger
|
||||
@@ -53,32 +58,32 @@ func NewProxy(pxyConf config.ProxyConf) (pxy Proxy) {
|
||||
switch cfg := pxyConf.(type) {
|
||||
case *config.TcpProxyConf:
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
BaseProxy: baseProxy,
|
||||
BaseProxy: &baseProxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
@@ -93,7 +98,7 @@ type BaseProxy struct {
|
||||
|
||||
// TCP
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.TcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -115,14 +120,14 @@ func (pxy *TcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *TcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTP
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -144,14 +149,14 @@ func (pxy *HttpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *HttpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// HTTPS
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.HttpsProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -173,14 +178,14 @@ func (pxy *HttpsProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *HttpsProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// STCP
|
||||
type StcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.StcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -202,14 +207,14 @@ func (pxy *StcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *StcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf, conn,
|
||||
[]byte(g.GlbClientCfg.Token))
|
||||
[]byte(g.GlbClientCfg.Token), m)
|
||||
}
|
||||
|
||||
// XTCP
|
||||
type XtcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.XtcpProxyConf
|
||||
proxyPlugin plugin.Plugin
|
||||
@@ -231,7 +236,7 @@ func (pxy *XtcpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
defer conn.Close()
|
||||
var natHoleSidMsg msg.NatHoleSid
|
||||
err := msg.ReadMsgInto(conn, &natHoleSidMsg)
|
||||
@@ -278,37 +283,102 @@ func (pxy *XtcpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
pxy.Trace("get natHoleRespMsg, sid [%s], client address [%s] visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Send sid to visitor udp address.
|
||||
time.Sleep(time.Second)
|
||||
// Send detect message
|
||||
array := strings.Split(natHoleRespMsg.VisitorAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
pxy.Error("get NatHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
}
|
||||
laddr, _ := net.ResolveUDPAddr("udp", clientConn.LocalAddr().String())
|
||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.VisitorAddr)
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
pxy.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
if err != nil {
|
||||
pxy.Error("resolve visitor udp address error: %v", err)
|
||||
pxy.Error("get natHoleResp visitor address error: %v", natHoleRespMsg.VisitorAddr)
|
||||
return
|
||||
}
|
||||
pxy.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
pxy.Trace("send all detect msg done")
|
||||
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
msg.WriteMsg(conn, &msg.NatHoleClientDetectOK{})
|
||||
|
||||
// Listen for clientConn's address and wait for visitor connection
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
pxy.Error("dial visitor udp address error: %v", err)
|
||||
pxy.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
defer lConn.Close()
|
||||
|
||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.VisitorAddr)
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
var uAddr *net.UDPAddr
|
||||
n, uAddr, err = lConn.ReadFromUDP(sidBuf)
|
||||
if err != nil {
|
||||
pxy.Warn("get sid from visitor error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Time{})
|
||||
if string(sidBuf[:n]) != natHoleRespMsg.Sid {
|
||||
pxy.Warn("incorrect sid from visitor")
|
||||
return
|
||||
}
|
||||
pool.PutBuf(sidBuf)
|
||||
pxy.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
lConn.WriteToUDP(sidBuf[:n], uAddr)
|
||||
|
||||
kcpConn, err := frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.VisitorAddr)
|
||||
if err != nil {
|
||||
pxy.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Server(kcpConn, fmuxCfg)
|
||||
if err != nil {
|
||||
pxy.Error("create yamux server from kcp connection error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Accept()
|
||||
if err != nil {
|
||||
pxy.Error("accept for yamux connection error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
HandleTcpWorkConnection(&pxy.cfg.LocalSvrConf, pxy.proxyPlugin, &pxy.cfg.BaseProxyConf,
|
||||
frpNet.WrapConn(kcpConn), []byte(pxy.cfg.Sk))
|
||||
frpNet.WrapConn(muxConn), []byte(pxy.cfg.Sk), m)
|
||||
}
|
||||
|
||||
func (pxy *XtcpProxy) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//uConn := ipv4.NewConn(tConn)
|
||||
//uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
@@ -346,7 +416,7 @@ func (pxy *UdpProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
pxy.Info("incoming a new work connection for udp proxy, %s", conn.RemoteAddr().String())
|
||||
// close resources releated with old workConn
|
||||
pxy.Close()
|
||||
@@ -413,7 +483,7 @@ func (pxy *UdpProxy) InWorkConn(conn frpNet.Conn) {
|
||||
|
||||
// Common handler for tcp work connections.
|
||||
func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.Plugin,
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte) {
|
||||
baseInfo *config.BaseProxyConf, workConn frpNet.Conn, encKey []byte, m *msg.StartWorkConn) {
|
||||
|
||||
var (
|
||||
remote io.ReadWriteCloser
|
||||
@@ -433,10 +503,43 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
// check if we need to send proxy protocol info
|
||||
var extraInfo []byte
|
||||
if baseInfo.ProxyProtocolVersion != "" {
|
||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||
if m.DstAddr == "" {
|
||||
m.DstAddr = "127.0.0.1"
|
||||
}
|
||||
h := &pp.Header{
|
||||
Command: pp.PROXY,
|
||||
SourceAddress: net.ParseIP(m.SrcAddr),
|
||||
SourcePort: m.SrcPort,
|
||||
DestinationAddress: net.ParseIP(m.DstAddr),
|
||||
DestinationPort: m.DstPort,
|
||||
}
|
||||
|
||||
if h.SourceAddress.To16() == nil {
|
||||
h.TransportProtocol = pp.TCPv4
|
||||
} else {
|
||||
h.TransportProtocol = pp.TCPv6
|
||||
}
|
||||
|
||||
if baseInfo.ProxyProtocolVersion == "v1" {
|
||||
h.Version = 1
|
||||
} else if baseInfo.ProxyProtocolVersion == "v2" {
|
||||
h.Version = 2
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
h.WriteTo(buf)
|
||||
extraInfo = buf.Bytes()
|
||||
}
|
||||
}
|
||||
|
||||
if proxyPlugin != nil {
|
||||
// if plugin is set, let plugin handle connections first
|
||||
workConn.Debug("handle by plugin: %s", proxyPlugin.Name())
|
||||
proxyPlugin.Handle(remote, workConn)
|
||||
proxyPlugin.Handle(remote, workConn, extraInfo)
|
||||
workConn.Debug("handle by plugin finished")
|
||||
return
|
||||
} else {
|
||||
@@ -449,6 +552,11 @@ func HandleTcpWorkConnection(localInfo *config.LocalSvrConf, proxyPlugin plugin.
|
||||
|
||||
workConn.Debug("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
|
||||
if len(extraInfo) > 0 {
|
||||
localConn.Write(extraInfo)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, remote)
|
||||
workConn.Debug("join connections closed")
|
||||
}
|
||||
|
||||
@@ -58,12 +58,12 @@ func (pm *ProxyManager) Close() {
|
||||
pm.proxies = make(map[string]*ProxyWrapper)
|
||||
}
|
||||
|
||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn) {
|
||||
func (pm *ProxyManager) HandleWorkConn(name string, workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
pm.mu.RLock()
|
||||
pw, ok := pm.proxies[name]
|
||||
pm.mu.RUnlock()
|
||||
if ok {
|
||||
pw.InWorkConn(workConn)
|
||||
pw.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
|
||||
@@ -217,13 +217,13 @@ func (pw *ProxyWrapper) statusFailedCallback() {
|
||||
pw.Info("health check failed")
|
||||
}
|
||||
|
||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn) {
|
||||
func (pw *ProxyWrapper) InWorkConn(workConn frpNet.Conn, m *msg.StartWorkConn) {
|
||||
pw.mu.RLock()
|
||||
pxy := pw.pxy
|
||||
pw.mu.RUnlock()
|
||||
if pxy != nil {
|
||||
workConn.Debug("start a new work connection, localAddr: %s remoteAddr: %s", workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||
go pxy.InWorkConn(workConn)
|
||||
go pxy.InWorkConn(workConn, m)
|
||||
} else {
|
||||
workConn.Close()
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/assets"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
@@ -49,7 +51,14 @@ type Service struct {
|
||||
closedCh chan int
|
||||
}
|
||||
|
||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service) {
|
||||
func NewService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]config.VisitorConf) (svr *Service, err error) {
|
||||
// Init assets
|
||||
err = assets.Load("")
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
svr = &Service{
|
||||
pxyCfgs: pxyCfgs,
|
||||
visitorCfgs: visitorCfgs,
|
||||
@@ -143,8 +152,14 @@ func (svr *Service) keepControllerWorking() {
|
||||
// conn: control connection
|
||||
// session: if it's not nil, using tcp mux
|
||||
func (svr *Service) login() (conn frpNet.Conn, session *fmux.Session, err error) {
|
||||
conn, err = frpNet.ConnectServerByProxy(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort))
|
||||
var tlsConfig *tls.Config
|
||||
if g.GlbClientCfg.TLSEnable {
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
}
|
||||
conn, err = frpNet.ConnectServerByProxyWithTLS(g.GlbClientCfg.HttpProxy, g.GlbClientCfg.Protocol,
|
||||
fmt.Sprintf("%s:%d", g.GlbClientCfg.ServerAddr, g.GlbClientCfg.ServerPort), tlsConfig)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -18,14 +18,11 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
"github.com/fatedier/frp/models/msg"
|
||||
@@ -35,6 +32,7 @@ import (
|
||||
|
||||
frpIo "github.com/fatedier/golib/io"
|
||||
"github.com/fatedier/golib/pool"
|
||||
fmux "github.com/hashicorp/yamux"
|
||||
)
|
||||
|
||||
// Visitor is used for forward traffics from local port tot remote service.
|
||||
@@ -52,12 +50,12 @@ func NewVisitor(ctl *Control, cfg config.VisitorConf) (visitor Visitor) {
|
||||
switch cfg := cfg.(type) {
|
||||
case *config.StcpVisitorConf:
|
||||
visitor = &StcpVisitor{
|
||||
BaseVisitor: baseVisitor,
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpVisitorConf:
|
||||
visitor = &XtcpVisitor{
|
||||
BaseVisitor: baseVisitor,
|
||||
BaseVisitor: &baseVisitor,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
@@ -73,7 +71,7 @@ type BaseVisitor struct {
|
||||
}
|
||||
|
||||
type StcpVisitor struct {
|
||||
BaseVisitor
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.StcpVisitorConf
|
||||
}
|
||||
@@ -160,7 +158,7 @@ func (sv *StcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
}
|
||||
|
||||
type XtcpVisitor struct {
|
||||
BaseVisitor
|
||||
*BaseVisitor
|
||||
|
||||
cfg *config.XtcpVisitorConf
|
||||
}
|
||||
@@ -249,40 +247,31 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
return
|
||||
}
|
||||
|
||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr)
|
||||
sv.Trace("get natHoleRespMsg, sid [%s], client address [%s], visitor address [%s]", natHoleRespMsg.Sid, natHoleRespMsg.ClientAddr, natHoleRespMsg.VisitorAddr)
|
||||
|
||||
// Close visitorConn, so we can use it's local address.
|
||||
visitorConn.Close()
|
||||
|
||||
// Send detect message.
|
||||
array := strings.Split(natHoleRespMsg.ClientAddr, ":")
|
||||
if len(array) <= 1 {
|
||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||
return
|
||||
}
|
||||
// send sid message to client
|
||||
laddr, _ := net.ResolveUDPAddr("udp", visitorConn.LocalAddr().String())
|
||||
/*
|
||||
for i := 1000; i < 65000; i++ {
|
||||
sv.sendDetectMsg(array[0], int64(i), laddr, "a")
|
||||
}
|
||||
*/
|
||||
port, err := strconv.ParseInt(array[1], 10, 64)
|
||||
daddr, err := net.ResolveUDPAddr("udp", natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
sv.Error("get natHoleResp client address error: %s", natHoleRespMsg.ClientAddr)
|
||||
sv.Error("resolve client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
sv.sendDetectMsg(array[0], int(port), laddr, []byte(natHoleRespMsg.Sid))
|
||||
sv.Trace("send all detect msg done")
|
||||
lConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
sv.Error("dial client udp address error: %v", err)
|
||||
return
|
||||
}
|
||||
defer lConn.Close()
|
||||
|
||||
// Listen for visitorConn's address and wait for client connection.
|
||||
lConn, err := net.ListenUDP("udp", laddr)
|
||||
if err != nil {
|
||||
sv.Error("listen on visitorConn's local adress error: %v", err)
|
||||
return
|
||||
}
|
||||
lConn.SetReadDeadline(time.Now().Add(5 * time.Second))
|
||||
lConn.Write([]byte(natHoleRespMsg.Sid))
|
||||
|
||||
// read ack sid from client
|
||||
sidBuf := pool.GetBuf(1024)
|
||||
n, _, err = lConn.ReadFromUDP(sidBuf)
|
||||
lConn.SetReadDeadline(time.Now().Add(8 * time.Second))
|
||||
n, err = lConn.Read(sidBuf)
|
||||
if err != nil {
|
||||
sv.Warn("get sid from client error: %v", err)
|
||||
return
|
||||
@@ -292,11 +281,13 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
sv.Warn("incorrect sid from client")
|
||||
return
|
||||
}
|
||||
sv.Info("nat hole connection make success, sid [%s]", string(sidBuf[:n]))
|
||||
pool.PutBuf(sidBuf)
|
||||
|
||||
sv.Info("nat hole connection make success, sid [%s]", natHoleRespMsg.Sid)
|
||||
|
||||
// wrap kcp connection
|
||||
var remote io.ReadWriteCloser
|
||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, false, natHoleRespMsg.ClientAddr)
|
||||
remote, err = frpNet.NewKcpConnFromUdp(lConn, true, natHoleRespMsg.ClientAddr)
|
||||
if err != nil {
|
||||
sv.Error("create kcp connection from udp connection error: %v", err)
|
||||
return
|
||||
@@ -314,25 +305,21 @@ func (sv *XtcpVisitor) handleConn(userConn frpNet.Conn) {
|
||||
remote = frpIo.WithCompression(remote)
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, remote)
|
||||
fmuxCfg := fmux.DefaultConfig()
|
||||
fmuxCfg.KeepAliveInterval = 5 * time.Second
|
||||
fmuxCfg.LogOutput = ioutil.Discard
|
||||
sess, err := fmux.Client(remote, fmuxCfg)
|
||||
if err != nil {
|
||||
sv.Error("create yamux session error: %v", err)
|
||||
return
|
||||
}
|
||||
defer sess.Close()
|
||||
muxConn, err := sess.Open()
|
||||
if err != nil {
|
||||
sv.Error("open yamux stream error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
frpIo.Join(userConn, muxConn)
|
||||
sv.Debug("join connections closed")
|
||||
}
|
||||
|
||||
func (sv *XtcpVisitor) sendDetectMsg(addr string, port int, laddr *net.UDPAddr, content []byte) (err error) {
|
||||
daddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", addr, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tConn, err := net.DialUDP("udp", laddr, daddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uConn := ipv4.NewConn(tConn)
|
||||
uConn.SetTTL(3)
|
||||
|
||||
tConn.Write(content)
|
||||
tConn.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main // "github.com/fatedier/frp/cmd/frpc"
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/fatedier/frp/assets/frpc/statik"
|
||||
"github.com/fatedier/frp/cmd/frpc/sub"
|
||||
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
func init() {
|
||||
httpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
httpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
|
||||
@@ -28,7 +28,7 @@ import (
|
||||
func init() {
|
||||
httpsCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
httpsCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
httpsCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
httpsCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
httpsCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
|
||||
@@ -16,7 +16,6 @@ package sub
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
@@ -25,7 +24,6 @@ import (
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/fatedier/frp/client"
|
||||
"github.com/fatedier/frp/g"
|
||||
"github.com/fatedier/frp/models/config"
|
||||
)
|
||||
@@ -78,22 +76,16 @@ func reload() error {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.GeneralResponse{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
} else if res.Code != 0 {
|
||||
return fmt.Errorf(res.Msg)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
return nil
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("code [%d], %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "./frpc.ini", "config file of frpc")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
|
||||
kcpDoneCh = make(chan struct{})
|
||||
@@ -205,7 +205,11 @@ func startService(pxyCfgs map[string]config.ProxyConf, visitorCfgs map[string]co
|
||||
},
|
||||
}
|
||||
}
|
||||
svr := client.NewService(pxyCfgs, visitorCfgs)
|
||||
svr, errRet := client.NewService(pxyCfgs, visitorCfgs)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
}
|
||||
|
||||
// Capture the exit signal if we use kcp.
|
||||
if g.GlbClientCfg.Protocol == "kcp" {
|
||||
|
||||
@@ -78,76 +78,78 @@ func status() error {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
|
||||
fmt.Println("Proxy Status...")
|
||||
if len(res.Tcp) > 0 {
|
||||
fmt.Printf("TCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Tcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Udp) > 0 {
|
||||
fmt.Printf("UDP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Udp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Http) > 0 {
|
||||
fmt.Printf("HTTP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Http {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Https) > 0 {
|
||||
fmt.Printf("HTTPS")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Https {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Stcp) > 0 {
|
||||
fmt.Printf("STCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Stcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
if len(res.Xtcp) > 0 {
|
||||
fmt.Printf("XTCP")
|
||||
tbl := table.New("Name", "Status", "LocalAddr", "Plugin", "RemoteAddr", "Error")
|
||||
for _, ps := range res.Xtcp {
|
||||
tbl.AddRow(ps.Name, ps.Status, ps.LocalAddr, ps.Plugin, ps.RemoteAddr, ps.Err)
|
||||
}
|
||||
tbl.Print()
|
||||
fmt.Println("")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func init() {
|
||||
stcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
stcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
stcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
stcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
stcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func init() {
|
||||
udpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
udpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
udpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
udpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
udpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
udpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func init() {
|
||||
xtcpCmd.PersistentFlags().StringVarP(&serverAddr, "server_addr", "s", "127.0.0.1:7000", "frp server's address")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&user, "user", "u", "", "user")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&protocol, "protocol", "p", "tcp", "tcp or kcp or websocket")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logLevel, "log_level", "", "info", "log level")
|
||||
xtcpCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "console or file path")
|
||||
@@ -68,7 +68,7 @@ var xtcpCmd = &cobra.Command{
|
||||
if role == "server" {
|
||||
cfg := &config.XtcpProxyConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.XtcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
@@ -84,7 +84,7 @@ var xtcpCmd = &cobra.Command{
|
||||
} else if role == "visitor" {
|
||||
cfg := &config.XtcpVisitorConf{}
|
||||
cfg.ProxyName = prefix + proxyName
|
||||
cfg.ProxyType = consts.StcpProxy
|
||||
cfg.ProxyType = consts.XtcpProxy
|
||||
cfg.UseEncryption = useEncryption
|
||||
cfg.UseCompression = useCompression
|
||||
cfg.Role = role
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main // "github.com/fatedier/frp/cmd/frps"
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/fatedier/golib/crypto"
|
||||
|
||||
_ "github.com/fatedier/frp/assets/frps/statik"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -62,7 +62,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file of frps")
|
||||
rootCmd.PersistentFlags().BoolVarP(&showVersion, "version", "v", false, "version of frpc")
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&bindAddr, "bind_addr", "", "0.0.0.0", "bind address")
|
||||
@@ -79,7 +79,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&dashboardPwd, "dashboard_pwd", "", "admin", "dashboard password")
|
||||
rootCmd.PersistentFlags().StringVarP(&logFile, "log_file", "", "console", "log file")
|
||||
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")
|
||||
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "auth token")
|
||||
rootCmd.PersistentFlags().StringVarP(&subDomainHost, "subdomain_host", "", "", "subdomain host")
|
||||
rootCmd.PersistentFlags().StringVarP(&allowPorts, "allow_ports", "", "", "allow ports")
|
||||
@@ -187,9 +187,9 @@ func parseServerCommonCfgFromCmd() (err error) {
|
||||
g.GlbServerCfg.MaxPortsPerClient = maxPortsPerClient
|
||||
|
||||
if logFile == "console" {
|
||||
g.GlbClientCfg.LogWay = "console"
|
||||
g.GlbServerCfg.LogWay = "console"
|
||||
} else {
|
||||
g.GlbClientCfg.LogWay = "file"
|
||||
g.GlbServerCfg.LogWay = "file"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ login_fail_exit = true
|
||||
# now it supports tcp and kcp and websocket, default is tcp
|
||||
protocol = tcp
|
||||
|
||||
# if tls_enable is true, frpc will connect frps by tls
|
||||
tls_enable = true
|
||||
|
||||
# specify a dns server, so frpc will use this instead of default one
|
||||
# dns_server = 8.8.8.8
|
||||
|
||||
@@ -151,6 +154,9 @@ use_encryption = false
|
||||
use_compression = false
|
||||
subdomain = web01
|
||||
custom_domains = web02.yourdomain.com
|
||||
# if not empty, frpc will use proxy protocol to transfer connection info to your local service
|
||||
# v1 or v2 or empty
|
||||
proxy_protocol_version = v2
|
||||
|
||||
[plugin_unix_domain_socket]
|
||||
type = tcp
|
||||
@@ -184,6 +190,15 @@ plugin_strip_prefix = static
|
||||
plugin_http_user = abc
|
||||
plugin_http_passwd = abc
|
||||
|
||||
[plugin_https2http]
|
||||
type = https
|
||||
custom_domains = test.yourdomain.com
|
||||
plugin = https2http
|
||||
plugin_local_addr = 127.0.0.1:80
|
||||
plugin_crt_path = ./server.crt
|
||||
plugin_key_path = ./server.key
|
||||
plugin_host_header_rewrite = 127.0.0.1
|
||||
|
||||
[secret_tcp]
|
||||
# If the type is secret tcp, remote_port is useless
|
||||
# Who want to connect local port should deploy another frpc with stcp proxy and role is visitor
|
||||
|
||||
@@ -65,3 +65,6 @@ subdomain_host = frps.com
|
||||
|
||||
# if tcp stream multiplexing is used, default is true
|
||||
tcp_mux = true
|
||||
|
||||
# custom 404 page for HTTP requests
|
||||
# custom_404_page = /path/to/404.html
|
||||
|
||||
14
conf/systemd/frpc.service
Normal file
14
conf/systemd/frpc.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/frpc.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/frpc.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
14
conf/systemd/frpc@.service
Normal file
14
conf/systemd/frpc@.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Frp Client Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frpc -c /etc/frp/%i.ini
|
||||
ExecReload=/usr/bin/frpc reload -c /etc/frp/%i.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
conf/systemd/frps.service
Normal file
13
conf/systemd/frps.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/frps.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
13
conf/systemd/frps@.service
Normal file
13
conf/systemd/frps@.service
Normal file
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Frp Server Service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=nobody
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
ExecStart=/usr/bin/frps -c /etc/frp/%i.ini
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
34
go.mod
Normal file
34
go.mod
Normal file
@@ -0,0 +1,34 @@
|
||||
module github.com/fatedier/frp
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/davecgh/go-spew v1.1.0 // indirect
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/mux v1.6.2
|
||||
github.com/gorilla/websocket v1.2.0
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/klauspost/cpuid v1.2.0 // indirect
|
||||
github.com/klauspost/reedsolomon v1.9.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rakyll/statik v0.1.1
|
||||
github.com/rodaine/table v1.0.0
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/pflag v1.0.1 // indirect
|
||||
github.com/stretchr/testify v1.2.1
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 // indirect
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 // indirect
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 // indirect
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab // indirect
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79
|
||||
)
|
||||
52
go.sum
Normal file
52
go.sum
Normal file
@@ -0,0 +1,52 @@
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb h1:wCrNShQidLmvVWn/0PikGmpdP0vtQmnvyRg3ZBEhczw=
|
||||
github.com/fatedier/beego v0.0.0-20171024143340-6c6a4f5bd5eb/go.mod h1:wx3gB6dbIfBRcucp94PI9Bt3I0F2c/MyNEWuhzpWiwk=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049 h1:teH578mf2ii42NHhIp3PhgvjU5bv+NFMq9fSQR8NaG8=
|
||||
github.com/fatedier/golib v0.0.0-20181107124048-ff8cd814b049/go.mod h1:DqIrnl0rp3Zybg9zbJmozTy1n8fYJoX+QoAj9slIkKM=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible h1:pNNeBKz1jtMDupiwvtEGFTujA3J86xoEXGSkwVeYFsw=
|
||||
github.com/fatedier/kcp-go v2.0.4-0.20190317085623-2063a803e6fe+incompatible/go.mod h1:YpCOaxj7vvMThhIQ9AfTOPW2sfztQR5WDfs7AflSy4s=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049 h1:K9KHZbXKpGydfDN0aZrsoHpLJlZsBrGMFWbgLDGnPZk=
|
||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/reedsolomon v1.9.1 h1:kYrT1MlR4JH6PqOpC+okdb9CDTcwEC/BqpzK4WFyXL8=
|
||||
github.com/klauspost/reedsolomon v1.9.1/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc h1:lNOt1SMsgHXTdpuGw+RpnJtzUcCb/oRKZP65pBy9pr8=
|
||||
github.com/pires/go-proxyproto v0.0.0-20190111085350-4d51b51e3bfc/go.mod h1:6/gX3+E/IYGa0wMORlSMla999awQFdbaeQCHjSMKIzY=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rakyll/statik v0.1.1 h1:fCLHsIMajHqD5RKigbFXpvX3dN7c80Pm12+NCrI3kvg=
|
||||
github.com/rakyll/statik v0.1.1/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
|
||||
github.com/rodaine/table v1.0.0 h1:UaCJG5Axc/cNXVGXqnCrffm1KxP0OfYLe1HuJLf5sFY=
|
||||
github.com/rodaine/table v1.0.0/go.mod h1:YAUzwPOji0DUJNEvggdxyQcUAl4g3hDRcFlyjnnR51I=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047 h1:K+jtWCOuZgCra7eXZ/VWn2FbJmrA/D058mTXhh2rq+8=
|
||||
github.com/templexxx/cpufeat v0.0.0-20170927014610-3794dfbfb047/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554 h1:pexgSe+JCFuxG+uoMZLO+ce8KHtdHGhst4cs6rw3gmk=
|
||||
github.com/templexxx/xor v0.0.0-20170926022130-0af8e873c554/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8 h1:6CNSDqI1wiE+JqyOy5Qt/yo/DoNI2/QmmOZeiCid2Nw=
|
||||
github.com/tjfoc/gmsm v0.0.0-20171124023159-98aa888b79d8/go.mod h1:XxO4hdhhrzAd+G4CjDqaOkd0hUzmtPR/d3EiBBMn/wc=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab h1:w4c/LoOA2vE8SYwh8wEEQVRUwpph7TtcjH7AtZvOjy0=
|
||||
golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79 h1:1FDlG4HI84rVePw1/0E/crL5tt2N+1blLJpY6UZ6krs=
|
||||
golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -44,6 +44,7 @@ type ClientCommonConf struct {
|
||||
LoginFailExit bool `json:"login_fail_exit"`
|
||||
Start map[string]struct{} `json:"start"`
|
||||
Protocol string `json:"protocol"`
|
||||
TLSEnable bool `json:"tls_enable"`
|
||||
HeartBeatInterval int64 `json:"heartbeat_interval"`
|
||||
HeartBeatTimeout int64 `json:"heartbeat_timeout"`
|
||||
}
|
||||
@@ -69,6 +70,7 @@ func GetDefaultClientConf() *ClientCommonConf {
|
||||
LoginFailExit: true,
|
||||
Start: make(map[string]struct{}),
|
||||
Protocol: "tcp",
|
||||
TLSEnable: false,
|
||||
HeartBeatInterval: 30,
|
||||
HeartBeatTimeout: 90,
|
||||
}
|
||||
@@ -194,6 +196,12 @@ func UnmarshalClientConfFromIni(defaultCfg *ClientCommonConf, content string) (c
|
||||
cfg.Protocol = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "tls_enable"); ok && tmpStr == "true" {
|
||||
cfg.TLSEnable = true
|
||||
} else {
|
||||
cfg.TLSEnable = false
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
|
||||
err = fmt.Errorf("Parse conf error: invalid heartbeat_timeout")
|
||||
|
||||
@@ -107,8 +107,10 @@ type BaseProxyConf struct {
|
||||
Group string `json:"group"`
|
||||
GroupKey string `json:"group_key"`
|
||||
|
||||
// only used for client
|
||||
ProxyProtocolVersion string `json:"proxy_protocol_version"`
|
||||
LocalSvrConf
|
||||
HealthCheckConf // only used for client
|
||||
HealthCheckConf
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) GetBaseInfo() *BaseProxyConf {
|
||||
@@ -121,7 +123,8 @@ func (cfg *BaseProxyConf) compare(cmp *BaseProxyConf) bool {
|
||||
cfg.UseEncryption != cmp.UseEncryption ||
|
||||
cfg.UseCompression != cmp.UseCompression ||
|
||||
cfg.Group != cmp.Group ||
|
||||
cfg.GroupKey != cmp.GroupKey {
|
||||
cfg.GroupKey != cmp.GroupKey ||
|
||||
cfg.ProxyProtocolVersion != cmp.ProxyProtocolVersion {
|
||||
return false
|
||||
}
|
||||
if !cfg.LocalSvrConf.compare(&cmp.LocalSvrConf) {
|
||||
@@ -162,6 +165,7 @@ func (cfg *BaseProxyConf) UnmarshalFromIni(prefix string, name string, section i
|
||||
|
||||
cfg.Group = section["group"]
|
||||
cfg.GroupKey = section["group_key"]
|
||||
cfg.ProxyProtocolVersion = section["proxy_protocol_version"]
|
||||
|
||||
if err := cfg.LocalSvrConf.UnmarshalFromIni(prefix, name, section); err != nil {
|
||||
return err
|
||||
@@ -194,6 +198,12 @@ func (cfg *BaseProxyConf) MarshalToMsg(pMsg *msg.NewProxy) {
|
||||
}
|
||||
|
||||
func (cfg *BaseProxyConf) checkForCli() (err error) {
|
||||
if cfg.ProxyProtocolVersion != "" {
|
||||
if cfg.ProxyProtocolVersion != "v1" && cfg.ProxyProtocolVersion != "v2" {
|
||||
return fmt.Errorf("no support proxy protocol version: %s", cfg.ProxyProtocolVersion)
|
||||
}
|
||||
}
|
||||
|
||||
if err = cfg.LocalSvrConf.checkForCli(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ type ServerCommonConf struct {
|
||||
VhostHttpPort int `json:"vhost_http_port"`
|
||||
|
||||
// if VhostHttpsPort equals 0, don't listen a public port for https protocol
|
||||
VhostHttpsPort int `json:"vhost_http_port"`
|
||||
VhostHttpsPort int `json:"vhost_https_port"`
|
||||
|
||||
VhostHttpTimeout int64 `json:"vhost_http_timeout"`
|
||||
|
||||
@@ -69,6 +69,7 @@ type ServerCommonConf struct {
|
||||
Token string `json:"token"`
|
||||
SubDomainHost string `json:"subdomain_host"`
|
||||
TcpMux bool `json:"tcp_mux"`
|
||||
Custom404Page string `json:"custom_404_page"`
|
||||
|
||||
AllowPorts map[int]struct{}
|
||||
MaxPoolCount int64 `json:"max_pool_count"`
|
||||
@@ -104,6 +105,7 @@ func GetDefaultServerConf() *ServerCommonConf {
|
||||
MaxPortsPerClient: 0,
|
||||
HeartBeatTimeout: 90,
|
||||
UserConnTimeout: 10,
|
||||
Custom404Page: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +295,10 @@ func UnmarshalServerConfFromIni(defaultCfg *ServerCommonConf, content string) (c
|
||||
cfg.TcpMux = true
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "custom_404_page"); ok {
|
||||
cfg.Custom404Page = tmpStr
|
||||
}
|
||||
|
||||
if tmpStr, ok = conf.Get("common", "heartbeat_timeout"); ok {
|
||||
v, errRet := strconv.ParseInt(tmpStr, 10, 64)
|
||||
if errRet != nil {
|
||||
|
||||
@@ -17,44 +17,46 @@ package msg
|
||||
import "net"
|
||||
|
||||
const (
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeCloseProxy = 'c'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypeNewVisitorConn = 'v'
|
||||
TypeNewVisitorConnResp = '3'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUdpPacket = 'u'
|
||||
TypeNatHoleVisitor = 'i'
|
||||
TypeNatHoleClient = 'n'
|
||||
TypeNatHoleResp = 'm'
|
||||
TypeNatHoleSid = '5'
|
||||
TypeLogin = 'o'
|
||||
TypeLoginResp = '1'
|
||||
TypeNewProxy = 'p'
|
||||
TypeNewProxyResp = '2'
|
||||
TypeCloseProxy = 'c'
|
||||
TypeNewWorkConn = 'w'
|
||||
TypeReqWorkConn = 'r'
|
||||
TypeStartWorkConn = 's'
|
||||
TypeNewVisitorConn = 'v'
|
||||
TypeNewVisitorConnResp = '3'
|
||||
TypePing = 'h'
|
||||
TypePong = '4'
|
||||
TypeUdpPacket = 'u'
|
||||
TypeNatHoleVisitor = 'i'
|
||||
TypeNatHoleClient = 'n'
|
||||
TypeNatHoleResp = 'm'
|
||||
TypeNatHoleClientDetectOK = 'd'
|
||||
TypeNatHoleSid = '5'
|
||||
)
|
||||
|
||||
var (
|
||||
msgTypeMap = map[byte]interface{}{
|
||||
TypeLogin: Login{},
|
||||
TypeLoginResp: LoginResp{},
|
||||
TypeNewProxy: NewProxy{},
|
||||
TypeNewProxyResp: NewProxyResp{},
|
||||
TypeCloseProxy: CloseProxy{},
|
||||
TypeNewWorkConn: NewWorkConn{},
|
||||
TypeReqWorkConn: ReqWorkConn{},
|
||||
TypeStartWorkConn: StartWorkConn{},
|
||||
TypeNewVisitorConn: NewVisitorConn{},
|
||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||
TypePing: Ping{},
|
||||
TypePong: Pong{},
|
||||
TypeUdpPacket: UdpPacket{},
|
||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||
TypeNatHoleClient: NatHoleClient{},
|
||||
TypeNatHoleResp: NatHoleResp{},
|
||||
TypeNatHoleSid: NatHoleSid{},
|
||||
TypeLogin: Login{},
|
||||
TypeLoginResp: LoginResp{},
|
||||
TypeNewProxy: NewProxy{},
|
||||
TypeNewProxyResp: NewProxyResp{},
|
||||
TypeCloseProxy: CloseProxy{},
|
||||
TypeNewWorkConn: NewWorkConn{},
|
||||
TypeReqWorkConn: ReqWorkConn{},
|
||||
TypeStartWorkConn: StartWorkConn{},
|
||||
TypeNewVisitorConn: NewVisitorConn{},
|
||||
TypeNewVisitorConnResp: NewVisitorConnResp{},
|
||||
TypePing: Ping{},
|
||||
TypePong: Pong{},
|
||||
TypeUdpPacket: UdpPacket{},
|
||||
TypeNatHoleVisitor: NatHoleVisitor{},
|
||||
TypeNatHoleClient: NatHoleClient{},
|
||||
TypeNatHoleResp: NatHoleResp{},
|
||||
TypeNatHoleClientDetectOK: NatHoleClientDetectOK{},
|
||||
TypeNatHoleSid: NatHoleSid{},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -124,6 +126,10 @@ type ReqWorkConn struct {
|
||||
|
||||
type StartWorkConn struct {
|
||||
ProxyName string `json:"proxy_name"`
|
||||
SrcAddr string `json:"src_addr"`
|
||||
DstAddr string `json:"dst_addr"`
|
||||
SrcPort uint16 `json:"src_port"`
|
||||
DstPort uint16 `json:"dst_port"`
|
||||
}
|
||||
|
||||
type NewVisitorConn struct {
|
||||
@@ -169,6 +175,9 @@ type NatHoleResp struct {
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
type NatHoleClientDetectOK struct {
|
||||
}
|
||||
|
||||
type NatHoleSid struct {
|
||||
Sid string `json:"sid"`
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ import (
|
||||
// Timeout seconds.
|
||||
var NatHoleTimeout int64 = 10
|
||||
|
||||
type SidRequest struct {
|
||||
Sid string
|
||||
NotifyCh chan struct{}
|
||||
}
|
||||
|
||||
type NatHoleController struct {
|
||||
listener *net.UDPConn
|
||||
|
||||
@@ -44,11 +49,11 @@ func NewNatHoleController(udpBindAddr string) (nc *NatHoleController, err error)
|
||||
return nc, nil
|
||||
}
|
||||
|
||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan string) {
|
||||
func (nc *NatHoleController) ListenClient(name string, sk string) (sidCh chan *SidRequest) {
|
||||
clientCfg := &NatHoleClientCfg{
|
||||
Name: name,
|
||||
Sk: sk,
|
||||
SidCh: make(chan string),
|
||||
SidCh: make(chan *SidRequest),
|
||||
}
|
||||
nc.mu.Lock()
|
||||
nc.clientCfgs[name] = clientCfg
|
||||
@@ -132,7 +137,10 @@ func (nc *NatHoleController) HandleVisitor(m *msg.NatHoleVisitor, raddr *net.UDP
|
||||
}()
|
||||
|
||||
err := errors.PanicToError(func() {
|
||||
clientCfg.SidCh <- sid
|
||||
clientCfg.SidCh <- &SidRequest{
|
||||
Sid: sid,
|
||||
NotifyCh: session.NotifyCh,
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
@@ -158,7 +166,6 @@ func (nc *NatHoleController) HandleClient(m *msg.NatHoleClient, raddr *net.UDPAd
|
||||
}
|
||||
log.Trace("handle client message, sid [%s]", session.Sid)
|
||||
session.ClientAddr = raddr
|
||||
session.NotifyCh <- struct{}{}
|
||||
|
||||
resp := nc.GenNatHoleResponse(session, "")
|
||||
log.Trace("send nat hole response to client")
|
||||
@@ -201,5 +208,5 @@ type NatHoleSession struct {
|
||||
type NatHoleClientCfg struct {
|
||||
Name string
|
||||
Sk string
|
||||
SidCh chan string
|
||||
SidCh chan *SidRequest
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func (hp *HttpProxy) Name() string {
|
||||
return PluginHttpProxy
|
||||
}
|
||||
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (hp *HttpProxy) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
|
||||
sc, rd := gnet.NewSharedConn(wrapConn)
|
||||
|
||||
114
models/plugin/https2http.go
Normal file
114
models/plugin/https2http.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// 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.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
)
|
||||
|
||||
const PluginHTTPS2HTTP = "https2http"
|
||||
|
||||
func init() {
|
||||
Register(PluginHTTPS2HTTP, NewHTTPS2HTTPPlugin)
|
||||
}
|
||||
|
||||
type HTTPS2HTTPPlugin struct {
|
||||
crtPath string
|
||||
keyPath string
|
||||
hostHeaderRewrite string
|
||||
localAddr string
|
||||
|
||||
l *Listener
|
||||
s *http.Server
|
||||
}
|
||||
|
||||
func NewHTTPS2HTTPPlugin(params map[string]string) (Plugin, error) {
|
||||
crtPath := params["plugin_crt_path"]
|
||||
keyPath := params["plugin_key_path"]
|
||||
localAddr := params["plugin_local_addr"]
|
||||
hostHeaderRewrite := params["plugin_host_header_rewrite"]
|
||||
|
||||
if crtPath == "" {
|
||||
return nil, fmt.Errorf("plugin_crt_path is required")
|
||||
}
|
||||
if keyPath == "" {
|
||||
return nil, fmt.Errorf("plugin_key_path is required")
|
||||
}
|
||||
if localAddr == "" {
|
||||
return nil, fmt.Errorf("plugin_local_addr is required")
|
||||
}
|
||||
|
||||
listener := NewProxyListener()
|
||||
|
||||
p := &HTTPS2HTTPPlugin{
|
||||
crtPath: crtPath,
|
||||
keyPath: keyPath,
|
||||
localAddr: localAddr,
|
||||
hostHeaderRewrite: hostHeaderRewrite,
|
||||
l: listener,
|
||||
}
|
||||
|
||||
rp := &httputil.ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
req.URL.Host = p.localAddr
|
||||
if p.hostHeaderRewrite != "" {
|
||||
req.Host = p.hostHeaderRewrite
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
p.s = &http.Server{
|
||||
Handler: rp,
|
||||
}
|
||||
|
||||
tlsConfig, err := p.genTLSConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gen TLS config error: %v", err)
|
||||
}
|
||||
ln := tls.NewListener(listener, tlsConfig)
|
||||
|
||||
go p.s.Serve(ln)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) genTLSConfig() (*tls.Config, error) {
|
||||
cert, err := tls.LoadX509KeyPair(p.crtPath, p.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 frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
p.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Name() string {
|
||||
return PluginHTTPS2HTTP
|
||||
}
|
||||
|
||||
func (p *HTTPS2HTTPPlugin) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -46,7 +46,7 @@ func Create(name string, params map[string]string) (p Plugin, err error) {
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn)
|
||||
Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ func NewSocks5Plugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *Socks5Plugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
defer conn.Close()
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.Server.ServeConn(wrapConn)
|
||||
|
||||
@@ -72,7 +72,7 @@ func NewStaticFilePlugin(params map[string]string) (Plugin, error) {
|
||||
return sp, nil
|
||||
}
|
||||
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (sp *StaticFilePlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
wrapConn := frpNet.WrapReadWriteCloserToConn(conn, realConn)
|
||||
sp.l.PutConn(wrapConn)
|
||||
}
|
||||
|
||||
@@ -53,11 +53,14 @@ func NewUnixDomainSocketPlugin(params map[string]string) (p Plugin, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn) {
|
||||
func (uds *UnixDomainSocketPlugin) Handle(conn io.ReadWriteCloser, realConn frpNet.Conn, extraBufToLocal []byte) {
|
||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(extraBufToLocal) > 0 {
|
||||
localConn.Write(extraBufToLocal)
|
||||
}
|
||||
|
||||
frpIo.Join(localConn, conn)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,6 @@ func ForwardUserConn(udpConn *net.UDPConn, readCh <-chan *msg.UdpPacket, sendCh
|
||||
default:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Forwarder(dstAddr *net.UDPAddr, readCh <-chan *msg.UdpPacket, sendCh chan<- msg.Message) {
|
||||
|
||||
@@ -44,7 +44,7 @@ for os in $os_all; do
|
||||
mv ./frps_${os}_${arch} ${frp_path}/frps
|
||||
fi
|
||||
cp ./LICENSE ${frp_path}
|
||||
cp ./conf/* ${frp_path}
|
||||
cp -rf ./conf/* ${frp_path}
|
||||
|
||||
# packages
|
||||
cd ./packages
|
||||
|
||||
@@ -62,10 +62,13 @@ func (cm *ControlManager) Add(runId string, ctl *Control) (oldCtl *Control) {
|
||||
return
|
||||
}
|
||||
|
||||
func (cm *ControlManager) Del(runId string) {
|
||||
// we should make sure if it's the same control to prevent delete a new one
|
||||
func (cm *ControlManager) Del(runId string, ctl *Control) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
delete(cm.ctlsByRunId, runId)
|
||||
if c, ok := cm.ctlsByRunId[runId]; ok && c == ctl {
|
||||
delete(cm.ctlsByRunId, runId)
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ControlManager) GetById(runId string) (ctl *Control, ok bool) {
|
||||
@@ -298,6 +301,7 @@ func (ctl *Control) reader() {
|
||||
return
|
||||
} else {
|
||||
ctl.conn.Warn("read error: %v", err)
|
||||
ctl.conn.Close()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -28,13 +28,11 @@ import (
|
||||
)
|
||||
|
||||
type GeneralResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
type ServerInfoResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Version string `json:"version"`
|
||||
BindPort int `json:"bind_port"`
|
||||
BindUdpPort int `json:"bind_udp_port"`
|
||||
@@ -55,18 +53,19 @@ type ServerInfoResp struct {
|
||||
|
||||
// api/serverinfo
|
||||
func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res ServerInfoResp
|
||||
)
|
||||
res := GeneralResponse{Code: 200}
|
||||
defer func() {
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
cfg := &g.GlbServerCfg.ServerCommonConf
|
||||
serverStats := svr.statsCollector.GetServer()
|
||||
res = ServerInfoResp{
|
||||
svrResp := ServerInfoResp{
|
||||
Version: version.Full(),
|
||||
BindPort: cfg.BindPort,
|
||||
BindUdpPort: cfg.BindUdpPort,
|
||||
@@ -85,8 +84,8 @@ func (svr *Service) ApiServerInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ProxyTypeCounts: serverStats.ProxyTypeCounts,
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&svrResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
type BaseOutConf struct {
|
||||
@@ -155,31 +154,29 @@ type ProxyStatsInfo struct {
|
||||
}
|
||||
|
||||
type GetProxyInfoResp struct {
|
||||
GeneralResponse
|
||||
Proxies []*ProxyStatsInfo `json:"proxies"`
|
||||
}
|
||||
|
||||
// api/proxy/:type
|
||||
func (svr *Service) ApiProxyByType(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyInfoResp
|
||||
)
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
log.Info(r.URL.Path)
|
||||
log.Info(r.URL.RawPath)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
res.Proxies = svr.getProxyStatsByType(proxyType)
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
proxyInfoResp := GetProxyInfoResp{}
|
||||
proxyInfoResp.Proxies = svr.getProxyStatsByType(proxyType)
|
||||
|
||||
buf, _ := json.Marshal(&proxyInfoResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxyStatsInfo) {
|
||||
@@ -215,8 +212,6 @@ func (svr *Service) getProxyStatsByType(proxyType string) (proxyInfos []*ProxySt
|
||||
|
||||
// Get proxy info by name.
|
||||
type GetProxyStatsResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Name string `json:"name"`
|
||||
Conf interface{} `json:"conf"`
|
||||
TodayTrafficIn int64 `json:"today_traffic_in"`
|
||||
@@ -229,45 +224,50 @@ type GetProxyStatsResp struct {
|
||||
|
||||
// api/proxy/:type/:name
|
||||
func (svr *Service) ApiProxyByTypeAndName(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyStatsResp
|
||||
)
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
proxyType := params["type"]
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
res = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||
proxyStatsResp := GetProxyStatsResp{}
|
||||
proxyStatsResp, res.Code, res.Msg = svr.getProxyStatsByTypeAndName(proxyType, name)
|
||||
if res.Code != 200 {
|
||||
return
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&proxyStatsResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp) {
|
||||
func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName string) (proxyInfo GetProxyStatsResp, code int, msg string) {
|
||||
proxyInfo.Name = proxyName
|
||||
ps := svr.statsCollector.GetProxiesByTypeAndName(proxyType, proxyName)
|
||||
if ps == nil {
|
||||
proxyInfo.Code = 1
|
||||
proxyInfo.Msg = "no proxy info found"
|
||||
code = 404
|
||||
msg = "no proxy info found"
|
||||
} else {
|
||||
if pxy, ok := svr.pxyManager.GetByName(proxyName); ok {
|
||||
content, err := json.Marshal(pxy.GetConf())
|
||||
if err != nil {
|
||||
log.Warn("marshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
proxyInfo.Code = 2
|
||||
proxyInfo.Msg = "parse conf error"
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Conf = getConfByType(ps.Type)
|
||||
if err = json.Unmarshal(content, &proxyInfo.Conf); err != nil {
|
||||
log.Warn("unmarshal proxy [%s] conf info error: %v", ps.Name, err)
|
||||
proxyInfo.Code = 2
|
||||
proxyInfo.Msg = "parse conf error"
|
||||
code = 400
|
||||
msg = "parse conf error"
|
||||
return
|
||||
}
|
||||
proxyInfo.Status = consts.Online
|
||||
@@ -279,6 +279,7 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
||||
proxyInfo.CurConns = ps.CurConns
|
||||
proxyInfo.LastStartTime = ps.LastStartTime
|
||||
proxyInfo.LastCloseTime = ps.LastCloseTime
|
||||
code = 200
|
||||
}
|
||||
|
||||
return
|
||||
@@ -286,36 +287,38 @@ func (svr *Service) getProxyStatsByTypeAndName(proxyType string, proxyName strin
|
||||
|
||||
// api/traffic/:name
|
||||
type GetProxyTrafficResp struct {
|
||||
GeneralResponse
|
||||
|
||||
Name string `json:"name"`
|
||||
TrafficIn []int64 `json:"traffic_in"`
|
||||
TrafficOut []int64 `json:"traffic_out"`
|
||||
}
|
||||
|
||||
func (svr *Service) ApiProxyTraffic(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
buf []byte
|
||||
res GetProxyTrafficResp
|
||||
)
|
||||
res := GeneralResponse{Code: 200}
|
||||
params := mux.Vars(r)
|
||||
name := params["name"]
|
||||
|
||||
defer func() {
|
||||
log.Info("Http response [%s]: code [%d]", r.URL.Path, res.Code)
|
||||
w.WriteHeader(res.Code)
|
||||
if len(res.Msg) > 0 {
|
||||
w.Write([]byte(res.Msg))
|
||||
}
|
||||
}()
|
||||
log.Info("Http request: [%s]", r.URL.Path)
|
||||
|
||||
res.Name = name
|
||||
trafficResp := GetProxyTrafficResp{}
|
||||
trafficResp.Name = name
|
||||
proxyTrafficInfo := svr.statsCollector.GetProxyTraffic(name)
|
||||
|
||||
if proxyTrafficInfo == nil {
|
||||
res.Code = 1
|
||||
res.Code = 404
|
||||
res.Msg = "no proxy info found"
|
||||
return
|
||||
} else {
|
||||
res.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
res.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
trafficResp.TrafficIn = proxyTrafficInfo.TrafficIn
|
||||
trafficResp.TrafficOut = proxyTrafficInfo.TrafficOut
|
||||
}
|
||||
|
||||
buf, _ = json.Marshal(&res)
|
||||
w.Write(buf)
|
||||
buf, _ := json.Marshal(&trafficResp)
|
||||
res.Msg = string(buf)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package proxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
@@ -29,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
type HttpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.HttpProxyConf
|
||||
|
||||
closeFuncs []func()
|
||||
@@ -51,6 +52,10 @@ func (pxy *HttpProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
routeConfig.Domain = domain
|
||||
for _, location := range locations {
|
||||
routeConfig.Location = location
|
||||
@@ -93,8 +98,14 @@ func (pxy *HttpProxy) GetConf() config.ProxyConf {
|
||||
return pxy.cfg
|
||||
}
|
||||
|
||||
func (pxy *HttpProxy) GetRealConn() (workConn frpNet.Conn, err error) {
|
||||
tmpConn, errRet := pxy.GetWorkConnFromPool()
|
||||
func (pxy *HttpProxy) GetRealConn(remoteAddr string) (workConn frpNet.Conn, err error) {
|
||||
rAddr, errRet := net.ResolveTCPAddr("tcp", remoteAddr)
|
||||
if errRet != nil {
|
||||
pxy.Warn("resolve TCP addr [%s] error: %v", remoteAddr, errRet)
|
||||
// we do not return error here since remoteAddr is not necessary for proxies without proxy protocol enabled
|
||||
}
|
||||
|
||||
tmpConn, errRet := pxy.GetWorkConnFromPool(rAddr, nil)
|
||||
if errRet != nil {
|
||||
err = errRet
|
||||
return
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
type HttpsProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.HttpsProxyConf
|
||||
}
|
||||
|
||||
@@ -33,6 +33,10 @@ func (pxy *HttpsProxy) Run() (remoteAddr string, err error) {
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, domain := range pxy.cfg.CustomDomains {
|
||||
if domain == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
routeConfig.Domain = domain
|
||||
l, errRet := pxy.rc.VhostHttpsMuxer.Listen(routeConfig)
|
||||
if errRet != nil {
|
||||
|
||||
@@ -17,6 +17,8 @@ package proxy
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/fatedier/frp/g"
|
||||
@@ -36,7 +38,7 @@ type Proxy interface {
|
||||
Run() (remoteAddr string, err error)
|
||||
GetName() string
|
||||
GetConf() config.ProxyConf
|
||||
GetWorkConnFromPool() (workConn frpNet.Conn, err error)
|
||||
GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error)
|
||||
GetUsedPortsNum() int
|
||||
Close()
|
||||
log.Logger
|
||||
@@ -70,7 +72,7 @@ func (pxy *BaseProxy) Close() {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
||||
func (pxy *BaseProxy) GetWorkConnFromPool(src, dst net.Addr) (workConn frpNet.Conn, err error) {
|
||||
// try all connections from the pool
|
||||
for i := 0; i < pxy.poolCount+1; i++ {
|
||||
if workConn, err = pxy.getWorkConnFn(); err != nil {
|
||||
@@ -80,8 +82,29 @@ func (pxy *BaseProxy) GetWorkConnFromPool() (workConn frpNet.Conn, err error) {
|
||||
pxy.Info("get a new work connection: [%s]", workConn.RemoteAddr().String())
|
||||
workConn.AddLogPrefix(pxy.GetName())
|
||||
|
||||
var (
|
||||
srcAddr string
|
||||
dstAddr string
|
||||
srcPortStr string
|
||||
dstPortStr string
|
||||
srcPort int
|
||||
dstPort int
|
||||
)
|
||||
|
||||
if src != nil {
|
||||
srcAddr, srcPortStr, _ = net.SplitHostPort(src.String())
|
||||
srcPort, _ = strconv.Atoi(srcPortStr)
|
||||
}
|
||||
if dst != nil {
|
||||
dstAddr, dstPortStr, _ = net.SplitHostPort(dst.String())
|
||||
dstPort, _ = strconv.Atoi(dstPortStr)
|
||||
}
|
||||
err := msg.WriteMsg(workConn, &msg.StartWorkConn{
|
||||
ProxyName: pxy.GetName(),
|
||||
SrcAddr: srcAddr,
|
||||
SrcPort: uint16(srcPort),
|
||||
DstAddr: dstAddr,
|
||||
DstPort: uint16(dstPort),
|
||||
})
|
||||
if err != nil {
|
||||
workConn.Warn("failed to send message to work connection from pool: %v, times: %d", err, i)
|
||||
@@ -135,33 +158,33 @@ func NewProxy(runId string, rc *controller.ResourceController, statsCollector st
|
||||
case *config.TcpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &TcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpProxyConf:
|
||||
pxy = &HttpProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.HttpsProxyConf:
|
||||
pxy = &HttpsProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.UdpProxyConf:
|
||||
basePxy.usedPortsNum = 1
|
||||
pxy = &UdpProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.StcpProxyConf:
|
||||
pxy = &StcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
case *config.XtcpProxyConf:
|
||||
pxy = &XtcpProxy{
|
||||
BaseProxy: basePxy,
|
||||
BaseProxy: &basePxy,
|
||||
cfg: cfg,
|
||||
}
|
||||
default:
|
||||
@@ -177,7 +200,7 @@ func HandleUserTcpConnection(pxy Proxy, userConn frpNet.Conn, statsCollector sta
|
||||
defer userConn.Close()
|
||||
|
||||
// try all connections from the pool
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
workConn, err := pxy.GetWorkConnFromPool(userConn.RemoteAddr(), userConn.LocalAddr())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
)
|
||||
|
||||
type StcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.StcpProxyConf
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
)
|
||||
|
||||
type TcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.TcpProxyConf
|
||||
|
||||
realPort int
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
type UdpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.UdpProxyConf
|
||||
|
||||
realPort int
|
||||
@@ -160,7 +160,7 @@ func (pxy *UdpProxy) Run() (remoteAddr string, err error) {
|
||||
// Sleep a while for waiting control send the NewProxyResp to client.
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
for {
|
||||
workConn, err := pxy.GetWorkConnFromPool()
|
||||
workConn, err := pxy.GetWorkConnFromPool(nil, nil)
|
||||
if err != nil {
|
||||
time.Sleep(1 * time.Second)
|
||||
// check if proxy is closed
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
type XtcpProxy struct {
|
||||
BaseProxy
|
||||
*BaseProxy
|
||||
cfg *config.XtcpProxyConf
|
||||
|
||||
closeCh chan struct{}
|
||||
@@ -42,18 +42,40 @@ func (pxy *XtcpProxy) Run() (remoteAddr string, err error) {
|
||||
select {
|
||||
case <-pxy.closeCh:
|
||||
break
|
||||
case sid := <-sidCh:
|
||||
workConn, errRet := pxy.GetWorkConnFromPool()
|
||||
case sidRequest := <-sidCh:
|
||||
sr := sidRequest
|
||||
workConn, errRet := pxy.GetWorkConnFromPool(nil, nil)
|
||||
if errRet != nil {
|
||||
continue
|
||||
}
|
||||
m := &msg.NatHoleSid{
|
||||
Sid: sid,
|
||||
Sid: sr.Sid,
|
||||
}
|
||||
errRet = msg.WriteMsg(workConn, m)
|
||||
if errRet != nil {
|
||||
pxy.Warn("write nat hole sid package error, %v", errRet)
|
||||
workConn.Close()
|
||||
break
|
||||
}
|
||||
|
||||
go func() {
|
||||
raw, errRet := msg.ReadMsg(workConn)
|
||||
if errRet != nil {
|
||||
pxy.Warn("read nat hole client ok package error: %v", errRet)
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
if _, ok := raw.(*msg.NatHoleClientDetectOK); !ok {
|
||||
pxy.Warn("read nat hole client ok package format error")
|
||||
workConn.Close()
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case sr.NotifyCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -16,8 +16,14 @@ package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -61,6 +67,9 @@ type Service struct {
|
||||
// Accept connections using websocket
|
||||
websocketListener frpNet.Listener
|
||||
|
||||
// Accept frp tls connections
|
||||
tlsListener frpNet.Listener
|
||||
|
||||
// Manage all controllers
|
||||
ctlManager *ControlManager
|
||||
|
||||
@@ -72,6 +81,8 @@ type Service struct {
|
||||
|
||||
// stats collector to store server and proxies stats info
|
||||
statsCollector stats.Collector
|
||||
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func NewService() (svr *Service, err error) {
|
||||
@@ -84,18 +95,22 @@ func NewService() (svr *Service, err error) {
|
||||
TcpPortManager: ports.NewPortManager("tcp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
UdpPortManager: ports.NewPortManager("udp", cfg.ProxyBindAddr, cfg.AllowPorts),
|
||||
},
|
||||
tlsConfig: generateTLSConfig(),
|
||||
}
|
||||
|
||||
// Init group controller
|
||||
svr.rc.TcpGroupCtl = group.NewTcpGroupCtl(svr.rc.TcpPortManager)
|
||||
|
||||
// Init assets.
|
||||
// Init assets
|
||||
err = assets.Load(cfg.AssetsDir)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Load assets error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Init 404 not found page
|
||||
vhost.NotFoundPagePath = cfg.Custom404Page
|
||||
|
||||
var (
|
||||
httpMuxOn bool
|
||||
httpsMuxOn bool
|
||||
@@ -187,6 +202,12 @@ func NewService() (svr *Service, err error) {
|
||||
log.Info("https service listen on %s:%d", cfg.ProxyBindAddr, cfg.VhostHttpsPort)
|
||||
}
|
||||
|
||||
// frp tls listener
|
||||
tlsListener := svr.muxer.Listen(1, 1, func(data []byte) bool {
|
||||
return int(data[0]) == frpNet.FRP_TLS_HEAD_BYTE
|
||||
})
|
||||
svr.tlsListener = frpNet.WrapLogListener(tlsListener)
|
||||
|
||||
// Create nat hole controller.
|
||||
if cfg.BindUdpPort > 0 {
|
||||
var nc *nathole.NatHoleController
|
||||
@@ -225,6 +246,7 @@ func (svr *Service) Run() {
|
||||
}
|
||||
|
||||
go svr.HandleListener(svr.websocketListener)
|
||||
go svr.HandleListener(svr.tlsListener)
|
||||
|
||||
svr.HandleListener(svr.listener)
|
||||
}
|
||||
@@ -238,6 +260,16 @@ func (svr *Service) HandleListener(l frpNet.Listener) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("start check TLS connection...")
|
||||
originConn := c
|
||||
c, err = frpNet.CheckAndEnableTLSServerConnWithTimeout(c, svr.tlsConfig, connReadTimeout)
|
||||
if err != nil {
|
||||
log.Warn("CheckAndEnableTLSServerConnWithTimeout error: %v", err)
|
||||
originConn.Close()
|
||||
continue
|
||||
}
|
||||
log.Trace("success check TLS connection")
|
||||
|
||||
// Start a new goroutine for dealing connections.
|
||||
go func(frpConn frpNet.Conn) {
|
||||
dealFn := func(conn frpNet.Conn) {
|
||||
@@ -353,7 +385,7 @@ func (svr *Service) RegisterControl(ctlConn frpNet.Conn, loginMsg *msg.Login) (e
|
||||
go func() {
|
||||
// block until control closed
|
||||
ctl.WaitClosed()
|
||||
svr.ctlManager.Del(loginMsg.RunId)
|
||||
svr.ctlManager.Del(loginMsg.RunId, ctl)
|
||||
}()
|
||||
return
|
||||
}
|
||||
@@ -373,3 +405,24 @@ func (svr *Service) RegisterVisitorConn(visitorConn frpNet.Conn, newMsg *msg.New
|
||||
return svr.rc.VisitorManager.NewConn(newMsg.ProxyName, visitorConn, newMsg.Timestamp, newMsg.SignKey,
|
||||
newMsg.UseEncryption, newMsg.UseCompression)
|
||||
}
|
||||
|
||||
// Setup a bare-bones TLS config for the server
|
||||
func generateTLSConfig() *tls.Config {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &key.PublicKey, key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &tls.Config{Certificates: []tls.Certificate{tlsCert}}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,12 @@ custom_domains = test6.frp.com
|
||||
host_header_rewrite = test6.frp.com
|
||||
header_X-From-Where = frp
|
||||
|
||||
[wildcard_http]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
local_port = 10704
|
||||
custom_domains = *.frp1.com
|
||||
|
||||
[subhost01]
|
||||
type = http
|
||||
local_ip = 127.0.0.1
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestCmdTcp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"tcp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-l", "10701", "-r", "20801", "-n", "tcp_test"})
|
||||
@@ -27,7 +27,7 @@ func TestCmdTcp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer c.Stop()
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
assert.NoError(err)
|
||||
@@ -43,7 +43,7 @@ func TestCmdUdp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"udp", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-l", "10702", "-r", "20802", "-n", "udp_test"})
|
||||
@@ -51,7 +51,7 @@ func TestCmdUdp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer c.Stop()
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
res, err := util.SendUdpMsg("127.0.0.1:20802", consts.TEST_UDP_ECHO_STR)
|
||||
assert.NoError(err)
|
||||
@@ -67,7 +67,7 @@ func TestCmdHttp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer s.Stop()
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
c := util.NewProcess(consts.FRPC_BIN_PATH, []string{"http", "-s", "127.0.0.1:20000", "-t", "123", "-u", "test",
|
||||
"-n", "udp_test", "-l", "10704", "--custom_domain", "127.0.0.1"})
|
||||
@@ -75,7 +75,7 @@ func TestCmdHttp(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer c.Stop()
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
code, body, _, err := util.SendHttpMsg("GET", "http://127.0.0.1:20001", "", nil, "")
|
||||
if assert.NoError(err) {
|
||||
|
||||
@@ -182,6 +182,21 @@ func TestHttp(t *testing.T) {
|
||||
assert.Equal("true", header.Get("X-Header-Set"))
|
||||
}
|
||||
|
||||
// wildcard_http
|
||||
// test.frp1.com match *.frp1.com
|
||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test.frp1.com", nil, "")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal(200, code)
|
||||
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||
}
|
||||
|
||||
// new.test.frp1.com also match *.frp1.com
|
||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "new.test.frp1.com", nil, "")
|
||||
if assert.NoError(err) {
|
||||
assert.Equal(200, code)
|
||||
assert.Equal(consts.TEST_HTTP_NORMAL_STR, body)
|
||||
}
|
||||
|
||||
// subhost01
|
||||
code, body, _, err = util.SendHttpMsg("GET", fmt.Sprintf("http://127.0.0.1:%d", consts.TEST_HTTP_FRP_PORT), "test01.sub.com", nil, "")
|
||||
if assert.NoError(err) {
|
||||
|
||||
@@ -56,14 +56,14 @@ func TestReconnect(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
@@ -72,7 +72,7 @@ func TestReconnect(t *testing.T) {
|
||||
|
||||
// stop frpc
|
||||
frpcProcess.Stop()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// test tcp, expect failed
|
||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
@@ -84,7 +84,7 @@ func TestReconnect(t *testing.T) {
|
||||
if assert.NoError(err) {
|
||||
defer newFrpcProcess.Stop()
|
||||
}
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp
|
||||
res, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
@@ -93,7 +93,7 @@ func TestReconnect(t *testing.T) {
|
||||
|
||||
// stop frps
|
||||
frpsProcess.Stop()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// test tcp, expect failed
|
||||
_, err = util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestReload(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
@@ -102,7 +102,7 @@ func TestReload(t *testing.T) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp1
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
|
||||
@@ -55,7 +55,7 @@ func TestConfTemplate(t *testing.T) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess("env", []string{"FRP_TOKEN=123456", "TCP_REMOTE_PORT=20801", consts.FRPC_BIN_PATH, "-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
@@ -63,7 +63,7 @@ func TestConfTemplate(t *testing.T) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp1
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
|
||||
188
tests/ci/tls_test.go
Normal file
188
tests/ci/tls_test.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package ci
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fatedier/frp/tests/config"
|
||||
"github.com/fatedier/frp/tests/consts"
|
||||
"github.com/fatedier/frp/tests/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const FRPS_TLS_TCP_CONF = `
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
`
|
||||
|
||||
const FRPC_TLS_TCP_CONF = `
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
protocol = tcp
|
||||
tls_enable = true
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = 10701
|
||||
remote_port = 20801
|
||||
`
|
||||
|
||||
func TestTlsOverTCP(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_TCP_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpsCfgPath)
|
||||
}
|
||||
|
||||
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_TCP_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpcCfgPath)
|
||||
}
|
||||
|
||||
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||
err = frpsProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
assert.NoError(err)
|
||||
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||
}
|
||||
|
||||
const FRPS_TLS_KCP_CONF = `
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 20000
|
||||
kcp_bind_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
`
|
||||
|
||||
const FRPC_TLS_KCP_CONF = `
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
protocol = kcp
|
||||
tls_enable = true
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = 10701
|
||||
remote_port = 20801
|
||||
`
|
||||
|
||||
func TestTLSOverKCP(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_KCP_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpsCfgPath)
|
||||
}
|
||||
|
||||
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_KCP_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpcCfgPath)
|
||||
}
|
||||
|
||||
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||
err = frpsProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
assert.NoError(err)
|
||||
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||
}
|
||||
|
||||
const FRPS_TLS_WS_CONF = `
|
||||
[common]
|
||||
bind_addr = 0.0.0.0
|
||||
bind_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
`
|
||||
|
||||
const FRPC_TLS_WS_CONF = `
|
||||
[common]
|
||||
server_addr = 127.0.0.1
|
||||
server_port = 20000
|
||||
log_file = console
|
||||
log_level = debug
|
||||
token = 123456
|
||||
protocol = websocket
|
||||
tls_enable = true
|
||||
|
||||
[tcp]
|
||||
type = tcp
|
||||
local_port = 10701
|
||||
remote_port = 20801
|
||||
`
|
||||
|
||||
func TestTLSOverWebsocket(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
frpsCfgPath, err := config.GenerateConfigFile(consts.FRPS_NORMAL_CONFIG, FRPS_TLS_WS_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpsCfgPath)
|
||||
}
|
||||
|
||||
frpcCfgPath, err := config.GenerateConfigFile(consts.FRPC_NORMAL_CONFIG, FRPC_TLS_WS_CONF)
|
||||
if assert.NoError(err) {
|
||||
defer os.Remove(frpcCfgPath)
|
||||
}
|
||||
|
||||
frpsProcess := util.NewProcess(consts.FRPS_BIN_PATH, []string{"-c", frpsCfgPath})
|
||||
err = frpsProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpsProcess.Stop()
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
frpcProcess := util.NewProcess(consts.FRPC_BIN_PATH, []string{"-c", frpcCfgPath})
|
||||
err = frpcProcess.Start()
|
||||
if assert.NoError(err) {
|
||||
defer frpcProcess.Stop()
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
// test tcp
|
||||
res, err := util.SendTcpMsg("127.0.0.1:20801", consts.TEST_TCP_ECHO_STR)
|
||||
assert.NoError(err)
|
||||
assert.Equal(consts.TEST_TCP_ECHO_STR, res)
|
||||
}
|
||||
@@ -88,8 +88,10 @@ func handleHttp(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(r.Host, "127.0.0.1") || strings.Contains(r.Host, "test2.frp.com") ||
|
||||
strings.Contains(r.Host, "test5.frp.com") || strings.Contains(r.Host, "test6.frp.com") {
|
||||
if strings.HasPrefix(r.Host, "127.0.0.1") || strings.HasPrefix(r.Host, "test2.frp.com") ||
|
||||
strings.HasPrefix(r.Host, "test5.frp.com") || strings.HasPrefix(r.Host, "test6.frp.com") ||
|
||||
strings.HasPrefix(r.Host, "test.frp1.com") || strings.HasPrefix(r.Host, "new.test.frp1.com") {
|
||||
|
||||
w.WriteHeader(200)
|
||||
w.Write([]byte(consts.TEST_HTTP_NORMAL_STR))
|
||||
} else if strings.Contains(r.Host, "test3.frp.com") {
|
||||
|
||||
@@ -28,51 +28,51 @@ func GetProxyStatus(statusAddr string, user string, passwd string, name string)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return status, err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
allStatus := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &allStatus)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
for _, s := range allStatus.Tcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Udp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Http {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Https {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Stcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Xtcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != 200 {
|
||||
return status, fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return status, err
|
||||
}
|
||||
allStatus := &client.StatusResp{}
|
||||
err = json.Unmarshal(body, &allStatus)
|
||||
if err != nil {
|
||||
return status, fmt.Errorf("unmarshal http response error: %s", strings.TrimSpace(string(body)))
|
||||
}
|
||||
for _, s := range allStatus.Tcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Udp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Http {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Https {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Stcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
for _, s := range allStatus.Xtcp {
|
||||
if s.Name == name {
|
||||
return &s, nil
|
||||
}
|
||||
}
|
||||
|
||||
return status, errors.New("no proxy status found")
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ func ReloadConf(reloadAddr string, user string, passwd string) error {
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return fmt.Errorf("admin api status code [%d]", resp.StatusCode)
|
||||
}
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/fatedier/beego/logs"
|
||||
)
|
||||
|
||||
// Log is the under log object
|
||||
var Log *logs.BeeLogger
|
||||
|
||||
func init() {
|
||||
@@ -33,6 +34,7 @@ func InitLog(logWay string, logFile string, logLevel string, maxdays int64) {
|
||||
SetLogLevel(logLevel)
|
||||
}
|
||||
|
||||
// SetLogFile to configure log params
|
||||
// logWay: file or console
|
||||
func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
if logWay == "console" {
|
||||
@@ -43,6 +45,7 @@ func SetLogFile(logWay string, logFile string, maxdays int64) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetLogLevel set log level, default is warning
|
||||
// value: error, warning, info, debug, trace
|
||||
func SetLogLevel(logLevel string) {
|
||||
level := 4 // warning
|
||||
@@ -85,7 +88,7 @@ func Trace(format string, v ...interface{}) {
|
||||
Log.Trace(format, v...)
|
||||
}
|
||||
|
||||
// Logger
|
||||
// Logger is the log interface
|
||||
type Logger interface {
|
||||
AddLogPrefix(string)
|
||||
GetPrefixStr() string
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -207,3 +208,17 @@ func ConnectServerByProxy(proxyUrl string, protocol string, addr string) (c Conn
|
||||
return nil, fmt.Errorf("unsupport protocol: %s", protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func ConnectServerByProxyWithTLS(proxyUrl string, protocol string, addr string, tlsConfig *tls.Config) (c Conn, err error) {
|
||||
c, err = ConnectServerByProxy(proxyUrl, protocol, addr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if tlsConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c = WrapTLSClientConn(c, tlsConfig)
|
||||
return
|
||||
}
|
||||
|
||||
52
utils/net/tls.go
Normal file
52
utils/net/tls.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// 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.
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
)
|
||||
|
||||
var (
|
||||
FRP_TLS_HEAD_BYTE = 0x17
|
||||
)
|
||||
|
||||
func WrapTLSClientConn(c net.Conn, tlsConfig *tls.Config) (out Conn) {
|
||||
c.Write([]byte{byte(FRP_TLS_HEAD_BYTE)})
|
||||
out = WrapConn(tls.Client(c, tlsConfig))
|
||||
return
|
||||
}
|
||||
|
||||
func CheckAndEnableTLSServerConnWithTimeout(c net.Conn, tlsConfig *tls.Config, timeout time.Duration) (out Conn, err error) {
|
||||
sc, r := gnet.NewSharedConnSize(c, 2)
|
||||
buf := make([]byte, 1)
|
||||
var n int
|
||||
c.SetReadDeadline(time.Now().Add(timeout))
|
||||
n, err = r.Read(buf)
|
||||
c.SetReadDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n == 1 && int(buf[0]) == FRP_TLS_HEAD_BYTE {
|
||||
out = WrapConn(tls.Server(c, tlsConfig))
|
||||
} else {
|
||||
out = WrapConn(sc)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -31,6 +31,7 @@ type WebsocketListener struct {
|
||||
httpMutex *http.ServeMux
|
||||
}
|
||||
|
||||
// NewWebsocketListener to handle websocket connections
|
||||
// ln: tcp listener for websocket connections
|
||||
func NewWebsocketListener(ln net.Listener) (wl *WebsocketListener) {
|
||||
wl = &WebsocketListener{
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var version string = "0.23.1"
|
||||
var version string = "0.27.1"
|
||||
|
||||
func Full() string {
|
||||
return version
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 fatedier, fatedier@gmail.com
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -15,221 +15,211 @@
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpNet "github.com/fatedier/frp/utils/net"
|
||||
frpLog "github.com/fatedier/frp/utils/log"
|
||||
|
||||
gnet "github.com/fatedier/golib/net"
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
type HttpMuxer struct {
|
||||
*VhostMuxer
|
||||
var (
|
||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||
ErrNoDomain = errors.New("no such domain")
|
||||
)
|
||||
|
||||
func getHostFromAddr(addr string) (host string) {
|
||||
strs := strings.Split(addr, ":")
|
||||
if len(strs) > 1 {
|
||||
host = strs[0]
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetHttpRequestInfo(c frpNet.Conn) (_ frpNet.Conn, _ map[string]string, err error) {
|
||||
reqInfoMap := make(map[string]string, 0)
|
||||
sc, rd := gnet.NewSharedConn(c)
|
||||
|
||||
request, err := http.ReadRequest(bufio.NewReader(rd))
|
||||
if err != nil {
|
||||
return nil, reqInfoMap, err
|
||||
}
|
||||
// hostName
|
||||
tmpArr := strings.Split(request.Host, ":")
|
||||
reqInfoMap["Host"] = tmpArr[0]
|
||||
reqInfoMap["Path"] = request.URL.Path
|
||||
reqInfoMap["Scheme"] = request.URL.Scheme
|
||||
|
||||
// Authorization
|
||||
authStr := request.Header.Get("Authorization")
|
||||
if authStr != "" {
|
||||
reqInfoMap["Authorization"] = authStr
|
||||
}
|
||||
request.Body.Close()
|
||||
return frpNet.WrapConn(sc), reqInfoMap, nil
|
||||
type HttpReverseProxyOptions struct {
|
||||
ResponseHeaderTimeoutS int64
|
||||
}
|
||||
|
||||
func NewHttpMuxer(listener frpNet.Listener, timeout time.Duration) (*HttpMuxer, error) {
|
||||
mux, err := NewVhostMuxer(listener, GetHttpRequestInfo, HttpAuthFunc, ModifyHttpRequest, timeout)
|
||||
return &HttpMuxer{mux}, err
|
||||
type HttpReverseProxy struct {
|
||||
proxy *ReverseProxy
|
||||
|
||||
vhostRouter *VhostRouters
|
||||
|
||||
responseHeaderTimeout time.Duration
|
||||
cfgMu sync.RWMutex
|
||||
}
|
||||
|
||||
func ModifyHttpRequest(c frpNet.Conn, rewriteHost string) (_ frpNet.Conn, err error) {
|
||||
sc, rd := gnet.NewSharedConn(c)
|
||||
var buff []byte
|
||||
remoteIP := strings.Split(c.RemoteAddr().String(), ":")[0]
|
||||
if buff, err = hostNameRewrite(rd, rewriteHost, remoteIP); err != nil {
|
||||
return nil, err
|
||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
||||
if option.ResponseHeaderTimeoutS <= 0 {
|
||||
option.ResponseHeaderTimeoutS = 60
|
||||
}
|
||||
err = sc.ResetBuf(buff)
|
||||
return frpNet.WrapConn(sc), err
|
||||
}
|
||||
|
||||
func hostNameRewrite(request io.Reader, rewriteHost string, remoteIP string) (_ []byte, err error) {
|
||||
buf := pool.GetBuf(1024)
|
||||
defer pool.PutBuf(buf)
|
||||
|
||||
var n int
|
||||
n, err = request.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
rp := &HttpReverseProxy{
|
||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||
vhostRouter: NewVhostRouters(),
|
||||
}
|
||||
retBuffer, err := parseRequest(buf[:n], rewriteHost, remoteIP)
|
||||
return retBuffer, err
|
||||
}
|
||||
|
||||
func parseRequest(org []byte, rewriteHost string, remoteIP string) (ret []byte, err error) {
|
||||
tp := bytes.NewBuffer(org)
|
||||
// First line: GET /index.html HTTP/1.0
|
||||
var b []byte
|
||||
if b, err = tp.ReadBytes('\n'); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req := new(http.Request)
|
||||
// we invoked ReadRequest in GetHttpHostname before, so we ignore error
|
||||
req.Method, req.RequestURI, req.Proto, _ = parseRequestLine(string(b))
|
||||
rawurl := req.RequestURI
|
||||
// CONNECT www.google.com:443 HTTP/1.1
|
||||
justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
|
||||
if justAuthority {
|
||||
rawurl = "http://" + rawurl
|
||||
}
|
||||
req.URL, _ = url.ParseRequestURI(rawurl)
|
||||
if justAuthority {
|
||||
// Strip the bogus "http://" back off.
|
||||
req.URL.Scheme = ""
|
||||
}
|
||||
|
||||
// RFC2616: first case
|
||||
// GET /index.html HTTP/1.1
|
||||
// Host: www.google.com
|
||||
if req.URL.Host == "" {
|
||||
var changedBuf []byte
|
||||
if rewriteHost != "" {
|
||||
changedBuf, err = changeHostName(tp, rewriteHost)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
buf.Write(b)
|
||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
||||
if len(changedBuf) == 0 {
|
||||
tp.WriteTo(buf)
|
||||
} else {
|
||||
buf.Write(changedBuf)
|
||||
}
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// RFC2616: second case
|
||||
// GET http://www.google.com/index.html HTTP/1.1
|
||||
// Host: doesntmatter
|
||||
// In this case, any Host line is ignored.
|
||||
if rewriteHost != "" {
|
||||
hostPort := strings.Split(req.URL.Host, ":")
|
||||
if len(hostPort) == 1 {
|
||||
req.URL.Host = rewriteHost
|
||||
} else if len(hostPort) == 2 {
|
||||
req.URL.Host = fmt.Sprintf("%s:%s", rewriteHost, hostPort[1])
|
||||
}
|
||||
}
|
||||
firstLine := req.Method + " " + req.URL.String() + " " + req.Proto
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteString(firstLine)
|
||||
buf.WriteString(fmt.Sprintf("X-Forwarded-For: %s\r\n", remoteIP))
|
||||
buf.WriteString(fmt.Sprintf("X-Real-IP: %s\r\n", remoteIP))
|
||||
tp.WriteTo(buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
// parseRequestLine parses "GET /foo HTTP/1.1" into its three parts.
|
||||
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
|
||||
s1 := strings.Index(line, " ")
|
||||
s2 := strings.Index(line[s1+1:], " ")
|
||||
if s1 < 0 || s2 < 0 {
|
||||
return
|
||||
}
|
||||
s2 += s1 + 1
|
||||
return line[:s1], line[s1+1 : s2], line[s2+1:], true
|
||||
}
|
||||
|
||||
func changeHostName(buff *bytes.Buffer, rewriteHost string) (_ []byte, err error) {
|
||||
retBuf := new(bytes.Buffer)
|
||||
|
||||
peek := buff.Bytes()
|
||||
for len(peek) > 0 {
|
||||
i := bytes.IndexByte(peek, '\n')
|
||||
if i < 3 {
|
||||
// Not present (-1) or found within the next few bytes,
|
||||
// implying we're at the end ("\r\n\r\n" or "\n\n")
|
||||
return nil, err
|
||||
}
|
||||
kv := peek[:i]
|
||||
j := bytes.IndexByte(kv, ':')
|
||||
if j < 0 {
|
||||
return nil, fmt.Errorf("malformed MIME header line: " + string(kv))
|
||||
}
|
||||
if strings.Contains(strings.ToLower(string(kv[:j])), "host") {
|
||||
var hostHeader string
|
||||
portPos := bytes.IndexByte(kv[j+1:], ':')
|
||||
if portPos == -1 {
|
||||
hostHeader = fmt.Sprintf("Host: %s\r\n", rewriteHost)
|
||||
} else {
|
||||
hostHeader = fmt.Sprintf("Host: %s:%s\r\n", rewriteHost, kv[j+portPos+2:])
|
||||
proxy := &ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
url := req.Context().Value("url").(string)
|
||||
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
||||
host := rp.GetRealHost(oldHost, url)
|
||||
if host != "" {
|
||||
req.Host = host
|
||||
}
|
||||
retBuf.WriteString(hostHeader)
|
||||
peek = peek[i+1:]
|
||||
break
|
||||
} else {
|
||||
retBuf.Write(peek[:i])
|
||||
retBuf.WriteByte('\n')
|
||||
req.URL.Host = req.Host
|
||||
|
||||
headers := rp.GetHeaders(oldHost, url)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||
DisableKeepAlives: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value("url").(string)
|
||||
host := getHostFromAddr(ctx.Value("host").(string))
|
||||
remote := ctx.Value("remote").(string)
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
},
|
||||
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value("url").(string)
|
||||
host := getHostFromAddr(ctx.Value("host").(string))
|
||||
remote := ctx.Value("remote").(string)
|
||||
return rp.CreateConnection(host, url, remote)
|
||||
},
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
}
|
||||
rp.proxy = proxy
|
||||
return rp
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
||||
if ok {
|
||||
return ErrRouterConfigConflict
|
||||
} else {
|
||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
rp.vhostRouter.Del(domain, location)
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
headers = vr.payload.(*VhostRouteConfig).Headers
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string, remoteAddr string) (net.Conn, error) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||
if fn != nil {
|
||||
return fn(remoteAddr)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%v: %s %s", ErrNoDomain, domain, location)
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
checkUser := vr.payload.(*VhostRouteConfig).Username
|
||||
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
||||
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||
rp.cfgMu.RLock()
|
||||
defer rp.cfgMu.RUnlock()
|
||||
|
||||
// first we check the full hostname
|
||||
// if not exist, then check the wildcard_domain such as *.example.com
|
||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
domainSplit := strings.Split(domain, ".")
|
||||
if len(domainSplit) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for {
|
||||
if len(domainSplit) < 3 {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
peek = peek[i+1:]
|
||||
domainSplit[0] = "*"
|
||||
domain = strings.Join(domainSplit, ".")
|
||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||
if ok {
|
||||
return vr, true
|
||||
}
|
||||
domainSplit = domainSplit[1:]
|
||||
}
|
||||
retBuf.Write(peek)
|
||||
return retBuf.Bytes(), err
|
||||
return
|
||||
}
|
||||
|
||||
func HttpAuthFunc(c frpNet.Conn, userName, passWord, authorization string) (bAccess bool, err error) {
|
||||
s := strings.SplitN(authorization, " ", 2)
|
||||
if len(s) != 2 {
|
||||
res := noAuthResponse()
|
||||
res.Write(c)
|
||||
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
domain := getHostFromAddr(req.Host)
|
||||
location := req.URL.Path
|
||||
user, passwd, _ := req.BasicAuth()
|
||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
b, err := base64.StdEncoding.DecodeString(s[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pair := strings.SplitN(string(b), ":", 2)
|
||||
if len(pair) != 2 {
|
||||
return
|
||||
}
|
||||
if pair[0] != userName || pair[1] != passWord {
|
||||
return
|
||||
}
|
||||
return true, nil
|
||||
rp.proxy.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func noAuthResponse() *http.Response {
|
||||
header := make(map[string][]string)
|
||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||
res := &http.Response{
|
||||
Status: "401 Not authorized",
|
||||
StatusCode: 401,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: header,
|
||||
}
|
||||
return res
|
||||
type wrapPool struct{}
|
||||
|
||||
func newWrapPool() *wrapPool { return &wrapPool{} }
|
||||
|
||||
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
||||
|
||||
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
||||
|
||||
type wrapLogger struct{}
|
||||
|
||||
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||
|
||||
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
// Copyright 2017 fatedier, fatedier@gmail.com
|
||||
//
|
||||
// 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.
|
||||
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
frpLog "github.com/fatedier/frp/utils/log"
|
||||
|
||||
"github.com/fatedier/golib/pool"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRouterConfigConflict = errors.New("router config conflict")
|
||||
ErrNoDomain = errors.New("no such domain")
|
||||
)
|
||||
|
||||
func getHostFromAddr(addr string) (host string) {
|
||||
strs := strings.Split(addr, ":")
|
||||
if len(strs) > 1 {
|
||||
host = strs[0]
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type HttpReverseProxyOptions struct {
|
||||
ResponseHeaderTimeoutS int64
|
||||
}
|
||||
|
||||
type HttpReverseProxy struct {
|
||||
proxy *ReverseProxy
|
||||
|
||||
vhostRouter *VhostRouters
|
||||
|
||||
responseHeaderTimeout time.Duration
|
||||
cfgMu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHttpReverseProxy(option HttpReverseProxyOptions) *HttpReverseProxy {
|
||||
if option.ResponseHeaderTimeoutS <= 0 {
|
||||
option.ResponseHeaderTimeoutS = 60
|
||||
}
|
||||
rp := &HttpReverseProxy{
|
||||
responseHeaderTimeout: time.Duration(option.ResponseHeaderTimeoutS) * time.Second,
|
||||
vhostRouter: NewVhostRouters(),
|
||||
}
|
||||
proxy := &ReverseProxy{
|
||||
Director: func(req *http.Request) {
|
||||
req.URL.Scheme = "http"
|
||||
url := req.Context().Value("url").(string)
|
||||
oldHost := getHostFromAddr(req.Context().Value("host").(string))
|
||||
host := rp.GetRealHost(oldHost, url)
|
||||
if host != "" {
|
||||
req.Host = host
|
||||
}
|
||||
req.URL.Host = req.Host
|
||||
|
||||
headers := rp.GetHeaders(oldHost, url)
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
ResponseHeaderTimeout: rp.responseHeaderTimeout,
|
||||
DisableKeepAlives: true,
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value("url").(string)
|
||||
host := getHostFromAddr(ctx.Value("host").(string))
|
||||
return rp.CreateConnection(host, url)
|
||||
},
|
||||
},
|
||||
WebSocketDialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
url := ctx.Value("url").(string)
|
||||
host := getHostFromAddr(ctx.Value("host").(string))
|
||||
return rp.CreateConnection(host, url)
|
||||
},
|
||||
BufferPool: newWrapPool(),
|
||||
ErrorLog: log.New(newWrapLogger(), "", 0),
|
||||
}
|
||||
rp.proxy = proxy
|
||||
return rp
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) Register(routeCfg VhostRouteConfig) error {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
_, ok := rp.vhostRouter.Exist(routeCfg.Domain, routeCfg.Location)
|
||||
if ok {
|
||||
return ErrRouterConfigConflict
|
||||
} else {
|
||||
rp.vhostRouter.Add(routeCfg.Domain, routeCfg.Location, &routeCfg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) UnRegister(domain string, location string) {
|
||||
rp.cfgMu.Lock()
|
||||
defer rp.cfgMu.Unlock()
|
||||
rp.vhostRouter.Del(domain, location)
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) GetRealHost(domain string, location string) (host string) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
host = vr.payload.(*VhostRouteConfig).RewriteHost
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) GetHeaders(domain string, location string) (headers map[string]string) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
headers = vr.payload.(*VhostRouteConfig).Headers
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) CreateConnection(domain string, location string) (net.Conn, error) {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
fn := vr.payload.(*VhostRouteConfig).CreateConnFn
|
||||
if fn != nil {
|
||||
return fn()
|
||||
}
|
||||
}
|
||||
return nil, ErrNoDomain
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) CheckAuth(domain, location, user, passwd string) bool {
|
||||
vr, ok := rp.getVhost(domain, location)
|
||||
if ok {
|
||||
checkUser := vr.payload.(*VhostRouteConfig).Username
|
||||
checkPasswd := vr.payload.(*VhostRouteConfig).Password
|
||||
if (checkUser != "" || checkPasswd != "") && (checkUser != user || checkPasswd != passwd) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) getVhost(domain string, location string) (vr *VhostRouter, ok bool) {
|
||||
rp.cfgMu.RLock()
|
||||
defer rp.cfgMu.RUnlock()
|
||||
|
||||
// first we check the full hostname
|
||||
// if not exist, then check the wildcard_domain such as *.example.com
|
||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
|
||||
domainSplit := strings.Split(domain, ".")
|
||||
if len(domainSplit) < 3 {
|
||||
return vr, false
|
||||
}
|
||||
domainSplit[0] = "*"
|
||||
domain = strings.Join(domainSplit, ".")
|
||||
vr, ok = rp.vhostRouter.Get(domain, location)
|
||||
return
|
||||
}
|
||||
|
||||
func (rp *HttpReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
domain := getHostFromAddr(req.Host)
|
||||
location := req.URL.Path
|
||||
user, passwd, _ := req.BasicAuth()
|
||||
if !rp.CheckAuth(domain, location, user, passwd) {
|
||||
rw.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||
http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
rp.proxy.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
type wrapPool struct{}
|
||||
|
||||
func newWrapPool() *wrapPool { return &wrapPool{} }
|
||||
|
||||
func (p *wrapPool) Get() []byte { return pool.GetBuf(32 * 1024) }
|
||||
|
||||
func (p *wrapPool) Put(buf []byte) { pool.PutBuf(buf) }
|
||||
|
||||
type wrapLogger struct{}
|
||||
|
||||
func newWrapLogger() *wrapLogger { return &wrapLogger{} }
|
||||
|
||||
func (l *wrapLogger) Write(p []byte) (n int, err error) {
|
||||
frpLog.Warn("%s", string(bytes.TrimRight(p, "\n")))
|
||||
return len(p), nil
|
||||
}
|
||||
@@ -15,13 +15,18 @@
|
||||
package vhost
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
frpLog "github.com/fatedier/frp/utils/log"
|
||||
"github.com/fatedier/frp/utils/version"
|
||||
)
|
||||
|
||||
var (
|
||||
NotFoundPagePath = ""
|
||||
)
|
||||
|
||||
const (
|
||||
NotFound = `<!DOCTYPE html>
|
||||
<html>
|
||||
@@ -46,10 +51,28 @@ Please try again later.</p>
|
||||
`
|
||||
)
|
||||
|
||||
func getNotFoundPageContent() []byte {
|
||||
var (
|
||||
buf []byte
|
||||
err error
|
||||
)
|
||||
if NotFoundPagePath != "" {
|
||||
buf, err = ioutil.ReadFile(NotFoundPagePath)
|
||||
if err != nil {
|
||||
frpLog.Warn("read custom 404 page error: %v", err)
|
||||
buf = []byte(NotFound)
|
||||
}
|
||||
} else {
|
||||
buf = []byte(NotFound)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func notFoundResponse() *http.Response {
|
||||
header := make(http.Header)
|
||||
header.Set("server", "frp/"+version.Full())
|
||||
header.Set("Content-Type", "text/html")
|
||||
|
||||
res := &http.Response{
|
||||
Status: "Not Found",
|
||||
StatusCode: 404,
|
||||
@@ -57,7 +80,21 @@ func notFoundResponse() *http.Response {
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 0,
|
||||
Header: header,
|
||||
Body: ioutil.NopCloser(strings.NewReader(NotFound)),
|
||||
Body: ioutil.NopCloser(bytes.NewReader(getNotFoundPageContent())),
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func noAuthResponse() *http.Response {
|
||||
header := make(map[string][]string)
|
||||
header["WWW-Authenticate"] = []string{`Basic realm="Restricted"`}
|
||||
res := &http.Response{
|
||||
Status: "401 Not authorized",
|
||||
StatusCode: 401,
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: header,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -158,6 +158,7 @@ func (p *ReverseProxy) serveWebSocket(rw http.ResponseWriter, req *http.Request)
|
||||
|
||||
req = req.WithContext(context.WithValue(req.Context(), "url", req.URL.Path))
|
||||
req = req.WithContext(context.WithValue(req.Context(), "host", req.Host))
|
||||
req = req.WithContext(context.WithValue(req.Context(), "remote", req.RemoteAddr))
|
||||
|
||||
targetConn, err := p.WebSocketDialContext(req.Context(), "tcp", "")
|
||||
if err != nil {
|
||||
@@ -215,6 +216,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Modify for frp
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "url", req.URL.Path))
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "host", req.Host))
|
||||
outreq = outreq.WithContext(context.WithValue(outreq.Context(), "remote", req.RemoteAddr))
|
||||
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
@@ -252,7 +254,8 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if err != nil {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
rw.Write([]byte(NotFound))
|
||||
|
||||
rw.Write(getNotFoundPageContent())
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func NewVhostMuxer(listener frpNet.Listener, vhostFunc muxFunc, authFunc httpAut
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
type CreateConnFunc func() (frpNet.Conn, error)
|
||||
type CreateConnFunc func(remoteAddr string) (frpNet.Conn, error)
|
||||
|
||||
type VhostRouteConfig struct {
|
||||
Domain string
|
||||
@@ -102,17 +102,24 @@ func (v *VhostMuxer) getListener(name, path string) (l *Listener, exist bool) {
|
||||
|
||||
domainSplit := strings.Split(name, ".")
|
||||
if len(domainSplit) < 3 {
|
||||
return l, false
|
||||
}
|
||||
domainSplit[0] = "*"
|
||||
name = strings.Join(domainSplit, ".")
|
||||
|
||||
vr, found = v.registryRouter.Get(name, path)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
return vr.payload.(*Listener), true
|
||||
for {
|
||||
if len(domainSplit) < 3 {
|
||||
return
|
||||
}
|
||||
|
||||
domainSplit[0] = "*"
|
||||
name = strings.Join(domainSplit, ".")
|
||||
|
||||
vr, found = v.registryRouter.Get(name, path)
|
||||
if found {
|
||||
return vr.payload.(*Listener), true
|
||||
}
|
||||
domainSplit = domainSplit[1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (v *VhostMuxer) run() {
|
||||
|
||||
4
vendor/github.com/fatedier/kcp-go/.travis.yml
generated
vendored
4
vendor/github.com/fatedier/kcp-go/.travis.yml
generated
vendored
@@ -1,6 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.9
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
98
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
98
vendor/github.com/fatedier/kcp-go/README.md
generated
vendored
@@ -20,24 +20,20 @@
|
||||
|
||||
**kcp-go** is a **Production-Grade Reliable-UDP** library for [golang](https://golang.org/).
|
||||
|
||||
It provides **fast, ordered and error-checked** delivery of streams over **UDP** packets, has been well tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) are running with **kcp-go** at present, including applications like **online games, live broadcasting, file synchronization and network acceleration**.
|
||||
This library intents to provide a **smooth, resilient, ordered, error-checked and anonymous** delivery of streams over **UDP** packets, it has been battle-tested with opensource project [kcptun](https://github.com/xtaci/kcptun). Millions of devices(from low-end MIPS routers to high-end servers) have deployed **kcp-go** powered program in a variety of forms like **online games, live broadcasting, file synchronization and network acceleration**.
|
||||
|
||||
[Lastest Release](https://github.com/xtaci/kcp-go/releases)
|
||||
|
||||
## Features
|
||||
|
||||
1. Optimized for **Realtime Online Games, Audio/Video Streaming and Latency-Sensitive Distributed Consensus**.
|
||||
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with language specific optimizations.
|
||||
1. Designed for **Latency-sensitive** scenarios.
|
||||
1. **Cache friendly** and **Memory optimized** design, offers extremely **High Performance** core.
|
||||
1. Handles **>5K concurrent connections** on a single commodity server.
|
||||
1. Compatible with [net.Conn](https://golang.org/pkg/net/#Conn) and [net.Listener](https://golang.org/pkg/net/#Listener), a drop-in replacement for [net.TCPConn](https://golang.org/pkg/net/#TCPConn).
|
||||
1. [FEC(Forward Error Correction)](https://en.wikipedia.org/wiki/Forward_error_correction) Support with [Reed-Solomon Codes](https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction)
|
||||
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode.
|
||||
1. **Fixed number of goroutines** created for the entire server application, minimized goroutine context switch.
|
||||
|
||||
## Conventions
|
||||
|
||||
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
|
||||
1. Packet level encryption support with [AES](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard), [TEA](https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm), [3DES](https://en.wikipedia.org/wiki/Triple_DES), [Blowfish](https://en.wikipedia.org/wiki/Blowfish_(cipher)), [Cast5](https://en.wikipedia.org/wiki/CAST-128), [Salsa20]( https://en.wikipedia.org/wiki/Salsa20), etc. in [CFB](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29) mode, which generates completely anonymous packet.
|
||||
1. Only **A fixed number of goroutines** will be created for the entire server application, costs in **context switch** between goroutines have been taken into consideration.
|
||||
1. Compatible with [skywind3000's](https://github.com/skywind3000) C version with various improvements.
|
||||
|
||||
## Documentation
|
||||
|
||||
@@ -80,47 +76,59 @@ Server: [full demo](https://github.com/xtaci/kcptun/blob/master/server/main.go
|
||||
lis, err := kcp.ListenWithOptions(":10000", nil, 10, 3)
|
||||
```
|
||||
|
||||
## Performance
|
||||
## Benchmark
|
||||
```
|
||||
Model Name: MacBook Pro
|
||||
Model Identifier: MacBookPro12,1
|
||||
Model Identifier: MacBookPro14,1
|
||||
Processor Name: Intel Core i5
|
||||
Processor Speed: 2.7 GHz
|
||||
Processor Speed: 3.1 GHz
|
||||
Number of Processors: 1
|
||||
Total Number of Cores: 2
|
||||
L2 Cache (per Core): 256 KB
|
||||
L3 Cache: 3 MB
|
||||
L3 Cache: 4 MB
|
||||
Memory: 8 GB
|
||||
```
|
||||
```
|
||||
$ go test -v -run=^$ -bench .
|
||||
beginning tests, encryption:salsa20, fec:10/3
|
||||
BenchmarkAES128-4 200000 8256 ns/op 363.33 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkAES192-4 200000 9153 ns/op 327.74 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkAES256-4 200000 10079 ns/op 297.64 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkTEA-4 100000 18643 ns/op 160.91 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkXOR-4 5000000 316 ns/op 9486.46 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkBlowfish-4 50000 35643 ns/op 84.17 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkNone-4 30000000 56.2 ns/op 53371.83 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkCast5-4 30000 44744 ns/op 67.05 MB/s 0 B/op 0 allocs/op
|
||||
Benchmark3DES-4 2000 639839 ns/op 4.69 MB/s 2 B/op 0 allocs/op
|
||||
BenchmarkTwofish-4 30000 43368 ns/op 69.17 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkXTEA-4 30000 57673 ns/op 52.02 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkSalsa20-4 300000 3917 ns/op 765.80 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkFlush-4 10000000 226 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEchoSpeed4K-4 5000 300030 ns/op 13.65 MB/s 5672 B/op 177 allocs/op
|
||||
BenchmarkEchoSpeed64K-4 500 3202335 ns/op 20.47 MB/s 73295 B/op 2198 allocs/op
|
||||
BenchmarkEchoSpeed512K-4 50 24926924 ns/op 21.03 MB/s 659339 B/op 17602 allocs/op
|
||||
BenchmarkEchoSpeed1M-4 20 64857821 ns/op 16.17 MB/s 1772437 B/op 42869 allocs/op
|
||||
BenchmarkSinkSpeed4K-4 30000 50230 ns/op 81.54 MB/s 2058 B/op 48 allocs/op
|
||||
BenchmarkSinkSpeed64K-4 2000 648718 ns/op 101.02 MB/s 31165 B/op 687 allocs/op
|
||||
BenchmarkSinkSpeed256K-4 300 4635905 ns/op 113.09 MB/s 286229 B/op 5516 allocs/op
|
||||
BenchmarkSinkSpeed1M-4 200 9566933 ns/op 109.60 MB/s 463771 B/op 10701 allocs/op
|
||||
goos: darwin
|
||||
goarch: amd64
|
||||
pkg: github.com/xtaci/kcp-go
|
||||
BenchmarkSM4-4 50000 32180 ns/op 93.23 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkAES128-4 500000 3285 ns/op 913.21 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkAES192-4 300000 3623 ns/op 827.85 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkAES256-4 300000 3874 ns/op 774.20 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkTEA-4 100000 15384 ns/op 195.00 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkXOR-4 20000000 89.9 ns/op 33372.00 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkBlowfish-4 50000 26927 ns/op 111.41 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkNone-4 30000000 45.7 ns/op 65597.94 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkCast5-4 50000 34258 ns/op 87.57 MB/s 0 B/op 0 allocs/op
|
||||
Benchmark3DES-4 10000 117149 ns/op 25.61 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkTwofish-4 50000 33538 ns/op 89.45 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkXTEA-4 30000 45666 ns/op 65.69 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkSalsa20-4 500000 3308 ns/op 906.76 MB/s 0 B/op 0 allocs/op
|
||||
BenchmarkCRC32-4 20000000 65.2 ns/op 15712.43 MB/s
|
||||
BenchmarkCsprngSystem-4 1000000 1150 ns/op 13.91 MB/s
|
||||
BenchmarkCsprngMD5-4 10000000 145 ns/op 110.26 MB/s
|
||||
BenchmarkCsprngSHA1-4 10000000 158 ns/op 126.54 MB/s
|
||||
BenchmarkCsprngNonceMD5-4 10000000 153 ns/op 104.22 MB/s
|
||||
BenchmarkCsprngNonceAES128-4 100000000 19.1 ns/op 837.81 MB/s
|
||||
BenchmarkFECDecode-4 1000000 1119 ns/op 1339.61 MB/s 1606 B/op 2 allocs/op
|
||||
BenchmarkFECEncode-4 2000000 832 ns/op 1801.83 MB/s 17 B/op 0 allocs/op
|
||||
BenchmarkFlush-4 5000000 272 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEchoSpeed4K-4 5000 259617 ns/op 15.78 MB/s 5451 B/op 149 allocs/op
|
||||
BenchmarkEchoSpeed64K-4 1000 1706084 ns/op 38.41 MB/s 56002 B/op 1604 allocs/op
|
||||
BenchmarkEchoSpeed512K-4 100 14345505 ns/op 36.55 MB/s 482597 B/op 13045 allocs/op
|
||||
BenchmarkEchoSpeed1M-4 30 34859104 ns/op 30.08 MB/s 1143773 B/op 27186 allocs/op
|
||||
BenchmarkSinkSpeed4K-4 50000 31369 ns/op 130.57 MB/s 1566 B/op 30 allocs/op
|
||||
BenchmarkSinkSpeed64K-4 5000 329065 ns/op 199.16 MB/s 21529 B/op 453 allocs/op
|
||||
BenchmarkSinkSpeed256K-4 500 2373354 ns/op 220.91 MB/s 166332 B/op 3554 allocs/op
|
||||
BenchmarkSinkSpeed1M-4 300 5117927 ns/op 204.88 MB/s 310378 B/op 6988 allocs/op
|
||||
PASS
|
||||
ok _/Users/xtaci/.godeps/src/github.com/xtaci/kcp-go 39.689s
|
||||
ok github.com/xtaci/kcp-go 50.349s
|
||||
```
|
||||
|
||||
## Design Considerations
|
||||
## Key Design Considerations
|
||||
|
||||
1. slice vs. container/list
|
||||
|
||||
@@ -139,7 +147,9 @@ List structure introduces **heavy cache misses** compared to slice which owns be
|
||||
|
||||
2. Timing accuracy vs. syscall clock_gettime
|
||||
|
||||
Timing is **critical** to **RTT estimator**, inaccurate timing introduces false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz), the benchmark for time.Now():
|
||||
Timing is **critical** to **RTT estimator**, inaccurate timing leads to false retransmissions in KCP, but calling `time.Now()` costs 42 cycles(10.5ns on 4GHz CPU, 15.6ns on my MacBook Pro 2.7GHz).
|
||||
|
||||
The benchmark for time.Now() lies here:
|
||||
|
||||
https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
|
||||
|
||||
@@ -147,14 +157,17 @@ https://github.com/xtaci/notes/blob/master/golang/benchmark2/syscall_test.go
|
||||
BenchmarkNow-4 100000000 15.6 ns/op
|
||||
```
|
||||
|
||||
In kcp-go, after each `kcp.output()` function call, current time will be updated upon return, and each `kcp.flush()` will get current time once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(no packet needs to be sent by `kcp.output()`), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
|
||||
In kcp-go, after each `kcp.output()` function call, current clock time will be updated upon return, and for a single `kcp.flush()` operation, current time will be queried from system once. For most of the time, 5000 connections costs 5000 * 15.6ns = 78us(a fixed cost while no packet needs to be sent), as for 10MB/s data transfering with 1400 MTU, `kcp.output()` will be called around 7500 times and costs 117us for `time.Now()` in **every second**.
|
||||
|
||||
## Connection Termination
|
||||
|
||||
## Tuning
|
||||
Control messages like **SYN/FIN/RST** in TCP **are not defined** in KCP, you need some **keepalive/heartbeat mechanism** in the application-level. A real world example is to use some **multiplexing** protocol over session, such as [smux](https://github.com/xtaci/smux)(with embedded keepalive mechanism), see [kcptun](https://github.com/xtaci/kcptun) for example.
|
||||
|
||||
Q: I'm handling >5K connections on my server. the CPU utilization is high.
|
||||
## FAQ
|
||||
|
||||
A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load.
|
||||
Q: I'm handling >5K connections on my server, the CPU utilization is so high.
|
||||
|
||||
A: A standalone `agent` or `gate` server for running kcp-go is suggested, not only for CPU utilization, but also important to the **precision** of RTT measurements(timing) which indirectly affects retransmission. By increasing update `interval` with `SetNoDelay` like `conn.SetNoDelay(1, 40, 1, 1)` will dramatically reduce system load, but lower the performance.
|
||||
|
||||
## Who is using this?
|
||||
|
||||
@@ -163,10 +176,9 @@ A: A standalone `agent` or `gate` server for kcp-go is suggested, not only for C
|
||||
3. https://github.com/smallnest/rpcx -- A RPC service framework based on net/rpc like alibaba Dubbo and weibo Motan.
|
||||
4. https://github.com/gonet2/agent -- A gateway for games with stream multiplexing.
|
||||
5. https://github.com/syncthing/syncthing -- Open Source Continuous File Synchronization.
|
||||
6. https://play.google.com/store/apps/details?id=com.k17game.k3 -- Battle Zone - Earth 2048, a world-wide strategy game.
|
||||
|
||||
## Links
|
||||
|
||||
1. https://github.com/xtaci/libkcp -- FEC enhanced KCP session library for iOS/Android in C++
|
||||
2. https://github.com/skywind3000/kcp -- A Fast and Reliable ARQ Protocol
|
||||
3. https://github.com/templexxx/reedsolomon -- Reed-Solomon Erasure Coding in Go
|
||||
3. https://github.com/klauspost/reedsolomon -- Reed-Solomon Erasure Coding in Go
|
||||
|
||||
601
vendor/github.com/fatedier/kcp-go/crypt.go
generated
vendored
601
vendor/github.com/fatedier/kcp-go/crypt.go
generated
vendored
@@ -57,8 +57,8 @@ func (c *salsa20BlockCrypt) Decrypt(dst, src []byte) {
|
||||
}
|
||||
|
||||
type sm4BlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [sm4.BlockSize]byte
|
||||
decbuf [2 * sm4.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -70,17 +70,15 @@ func NewSM4BlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, sm4.BlockSize)
|
||||
c.decbuf = make([]byte, 2*sm4.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *sm4BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *sm4BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type twofishBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [twofish.BlockSize]byte
|
||||
decbuf [2 * twofish.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -92,17 +90,15 @@ func NewTwofishBlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, twofish.BlockSize)
|
||||
c.decbuf = make([]byte, 2*twofish.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *twofishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *twofishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type tripleDESBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [des.BlockSize]byte
|
||||
decbuf [2 * des.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -114,17 +110,15 @@ func NewTripleDESBlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, des.BlockSize)
|
||||
c.decbuf = make([]byte, 2*des.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *tripleDESBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *tripleDESBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type cast5BlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [cast5.BlockSize]byte
|
||||
decbuf [2 * cast5.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -136,17 +130,15 @@ func NewCast5BlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, cast5.BlockSize)
|
||||
c.decbuf = make([]byte, 2*cast5.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *cast5BlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *cast5BlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type blowfishBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [blowfish.BlockSize]byte
|
||||
decbuf [2 * blowfish.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -158,17 +150,15 @@ func NewBlowfishBlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, blowfish.BlockSize)
|
||||
c.decbuf = make([]byte, 2*blowfish.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *blowfishBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *blowfishBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type aesBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [aes.BlockSize]byte
|
||||
decbuf [2 * aes.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -180,17 +170,15 @@ func NewAESBlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, aes.BlockSize)
|
||||
c.decbuf = make([]byte, 2*aes.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *aesBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *aesBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type teaBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [tea.BlockSize]byte
|
||||
decbuf [2 * tea.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -202,17 +190,15 @@ func NewTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, tea.BlockSize)
|
||||
c.decbuf = make([]byte, 2*tea.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *teaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *teaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type xteaBlockCrypt struct {
|
||||
encbuf []byte
|
||||
decbuf []byte
|
||||
encbuf [xtea.BlockSize]byte
|
||||
decbuf [2 * xtea.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
@@ -224,13 +210,11 @@ func NewXTEABlockCrypt(key []byte) (BlockCrypt, error) {
|
||||
return nil, err
|
||||
}
|
||||
c.block = block
|
||||
c.encbuf = make([]byte, xtea.BlockSize)
|
||||
c.decbuf = make([]byte, 2*xtea.BlockSize)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf) }
|
||||
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf) }
|
||||
func (c *xteaBlockCrypt) Encrypt(dst, src []byte) { encrypt(c.block, dst, src, c.encbuf[:]) }
|
||||
func (c *xteaBlockCrypt) Decrypt(dst, src []byte) { decrypt(c.block, dst, src, c.decbuf[:]) }
|
||||
|
||||
type simpleXORBlockCrypt struct {
|
||||
xortbl []byte
|
||||
@@ -258,31 +242,544 @@ func (c *noneBlockCrypt) Decrypt(dst, src []byte) { copy(dst, src) }
|
||||
|
||||
// packet encryption with local CFB mode
|
||||
func encrypt(block cipher.Block, dst, src, buf []byte) {
|
||||
switch block.BlockSize() {
|
||||
case 8:
|
||||
encrypt8(block, dst, src, buf)
|
||||
case 16:
|
||||
encrypt16(block, dst, src, buf)
|
||||
default:
|
||||
encryptVariant(block, dst, src, buf)
|
||||
}
|
||||
}
|
||||
|
||||
// optimized encryption for the ciphers which works in 8-bytes
|
||||
func encrypt8(block cipher.Block, dst, src, buf []byte) {
|
||||
tbl := buf[:8]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / 8
|
||||
base := 0
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
s := src[base:][0:64]
|
||||
d := dst[base:][0:64]
|
||||
// 1
|
||||
xor.BytesSrc1(d[0:8], s[0:8], tbl)
|
||||
block.Encrypt(tbl, d[0:8])
|
||||
// 2
|
||||
xor.BytesSrc1(d[8:16], s[8:16], tbl)
|
||||
block.Encrypt(tbl, d[8:16])
|
||||
// 3
|
||||
xor.BytesSrc1(d[16:24], s[16:24], tbl)
|
||||
block.Encrypt(tbl, d[16:24])
|
||||
// 4
|
||||
xor.BytesSrc1(d[24:32], s[24:32], tbl)
|
||||
block.Encrypt(tbl, d[24:32])
|
||||
// 5
|
||||
xor.BytesSrc1(d[32:40], s[32:40], tbl)
|
||||
block.Encrypt(tbl, d[32:40])
|
||||
// 6
|
||||
xor.BytesSrc1(d[40:48], s[40:48], tbl)
|
||||
block.Encrypt(tbl, d[40:48])
|
||||
// 7
|
||||
xor.BytesSrc1(d[48:56], s[48:56], tbl)
|
||||
block.Encrypt(tbl, d[48:56])
|
||||
// 8
|
||||
xor.BytesSrc1(d[56:64], s[56:64], tbl)
|
||||
block.Encrypt(tbl, d[56:64])
|
||||
base += 64
|
||||
}
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 6:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 5:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 4:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 3:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 2:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 1:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 8
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
}
|
||||
|
||||
// optimized encryption for the ciphers which works in 16-bytes
|
||||
func encrypt16(block cipher.Block, dst, src, buf []byte) {
|
||||
tbl := buf[:16]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / 16
|
||||
base := 0
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
s := src[base:][0:128]
|
||||
d := dst[base:][0:128]
|
||||
// 1
|
||||
xor.BytesSrc1(d[0:16], s[0:16], tbl)
|
||||
block.Encrypt(tbl, d[0:16])
|
||||
// 2
|
||||
xor.BytesSrc1(d[16:32], s[16:32], tbl)
|
||||
block.Encrypt(tbl, d[16:32])
|
||||
// 3
|
||||
xor.BytesSrc1(d[32:48], s[32:48], tbl)
|
||||
block.Encrypt(tbl, d[32:48])
|
||||
// 4
|
||||
xor.BytesSrc1(d[48:64], s[48:64], tbl)
|
||||
block.Encrypt(tbl, d[48:64])
|
||||
// 5
|
||||
xor.BytesSrc1(d[64:80], s[64:80], tbl)
|
||||
block.Encrypt(tbl, d[64:80])
|
||||
// 6
|
||||
xor.BytesSrc1(d[80:96], s[80:96], tbl)
|
||||
block.Encrypt(tbl, d[80:96])
|
||||
// 7
|
||||
xor.BytesSrc1(d[96:112], s[96:112], tbl)
|
||||
block.Encrypt(tbl, d[96:112])
|
||||
// 8
|
||||
xor.BytesSrc1(d[112:128], s[112:128], tbl)
|
||||
block.Encrypt(tbl, d[112:128])
|
||||
base += 128
|
||||
}
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 6:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 5:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 4:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 3:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 2:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 1:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += 16
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
}
|
||||
|
||||
func encryptVariant(block cipher.Block, dst, src, buf []byte) {
|
||||
blocksize := block.BlockSize()
|
||||
tbl := buf[:blocksize]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / blocksize
|
||||
base := 0
|
||||
for i := 0; i < n; i++ {
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
// 1
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 2
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 3
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 4
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 5
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 6
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 7
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
|
||||
// 8
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
}
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 6:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 5:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 4:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 3:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 2:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 1:
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
block.Encrypt(tbl, dst[base:])
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
}
|
||||
|
||||
// decryption
|
||||
func decrypt(block cipher.Block, dst, src, buf []byte) {
|
||||
switch block.BlockSize() {
|
||||
case 8:
|
||||
decrypt8(block, dst, src, buf)
|
||||
case 16:
|
||||
decrypt16(block, dst, src, buf)
|
||||
default:
|
||||
decryptVariant(block, dst, src, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func decrypt8(block cipher.Block, dst, src, buf []byte) {
|
||||
tbl := buf[0:8]
|
||||
next := buf[8:16]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / 8
|
||||
base := 0
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
s := src[base:][0:64]
|
||||
d := dst[base:][0:64]
|
||||
// 1
|
||||
block.Encrypt(next, s[0:8])
|
||||
xor.BytesSrc1(d[0:8], s[0:8], tbl)
|
||||
// 2
|
||||
block.Encrypt(tbl, s[8:16])
|
||||
xor.BytesSrc1(d[8:16], s[8:16], next)
|
||||
// 3
|
||||
block.Encrypt(next, s[16:24])
|
||||
xor.BytesSrc1(d[16:24], s[16:24], tbl)
|
||||
// 4
|
||||
block.Encrypt(tbl, s[24:32])
|
||||
xor.BytesSrc1(d[24:32], s[24:32], next)
|
||||
// 5
|
||||
block.Encrypt(next, s[32:40])
|
||||
xor.BytesSrc1(d[32:40], s[32:40], tbl)
|
||||
// 6
|
||||
block.Encrypt(tbl, s[40:48])
|
||||
xor.BytesSrc1(d[40:48], s[40:48], next)
|
||||
// 7
|
||||
block.Encrypt(next, s[48:56])
|
||||
xor.BytesSrc1(d[48:56], s[48:56], tbl)
|
||||
// 8
|
||||
block.Encrypt(tbl, s[56:64])
|
||||
xor.BytesSrc1(d[56:64], s[56:64], next)
|
||||
base += 64
|
||||
}
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 6:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 5:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 4:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 3:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 2:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 1:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 8
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
}
|
||||
|
||||
func decrypt16(block cipher.Block, dst, src, buf []byte) {
|
||||
tbl := buf[0:16]
|
||||
next := buf[16:32]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / 16
|
||||
base := 0
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
s := src[base:][0:128]
|
||||
d := dst[base:][0:128]
|
||||
// 1
|
||||
block.Encrypt(next, s[0:16])
|
||||
xor.BytesSrc1(d[0:16], s[0:16], tbl)
|
||||
// 2
|
||||
block.Encrypt(tbl, s[16:32])
|
||||
xor.BytesSrc1(d[16:32], s[16:32], next)
|
||||
// 3
|
||||
block.Encrypt(next, s[32:48])
|
||||
xor.BytesSrc1(d[32:48], s[32:48], tbl)
|
||||
// 4
|
||||
block.Encrypt(tbl, s[48:64])
|
||||
xor.BytesSrc1(d[48:64], s[48:64], next)
|
||||
// 5
|
||||
block.Encrypt(next, s[64:80])
|
||||
xor.BytesSrc1(d[64:80], s[64:80], tbl)
|
||||
// 6
|
||||
block.Encrypt(tbl, s[80:96])
|
||||
xor.BytesSrc1(d[80:96], s[80:96], next)
|
||||
// 7
|
||||
block.Encrypt(next, s[96:112])
|
||||
xor.BytesSrc1(d[96:112], s[96:112], tbl)
|
||||
// 8
|
||||
block.Encrypt(tbl, s[112:128])
|
||||
xor.BytesSrc1(d[112:128], s[112:128], next)
|
||||
base += 128
|
||||
}
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 6:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 5:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 4:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 3:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 2:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 1:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += 16
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
}
|
||||
|
||||
func decryptVariant(block cipher.Block, dst, src, buf []byte) {
|
||||
blocksize := block.BlockSize()
|
||||
tbl := buf[:blocksize]
|
||||
next := buf[blocksize:]
|
||||
block.Encrypt(tbl, initialVector)
|
||||
n := len(src) / blocksize
|
||||
base := 0
|
||||
for i := 0; i < n; i++ {
|
||||
repeat := n / 8
|
||||
left := n % 8
|
||||
for i := 0; i < repeat; i++ {
|
||||
// 1
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
base += blocksize
|
||||
|
||||
// 2
|
||||
block.Encrypt(tbl, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||
base += blocksize
|
||||
|
||||
// 3
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
base += blocksize
|
||||
|
||||
// 4
|
||||
block.Encrypt(tbl, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||
base += blocksize
|
||||
|
||||
// 5
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
base += blocksize
|
||||
|
||||
// 6
|
||||
block.Encrypt(tbl, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||
base += blocksize
|
||||
|
||||
// 7
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
base += blocksize
|
||||
|
||||
// 8
|
||||
block.Encrypt(tbl, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], next)
|
||||
base += blocksize
|
||||
}
|
||||
|
||||
switch left {
|
||||
case 7:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 6:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 5:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 4:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 3:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 2:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 1:
|
||||
block.Encrypt(next, src[base:])
|
||||
xor.BytesSrc1(dst[base:], src[base:], tbl)
|
||||
tbl, next = next, tbl
|
||||
base += blocksize
|
||||
fallthrough
|
||||
case 0:
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
xor.BytesSrc0(dst[base:], src[base:], tbl)
|
||||
}
|
||||
|
||||
52
vendor/github.com/fatedier/kcp-go/entropy.go
generated
vendored
Normal file
52
vendor/github.com/fatedier/kcp-go/entropy.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Entropy defines a entropy source
|
||||
type Entropy interface {
|
||||
Init()
|
||||
Fill(nonce []byte)
|
||||
}
|
||||
|
||||
// nonceMD5 nonce generator for packet header
|
||||
type nonceMD5 struct {
|
||||
seed [md5.Size]byte
|
||||
}
|
||||
|
||||
func (n *nonceMD5) Init() { /*nothing required*/ }
|
||||
|
||||
func (n *nonceMD5) Fill(nonce []byte) {
|
||||
if n.seed[0] == 0 { // entropy update
|
||||
io.ReadFull(rand.Reader, n.seed[:])
|
||||
}
|
||||
n.seed = md5.Sum(n.seed[:])
|
||||
copy(nonce, n.seed[:])
|
||||
}
|
||||
|
||||
// nonceAES128 nonce generator for packet headers
|
||||
type nonceAES128 struct {
|
||||
seed [aes.BlockSize]byte
|
||||
block cipher.Block
|
||||
}
|
||||
|
||||
func (n *nonceAES128) Init() {
|
||||
var key [16]byte //aes-128
|
||||
io.ReadFull(rand.Reader, key[:])
|
||||
io.ReadFull(rand.Reader, n.seed[:])
|
||||
block, _ := aes.NewCipher(key[:])
|
||||
n.block = block
|
||||
}
|
||||
|
||||
func (n *nonceAES128) Fill(nonce []byte) {
|
||||
if n.seed[0] == 0 { // entropy update
|
||||
io.ReadFull(rand.Reader, n.seed[:])
|
||||
}
|
||||
n.block.Encrypt(n.seed[:], n.seed[:])
|
||||
copy(nonce, n.seed[:])
|
||||
}
|
||||
88
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
88
vendor/github.com/fatedier/kcp-go/fec.go
generated
vendored
@@ -4,7 +4,7 @@ import (
|
||||
"encoding/binary"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/templexxx/reedsolomon"
|
||||
"github.com/klauspost/reedsolomon"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,6 +34,9 @@ type (
|
||||
decodeCache [][]byte
|
||||
flagCache []bool
|
||||
|
||||
// zeros
|
||||
zeros []byte
|
||||
|
||||
// RS decoder
|
||||
codec reedsolomon.Encoder
|
||||
}
|
||||
@@ -47,19 +50,20 @@ func newFECDecoder(rxlimit, dataShards, parityShards int) *fecDecoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
fec := new(fecDecoder)
|
||||
fec.rxlimit = rxlimit
|
||||
fec.dataShards = dataShards
|
||||
fec.parityShards = parityShards
|
||||
fec.shardSize = dataShards + parityShards
|
||||
enc, err := reedsolomon.New(dataShards, parityShards)
|
||||
dec := new(fecDecoder)
|
||||
dec.rxlimit = rxlimit
|
||||
dec.dataShards = dataShards
|
||||
dec.parityShards = parityShards
|
||||
dec.shardSize = dataShards + parityShards
|
||||
codec, err := reedsolomon.New(dataShards, parityShards)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fec.codec = enc
|
||||
fec.decodeCache = make([][]byte, fec.shardSize)
|
||||
fec.flagCache = make([]bool, fec.shardSize)
|
||||
return fec
|
||||
dec.codec = codec
|
||||
dec.decodeCache = make([][]byte, dec.shardSize)
|
||||
dec.flagCache = make([]bool, dec.shardSize)
|
||||
dec.zeros = make([]byte, mtuLimit)
|
||||
return dec
|
||||
}
|
||||
|
||||
// decodeBytes a fec packet
|
||||
@@ -116,7 +120,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
if searchEnd-searchBegin+1 >= dec.dataShards {
|
||||
var numshard, numDataShard, first, maxlen int
|
||||
|
||||
// zero cache
|
||||
// zero caches
|
||||
shards := dec.decodeCache
|
||||
shardsflag := dec.flagCache
|
||||
for k := range dec.decodeCache {
|
||||
@@ -146,15 +150,15 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
}
|
||||
|
||||
if numDataShard == dec.dataShards {
|
||||
// case 1: no lost data shards
|
||||
// case 1: no loss on data shards
|
||||
dec.rx = dec.freeRange(first, numshard, dec.rx)
|
||||
} else if numshard >= dec.dataShards {
|
||||
// case 2: data shard lost, but recoverable from parity shard
|
||||
// case 2: loss on data shards, but it's recoverable from parity shards
|
||||
for k := range shards {
|
||||
if shards[k] != nil {
|
||||
dlen := len(shards[k])
|
||||
shards[k] = shards[k][:maxlen]
|
||||
xorBytes(shards[k][dlen:], shards[k][dlen:], shards[k][dlen:])
|
||||
copy(shards[k][dlen:], dec.zeros)
|
||||
}
|
||||
}
|
||||
if err := dec.codec.ReconstructData(shards); err == nil {
|
||||
@@ -170,7 +174,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
|
||||
// keep rxlimit
|
||||
if len(dec.rx) > dec.rxlimit {
|
||||
if dec.rx[0].flag == typeData { // record unrecoverable data
|
||||
if dec.rx[0].flag == typeData { // track the unrecoverable data
|
||||
atomic.AddUint64(&DefaultSnmp.FECShortShards, 1)
|
||||
}
|
||||
dec.rx = dec.freeRange(0, 1, dec.rx)
|
||||
@@ -180,7 +184,7 @@ func (dec *fecDecoder) decode(pkt fecPacket) (recovered [][]byte) {
|
||||
|
||||
// free a range of fecPacket, and zero for GC recycling
|
||||
func (dec *fecDecoder) freeRange(first, n int, q []fecPacket) []fecPacket {
|
||||
for i := first; i < first+n; i++ { // free
|
||||
for i := first; i < first+n; i++ { // recycle buffer
|
||||
xmitBuf.Put(q[i].data)
|
||||
}
|
||||
copy(q[first:], q[first+n:])
|
||||
@@ -200,7 +204,7 @@ type (
|
||||
next uint32 // next seqid
|
||||
|
||||
shardCount int // count the number of datashards collected
|
||||
maxSize int // record maximum data length in datashard
|
||||
maxSize int // track maximum data length in datashard
|
||||
|
||||
headerOffset int // FEC header offset
|
||||
payloadOffset int // FEC payload offset
|
||||
@@ -209,6 +213,9 @@ type (
|
||||
shardCache [][]byte
|
||||
encodeCache [][]byte
|
||||
|
||||
// zeros
|
||||
zeros []byte
|
||||
|
||||
// RS encoder
|
||||
codec reedsolomon.Encoder
|
||||
}
|
||||
@@ -218,31 +225,32 @@ func newFECEncoder(dataShards, parityShards, offset int) *fecEncoder {
|
||||
if dataShards <= 0 || parityShards <= 0 {
|
||||
return nil
|
||||
}
|
||||
fec := new(fecEncoder)
|
||||
fec.dataShards = dataShards
|
||||
fec.parityShards = parityShards
|
||||
fec.shardSize = dataShards + parityShards
|
||||
fec.paws = (0xffffffff/uint32(fec.shardSize) - 1) * uint32(fec.shardSize)
|
||||
fec.headerOffset = offset
|
||||
fec.payloadOffset = fec.headerOffset + fecHeaderSize
|
||||
enc := new(fecEncoder)
|
||||
enc.dataShards = dataShards
|
||||
enc.parityShards = parityShards
|
||||
enc.shardSize = dataShards + parityShards
|
||||
enc.paws = (0xffffffff/uint32(enc.shardSize) - 1) * uint32(enc.shardSize)
|
||||
enc.headerOffset = offset
|
||||
enc.payloadOffset = enc.headerOffset + fecHeaderSize
|
||||
|
||||
enc, err := reedsolomon.New(dataShards, parityShards)
|
||||
codec, err := reedsolomon.New(dataShards, parityShards)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fec.codec = enc
|
||||
enc.codec = codec
|
||||
|
||||
// caches
|
||||
fec.encodeCache = make([][]byte, fec.shardSize)
|
||||
fec.shardCache = make([][]byte, fec.shardSize)
|
||||
for k := range fec.shardCache {
|
||||
fec.shardCache[k] = make([]byte, mtuLimit)
|
||||
enc.encodeCache = make([][]byte, enc.shardSize)
|
||||
enc.shardCache = make([][]byte, enc.shardSize)
|
||||
for k := range enc.shardCache {
|
||||
enc.shardCache[k] = make([]byte, mtuLimit)
|
||||
}
|
||||
return fec
|
||||
enc.zeros = make([]byte, mtuLimit)
|
||||
return enc
|
||||
}
|
||||
|
||||
// encode the packet, output parity shards if we have enough datashards
|
||||
// the content of returned parityshards will change in next encode
|
||||
// encodes the packet, outputs parity shards if we have collected quorum datashards
|
||||
// notice: the contents of 'ps' will be re-written in successive calling
|
||||
func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
enc.markData(b[enc.headerOffset:])
|
||||
binary.LittleEndian.PutUint16(b[enc.payloadOffset:], uint16(len(b[enc.payloadOffset:])))
|
||||
@@ -253,18 +261,18 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
copy(enc.shardCache[enc.shardCount], b)
|
||||
enc.shardCount++
|
||||
|
||||
// record max datashard length
|
||||
// track max datashard length
|
||||
if sz > enc.maxSize {
|
||||
enc.maxSize = sz
|
||||
}
|
||||
|
||||
// calculate Reed-Solomon Erasure Code
|
||||
// Generation of Reed-Solomon Erasure Code
|
||||
if enc.shardCount == enc.dataShards {
|
||||
// bzero each datashard's tail
|
||||
// fill '0' into the tail of each datashard
|
||||
for i := 0; i < enc.dataShards; i++ {
|
||||
shard := enc.shardCache[i]
|
||||
slen := len(shard)
|
||||
xorBytes(shard[slen:enc.maxSize], shard[slen:enc.maxSize], shard[slen:enc.maxSize])
|
||||
copy(shard[slen:enc.maxSize], enc.zeros)
|
||||
}
|
||||
|
||||
// construct equal-sized slice with stripped header
|
||||
@@ -273,7 +281,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
cache[k] = enc.shardCache[k][enc.payloadOffset:enc.maxSize]
|
||||
}
|
||||
|
||||
// rs encode
|
||||
// encoding
|
||||
if err := enc.codec.Encode(cache); err == nil {
|
||||
ps = enc.shardCache[enc.dataShards:]
|
||||
for k := range ps {
|
||||
@@ -282,7 +290,7 @@ func (enc *fecEncoder) encode(b []byte) (ps [][]byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// reset counters to zero
|
||||
// counters resetting
|
||||
enc.shardCount = 0
|
||||
enc.maxSize = 0
|
||||
}
|
||||
|
||||
186
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
186
vendor/github.com/fatedier/kcp-go/kcp.go
generated
vendored
@@ -104,6 +104,7 @@ type segment struct {
|
||||
xmit uint32
|
||||
resendts uint32
|
||||
fastack uint32
|
||||
acked uint32 // mark if the seg has acked
|
||||
data []byte
|
||||
}
|
||||
|
||||
@@ -181,8 +182,11 @@ func (kcp *KCP) newSegment(size int) (seg segment) {
|
||||
}
|
||||
|
||||
// delSegment recycles a KCP segment
|
||||
func (kcp *KCP) delSegment(seg segment) {
|
||||
xmitBuf.Put(seg.data)
|
||||
func (kcp *KCP) delSegment(seg *segment) {
|
||||
if seg.data != nil {
|
||||
xmitBuf.Put(seg.data)
|
||||
seg.data = nil
|
||||
}
|
||||
}
|
||||
|
||||
// PeekSize checks the size of next message in the recv queue
|
||||
@@ -238,7 +242,7 @@ func (kcp *KCP) Recv(buffer []byte) (n int) {
|
||||
buffer = buffer[len(seg.data):]
|
||||
n += len(seg.data)
|
||||
count++
|
||||
kcp.delSegment(*seg)
|
||||
kcp.delSegment(seg)
|
||||
if seg.frg == 0 {
|
||||
break
|
||||
}
|
||||
@@ -382,10 +386,8 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
||||
for k := range kcp.snd_buf {
|
||||
seg := &kcp.snd_buf[k]
|
||||
if sn == seg.sn {
|
||||
kcp.delSegment(*seg)
|
||||
copy(kcp.snd_buf[k:], kcp.snd_buf[k+1:])
|
||||
kcp.snd_buf[len(kcp.snd_buf)-1] = segment{}
|
||||
kcp.snd_buf = kcp.snd_buf[:len(kcp.snd_buf)-1]
|
||||
seg.acked = 1
|
||||
kcp.delSegment(seg)
|
||||
break
|
||||
}
|
||||
if _itimediff(sn, seg.sn) < 0 {
|
||||
@@ -394,7 +396,7 @@ func (kcp *KCP) parse_ack(sn uint32) {
|
||||
}
|
||||
}
|
||||
|
||||
func (kcp *KCP) parse_fastack(sn uint32) {
|
||||
func (kcp *KCP) parse_fastack(sn, ts uint32) {
|
||||
if _itimediff(sn, kcp.snd_una) < 0 || _itimediff(sn, kcp.snd_nxt) >= 0 {
|
||||
return
|
||||
}
|
||||
@@ -403,7 +405,7 @@ func (kcp *KCP) parse_fastack(sn uint32) {
|
||||
seg := &kcp.snd_buf[k]
|
||||
if _itimediff(sn, seg.sn) < 0 {
|
||||
break
|
||||
} else if sn != seg.sn {
|
||||
} else if sn != seg.sn && _itimediff(seg.ts, ts) <= 0 {
|
||||
seg.fastack++
|
||||
}
|
||||
}
|
||||
@@ -414,7 +416,7 @@ func (kcp *KCP) parse_una(una uint32) {
|
||||
for k := range kcp.snd_buf {
|
||||
seg := &kcp.snd_buf[k]
|
||||
if _itimediff(una, seg.sn) > 0 {
|
||||
kcp.delSegment(*seg)
|
||||
kcp.delSegment(seg)
|
||||
count++
|
||||
} else {
|
||||
break
|
||||
@@ -430,12 +432,12 @@ func (kcp *KCP) ack_push(sn, ts uint32) {
|
||||
kcp.acklist = append(kcp.acklist, ackItem{sn, ts})
|
||||
}
|
||||
|
||||
func (kcp *KCP) parse_data(newseg segment) {
|
||||
// returns true if data has repeated
|
||||
func (kcp *KCP) parse_data(newseg segment) bool {
|
||||
sn := newseg.sn
|
||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) >= 0 ||
|
||||
_itimediff(sn, kcp.rcv_nxt) < 0 {
|
||||
kcp.delSegment(newseg)
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
n := len(kcp.rcv_buf) - 1
|
||||
@@ -445,7 +447,6 @@ func (kcp *KCP) parse_data(newseg segment) {
|
||||
seg := &kcp.rcv_buf[i]
|
||||
if seg.sn == sn {
|
||||
repeat = true
|
||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
||||
break
|
||||
}
|
||||
if _itimediff(sn, seg.sn) > 0 {
|
||||
@@ -455,6 +456,11 @@ func (kcp *KCP) parse_data(newseg segment) {
|
||||
}
|
||||
|
||||
if !repeat {
|
||||
// replicate the content if it's new
|
||||
dataCopy := xmitBuf.Get().([]byte)[:len(newseg.data)]
|
||||
copy(dataCopy, newseg.data)
|
||||
newseg.data = dataCopy
|
||||
|
||||
if insert_idx == n+1 {
|
||||
kcp.rcv_buf = append(kcp.rcv_buf, newseg)
|
||||
} else {
|
||||
@@ -462,8 +468,6 @@ func (kcp *KCP) parse_data(newseg segment) {
|
||||
copy(kcp.rcv_buf[insert_idx+1:], kcp.rcv_buf[insert_idx:])
|
||||
kcp.rcv_buf[insert_idx] = newseg
|
||||
}
|
||||
} else {
|
||||
kcp.delSegment(newseg)
|
||||
}
|
||||
|
||||
// move available data from rcv_buf -> rcv_queue
|
||||
@@ -481,18 +485,19 @@ func (kcp *KCP) parse_data(newseg segment) {
|
||||
kcp.rcv_queue = append(kcp.rcv_queue, kcp.rcv_buf[:count]...)
|
||||
kcp.rcv_buf = kcp.remove_front(kcp.rcv_buf, count)
|
||||
}
|
||||
|
||||
return repeat
|
||||
}
|
||||
|
||||
// Input when you received a low level packet (eg. UDP packet), call it
|
||||
// regular indicates a regular packet has received(not from FEC)
|
||||
func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||
una := kcp.snd_una
|
||||
snd_una := kcp.snd_una
|
||||
if len(data) < IKCP_OVERHEAD {
|
||||
return -1
|
||||
}
|
||||
|
||||
var maxack uint32
|
||||
var lastackts uint32
|
||||
var latest uint32 // the latest ack packet
|
||||
var flag int
|
||||
var inSegs uint64
|
||||
|
||||
@@ -535,19 +540,15 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||
|
||||
if cmd == IKCP_CMD_ACK {
|
||||
kcp.parse_ack(sn)
|
||||
kcp.shrink_buf()
|
||||
if flag == 0 {
|
||||
flag = 1
|
||||
maxack = sn
|
||||
} else if _itimediff(sn, maxack) > 0 {
|
||||
maxack = sn
|
||||
}
|
||||
lastackts = ts
|
||||
kcp.parse_fastack(sn, ts)
|
||||
flag |= 1
|
||||
latest = ts
|
||||
} else if cmd == IKCP_CMD_PUSH {
|
||||
repeat := true
|
||||
if _itimediff(sn, kcp.rcv_nxt+kcp.rcv_wnd) < 0 {
|
||||
kcp.ack_push(sn, ts)
|
||||
if _itimediff(sn, kcp.rcv_nxt) >= 0 {
|
||||
seg := kcp.newSegment(int(length))
|
||||
var seg segment
|
||||
seg.conv = conv
|
||||
seg.cmd = cmd
|
||||
seg.frg = frg
|
||||
@@ -555,12 +556,11 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||
seg.ts = ts
|
||||
seg.sn = sn
|
||||
seg.una = una
|
||||
copy(seg.data, data[:length])
|
||||
kcp.parse_data(seg)
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
||||
seg.data = data[:length] // delayed data copying
|
||||
repeat = kcp.parse_data(seg)
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if regular && repeat {
|
||||
atomic.AddUint64(&DefaultSnmp.RepeatSegs, 1)
|
||||
}
|
||||
} else if cmd == IKCP_CMD_WASK {
|
||||
@@ -578,40 +578,42 @@ func (kcp *KCP) Input(data []byte, regular, ackNoDelay bool) int {
|
||||
}
|
||||
atomic.AddUint64(&DefaultSnmp.InSegs, inSegs)
|
||||
|
||||
// update rtt with the latest ts
|
||||
// ignore the FEC packet
|
||||
if flag != 0 && regular {
|
||||
kcp.parse_fastack(maxack)
|
||||
current := currentMs()
|
||||
if _itimediff(current, lastackts) >= 0 {
|
||||
kcp.update_ack(_itimediff(current, lastackts))
|
||||
if _itimediff(current, latest) >= 0 {
|
||||
kcp.update_ack(_itimediff(current, latest))
|
||||
}
|
||||
}
|
||||
|
||||
if _itimediff(kcp.snd_una, una) > 0 {
|
||||
if kcp.cwnd < kcp.rmt_wnd {
|
||||
mss := kcp.mss
|
||||
if kcp.cwnd < kcp.ssthresh {
|
||||
kcp.cwnd++
|
||||
kcp.incr += mss
|
||||
} else {
|
||||
if kcp.incr < mss {
|
||||
kcp.incr = mss
|
||||
}
|
||||
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
|
||||
if (kcp.cwnd+1)*mss <= kcp.incr {
|
||||
// cwnd update when packet arrived
|
||||
if kcp.nocwnd == 0 {
|
||||
if _itimediff(kcp.snd_una, snd_una) > 0 {
|
||||
if kcp.cwnd < kcp.rmt_wnd {
|
||||
mss := kcp.mss
|
||||
if kcp.cwnd < kcp.ssthresh {
|
||||
kcp.cwnd++
|
||||
kcp.incr += mss
|
||||
} else {
|
||||
if kcp.incr < mss {
|
||||
kcp.incr = mss
|
||||
}
|
||||
kcp.incr += (mss*mss)/kcp.incr + (mss / 16)
|
||||
if (kcp.cwnd+1)*mss <= kcp.incr {
|
||||
kcp.cwnd++
|
||||
}
|
||||
}
|
||||
if kcp.cwnd > kcp.rmt_wnd {
|
||||
kcp.cwnd = kcp.rmt_wnd
|
||||
kcp.incr = kcp.rmt_wnd * mss
|
||||
}
|
||||
}
|
||||
if kcp.cwnd > kcp.rmt_wnd {
|
||||
kcp.cwnd = kcp.rmt_wnd
|
||||
kcp.incr = kcp.rmt_wnd * mss
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ackNoDelay && len(kcp.acklist) > 0 { // ack immediately
|
||||
kcp.flush(true)
|
||||
} else if kcp.rmt_wnd == 0 && len(kcp.acklist) > 0 { // window zero
|
||||
kcp.flush(true)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@@ -624,7 +626,7 @@ func (kcp *KCP) wnd_unused() uint16 {
|
||||
}
|
||||
|
||||
// flush pending data
|
||||
func (kcp *KCP) flush(ackOnly bool) {
|
||||
func (kcp *KCP) flush(ackOnly bool) uint32 {
|
||||
var seg segment
|
||||
seg.conv = kcp.conv
|
||||
seg.cmd = IKCP_CMD_ACK
|
||||
@@ -653,7 +655,7 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
if size > 0 {
|
||||
kcp.output(buffer, size)
|
||||
}
|
||||
return
|
||||
return kcp.interval
|
||||
}
|
||||
|
||||
// probe window size (if remote window size equals zero)
|
||||
@@ -723,7 +725,6 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
kcp.snd_buf = append(kcp.snd_buf, newseg)
|
||||
kcp.snd_nxt++
|
||||
newSegsCount++
|
||||
kcp.snd_queue[k].data = nil
|
||||
}
|
||||
if newSegsCount > 0 {
|
||||
kcp.snd_queue = kcp.remove_front(kcp.snd_queue, newSegsCount)
|
||||
@@ -738,9 +739,15 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
// check for retransmissions
|
||||
current := currentMs()
|
||||
var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
|
||||
for k := range kcp.snd_buf {
|
||||
segment := &kcp.snd_buf[k]
|
||||
minrto := int32(kcp.interval)
|
||||
|
||||
ref := kcp.snd_buf[:len(kcp.snd_buf)] // for bounds check elimination
|
||||
for k := range ref {
|
||||
segment := &ref[k]
|
||||
needsend := false
|
||||
if segment.acked == 1 {
|
||||
continue
|
||||
}
|
||||
if segment.xmit == 0 { // initial transmit
|
||||
needsend = true
|
||||
segment.rto = kcp.rx_rto
|
||||
@@ -772,6 +779,7 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
}
|
||||
|
||||
if needsend {
|
||||
current = currentMs() // time update for a blocking call
|
||||
segment.xmit++
|
||||
segment.ts = current
|
||||
segment.wnd = seg.wnd
|
||||
@@ -782,7 +790,6 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
|
||||
if size+need > int(kcp.mtu) {
|
||||
kcp.output(buffer, size)
|
||||
current = currentMs() // time update for a blocking call
|
||||
ptr = buffer
|
||||
}
|
||||
|
||||
@@ -794,6 +801,11 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
kcp.state = 0xFFFFFFFF
|
||||
}
|
||||
}
|
||||
|
||||
// get the nearest rto
|
||||
if rto := _itimediff(segment.resendts, current); rto > 0 && rto < minrto {
|
||||
minrto = rto
|
||||
}
|
||||
}
|
||||
|
||||
// flash remain segments
|
||||
@@ -819,32 +831,37 @@ func (kcp *KCP) flush(ackOnly bool) {
|
||||
atomic.AddUint64(&DefaultSnmp.RetransSegs, sum)
|
||||
}
|
||||
|
||||
// update ssthresh
|
||||
// rate halving, https://tools.ietf.org/html/rfc6937
|
||||
if change > 0 {
|
||||
inflight := kcp.snd_nxt - kcp.snd_una
|
||||
kcp.ssthresh = inflight / 2
|
||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||
kcp.ssthresh = IKCP_THRESH_MIN
|
||||
// cwnd update
|
||||
if kcp.nocwnd == 0 {
|
||||
// update ssthresh
|
||||
// rate halving, https://tools.ietf.org/html/rfc6937
|
||||
if change > 0 {
|
||||
inflight := kcp.snd_nxt - kcp.snd_una
|
||||
kcp.ssthresh = inflight / 2
|
||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||
kcp.ssthresh = IKCP_THRESH_MIN
|
||||
}
|
||||
kcp.cwnd = kcp.ssthresh + resent
|
||||
kcp.incr = kcp.cwnd * kcp.mss
|
||||
}
|
||||
|
||||
// congestion control, https://tools.ietf.org/html/rfc5681
|
||||
if lost > 0 {
|
||||
kcp.ssthresh = cwnd / 2
|
||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||
kcp.ssthresh = IKCP_THRESH_MIN
|
||||
}
|
||||
kcp.cwnd = 1
|
||||
kcp.incr = kcp.mss
|
||||
}
|
||||
|
||||
if kcp.cwnd < 1 {
|
||||
kcp.cwnd = 1
|
||||
kcp.incr = kcp.mss
|
||||
}
|
||||
kcp.cwnd = kcp.ssthresh + resent
|
||||
kcp.incr = kcp.cwnd * kcp.mss
|
||||
}
|
||||
|
||||
// congestion control, https://tools.ietf.org/html/rfc5681
|
||||
if lost > 0 {
|
||||
kcp.ssthresh = cwnd / 2
|
||||
if kcp.ssthresh < IKCP_THRESH_MIN {
|
||||
kcp.ssthresh = IKCP_THRESH_MIN
|
||||
}
|
||||
kcp.cwnd = 1
|
||||
kcp.incr = kcp.mss
|
||||
}
|
||||
|
||||
if kcp.cwnd < 1 {
|
||||
kcp.cwnd = 1
|
||||
kcp.incr = kcp.mss
|
||||
}
|
||||
return uint32(minrto)
|
||||
}
|
||||
|
||||
// Update updates state (call it repeatedly, every 10ms-100ms), or you can ask
|
||||
@@ -991,8 +1008,5 @@ func (kcp *KCP) WaitSnd() int {
|
||||
// remove front n elements from queue
|
||||
func (kcp *KCP) remove_front(q []segment, n int) []segment {
|
||||
newn := copy(q, q[n:])
|
||||
for i := newn; i < len(q); i++ {
|
||||
q[i] = segment{} // manual set nil for GC
|
||||
}
|
||||
return q[:newn]
|
||||
}
|
||||
|
||||
472
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
472
vendor/github.com/fatedier/kcp-go/sess.go
generated
vendored
@@ -4,7 +4,6 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
)
|
||||
|
||||
type errTimeout struct {
|
||||
@@ -23,7 +23,7 @@ func (errTimeout) Temporary() bool { return true }
|
||||
func (errTimeout) Error() string { return "i/o timeout" }
|
||||
|
||||
const (
|
||||
// 16-bytes magic number for each packet
|
||||
// 16-bytes nonce for each packet
|
||||
nonceSize = 16
|
||||
|
||||
// 4-bytes packet checksum
|
||||
@@ -40,9 +40,6 @@ const (
|
||||
|
||||
// accept backlog
|
||||
acceptBacklog = 128
|
||||
|
||||
// prerouting(to session) queue
|
||||
qlen = 128
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -51,8 +48,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// global packet buffer
|
||||
// shared among sending/receiving/FEC
|
||||
// a system-wide packet buffer shared among sending, receiving and FEC
|
||||
// to mitigate high-frequency memory allocation for packets
|
||||
xmitBuf sync.Pool
|
||||
)
|
||||
|
||||
@@ -68,17 +65,17 @@ type (
|
||||
updaterIdx int // record slice index in updater
|
||||
conn net.PacketConn // the underlying packet connection
|
||||
kcp *KCP // KCP ARQ protocol
|
||||
l *Listener // point to the Listener if it's accepted by Listener
|
||||
block BlockCrypt // block encryption
|
||||
l *Listener // pointing to the Listener object if it's been accepted by a Listener
|
||||
block BlockCrypt // block encryption object
|
||||
|
||||
// kcp receiving is based on packets
|
||||
// recvbuf turns packets into stream
|
||||
recvbuf []byte
|
||||
bufptr []byte
|
||||
// extended output buffer(with header)
|
||||
// header extended output buffer, if has header
|
||||
ext []byte
|
||||
|
||||
// FEC
|
||||
// FEC codec
|
||||
fecDecoder *fecDecoder
|
||||
fecEncoder *fecEncoder
|
||||
|
||||
@@ -86,16 +83,20 @@ type (
|
||||
remote net.Addr // remote peer address
|
||||
rd time.Time // read deadline
|
||||
wd time.Time // write deadline
|
||||
headerSize int // the overall header size added before KCP frame
|
||||
ackNoDelay bool // send ack immediately for each incoming packet
|
||||
headerSize int // the header size additional to a KCP frame
|
||||
ackNoDelay bool // send ack immediately for each incoming packet(testing purpose)
|
||||
writeDelay bool // delay kcp.flush() for Write() for bulk transfer
|
||||
dup int // duplicate udp packets
|
||||
dup int // duplicate udp packets(testing purpose)
|
||||
|
||||
// notifications
|
||||
die chan struct{} // notify session has Closed
|
||||
die chan struct{} // notify current session has Closed
|
||||
chReadEvent chan struct{} // notify Read() can be called without blocking
|
||||
chWriteEvent chan struct{} // notify Write() can be called without blocking
|
||||
chErrorEvent chan error // notify Read() have an error
|
||||
chReadError chan error // notify PacketConn.Read() have an error
|
||||
chWriteError chan error // notify PacketConn.Write() have an error
|
||||
|
||||
// nonce generator
|
||||
nonce Entropy
|
||||
|
||||
isClosed bool // flag the session has Closed
|
||||
mu sync.Mutex
|
||||
@@ -114,16 +115,19 @@ type (
|
||||
func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn net.PacketConn, remote net.Addr, block BlockCrypt) *UDPSession {
|
||||
sess := new(UDPSession)
|
||||
sess.die = make(chan struct{})
|
||||
sess.nonce = new(nonceAES128)
|
||||
sess.nonce.Init()
|
||||
sess.chReadEvent = make(chan struct{}, 1)
|
||||
sess.chWriteEvent = make(chan struct{}, 1)
|
||||
sess.chErrorEvent = make(chan error, 1)
|
||||
sess.chReadError = make(chan error, 1)
|
||||
sess.chWriteError = make(chan error, 1)
|
||||
sess.remote = remote
|
||||
sess.conn = conn
|
||||
sess.l = l
|
||||
sess.block = block
|
||||
sess.recvbuf = make([]byte, mtuLimit)
|
||||
|
||||
// FEC initialization
|
||||
// FEC codec initialization
|
||||
sess.fecDecoder = newFECDecoder(rxFECMulti*(dataShards+parityShards), dataShards, parityShards)
|
||||
if sess.block != nil {
|
||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, cryptHeaderSize)
|
||||
@@ -131,7 +135,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
sess.fecEncoder = newFECEncoder(dataShards, parityShards, 0)
|
||||
}
|
||||
|
||||
// calculate header size
|
||||
// calculate additional header size introduced by FEC and encryption
|
||||
if sess.block != nil {
|
||||
sess.headerSize += cryptHeaderSize
|
||||
}
|
||||
@@ -139,8 +143,7 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
sess.headerSize += fecHeaderSizePlus2
|
||||
}
|
||||
|
||||
// only allocate extended packet buffer
|
||||
// when the extra header is required
|
||||
// we only need to allocate extended packet buffer if we have the additional header
|
||||
if sess.headerSize > 0 {
|
||||
sess.ext = make([]byte, mtuLimit)
|
||||
}
|
||||
@@ -152,8 +155,8 @@ func newUDPSession(conv uint32, dataShards, parityShards int, l *Listener, conn
|
||||
})
|
||||
sess.kcp.SetMtu(IKCP_MTU_DEF - sess.headerSize)
|
||||
|
||||
// add current session to the global updater,
|
||||
// which periodically calls sess.update()
|
||||
// register current session to the global updater,
|
||||
// which call sess.update() periodically.
|
||||
updater.addSession(sess)
|
||||
|
||||
if sess.l == nil { // it's a client connection
|
||||
@@ -179,6 +182,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
n = copy(b, s.bufptr)
|
||||
s.bufptr = s.bufptr[n:]
|
||||
s.mu.Unlock()
|
||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(n))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@@ -188,29 +192,29 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
if size := s.kcp.PeekSize(); size > 0 { // peek data size from kcp
|
||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(size))
|
||||
if len(b) >= size { // direct write to b
|
||||
if len(b) >= size { // receive data into 'b' directly
|
||||
s.kcp.Recv(b)
|
||||
s.mu.Unlock()
|
||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(size))
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// resize kcp receive buffer
|
||||
// to make sure recvbuf has enough capacity
|
||||
// if necessary resize the stream buffer to guarantee a sufficent buffer space
|
||||
if cap(s.recvbuf) < size {
|
||||
s.recvbuf = make([]byte, size)
|
||||
}
|
||||
|
||||
// resize recvbuf slice length
|
||||
// resize the length of recvbuf to correspond to data size
|
||||
s.recvbuf = s.recvbuf[:size]
|
||||
s.kcp.Recv(s.recvbuf)
|
||||
n = copy(b, s.recvbuf) // copy to b
|
||||
s.bufptr = s.recvbuf[n:] // update pointer
|
||||
n = copy(b, s.recvbuf) // copy to 'b'
|
||||
s.bufptr = s.recvbuf[n:] // pointer update
|
||||
s.mu.Unlock()
|
||||
atomic.AddUint64(&DefaultSnmp.BytesReceived, uint64(n))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// read deadline
|
||||
// deadline for current reading operation
|
||||
var timeout *time.Timer
|
||||
var c <-chan time.Time
|
||||
if !s.rd.IsZero() {
|
||||
@@ -230,7 +234,7 @@ func (s *UDPSession) Read(b []byte) (n int, err error) {
|
||||
case <-s.chReadEvent:
|
||||
case <-c:
|
||||
case <-s.die:
|
||||
case err = <-s.chErrorEvent:
|
||||
case err = <-s.chReadError:
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
}
|
||||
@@ -252,7 +256,8 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
return 0, errors.New(errBrokenPipe)
|
||||
}
|
||||
|
||||
// api flow control
|
||||
// controls how much data will be sent to kcp core
|
||||
// to prevent the memory from exhuasting
|
||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
||||
n = len(b)
|
||||
for {
|
||||
@@ -265,7 +270,8 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if !s.writeDelay {
|
||||
// flush immediately if the queue is full
|
||||
if s.kcp.WaitSnd() >= int(s.kcp.snd_wnd) || !s.writeDelay {
|
||||
s.kcp.flush(false)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
@@ -273,7 +279,7 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// write deadline
|
||||
// deadline for current writing operation
|
||||
var timeout *time.Timer
|
||||
var c <-chan time.Time
|
||||
if !s.wd.IsZero() {
|
||||
@@ -292,6 +298,11 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
case <-s.chWriteEvent:
|
||||
case <-c:
|
||||
case <-s.die:
|
||||
case err = <-s.chWriteError:
|
||||
if timeout != nil {
|
||||
timeout.Stop()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
if timeout != nil {
|
||||
@@ -302,13 +313,10 @@ func (s *UDPSession) Write(b []byte) (n int, err error) {
|
||||
|
||||
// Close closes the connection.
|
||||
func (s *UDPSession) Close() error {
|
||||
// remove this session from updater & listener(if necessary)
|
||||
// remove current session from updater & listener(if necessary)
|
||||
updater.removeSession(s)
|
||||
if s.l != nil { // notify listener
|
||||
s.l.closeSession(sessionKey{
|
||||
addr: s.remote.String(),
|
||||
convID: s.kcp.conv,
|
||||
})
|
||||
s.l.closeSession(s.remote)
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
@@ -337,6 +345,8 @@ func (s *UDPSession) SetDeadline(t time.Time) error {
|
||||
defer s.mu.Unlock()
|
||||
s.rd = t
|
||||
s.wd = t
|
||||
s.notifyReadEvent()
|
||||
s.notifyWriteEvent()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -345,6 +355,7 @@ func (s *UDPSession) SetReadDeadline(t time.Time) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.rd = t
|
||||
s.notifyReadEvent()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -353,6 +364,7 @@ func (s *UDPSession) SetWriteDeadline(t time.Time) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.wd = t
|
||||
s.notifyWriteEvent()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -420,10 +432,11 @@ func (s *UDPSession) SetDSCP(dscp int) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.l == nil {
|
||||
if nc, ok := s.conn.(*connectedUDPConn); ok {
|
||||
return ipv4.NewConn(nc.UDPConn).SetTOS(dscp << 2)
|
||||
} else if nc, ok := s.conn.(net.Conn); ok {
|
||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
||||
if nc, ok := s.conn.(net.Conn); ok {
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
@@ -453,11 +466,11 @@ func (s *UDPSession) SetWriteBuffer(bytes int) error {
|
||||
return errors.New(errInvalidOperation)
|
||||
}
|
||||
|
||||
// output pipeline entry
|
||||
// steps for output data processing:
|
||||
// 0. Header extends
|
||||
// 1. FEC
|
||||
// 2. CRC32
|
||||
// post-processing for sending a packet from kcp core
|
||||
// steps:
|
||||
// 0. Header extending
|
||||
// 1. FEC packet generation
|
||||
// 2. CRC32 integrity
|
||||
// 3. Encryption
|
||||
// 4. WriteTo kernel
|
||||
func (s *UDPSession) output(buf []byte) {
|
||||
@@ -477,13 +490,13 @@ func (s *UDPSession) output(buf []byte) {
|
||||
|
||||
// 2&3. crc32 & encryption
|
||||
if s.block != nil {
|
||||
io.ReadFull(rand.Reader, ext[:nonceSize])
|
||||
s.nonce.Fill(ext[:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ext[cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
|
||||
s.block.Encrypt(ext, ext)
|
||||
|
||||
for k := range ecc {
|
||||
io.ReadFull(rand.Reader, ecc[k][:nonceSize])
|
||||
s.nonce.Fill(ecc[k][:nonceSize])
|
||||
checksum := crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
|
||||
binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
|
||||
s.block.Encrypt(ecc[k], ecc[k])
|
||||
@@ -497,6 +510,8 @@ func (s *UDPSession) output(buf []byte) {
|
||||
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
} else {
|
||||
s.notifyWriteError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,6 +519,8 @@ func (s *UDPSession) output(buf []byte) {
|
||||
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
|
||||
nbytes += n
|
||||
npkts++
|
||||
} else {
|
||||
s.notifyWriteError(err)
|
||||
}
|
||||
}
|
||||
atomic.AddUint64(&DefaultSnmp.OutPkts, uint64(npkts))
|
||||
@@ -513,11 +530,11 @@ func (s *UDPSession) output(buf []byte) {
|
||||
// kcp update, returns interval for next calling
|
||||
func (s *UDPSession) update() (interval time.Duration) {
|
||||
s.mu.Lock()
|
||||
s.kcp.flush(false)
|
||||
if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
|
||||
waitsnd := s.kcp.WaitSnd()
|
||||
interval = time.Duration(s.kcp.flush(false)) * time.Millisecond
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
interval = time.Duration(s.kcp.interval) * time.Millisecond
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -539,56 +556,77 @@ func (s *UDPSession) notifyWriteEvent() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPSession) notifyWriteError(err error) {
|
||||
select {
|
||||
case s.chWriteError <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPSession) kcpInput(data []byte) {
|
||||
var kcpInErrors, fecErrs, fecRecovered, fecParityShards uint64
|
||||
|
||||
if s.fecDecoder != nil {
|
||||
f := s.fecDecoder.decodeBytes(data)
|
||||
s.mu.Lock()
|
||||
if f.flag == typeData {
|
||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
||||
kcpInErrors++
|
||||
}
|
||||
}
|
||||
if len(data) > fecHeaderSize { // must be larger than fec header size
|
||||
f := s.fecDecoder.decodeBytes(data)
|
||||
if f.flag == typeData || f.flag == typeFEC { // header check
|
||||
if f.flag == typeFEC {
|
||||
fecParityShards++
|
||||
}
|
||||
recovers := s.fecDecoder.decode(f)
|
||||
|
||||
if f.flag == typeData || f.flag == typeFEC {
|
||||
if f.flag == typeFEC {
|
||||
fecParityShards++
|
||||
}
|
||||
s.mu.Lock()
|
||||
waitsnd := s.kcp.WaitSnd()
|
||||
if f.flag == typeData {
|
||||
if ret := s.kcp.Input(data[fecHeaderSizePlus2:], true, s.ackNoDelay); ret != 0 {
|
||||
kcpInErrors++
|
||||
}
|
||||
}
|
||||
|
||||
recovers := s.fecDecoder.decode(f)
|
||||
for _, r := range recovers {
|
||||
if len(r) >= 2 { // must be larger than 2bytes
|
||||
sz := binary.LittleEndian.Uint16(r)
|
||||
if int(sz) <= len(r) && sz >= 2 {
|
||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
||||
fecRecovered++
|
||||
for _, r := range recovers {
|
||||
if len(r) >= 2 { // must be larger than 2bytes
|
||||
sz := binary.LittleEndian.Uint16(r)
|
||||
if int(sz) <= len(r) && sz >= 2 {
|
||||
if ret := s.kcp.Input(r[2:sz], false, s.ackNoDelay); ret == 0 {
|
||||
fecRecovered++
|
||||
} else {
|
||||
kcpInErrors++
|
||||
}
|
||||
} else {
|
||||
kcpInErrors++
|
||||
fecErrs++
|
||||
}
|
||||
} else {
|
||||
fecErrs++
|
||||
}
|
||||
} else {
|
||||
fecErrs++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notify reader
|
||||
if n := s.kcp.PeekSize(); n > 0 {
|
||||
s.notifyReadEvent()
|
||||
// to notify the readers to receive the data
|
||||
if n := s.kcp.PeekSize(); n > 0 {
|
||||
s.notifyReadEvent()
|
||||
}
|
||||
// to notify the writers when queue is shorter(e.g. ACKed)
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
} else {
|
||||
s.mu.Lock()
|
||||
waitsnd := s.kcp.WaitSnd()
|
||||
if ret := s.kcp.Input(data, true, s.ackNoDelay); ret != 0 {
|
||||
kcpInErrors++
|
||||
}
|
||||
// notify reader
|
||||
if n := s.kcp.PeekSize(); n > 0 {
|
||||
s.notifyReadEvent()
|
||||
}
|
||||
if s.kcp.WaitSnd() < waitsnd {
|
||||
s.notifyWriteEvent()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -608,65 +646,52 @@ func (s *UDPSession) kcpInput(data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UDPSession) receiver(ch chan<- []byte) {
|
||||
for {
|
||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
||||
if n, _, err := s.conn.ReadFrom(data); err == nil && n >= s.headerSize+IKCP_OVERHEAD {
|
||||
select {
|
||||
case ch <- data[:n]:
|
||||
case <-s.die:
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
s.chErrorEvent <- err
|
||||
return
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// read loop for client session
|
||||
// the read loop for a client session
|
||||
func (s *UDPSession) readLoop() {
|
||||
chPacket := make(chan []byte, qlen)
|
||||
go s.receiver(chPacket)
|
||||
|
||||
buf := make([]byte, mtuLimit)
|
||||
var src string
|
||||
for {
|
||||
select {
|
||||
case data := <-chPacket:
|
||||
raw := data
|
||||
dataValid := false
|
||||
if s.block != nil {
|
||||
s.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if s.block == nil {
|
||||
dataValid = true
|
||||
if n, addr, err := s.conn.ReadFrom(buf); err == nil {
|
||||
// make sure the packet is from the same source
|
||||
if src == "" { // set source address
|
||||
src = addr.String()
|
||||
} else if addr.String() != src {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
s.kcpInput(data)
|
||||
if n >= s.headerSize+IKCP_OVERHEAD {
|
||||
data := buf[:n]
|
||||
dataValid := false
|
||||
if s.block != nil {
|
||||
s.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if s.block == nil {
|
||||
dataValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
xmitBuf.Put(raw)
|
||||
case <-s.die:
|
||||
} else {
|
||||
s.chReadError <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
sessionKey struct {
|
||||
addr string
|
||||
convID uint32
|
||||
}
|
||||
|
||||
// Listener defines a server listening for connections
|
||||
// Listener defines a server which will be waiting to accept incoming connections
|
||||
Listener struct {
|
||||
block BlockCrypt // block encryption
|
||||
dataShards int // FEC data shard
|
||||
@@ -674,120 +699,93 @@ type (
|
||||
fecDecoder *fecDecoder // FEC mock initialization
|
||||
conn net.PacketConn // the underlying packet connection
|
||||
|
||||
sessions map[sessionKey]*UDPSession // all sessions accepted by this Listener
|
||||
chAccepts chan *UDPSession // Listen() backlog
|
||||
chSessionClosed chan sessionKey // session close queue
|
||||
headerSize int // the overall header size added before KCP frame
|
||||
die chan struct{} // notify the listener has closed
|
||||
rd atomic.Value // read deadline for Accept()
|
||||
sessions map[string]*UDPSession // all sessions accepted by this Listener
|
||||
sessionLock sync.Mutex
|
||||
chAccepts chan *UDPSession // Listen() backlog
|
||||
chSessionClosed chan net.Addr // session close queue
|
||||
headerSize int // the additional header to a KCP frame
|
||||
die chan struct{} // notify the listener has closed
|
||||
rd atomic.Value // read deadline for Accept()
|
||||
wd atomic.Value
|
||||
}
|
||||
|
||||
// incoming packet
|
||||
inPacket struct {
|
||||
from net.Addr
|
||||
data []byte
|
||||
}
|
||||
)
|
||||
|
||||
// monitor incoming data for all connections of server
|
||||
func (l *Listener) monitor() {
|
||||
// cache last session
|
||||
var lastKey sessionKey
|
||||
// a cache for session object last used
|
||||
var lastAddr string
|
||||
var lastSession *UDPSession
|
||||
|
||||
chPacket := make(chan inPacket, qlen)
|
||||
go l.receiver(chPacket)
|
||||
buf := make([]byte, mtuLimit)
|
||||
for {
|
||||
select {
|
||||
case p := <-chPacket:
|
||||
raw := p.data
|
||||
data := p.data
|
||||
from := p.from
|
||||
dataValid := false
|
||||
if l.block != nil {
|
||||
l.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
if n, from, err := l.conn.ReadFrom(buf); err == nil {
|
||||
if n >= l.headerSize+IKCP_OVERHEAD {
|
||||
data := buf[:n]
|
||||
dataValid := false
|
||||
if l.block != nil {
|
||||
l.block.Decrypt(data, data)
|
||||
data = data[nonceSize:]
|
||||
checksum := crc32.ChecksumIEEE(data[crcSize:])
|
||||
if checksum == binary.LittleEndian.Uint32(data) {
|
||||
data = data[crcSize:]
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if l.block == nil {
|
||||
dataValid = true
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InCsumErrors, 1)
|
||||
}
|
||||
} else if l.block == nil {
|
||||
dataValid = true
|
||||
}
|
||||
|
||||
if dataValid {
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else {
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if convValid {
|
||||
key := sessionKey{
|
||||
addr: from.String(),
|
||||
convID: conv,
|
||||
}
|
||||
if dataValid {
|
||||
addr := from.String()
|
||||
var s *UDPSession
|
||||
var ok bool
|
||||
|
||||
// packets received from an address always come in batch.
|
||||
// the packets received from an address always come in batch,
|
||||
// cache the session for next packet, without querying map.
|
||||
if key == lastKey {
|
||||
if addr == lastAddr {
|
||||
s, ok = lastSession, true
|
||||
} else if s, ok = l.sessions[key]; ok {
|
||||
lastSession = s
|
||||
lastKey = key
|
||||
} else {
|
||||
l.sessionLock.Lock()
|
||||
if s, ok = l.sessions[addr]; ok {
|
||||
lastSession = s
|
||||
lastAddr = addr
|
||||
}
|
||||
l.sessionLock.Unlock()
|
||||
}
|
||||
|
||||
if !ok { // new session
|
||||
if len(l.chAccepts) < cap(l.chAccepts) && len(l.sessions) < 4096 { // do not let new session overwhelm accept queue and connection count
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessions[key] = s
|
||||
l.chAccepts <- s
|
||||
if len(l.chAccepts) < cap(l.chAccepts) { // do not let the new sessions overwhelm accept queue
|
||||
var conv uint32
|
||||
convValid := false
|
||||
if l.fecDecoder != nil {
|
||||
isfec := binary.LittleEndian.Uint16(data[4:])
|
||||
if isfec == typeData {
|
||||
conv = binary.LittleEndian.Uint32(data[fecHeaderSizePlus2:])
|
||||
convValid = true
|
||||
}
|
||||
} else {
|
||||
conv = binary.LittleEndian.Uint32(data)
|
||||
convValid = true
|
||||
}
|
||||
|
||||
if convValid { // creates a new session only if the 'conv' field in kcp is accessible
|
||||
s := newUDPSession(conv, l.dataShards, l.parityShards, l, l.conn, from, l.block)
|
||||
s.kcpInput(data)
|
||||
l.sessionLock.Lock()
|
||||
l.sessions[addr] = s
|
||||
l.sessionLock.Unlock()
|
||||
l.chAccepts <- s
|
||||
}
|
||||
}
|
||||
} else {
|
||||
s.kcpInput(data)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
}
|
||||
|
||||
xmitBuf.Put(raw)
|
||||
case key := <-l.chSessionClosed:
|
||||
if key == lastKey {
|
||||
lastKey = sessionKey{}
|
||||
}
|
||||
delete(l.sessions, key)
|
||||
case <-l.die:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) receiver(ch chan<- inPacket) {
|
||||
for {
|
||||
data := xmitBuf.Get().([]byte)[:mtuLimit]
|
||||
if n, from, err := l.conn.ReadFrom(data); err == nil && n >= l.headerSize+IKCP_OVERHEAD {
|
||||
select {
|
||||
case ch <- inPacket{from, data[:n]}:
|
||||
case <-l.die:
|
||||
return
|
||||
}
|
||||
} else if err != nil {
|
||||
return
|
||||
} else {
|
||||
atomic.AddUint64(&DefaultSnmp.InErrs, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -811,7 +809,10 @@ func (l *Listener) SetWriteBuffer(bytes int) error {
|
||||
// SetDSCP sets the 6bit DSCP field of IP header
|
||||
func (l *Listener) SetDSCP(dscp int) error {
|
||||
if nc, ok := l.conn.(net.Conn); ok {
|
||||
return ipv4.NewConn(nc).SetTOS(dscp << 2)
|
||||
if err := ipv4.NewConn(nc).SetTOS(dscp << 2); err != nil {
|
||||
return ipv6.NewConn(nc).SetTrafficClass(dscp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New(errInvalidOperation)
|
||||
}
|
||||
@@ -864,13 +865,14 @@ func (l *Listener) Close() error {
|
||||
}
|
||||
|
||||
// closeSession notify the listener that a session has closed
|
||||
func (l *Listener) closeSession(key sessionKey) bool {
|
||||
select {
|
||||
case l.chSessionClosed <- key:
|
||||
func (l *Listener) closeSession(remote net.Addr) (ret bool) {
|
||||
l.sessionLock.Lock()
|
||||
defer l.sessionLock.Unlock()
|
||||
if _, ok := l.sessions[remote.String()]; ok {
|
||||
delete(l.sessions, remote.String())
|
||||
return true
|
||||
case <-l.die:
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address, The Addr returned is shared by all invocations of Addr, so do not modify it.
|
||||
@@ -898,9 +900,9 @@ func ListenWithOptions(laddr string, block BlockCrypt, dataShards, parityShards
|
||||
func ServeConn(block BlockCrypt, dataShards, parityShards int, conn net.PacketConn) (*Listener, error) {
|
||||
l := new(Listener)
|
||||
l.conn = conn
|
||||
l.sessions = make(map[sessionKey]*UDPSession)
|
||||
l.sessions = make(map[string]*UDPSession)
|
||||
l.chAccepts = make(chan *UDPSession, acceptBacklog)
|
||||
l.chSessionClosed = make(chan sessionKey)
|
||||
l.chSessionClosed = make(chan net.Addr)
|
||||
l.die = make(chan struct{})
|
||||
l.dataShards = dataShards
|
||||
l.parityShards = parityShards
|
||||
@@ -924,17 +926,22 @@ func Dial(raddr string) (net.Conn, error) { return DialWithOptions(raddr, nil, 0
|
||||
|
||||
// DialWithOptions connects to the remote address "raddr" on the network "udp" with packet encryption
|
||||
func DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int) (*UDPSession, error) {
|
||||
// network type detection
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.ResolveUDPAddr")
|
||||
}
|
||||
network := "udp4"
|
||||
if udpaddr.IP.To4() == nil {
|
||||
network = "udp"
|
||||
}
|
||||
|
||||
udpconn, err := net.DialUDP("udp", nil, udpaddr)
|
||||
conn, err := net.ListenUDP(network, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "net.DialUDP")
|
||||
}
|
||||
|
||||
return NewConn(raddr, block, dataShards, parityShards, &connectedUDPConn{udpconn})
|
||||
return NewConn(raddr, block, dataShards, parityShards, conn)
|
||||
}
|
||||
|
||||
// NewConn establishes a session and talks KCP protocol over a packet connection.
|
||||
@@ -949,6 +956,12 @@ func NewConn(raddr string, block BlockCrypt, dataShards, parityShards int, conn
|
||||
return newUDPSession(convid, dataShards, parityShards, nil, conn, udpaddr, block), nil
|
||||
}
|
||||
|
||||
// monotonic reference time point
|
||||
var refTime time.Time = time.Now()
|
||||
|
||||
// currentMs returns current elasped monotonic milliseconds since program startup
|
||||
func currentMs() uint32 { return uint32(time.Now().Sub(refTime) / time.Millisecond) }
|
||||
|
||||
func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, dataShards, parityShards int, conn *net.UDPConn) (*UDPSession, error) {
|
||||
udpaddr, err := net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
@@ -963,9 +976,6 @@ func NewConnEx(convid uint32, connected bool, raddr string, block BlockCrypt, da
|
||||
return newUDPSession(convid, dataShards, parityShards, nil, pConn, udpaddr, block), nil
|
||||
}
|
||||
|
||||
// returns current time in milliseconds
|
||||
func currentMs() uint32 { return uint32(time.Now().UnixNano() / int64(time.Millisecond)) }
|
||||
|
||||
// connectedUDPConn is a wrapper for net.UDPConn which converts WriteTo syscalls
|
||||
// to Write syscalls that are 4 times faster on some OS'es. This should only be
|
||||
// used for connections that were produced by a net.Dial* call.
|
||||
|
||||
13
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
13
vendor/github.com/fatedier/kcp-go/updater.go
generated
vendored
@@ -85,20 +85,19 @@ func (h *updateHeap) updateTask() {
|
||||
|
||||
h.mu.Lock()
|
||||
hlen := h.Len()
|
||||
now := time.Now()
|
||||
for i := 0; i < hlen; i++ {
|
||||
entry := heap.Pop(h).(entry)
|
||||
if now.After(entry.ts) {
|
||||
entry.ts = now.Add(entry.s.update())
|
||||
heap.Push(h, entry)
|
||||
entry := &h.entries[0]
|
||||
if time.Now().After(entry.ts) {
|
||||
interval := entry.s.update()
|
||||
entry.ts = time.Now().Add(interval)
|
||||
heap.Fix(h, 0)
|
||||
} else {
|
||||
heap.Push(h, entry)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if hlen > 0 {
|
||||
timer = time.After(h.entries[0].ts.Sub(now))
|
||||
timer = time.After(h.entries[0].ts.Sub(time.Now()))
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
110
vendor/github.com/fatedier/kcp-go/xor.go
generated
vendored
110
vendor/github.com/fatedier/kcp-go/xor.go
generated
vendored
@@ -1,110 +0,0 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package kcp
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const wordSize = int(unsafe.Sizeof(uintptr(0)))
|
||||
const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x"
|
||||
|
||||
// fastXORBytes xors in bulk. It only works on architectures that
|
||||
// support unaligned read/writes.
|
||||
func fastXORBytes(dst, a, b []byte) int {
|
||||
n := len(a)
|
||||
if len(b) < n {
|
||||
n = len(b)
|
||||
}
|
||||
|
||||
w := n / wordSize
|
||||
if w > 0 {
|
||||
wordBytes := w * wordSize
|
||||
fastXORWords(dst[:wordBytes], a[:wordBytes], b[:wordBytes])
|
||||
}
|
||||
|
||||
for i := (n - n%wordSize); i < n; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
func safeXORBytes(dst, a, b []byte) int {
|
||||
n := len(a)
|
||||
if len(b) < n {
|
||||
n = len(b)
|
||||
}
|
||||
ex := n % 8
|
||||
for i := 0; i < ex; i++ {
|
||||
dst[i] = a[i] ^ b[i]
|
||||
}
|
||||
|
||||
for i := ex; i < n; i += 8 {
|
||||
_dst := dst[i : i+8]
|
||||
_a := a[i : i+8]
|
||||
_b := b[i : i+8]
|
||||
_dst[0] = _a[0] ^ _b[0]
|
||||
_dst[1] = _a[1] ^ _b[1]
|
||||
_dst[2] = _a[2] ^ _b[2]
|
||||
_dst[3] = _a[3] ^ _b[3]
|
||||
|
||||
_dst[4] = _a[4] ^ _b[4]
|
||||
_dst[5] = _a[5] ^ _b[5]
|
||||
_dst[6] = _a[6] ^ _b[6]
|
||||
_dst[7] = _a[7] ^ _b[7]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// xorBytes xors the bytes in a and b. The destination is assumed to have enough
|
||||
// space. Returns the number of bytes xor'd.
|
||||
func xorBytes(dst, a, b []byte) int {
|
||||
if supportsUnaligned {
|
||||
return fastXORBytes(dst, a, b)
|
||||
}
|
||||
// TODO(hanwen): if (dst, a, b) have common alignment
|
||||
// we could still try fastXORBytes. It is not clear
|
||||
// how often this happens, and it's only worth it if
|
||||
// the block encryption itself is hardware
|
||||
// accelerated.
|
||||
return safeXORBytes(dst, a, b)
|
||||
}
|
||||
|
||||
// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.)
|
||||
// The arguments are assumed to be of equal length.
|
||||
func fastXORWords(dst, a, b []byte) {
|
||||
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
|
||||
aw := *(*[]uintptr)(unsafe.Pointer(&a))
|
||||
bw := *(*[]uintptr)(unsafe.Pointer(&b))
|
||||
n := len(b) / wordSize
|
||||
ex := n % 8
|
||||
for i := 0; i < ex; i++ {
|
||||
dw[i] = aw[i] ^ bw[i]
|
||||
}
|
||||
|
||||
for i := ex; i < n; i += 8 {
|
||||
_dw := dw[i : i+8]
|
||||
_aw := aw[i : i+8]
|
||||
_bw := bw[i : i+8]
|
||||
_dw[0] = _aw[0] ^ _bw[0]
|
||||
_dw[1] = _aw[1] ^ _bw[1]
|
||||
_dw[2] = _aw[2] ^ _bw[2]
|
||||
_dw[3] = _aw[3] ^ _bw[3]
|
||||
_dw[4] = _aw[4] ^ _bw[4]
|
||||
_dw[5] = _aw[5] ^ _bw[5]
|
||||
_dw[6] = _aw[6] ^ _bw[6]
|
||||
_dw[7] = _aw[7] ^ _bw[7]
|
||||
}
|
||||
}
|
||||
|
||||
func xorWords(dst, a, b []byte) {
|
||||
if supportsUnaligned {
|
||||
fastXORWords(dst, a, b)
|
||||
} else {
|
||||
safeXORBytes(dst, a, b)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user