add KCP support
This commit is contained in:
parent
944af50639
commit
de6b165b68
21
README.md
21
README.md
@ -16,6 +16,7 @@ gost - GO Simple Tunnel
|
|||||||
* 支持端口转发 (>=2.1)
|
* 支持端口转发 (>=2.1)
|
||||||
* 支持HTTP2.0 (>=2.2)
|
* 支持HTTP2.0 (>=2.2)
|
||||||
* 实验性支持QUIC (>=2.3)
|
* 实验性支持QUIC (>=2.3)
|
||||||
|
* KCP (>=2.3)
|
||||||
|
|
||||||
二进制文件下载:https://github.com/ginuerzh/gost/releases
|
二进制文件下载:https://github.com/ginuerzh/gost/releases
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost
|
|||||||
```
|
```
|
||||||
scheme分为两部分: protocol+transport
|
scheme分为两部分: protocol+transport
|
||||||
|
|
||||||
protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic), 二者可以任意组合,或单独使用:
|
protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输方式(ws, wss, tls, http2, quic, kcp), 二者可以任意组合,或单独使用:
|
||||||
|
|
||||||
> http - 作为HTTP代理: http://:8080
|
> http - 作为HTTP代理: http://:8080
|
||||||
|
|
||||||
@ -52,6 +53,8 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输
|
|||||||
|
|
||||||
> quic - 作为QUIC代理,quic://:6121
|
> quic - 作为QUIC代理,quic://:6121
|
||||||
|
|
||||||
|
> kcp - 作为KCP代理,kcp://:8388
|
||||||
|
|
||||||
#### 端口转发
|
#### 端口转发
|
||||||
|
|
||||||
适用于-L参数
|
适用于-L参数
|
||||||
@ -169,6 +172,22 @@ chrome --enable-quic --proxy-server=quic://server_ip:6121
|
|||||||
|
|
||||||
**注:** 由于Chrome自身的限制,目前只能通过QUIC访问HTTP网站,无法访问HTTPS网站。
|
**注:** 由于Chrome自身的限制,目前只能通过QUIC访问HTTP网站,无法访问HTTPS网站。
|
||||||
|
|
||||||
|
#### KCP
|
||||||
|
gost对KCP的支持是基于[kcp-go](https://github.com/xtaci/kcp-go)和[kcptun](https://github.com/xtaci/kcptun)库。
|
||||||
|
|
||||||
|
服务端:
|
||||||
|
```bash
|
||||||
|
gost -L=kcp://:8388
|
||||||
|
```
|
||||||
|
|
||||||
|
客户端:
|
||||||
|
```bash
|
||||||
|
gost -L=:8080 -F=kcp://server_ip:8388
|
||||||
|
```
|
||||||
|
|
||||||
|
**注:** 客户端若要开启KCP转发,当且仅当代理链不为空且首个代理节点(第一个-F参数)为kcp类型。
|
||||||
|
当KCP转发开启,代理链中的其他代理节点将被忽略。
|
||||||
|
|
||||||
加密机制
|
加密机制
|
||||||
------
|
------
|
||||||
#### HTTP
|
#### HTTP
|
||||||
|
79
chain.go
79
chain.go
@ -13,6 +13,7 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proxy chain holds a list of proxy nodes
|
// Proxy chain holds a list of proxy nodes
|
||||||
@ -22,6 +23,10 @@ type ProxyChain struct {
|
|||||||
http2NodeIndex int
|
http2NodeIndex int
|
||||||
http2Enabled bool
|
http2Enabled bool
|
||||||
http2Client *http.Client
|
http2Client *http.Client
|
||||||
|
kcpEnabled bool
|
||||||
|
kcpConfig *KCPConfig
|
||||||
|
kcpSession *KCPSession
|
||||||
|
kcpMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
func NewProxyChain(nodes ...ProxyNode) *ProxyChain {
|
||||||
@ -61,11 +66,12 @@ func (c *ProxyChain) SetNode(index int, node ProxyNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TryEnableHttp2 initialize HTTP2 if available.
|
// Init initialize the proxy chain.
|
||||||
|
// KCP will be enabled if the first proxy node is KCP proxy (transport == kcp), the remaining nodes are ignored.
|
||||||
// HTTP2 will be enabled when at least one HTTP2 proxy node (scheme == http2) is present.
|
// HTTP2 will be enabled when at least one HTTP2 proxy node (scheme == http2) is present.
|
||||||
//
|
//
|
||||||
// NOTE: Should be called immediately when proxy nodes are ready, HTTP2 will not be enabled if this function not be called.
|
// NOTE: Should be called immediately when proxy nodes are ready.
|
||||||
func (c *ProxyChain) TryEnableHttp2() {
|
func (c *ProxyChain) Init() {
|
||||||
length := len(c.nodes)
|
length := len(c.nodes)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return
|
return
|
||||||
@ -73,6 +79,17 @@ func (c *ProxyChain) TryEnableHttp2() {
|
|||||||
|
|
||||||
c.lastNode = &c.nodes[length-1]
|
c.lastNode = &c.nodes[length-1]
|
||||||
|
|
||||||
|
if c.nodes[0].Transport == "kcp" {
|
||||||
|
glog.V(LINFO).Infoln("KCP is enabled")
|
||||||
|
c.kcpEnabled = true
|
||||||
|
config, err := ParseKCPConfig(c.nodes[0].Get("c"))
|
||||||
|
if err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
c.kcpConfig = config
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP2 restrict: HTTP2 will be enabled when at least one HTTP2 proxy node is present.
|
// HTTP2 restrict: HTTP2 will be enabled when at least one HTTP2 proxy node is present.
|
||||||
for i, node := range c.nodes {
|
for i, node := range c.nodes {
|
||||||
if node.Transport == "http2" {
|
if node.Transport == "http2" {
|
||||||
@ -88,6 +105,10 @@ func (c *ProxyChain) TryEnableHttp2() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ProxyChain) KCPEnabled() bool {
|
||||||
|
return c.kcpEnabled
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ProxyChain) Http2Enabled() bool {
|
func (c *ProxyChain) Http2Enabled() bool {
|
||||||
return c.http2Enabled
|
return c.http2Enabled
|
||||||
}
|
}
|
||||||
@ -130,6 +151,19 @@ func (c *ProxyChain) GetConn() (net.Conn, error) {
|
|||||||
return nil, ErrEmptyChain
|
return nil, ErrEmptyChain
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.KCPEnabled() {
|
||||||
|
kcpConn, err := c.getKCPConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pc := NewProxyConn(kcpConn, c.nodes[0])
|
||||||
|
if err := pc.Handshake(); err != nil {
|
||||||
|
pc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
if c.Http2Enabled() {
|
if c.Http2Enabled() {
|
||||||
nodes = nodes[c.http2NodeIndex+1:]
|
nodes = nodes[c.http2NodeIndex+1:]
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
@ -162,6 +196,23 @@ func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNo
|
|||||||
return net.DialTimeout("tcp", addr, DialTimeout)
|
return net.DialTimeout("tcp", addr, DialTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.KCPEnabled() {
|
||||||
|
kcpConn, err := c.getKCPConn()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pc := NewProxyConn(kcpConn, nodes[0])
|
||||||
|
if err := pc.Handshake(); err != nil {
|
||||||
|
pc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := pc.Connect(addr); err != nil {
|
||||||
|
pc.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
if withHttp2 && c.Http2Enabled() {
|
if withHttp2 && c.Http2Enabled() {
|
||||||
nodes = nodes[c.http2NodeIndex+1:]
|
nodes = nodes[c.http2NodeIndex+1:]
|
||||||
if len(nodes) == 0 {
|
if len(nodes) == 0 {
|
||||||
@ -219,6 +270,28 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ProxyChain) initKCPSession() (err error) {
|
||||||
|
c.kcpMutex.Lock()
|
||||||
|
defer c.kcpMutex.Unlock()
|
||||||
|
|
||||||
|
if c.kcpSession == nil || c.kcpSession.IsClosed() {
|
||||||
|
glog.V(LINFO).Infoln("[kcp] new kcp session")
|
||||||
|
c.kcpSession, err = DialKCP(c.nodes[0].Addr, c.kcpConfig)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProxyChain) getKCPConn() (conn net.Conn, err error) {
|
||||||
|
if !c.KCPEnabled() {
|
||||||
|
return nil, errors.New("KCP is not enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = c.initKCPSession(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.kcpSession.GetConn()
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize an HTTP2 transport if HTTP2 is enabled.
|
// Initialize an HTTP2 transport if HTTP2 is enabled.
|
||||||
func (c *ProxyChain) getHttp2Conn(header http.Header) (net.Conn, error) {
|
func (c *ProxyChain) getHttp2Conn(header http.Header) (net.Conn, error) {
|
||||||
if !c.Http2Enabled() {
|
if !c.Http2Enabled() {
|
||||||
|
@ -43,8 +43,7 @@ func main() {
|
|||||||
if err := chain.AddProxyNodeString(chainNodes...); err != nil {
|
if err := chain.AddProxyNodeString(chainNodes...); err != nil {
|
||||||
glog.Fatal(err)
|
glog.Fatal(err)
|
||||||
}
|
}
|
||||||
// enable HTTP2
|
chain.Init()
|
||||||
chain.TryEnableHttp2()
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for _, ns := range serverNodes {
|
for _, ns := range serverNodes {
|
||||||
|
@ -1,80 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/ginuerzh/gost"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
"golang.org/x/net/http2"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
proxyNodes stringlist
|
|
||||||
urls []string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&proxyNodes, "F", "forward address, can make a forward chain")
|
|
||||||
flag.Parse()
|
|
||||||
if flag.NArg() == 0 {
|
|
||||||
log.Fatal("please specific at least one request URL")
|
|
||||||
}
|
|
||||||
urls = flag.Args()
|
|
||||||
if glog.V(5) {
|
|
||||||
http2.VerboseLogs = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringlist []string
|
|
||||||
|
|
||||||
func (list *stringlist) String() string {
|
|
||||||
return fmt.Sprintf("%s", *list)
|
|
||||||
}
|
|
||||||
func (list *stringlist) Set(value string) error {
|
|
||||||
*list = append(*list, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
chain := gost.NewProxyChain()
|
|
||||||
if err := chain.AddProxyNodeString(proxyNodes...); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
chain.TryEnableHttp2()
|
|
||||||
|
|
||||||
for _, u := range urls {
|
|
||||||
url, err := url.Parse(u)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Invalid url:", u)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("GET", u)
|
|
||||||
conn, err := chain.Dial(url.Host)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
req, err := http.NewRequest("GET", u, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := req.Write(conn); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(conn), req)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
header, _ := httputil.DumpResponse(resp, false)
|
|
||||||
log.Println(string(header))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"github.com/ginuerzh/gost"
|
|
||||||
"log"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
proxyNodes stringlist
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.Var(&proxyNodes, "L", "proxy server node")
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
type stringlist []string
|
|
||||||
|
|
||||||
func (list *stringlist) String() string {
|
|
||||||
return fmt.Sprintf("%s", *list)
|
|
||||||
}
|
|
||||||
func (list *stringlist) Set(value string) error {
|
|
||||||
*list = append(*list, value)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
chain := gost.NewProxyChain()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, ns := range proxyNodes {
|
|
||||||
serverNode, err := gost.ParseProxyNode(ns)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
go func(node gost.ProxyNode) {
|
|
||||||
defer wg.Done()
|
|
||||||
cert, err := gost.LoadCertificate(node.Get("cert"), node.Get("key"))
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server := gost.NewProxyServer(node, chain, &tls.Config{Certificates: []tls.Certificate{cert}})
|
|
||||||
log.Fatal(server.Serve())
|
|
||||||
}(serverNode)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
}
|
|
2
conn.go
2
conn.go
@ -82,6 +82,8 @@ func (c *ProxyConn) handshake() error {
|
|||||||
c.conn = tls.Client(c.conn, cfg)
|
c.conn = tls.Client(c.conn, cfg)
|
||||||
case "h2": // same as http2, but just set a flag for later using.
|
case "h2": // same as http2, but just set a flag for later using.
|
||||||
tlsUsed = true
|
tlsUsed = true
|
||||||
|
case "kcp": // kcp connection
|
||||||
|
tlsUsed = true
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
369
kcp.go
Normal file
369
kcp.go
Normal file
@ -0,0 +1,369 @@
|
|||||||
|
// KCP feature is based on https://github.com/xtaci/kcptun
|
||||||
|
|
||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/klauspost/compress/snappy"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"gopkg.in/xtaci/kcp-go.v2"
|
||||||
|
"gopkg.in/xtaci/smux.v1"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultKCPConfigFile = "kcp.json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
SALT = "kcp-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KCPConfig struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Crypt string `json:"crypt"`
|
||||||
|
Mode string `json:"mode"`
|
||||||
|
MTU int `json:"mtu"`
|
||||||
|
SndWnd int `json:"sndwnd"`
|
||||||
|
RcvWnd int `json:"rcvwnd"`
|
||||||
|
DataShard int `json:"datashard"`
|
||||||
|
ParityShard int `json:"parityshard"`
|
||||||
|
DSCP int `json:"dscp"`
|
||||||
|
NoComp bool `json:"nocomp"`
|
||||||
|
AckNodelay bool `json:"acknodelay"`
|
||||||
|
NoDelay int `json:"nodelay"`
|
||||||
|
Interval int `json:"interval"`
|
||||||
|
Resend int `json:"resend"`
|
||||||
|
NoCongestion int `json:"nc"`
|
||||||
|
SockBuf int `json:"sockbuf"`
|
||||||
|
KeepAlive int `json:"keepalive"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseKCPConfig(configFile string) (*KCPConfig, error) {
|
||||||
|
if configFile == "" {
|
||||||
|
configFile = DefaultKCPConfigFile
|
||||||
|
}
|
||||||
|
file, err := os.Open(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
config := &KCPConfig{}
|
||||||
|
if err = json.NewDecoder(file).Decode(config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConfig) Init() {
|
||||||
|
switch c.Mode {
|
||||||
|
case "normal":
|
||||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 30, 2, 1
|
||||||
|
case "fast2":
|
||||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 20, 2, 1
|
||||||
|
case "fast3":
|
||||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 1, 10, 2, 1
|
||||||
|
case "fast":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
c.NoDelay, c.Interval, c.Resend, c.NoCongestion = 0, 20, 2, 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
DefaultKCPConfig = &KCPConfig{
|
||||||
|
Key: "it's a secrect",
|
||||||
|
Crypt: "aes",
|
||||||
|
Mode: "fast",
|
||||||
|
MTU: 1350,
|
||||||
|
SndWnd: 1024,
|
||||||
|
RcvWnd: 1024,
|
||||||
|
DataShard: 10,
|
||||||
|
ParityShard: 3,
|
||||||
|
DSCP: 0,
|
||||||
|
NoComp: false,
|
||||||
|
AckNodelay: false,
|
||||||
|
NoDelay: 0,
|
||||||
|
Interval: 40,
|
||||||
|
Resend: 0,
|
||||||
|
NoCongestion: 0,
|
||||||
|
SockBuf: 4194304,
|
||||||
|
KeepAlive: 10,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type KCPServer struct {
|
||||||
|
Base *ProxyServer
|
||||||
|
Config *KCPConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKCPServer(base *ProxyServer, config *KCPConfig) *KCPServer {
|
||||||
|
return &KCPServer{Base: base, Config: config}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KCPServer) ListenAndServe() (err error) {
|
||||||
|
if s.Config == nil {
|
||||||
|
s.Config = DefaultKCPConfig
|
||||||
|
}
|
||||||
|
s.Config.Init()
|
||||||
|
|
||||||
|
ln, err := kcp.ListenWithOptions(s.Base.Node.Addr,
|
||||||
|
blockCrypt(s.Config.Key, s.Config.Crypt, SALT), s.Config.DataShard, s.Config.ParityShard)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = ln.SetDSCP(s.Config.DSCP); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
if err = ln.SetReadBuffer(s.Config.SockBuf); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
if err = ln.SetWriteBuffer(s.Config.SockBuf); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.AcceptKCP()
|
||||||
|
if err != nil {
|
||||||
|
glog.V(LWARNING).Infoln(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetStreamMode(true)
|
||||||
|
conn.SetNoDelay(s.Config.NoDelay, s.Config.Interval, s.Config.Resend, s.Config.NoCongestion)
|
||||||
|
conn.SetMtu(s.Config.MTU)
|
||||||
|
conn.SetWindowSize(s.Config.SndWnd, s.Config.RcvWnd)
|
||||||
|
conn.SetACKNoDelay(s.Config.AckNodelay)
|
||||||
|
conn.SetKeepAlive(s.Config.KeepAlive)
|
||||||
|
|
||||||
|
go s.handleMux(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *KCPServer) handleMux(conn net.Conn) {
|
||||||
|
smuxConfig := smux.DefaultConfig()
|
||||||
|
smuxConfig.MaxReceiveBuffer = s.Config.SockBuf
|
||||||
|
|
||||||
|
glog.V(LINFO).Infof("[kcp] %s - %s", conn.RemoteAddr(), s.Base.Node.Addr)
|
||||||
|
|
||||||
|
if !s.Config.NoComp {
|
||||||
|
conn = newCompStreamConn(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
mux, err := smux.Server(conn, smuxConfig)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mux.Close()
|
||||||
|
|
||||||
|
glog.V(LINFO).Infof("[kcp] %s <-> %s", conn.RemoteAddr(), s.Base.Node.Addr)
|
||||||
|
defer glog.V(LINFO).Infof("[kcp] %s >-< %s", conn.RemoteAddr(), s.Base.Node.Addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
stream, err := mux.AcceptStream()
|
||||||
|
if err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go s.Base.handleConn(NewKCPConn(conn, stream))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func blockCrypt(key, crypt, salt string) (block kcp.BlockCrypt) {
|
||||||
|
pass := pbkdf2.Key([]byte(key), []byte(salt), 4096, 32, sha1.New)
|
||||||
|
|
||||||
|
switch crypt {
|
||||||
|
case "tea":
|
||||||
|
block, _ = kcp.NewTEABlockCrypt(pass[:16])
|
||||||
|
case "xor":
|
||||||
|
block, _ = kcp.NewSimpleXORBlockCrypt(pass)
|
||||||
|
case "none":
|
||||||
|
block, _ = kcp.NewNoneBlockCrypt(pass)
|
||||||
|
case "aes-128":
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:16])
|
||||||
|
case "aes-192":
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass[:24])
|
||||||
|
case "blowfish":
|
||||||
|
block, _ = kcp.NewBlowfishBlockCrypt(pass)
|
||||||
|
case "twofish":
|
||||||
|
block, _ = kcp.NewTwofishBlockCrypt(pass)
|
||||||
|
case "cast5":
|
||||||
|
block, _ = kcp.NewCast5BlockCrypt(pass[:16])
|
||||||
|
case "3des":
|
||||||
|
block, _ = kcp.NewTripleDESBlockCrypt(pass[:24])
|
||||||
|
case "xtea":
|
||||||
|
block, _ = kcp.NewXTEABlockCrypt(pass[:16])
|
||||||
|
case "salsa20":
|
||||||
|
block, _ = kcp.NewSalsa20BlockCrypt(pass)
|
||||||
|
case "aes":
|
||||||
|
fallthrough
|
||||||
|
default: // aes
|
||||||
|
block, _ = kcp.NewAESBlockCrypt(pass)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type KCPSession struct {
|
||||||
|
conn net.Conn
|
||||||
|
session *smux.Session
|
||||||
|
}
|
||||||
|
|
||||||
|
func DialKCP(addr string, config *KCPConfig) (*KCPSession, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultKCPConfig
|
||||||
|
}
|
||||||
|
config.Init()
|
||||||
|
|
||||||
|
kcpconn, err := kcp.DialWithOptions(addr,
|
||||||
|
blockCrypt(config.Key, config.Crypt, SALT), config.DataShard, config.ParityShard)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kcpconn.SetStreamMode(true)
|
||||||
|
kcpconn.SetNoDelay(config.NoDelay, config.Interval, config.Resend, config.NoCongestion)
|
||||||
|
kcpconn.SetWindowSize(config.SndWnd, config.RcvWnd)
|
||||||
|
kcpconn.SetMtu(config.MTU)
|
||||||
|
kcpconn.SetACKNoDelay(config.AckNodelay)
|
||||||
|
kcpconn.SetKeepAlive(config.KeepAlive)
|
||||||
|
|
||||||
|
if err := kcpconn.SetDSCP(config.DSCP); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
if err := kcpconn.SetReadBuffer(config.SockBuf); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
if err := kcpconn.SetWriteBuffer(config.SockBuf); err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stream multiplex
|
||||||
|
smuxConfig := smux.DefaultConfig()
|
||||||
|
smuxConfig.MaxReceiveBuffer = config.SockBuf
|
||||||
|
var conn net.Conn = kcpconn
|
||||||
|
if !config.NoComp {
|
||||||
|
conn = newCompStreamConn(kcpconn)
|
||||||
|
}
|
||||||
|
session, err := smux.Client(conn, smuxConfig)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &KCPSession{conn: conn, session: session}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *KCPSession) GetConn() (*KCPConn, error) {
|
||||||
|
stream, err := session.session.OpenStream()
|
||||||
|
if err != nil {
|
||||||
|
session.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return NewKCPConn(session.conn, stream), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *KCPSession) Close() error {
|
||||||
|
return session.session.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *KCPSession) IsClosed() bool {
|
||||||
|
return session.session.IsClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session *KCPSession) NumStreams() int {
|
||||||
|
return session.session.NumStreams()
|
||||||
|
}
|
||||||
|
|
||||||
|
type KCPConn struct {
|
||||||
|
conn net.Conn
|
||||||
|
stream *smux.Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKCPConn(conn net.Conn, stream *smux.Stream) *KCPConn {
|
||||||
|
return &KCPConn{conn: conn, stream: stream}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) Read(b []byte) (n int, err error) {
|
||||||
|
return c.stream.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) Write(b []byte) (n int, err error) {
|
||||||
|
return c.stream.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) Close() error {
|
||||||
|
return c.stream.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) LocalAddr() net.Addr {
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) RemoteAddr() net.Addr {
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) SetDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *KCPConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type compStreamConn struct {
|
||||||
|
conn net.Conn
|
||||||
|
w *snappy.Writer
|
||||||
|
r *snappy.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCompStreamConn(conn net.Conn) *compStreamConn {
|
||||||
|
c := new(compStreamConn)
|
||||||
|
c.conn = conn
|
||||||
|
c.w = snappy.NewBufferedWriter(conn)
|
||||||
|
c.r = snappy.NewReader(conn)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) Read(b []byte) (n int, err error) {
|
||||||
|
return c.r.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) Write(b []byte) (n int, err error) {
|
||||||
|
n, err = c.w.Write(b)
|
||||||
|
err = c.w.Flush()
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) Close() error {
|
||||||
|
return c.conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) LocalAddr() net.Addr {
|
||||||
|
return c.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) RemoteAddr() net.Addr {
|
||||||
|
return c.conn.RemoteAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) SetDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compStreamConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return c.conn.SetWriteDeadline(t)
|
||||||
|
}
|
2
node.go
2
node.go
@ -57,7 +57,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch node.Transport {
|
switch node.Transport {
|
||||||
case "ws", "wss", "tls", "http2", "ssu", "quic":
|
case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp":
|
||||||
case "https":
|
case "https":
|
||||||
node.Protocol = "http"
|
node.Protocol = "http"
|
||||||
node.Transport = "tls"
|
node.Transport = "tls"
|
||||||
|
@ -84,6 +84,12 @@ func (s *ProxyServer) Serve() error {
|
|||||||
return NewShadowUdpServer(s).ListenAndServe()
|
return NewShadowUdpServer(s).ListenAndServe()
|
||||||
case "quic":
|
case "quic":
|
||||||
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig)
|
return NewQuicServer(s).ListenAndServeTLS(s.TLSConfig)
|
||||||
|
case "kcp":
|
||||||
|
config, err := ParseKCPConfig(s.Node.Get("c"))
|
||||||
|
if err != nil {
|
||||||
|
glog.V(LWARNING).Infoln("[kcp]", err)
|
||||||
|
}
|
||||||
|
return NewKCPServer(s, config).ListenAndServe()
|
||||||
default:
|
default:
|
||||||
ln, err = net.Listen("tcp", node.Addr)
|
ln, err = net.Listen("tcp", node.Addr)
|
||||||
}
|
}
|
||||||
@ -135,7 +141,6 @@ func (s *ProxyServer) handleConn(conn net.Conn) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
glog.V(LINFO).Infof("%s - %s", conn.RemoteAddr(), s.Node.Addr)
|
|
||||||
// http or socks5
|
// http or socks5
|
||||||
b := make([]byte, MediumBufferSize)
|
b := make([]byte, MediumBufferSize)
|
||||||
|
|
||||||
|
1
ss.go
1
ss.go
@ -101,6 +101,7 @@ func (s *ShadowUdpServer) ListenAndServe() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: shadowsocks udp relay handler
|
||||||
func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
|
func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user