merge branch 2.3

This commit is contained in:
rui.zheng 2016-10-25 12:18:16 +08:00
commit 89d044ad12
13 changed files with 627 additions and 151 deletions

View File

@ -16,6 +16,7 @@ gost - GO Simple Tunnel
* 支持端口转发 (>=2.1)
* 支持HTTP2.0 (>=2.2)
* 实验性支持QUIC (>=2.3)
* KCP (>=2.3)
二进制文件下载https://github.com/ginuerzh/gost/releases
@ -34,7 +35,7 @@ Google讨论组: https://groups.google.com/d/forum/go-gost
```
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
@ -52,6 +53,8 @@ protocol: 代理协议类型(http, socks5, shadowsocks), transport: 数据传输
> quic - 作为QUIC代理quic://:6121
> kcp - 作为KCP代理kcp://:8388
#### 端口转发
适用于-L参数
@ -69,7 +72,7 @@ scheme://[bind_address]:port/[host]:hostport
> -logtostderr : 输出到控制台
> -v=4 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug)
> -v=3 : 日志级别(1-5),级别越高,日志越详细(级别5将开启http2 debug)
> -log_dir=/log/dir/path : 输出到目录/log/dir/path
@ -169,6 +172,22 @@ chrome --enable-quic --proxy-server=quic://server_ip:6121
**注:** 由于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

View File

@ -7,13 +7,14 @@ Features
------
* Listening on multiple ports
* Multi-level forward proxy - proxy chain
* Standard HTTP/HTTPS/SOCKS5 proxy protocols
* Standard HTTP/HTTPS/SOCKS5 proxy protocols support
* TLS encryption via negotiation support for SOCKS5 proxy
* Tunnel UDP over TCP
* Shadowsocks protocol with OTA supported (OTA: >=2.2)
* Shadowsocks protocol support with OTA option (OTA: >=2.2)
* Local/remote port forwarding (>=2.1)
* HTTP2.0 (>=2.2)
* Experimental QUIC support (>=2.3)
* KCP (>=2.3)
Binary file downloadhttps://github.com/ginuerzh/gost/releases
@ -33,12 +34,12 @@ Effective for the -L and -F parameters
```
scheme can be divided into two parts: protocol+transport
protocol: proxy protocol types(http, socks5, shadowsocks),
transport: data transmission mode(ws, wss, tls, http2, quic), may be used in any combination or individually:
protocol: proxy protocol types (http, socks5, shadowsocks),
transport: data transmission mode (ws, wss, tls, http2, quic, kcp), may be used in any combination or individually:
> http - standard HTTP proxy: http://:8080
> http+tls - standard HTTPS proxy(may need to provide a trusted certificate): http+tls://:443
> http+tls - standard HTTPS proxy (may need to provide a trusted certificate): http+tls://:443
> http2 - HTTP2 proxy and backwards-compatible with HTTPS proxy: http2://:443
@ -52,6 +53,8 @@ transport: data transmission mode(ws, wss, tls, http2, quic), may be used in any
> quic - standard QUIC proxy, quic://:6121
> kcp - standard KCP tunnelkcp://:8388
#### Port forwarding
Effective for the -L parameter
@ -69,7 +72,7 @@ scheme://[bind_address]:port/[host]:hostport
> -logtostderr : log to console
> -v=4 : log level(1-5)The higher the level, the more detailed the log (level 5 will enable HTTP2 debug)
> -v=3 : log level (1-5)The higher the level, the more detailed the log (level 5 will enable HTTP2 debug)
> -log_dir=/log/dir/path : log to directory /log/dir/path
@ -163,7 +166,6 @@ Server:
```bash
gost -L=quic://:6121
```
Client(Chrome):
```bash
chrome --enable-quic --proxy-server=quic://server_ip:6121
@ -171,6 +173,21 @@ chrome --enable-quic --proxy-server=quic://server_ip:6121
**NOTE:** Due to Chrome's limitations, it is currently only possible to access the HTTP (but not HTTPS) site through QUIC.
#### KCP
Support for KCP is based on libraries [kcp-go](https://github.com/xtaci/kcp-go) and [kcptun](https://github.com/xtaci/kcptun).
Server:
```bash
gost -L=kcp://:8388
```
Client:
```bash
gost -L=:8080 -F=kcp://server_ip:8388
```
**NOTE:** KCP will be enabled if and only if the proxy chain is not empty and the first proxy node (the first -F parameter) is of type KCP.
When KCP is enabled, other proxy nodes are ignored.
Encryption Mechanism
------
#### HTTP
@ -199,7 +216,7 @@ gost -L=:8080 -F=http2://server_ip:443
#### SOCKS5
Gost supports the standard SOCKS5 protocol methods: no-auth (0x00) and user/pass (0x02),
and extends two methods for data encryption: tls(0x80)tls-auth(0x82).
and extends two methods for data encryption: tls(0x80) and tls-auth(0x82).
Server:
```bash

