initial commit
This commit is contained in:
commit
b74e4cc8a4
11
client/client.go
Normal file
11
client/client.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-gost/gost/client/connector"
|
||||||
|
"github.com/go-gost/gost/client/transporter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
Connector connector.Connector
|
||||||
|
Transporter transporter.Transporter
|
||||||
|
}
|
11
client/connector/connector.go
Normal file
11
client/connector/connector.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connector is responsible for connecting to the destination address.
|
||||||
|
type Connector interface {
|
||||||
|
Connect(ctx context.Context, conn net.Conn, network, address string) (net.Conn, error)
|
||||||
|
}
|
14
client/transporter/transporter.go
Normal file
14
client/transporter/transporter.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package transporter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transporter is responsible for handshaking with server.
|
||||||
|
type Transporter interface {
|
||||||
|
Dial(ctx context.Context, addr string) (net.Conn, error)
|
||||||
|
Handshake(ctx context.Context, conn net.Conn) (net.Conn, error)
|
||||||
|
// Indicate that the Transporter supports multiplex
|
||||||
|
Multiplex() bool
|
||||||
|
}
|
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module github.com/go-gost/gost
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect
|
||||||
|
github.com/go-gost/gosocks5 v0.3.0
|
||||||
|
github.com/shadowsocks/go-shadowsocks2 v0.1.4
|
||||||
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601
|
||||||
|
github.com/sirupsen/logrus v1.8.1
|
||||||
|
)
|
27
go.sum
Normal file
27
go.sum
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
|
||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJA=
|
||||||
|
github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||||
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||||
|
github.com/shadowsocks/go-shadowsocks2 v0.1.4 h1:4VzajPL7RwwmImysBSvI+lm/UaegDGQq3hr42dYo3gs=
|
||||||
|
github.com/shadowsocks/go-shadowsocks2 v0.1.4/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
|
||||||
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601 h1:XU9hik0exChEmY92ALW4l9WnDodxLVS9yOSNh2SizaQ=
|
||||||
|
github.com/shadowsocks/shadowsocks-go v0.0.0-20200409064450-3e585ff90601/go.mod h1:mttDPaeLm87u74HMrP+n2tugXvIKWcwff/cqSX0lehY=
|
||||||
|
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||||
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g=
|
||||||
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
96
logger/gost_logger.go
Normal file
96
logger/gost_logger.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Logger = (*logger)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type logger struct {
|
||||||
|
logger *logrus.Entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogger(name string) *logger {
|
||||||
|
l := logrus.New()
|
||||||
|
l.SetOutput(os.Stdout)
|
||||||
|
|
||||||
|
gl := &logger{
|
||||||
|
logger: l.WithFields(logrus.Fields{
|
||||||
|
logFieldScope: name,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return gl
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableJSONOutput enables JSON formatted output log.
|
||||||
|
func (l *logger) EnableJSONOutput(enabled bool) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutputLevel sets log output level
|
||||||
|
func (l *logger) SetLevel(level LogLevel) {
|
||||||
|
lvl, _ := logrus.ParseLevel(string(level))
|
||||||
|
l.logger.Logger.SetLevel(lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields adds new fields to log.
|
||||||
|
func (l *logger) WithFields(fields map[string]interface{}) Logger {
|
||||||
|
return &logger{
|
||||||
|
logger: l.logger.WithFields(logrus.Fields(fields)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at level Info.
|
||||||
|
func (l *logger) Info(args ...interface{}) {
|
||||||
|
l.logger.Log(logrus.InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a message at level Info.
|
||||||
|
func (l *logger) Infof(format string, args ...interface{}) {
|
||||||
|
l.logger.Logf(logrus.InfoLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at level Debug.
|
||||||
|
func (l *logger) Debug(args ...interface{}) {
|
||||||
|
l.logger.Log(logrus.DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a message at level Debug.
|
||||||
|
func (l *logger) Debugf(format string, args ...interface{}) {
|
||||||
|
l.logger.Logf(logrus.DebugLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at level Warn.
|
||||||
|
func (l *logger) Warn(args ...interface{}) {
|
||||||
|
l.logger.Log(logrus.WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a message at level Warn.
|
||||||
|
func (l *logger) Warnf(format string, args ...interface{}) {
|
||||||
|
l.logger.Logf(logrus.WarnLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at level Error.
|
||||||
|
func (l *logger) Error(args ...interface{}) {
|
||||||
|
l.logger.Log(logrus.ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a message at level Error.
|
||||||
|
func (l *logger) Errorf(format string, args ...interface{}) {
|
||||||
|
l.logger.Logf(logrus.ErrorLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at level Fatal then the process will exit with status set to 1.
|
||||||
|
func (l *logger) Fatal(args ...interface{}) {
|
||||||
|
l.logger.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a message at level Fatal then the process will exit with status set to 1.
|
||||||
|
func (l *logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
l.logger.Fatalf(format, args...)
|
||||||
|
}
|
57
logger/logger.go
Normal file
57
logger/logger.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
const (
|
||||||
|
logFieldScope = "scope"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogLevel is Logger Level type
|
||||||
|
type LogLevel string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DebugLevel has verbose message
|
||||||
|
DebugLevel LogLevel = "debug"
|
||||||
|
// InfoLevel is default log level
|
||||||
|
InfoLevel LogLevel = "info"
|
||||||
|
// WarnLevel is for logging messages about possible issues
|
||||||
|
WarnLevel LogLevel = "warn"
|
||||||
|
// ErrorLevel is for logging errors
|
||||||
|
ErrorLevel LogLevel = "error"
|
||||||
|
// FatalLevel is for logging fatal messages. The system shuts down after logging the message.
|
||||||
|
FatalLevel LogLevel = "fatal"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
globalLoggers = make(map[string]Logger)
|
||||||
|
globalLoggersLock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
EnableJSONOutput(enabled bool)
|
||||||
|
SetLevel(level LogLevel)
|
||||||
|
WithFields(map[string]interface{}) Logger
|
||||||
|
Debug(args ...interface{})
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
Info(args ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
Warn(args ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Error(args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(name string) Logger {
|
||||||
|
globalLoggersLock.Lock()
|
||||||
|
defer globalLoggersLock.Unlock()
|
||||||
|
|
||||||
|
logger, ok := globalLoggers[name]
|
||||||
|
if !ok {
|
||||||
|
logger = newLogger(name)
|
||||||
|
globalLoggers[name] = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
11
server/handler/handler.go
Normal file
11
server/handler/handler.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
Init(md Metadata) error
|
||||||
|
Handle(context.Context, net.Conn)
|
||||||
|
}
|
233
server/handler/http/handler.go
Normal file
233
server/handler/http/handler.go
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-gost/gost/logger"
|
||||||
|
"github.com/go-gost/gost/server/handler"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ handler.Handler = (*Handler)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
logger logger.Logger
|
||||||
|
md metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(opts ...handler.Option) *Handler {
|
||||||
|
options := &handler.Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handler{
|
||||||
|
logger: options.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Init(md handler.Metadata) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
req, err := http.ReadRequest(bufio.NewReader(conn))
|
||||||
|
if err != nil {
|
||||||
|
h.logger.WithFields(map[string]interface{}{
|
||||||
|
"src": conn.RemoteAddr(),
|
||||||
|
"local": conn.LocalAddr(),
|
||||||
|
}).Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer req.Body.Close()
|
||||||
|
|
||||||
|
h.handleRequest(conn, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) handleRequest(conn net.Conn, req *http.Request) {
|
||||||
|
if req == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// try to get the actual host.
|
||||||
|
if v := req.Header.Get("Gost-Target"); v != "" {
|
||||||
|
if h, err := decodeServerName(v); err == nil {
|
||||||
|
req.Host = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
host := req.Host
|
||||||
|
if _, port, _ := net.SplitHostPort(host); port == "" {
|
||||||
|
host = net.JoinHostPort(host, "80")
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
u, _, _ := basicProxyAuth(req.Header.Get("Proxy-Authorization"))
|
||||||
|
if u != "" {
|
||||||
|
u += "@"
|
||||||
|
}
|
||||||
|
log.Logf("[http] %s%s -> %s -> %s",
|
||||||
|
u, conn.RemoteAddr(), h.options.Node.String(), host)
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpRequest(req, false)
|
||||||
|
log.Logf("[http] %s -> %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Del("Gost-Target")
|
||||||
|
*/
|
||||||
|
resp := &http.Response{
|
||||||
|
ProtoMajor: 1,
|
||||||
|
ProtoMinor: 1,
|
||||||
|
Header: http.Header{},
|
||||||
|
}
|
||||||
|
resp.Header.Add("Proxy-Agent", h.md.proxyAgent)
|
||||||
|
|
||||||
|
/*
|
||||||
|
if !Can("tcp", host, h.options.Whitelist, h.options.Blacklist) {
|
||||||
|
log.Logf("[http] %s - %s : Unauthorized to tcp connect to %s",
|
||||||
|
conn.RemoteAddr(), conn.LocalAddr(), host)
|
||||||
|
resp.StatusCode = http.StatusForbidden
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
if h.options.Bypass.Contains(host) {
|
||||||
|
resp.StatusCode = http.StatusForbidden
|
||||||
|
|
||||||
|
log.Logf("[http] %s - %s bypass %s",
|
||||||
|
conn.RemoteAddr(), conn.LocalAddr(), host)
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
if !h.authenticate(conn, req, resp) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if req.Method == "PRI" ||
|
||||||
|
(req.Method != http.MethodConnect && req.URL.Scheme != "http") {
|
||||||
|
resp.StatusCode = http.StatusBadRequest
|
||||||
|
/*
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Logf("[http] %s <- %s\n%s",
|
||||||
|
conn.RemoteAddr(), conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
resp.Write(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Del("Proxy-Authorization")
|
||||||
|
|
||||||
|
/*
|
||||||
|
retries := 1
|
||||||
|
if h.options.Chain != nil && h.options.Chain.Retries > 0 {
|
||||||
|
retries = h.options.Chain.Retries
|
||||||
|
}
|
||||||
|
if h.options.Retries > 0 {
|
||||||
|
retries = h.options.Retries
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var cc net.Conn
|
||||||
|
var route *Chain
|
||||||
|
for i := 0; i < retries; i++ {
|
||||||
|
route, err = h.options.Chain.selectRouteFor(host)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[http] %s -> %s : %s",
|
||||||
|
conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
fmt.Fprintf(&buf, "%s -> %s -> ",
|
||||||
|
conn.RemoteAddr(), h.options.Node.String())
|
||||||
|
for _, nd := range route.route {
|
||||||
|
fmt.Fprintf(&buf, "%d@%s -> ", nd.ID, nd.String())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "%s", host)
|
||||||
|
log.Log("[route]", buf.String())
|
||||||
|
|
||||||
|
// forward http request
|
||||||
|
lastNode := route.LastNode()
|
||||||
|
if req.Method != http.MethodConnect && lastNode.Protocol == "http" {
|
||||||
|
err = h.forwardRequest(conn, req, route)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, err = route.Dial(host,
|
||||||
|
TimeoutChainOption(h.options.Timeout),
|
||||||
|
HostsChainOption(h.options.Hosts),
|
||||||
|
ResolverChainOption(h.options.Resolver),
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Logf("[http] %s -> %s : %s", conn.RemoteAddr(), conn.LocalAddr(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
resp.StatusCode = http.StatusServiceUnavailable
|
||||||
|
|
||||||
|
if Debug {
|
||||||
|
dump, _ := httputil.DumpResponse(resp, false)
|
||||||
|
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump))
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Write(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
cc, err := net.Dial("tcp", host)
|
||||||
|
if err != nil {
|
||||||
|
resp.StatusCode = http.StatusServiceUnavailable
|
||||||
|
resp.Write(conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
if req.Method == http.MethodConnect {
|
||||||
|
b := []byte("HTTP/1.1 200 Connection established\r\n" +
|
||||||
|
"Proxy-Agent: " + h.md.proxyAgent + "\r\n\r\n")
|
||||||
|
conn.Write(b)
|
||||||
|
} else {
|
||||||
|
req.Header.Del("Proxy-Connection")
|
||||||
|
|
||||||
|
if err = req.Write(cc); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.Transport(conn, cc)
|
||||||
|
}
|
6
server/handler/http/metadata.go
Normal file
6
server/handler/http/metadata.go
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
addr string
|
||||||
|
proxyAgent string
|
||||||
|
}
|
3
server/handler/metadata.go
Normal file
3
server/handler/metadata.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
type Metadata map[string]string
|
17
server/handler/option.go
Normal file
17
server/handler/option.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-gost/gost/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Logger logger.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type Option func(opts *Options)
|
||||||
|
|
||||||
|
func LoggerOption(logger logger.Logger) Option {
|
||||||
|
return func(opts *Options) {
|
||||||
|
opts.Logger = logger
|
||||||
|
}
|
||||||
|
}
|
129
server/handler/ss/handler.go
Normal file
129
server/handler/ss/handler.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package ss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-gost/gosocks5"
|
||||||
|
"github.com/go-gost/gost/logger"
|
||||||
|
"github.com/go-gost/gost/server/handler"
|
||||||
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
|
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ handler.Handler = (*Handler)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
logger logger.Logger
|
||||||
|
md metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(opts ...handler.Option) *Handler {
|
||||||
|
options := &handler.Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handler{
|
||||||
|
logger: options.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Init(md handler.Metadata) (err error) {
|
||||||
|
h.md, err = h.parseMetadata(md)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if h.md.cipher != nil {
|
||||||
|
conn = &shadowConn{
|
||||||
|
Conn: h.md.cipher.StreamConn(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.md.readTimeout > 0 {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(h.md.readTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &gosocks5.Addr{}
|
||||||
|
_, err := addr.ReadFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.SetReadDeadline(time.Time{})
|
||||||
|
|
||||||
|
host := addr.String()
|
||||||
|
cc, err := net.Dial("tcp", host)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cc.Close()
|
||||||
|
|
||||||
|
handler.Transport(conn, cc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) parseMetadata(md handler.Metadata) (m metadata, err error) {
|
||||||
|
m.cipher, err = h.initCipher(md[method], md[password], md[key])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, ok := md[readTimeout]; ok {
|
||||||
|
m.readTimeout, _ = time.ParseDuration(v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) initCipher(method, password string, key string) (core.Cipher, error) {
|
||||||
|
if method == "" && password == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c, _ := ss.NewCipher(method, password)
|
||||||
|
if c != nil {
|
||||||
|
return &shadowCipher{cipher: c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return core.PickCipher(method, []byte(key), password)
|
||||||
|
}
|
||||||
|
|
||||||
|
type shadowCipher struct {
|
||||||
|
cipher *ss.Cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
|
||||||
|
return ss.NewConn(conn, c.cipher.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
|
||||||
|
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to in/out byte length is inconsistent of the shadowsocks.Conn.Write,
|
||||||
|
// we wrap around it to make io.Copy happy.
|
||||||
|
type shadowConn struct {
|
||||||
|
net.Conn
|
||||||
|
wbuf bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowConn) Write(b []byte) (n int, err error) {
|
||||||
|
n = len(b) // force byte length consistent
|
||||||
|
if c.wbuf.Len() > 0 {
|
||||||
|
c.wbuf.Write(b) // append the data to the cached header
|
||||||
|
_, err = c.Conn.Write(c.wbuf.Bytes())
|
||||||
|
c.wbuf.Reset()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = c.Conn.Write(b)
|
||||||
|
return
|
||||||
|
}
|
19
server/handler/ss/metadata.go
Normal file
19
server/handler/ss/metadata.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package ss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
method = "method"
|
||||||
|
password = "password"
|
||||||
|
key = "key"
|
||||||
|
readTimeout = "readTimeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
cipher core.Cipher
|
||||||
|
readTimeout time.Duration
|
||||||
|
}
|
80
server/handler/ssu/handler.go
Normal file
80
server/handler/ssu/handler.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package ss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-gost/gost/logger"
|
||||||
|
"github.com/go-gost/gost/server/handler"
|
||||||
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
|
ss "github.com/shadowsocks/shadowsocks-go/shadowsocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ handler.Handler = (*Handler)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
logger logger.Logger
|
||||||
|
md metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(opts ...handler.Option) *Handler {
|
||||||
|
options := &handler.Options{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Handler{
|
||||||
|
logger: options.Logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Init(md handler.Metadata) (err error) {
|
||||||
|
h.md, err = h.parseMetadata(md)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) parseMetadata(md handler.Metadata) (m metadata, err error) {
|
||||||
|
m.cipher, err = h.initCipher(md[method], md[password], md[key])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v, ok := md[readTimeout]; ok {
|
||||||
|
m.readTimeout, _ = time.ParseDuration(v)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) initCipher(method, password string, key string) (core.Cipher, error) {
|
||||||
|
if method == "" && password == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c, _ := ss.NewCipher(method, password)
|
||||||
|
if c != nil {
|
||||||
|
return &shadowCipher{cipher: c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return core.PickCipher(method, []byte(key), password)
|
||||||
|
}
|
||||||
|
|
||||||
|
type shadowCipher struct {
|
||||||
|
cipher *ss.Cipher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowCipher) StreamConn(conn net.Conn) net.Conn {
|
||||||
|
return ss.NewConn(conn, c.cipher.Copy())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *shadowCipher) PacketConn(conn net.PacketConn) net.PacketConn {
|
||||||
|
return ss.NewSecurePacketConn(conn, c.cipher.Copy())
|
||||||
|
}
|
19
server/handler/ssu/metadata.go
Normal file
19
server/handler/ssu/metadata.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package ss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
method = "method"
|
||||||
|
password = "password"
|
||||||
|
key = "key"
|
||||||
|
readTimeout = "readTimeout"
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
cipher core.Cipher
|
||||||
|
readTimeout time.Duration
|
||||||
|
}
|
43
server/handler/transport.go
Normal file
43
server/handler/transport.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
poolBufferSize = 32 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
pool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return make([]byte, poolBufferSize)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Transport(rw1, rw2 io.ReadWriter) error {
|
||||||
|
errc := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errc <- copyBuffer(rw1, rw2)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
errc <- copyBuffer(rw2, rw1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err := <-errc
|
||||||
|
if err != nil && err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyBuffer(dst io.Writer, src io.Reader) error {
|
||||||
|
buf := pool.Get().([]byte)
|
||||||
|
defer pool.Put(buf)
|
||||||
|
|
||||||
|
_, err := io.CopyBuffer(dst, src, buf)
|
||||||
|
return err
|
||||||
|
}
|
14
server/listener/listener.go
Normal file
14
server/listener/listener.go
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
// Listener is a server listener, just like a net.Listener.
|
||||||
|
type Listener interface {
|
||||||
|
Init(md Metadata) error
|
||||||
|
net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accepter represents a network endpoint that can accept connection from peer.
|
||||||
|
type Accepter interface {
|
||||||
|
Accept() (net.Conn, error)
|
||||||
|
}
|
3
server/listener/metadata.go
Normal file
3
server/listener/metadata.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package listener
|
||||||
|
|
||||||
|
type Metadata map[string]string
|
19
server/listener/tcp/metadata.go
Normal file
19
server/listener/tcp/metadata.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package tcp
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
addr = "addr"
|
||||||
|
keepAlive = "keepAlive"
|
||||||
|
keepAlivePeriod = "keepAlivePeriod"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultKeepAlivePeriod = 180 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
type metadata struct {
|
||||||
|
addr string
|
||||||
|
keepAlive bool
|
||||||
|
keepAlivePeriod time.Duration
|
||||||
|
}
|
86
server/listener/tcp/tcp.go
Normal file
86
server/listener/tcp/tcp.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package tcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-gost/gost/server/listener"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
md metadata
|
||||||
|
net.Listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTCPListener() *Listener {
|
||||||
|
return &Listener{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Init(md listener.Metadata) (err error) {
|
||||||
|
l.md, err = l.parseMetadata(md)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
laddr, err := net.ResolveTCPAddr("tcp", l.md.addr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ln, err := net.ListenTCP("tcp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.md.keepAlive {
|
||||||
|
l.Listener = &keepAliveListener{
|
||||||
|
TCPListener: ln,
|
||||||
|
keepAlivePeriod: l.md.keepAlivePeriod,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Listener = ln
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) parseMetadata(md listener.Metadata) (m metadata, err error) {
|
||||||
|
if val, ok := md[addr]; ok {
|
||||||
|
m.addr = val
|
||||||
|
} else {
|
||||||
|
err = errors.New("tcp listener: missing address")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.keepAlive = true
|
||||||
|
if val, ok := md[keepAlive]; ok {
|
||||||
|
m.keepAlive, _ = strconv.ParseBool(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if val, ok := md[keepAlivePeriod]; ok {
|
||||||
|
m.keepAlivePeriod, _ = time.ParseDuration(val)
|
||||||
|
}
|
||||||
|
if m.keepAlivePeriod <= 0 {
|
||||||
|
m.keepAlivePeriod = defaultKeepAlivePeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type keepAliveListener struct {
|
||||||
|
keepAlivePeriod time.Duration
|
||||||
|
*net.TCPListener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *keepAliveListener) Accept() (c net.Conn, err error) {
|
||||||
|
tc, err := l.AcceptTCP()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.SetKeepAlive(true)
|
||||||
|
tc.SetKeepAlivePeriod(l.keepAlivePeriod)
|
||||||
|
|
||||||
|
return tc, nil
|
||||||
|
}
|
12
server/server.go
Normal file
12
server/server.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-gost/gost/server/handler"
|
||||||
|
"github.com/go-gost/gost/server/listener"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is a proxy server.
|
||||||
|
type Server struct {
|
||||||
|
Handler handler.Handler
|
||||||
|
Listener listener.Listener
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user