View File

@ -13,6 +13,7 @@ import (
"net/http/httputil"
"net/url"
"strings"
"sync"
)
// Proxy chain holds a list of proxy nodes
@ -22,6 +23,10 @@ type ProxyChain struct {
http2NodeIndex int
http2Enabled bool
http2Client *http.Client
kcpEnabled bool
kcpConfig *KCPConfig
kcpSession *KCPSession
kcpMutex sync.Mutex
}
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.
//
// NOTE: Should be called immediately when proxy nodes are ready, HTTP2 will not be enabled if this function not be called.
func (c *ProxyChain) TryEnableHttp2() {
// NOTE: Should be called immediately when proxy nodes are ready.
func (c *ProxyChain) Init() {
length := len(c.nodes)
if length == 0 {
return
@ -73,6 +79,17 @@ func (c *ProxyChain) TryEnableHttp2() {
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.
for i, node := range c.nodes {
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 {
return c.http2Enabled
}
@ -130,6 +151,19 @@ func (c *ProxyChain) GetConn() (net.Conn, error) {
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() {
nodes = nodes[c.http2NodeIndex+1:]
if len(nodes) == 0 {
@ -162,6 +196,23 @@ func (c *ProxyChain) dialWithNodes(withHttp2 bool, addr string, nodes ...ProxyNo
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() {
nodes = nodes[c.http2NodeIndex+1:]
if len(nodes) == 0 {
@ -219,6 +270,28 @@ func (c *ProxyChain) travelNodes(withHttp2 bool, nodes ...ProxyNode) (conn *Prox
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.
func (c *ProxyChain) getHttp2Conn(header http.Header) (net.Conn, error) {
if !c.Http2Enabled() {

View File

@ -43,8 +43,7 @@ func main() {
if err := chain.AddProxyNodeString(chainNodes...); err != nil {
glog.Fatal(err)
}
// enable HTTP2
chain.TryEnableHttp2()
chain.Init()
var wg sync.WaitGroup
for _, ns := range serverNodes {

View File

@ -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))
}
}

View File

@ -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()
}

View File

@ -82,6 +82,8 @@ func (c *ProxyConn) handshake() error {
c.conn = tls.Client(c.conn, cfg)
case "h2": // same as http2, but just set a flag for later using.
tlsUsed = true
case "kcp": // kcp connection
tlsUsed = true
default:
}

View File

@ -11,7 +11,7 @@ import (
)
const (
Version = "2.2"
Version = "2.3-dev"
)
// Log level for glog

369
kcp.go Normal file
View 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)
}

View File

@ -57,7 +57,7 @@ func ParseProxyNode(s string) (node ProxyNode, err error) {
}
switch node.Transport {
case "ws", "wss", "tls", "http2":
case "ws", "wss", "tls", "http2", "ssu", "quic", "kcp":
case "https":
node.Protocol = "http"
node.Transport = "tls"

80
quic.go Normal file
View File

@ -0,0 +1,80 @@
package gost
import (
"bufio"
"crypto/tls"
"github.com/golang/glog"
"github.com/lucas-clemente/quic-go/h2quic"
"io"
"net/http"
"net/http/httputil"
)
type QuicServer struct {
Base *ProxyServer
Handler http.Handler
TLSConfig *tls.Config
}
func NewQuicServer(base *ProxyServer) *QuicServer {
return &QuicServer{Base: base}
}
func (s *QuicServer) ListenAndServeTLS(config *tls.Config) error {
server := &h2quic.Server{
Server: &http.Server{
Addr: s.Base.Node.Addr,
Handler: s.Handler,
TLSConfig: config,
},
}
if server.Handler == nil {
server.Handler = http.HandlerFunc(s.HandleRequest)
}
return server.ListenAndServe()
}
func (s *QuicServer) HandleRequest(w http.ResponseWriter, req *http.Request) {
target := req.Host
glog.V(LINFO).Infof("[quic] %s %s - %s %s", req.Method, req.RemoteAddr, target, req.Proto)
if glog.V(LDEBUG) {
dump, _ := httputil.DumpRequest(req, false)
glog.Infoln(string(dump))
}
c, err := s.Base.Chain.Dial(target)
if err != nil {
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err)
w.WriteHeader(http.StatusServiceUnavailable)
return
}
defer c.Close()
glog.V(LINFO).Infof("[quic] %s <-> %s", req.RemoteAddr, target)
req.Header.Set("Connection", "Keep-Alive")
if err = req.Write(c); err != nil {
glog.V(LWARNING).Infof("[quic] %s -> %s : %s", req.RemoteAddr, target, err)
return
}
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
glog.V(LWARNING).Infoln(err)
return
}
defer resp.Body.Close()
for k, v := range resp.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.WriteHeader(resp.StatusCode)
if _, err := io.Copy(flushWriter{w}, resp.Body); err != nil {
glog.V(LWARNING).Infof("[quic] %s <- %s : %s", req.RemoteAddr, target, err)
}
glog.V(LINFO).Infof("[quic] %s >-< %s", req.RemoteAddr, target)
}

View File

@ -80,6 +80,16 @@ func (s *ProxyServer) Serve() error {
return NewRTcpForwardServer(s).Serve()
case "rudp": // Remote UDP port forwarding
return NewRUdpForwardServer(s).Serve()
case "ssu": // shadowsocks udp relay
return NewShadowUdpServer(s).ListenAndServe()
case "quic":
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:
ln, err = net.Listen("tcp", node.Addr)
}
@ -131,7 +141,6 @@ func (s *ProxyServer) handleConn(conn net.Conn) {
return
}
glog.V(LINFO).Infof("%s - %s", conn.RemoteAddr(), s.Node.Addr)
// http or socks5
b := make([]byte, MediumBufferSize)

41
ss.go
View File

@ -65,6 +65,47 @@ func (s *ShadowServer) Serve() {
glog.V(LINFO).Infof("[ss] %s >-< %s", s.conn.RemoteAddr(), addr)
}
type ShadowUdpServer struct {
Base *ProxyServer
Handler func(conn *net.UDPConn, addr *net.UDPAddr, data []byte)
}
func NewShadowUdpServer(base *ProxyServer) *ShadowUdpServer {
return &ShadowUdpServer{Base: base}
}
func (s *ShadowUdpServer) ListenAndServe() error {
laddr, err := net.ResolveUDPAddr("udp", s.Base.Node.Addr)
if err != nil {
return err
}
lconn, err := net.ListenUDP("udp", laddr)
if err != nil {
return err
}
defer lconn.Close()
if s.Handler == nil {
s.Handler = s.HandleConn
}
for {
b := make([]byte, LargeBufferSize)
n, addr, err := lconn.ReadFromUDP(b)
if err != nil {
glog.V(LWARNING).Infoln(err)
continue
}
go s.Handler(lconn, addr, b[:n])
}
}
// TODO: shadowsocks udp relay handler
func (s *ShadowUdpServer) HandleConn(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
}
// This function is copied from shadowsocks library with some modification.
func (s *ShadowServer) getRequest() (host string, ota bool, err error) {
// buf size should at least have the same size with the largest possible