add bypass

This commit is contained in:
ginuerzh 2021-10-31 22:26:27 +08:00
parent 64736585ee
commit e2995ece96
18 changed files with 433 additions and 132 deletions

View File

@ -4,6 +4,7 @@ import (
"io" "io"
"os" "os"
"github.com/go-gost/gost/pkg/bypass"
"github.com/go-gost/gost/pkg/chain" "github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/components/connector" "github.com/go-gost/gost/pkg/components/connector"
"github.com/go-gost/gost/pkg/components/dialer" "github.com/go-gost/gost/pkg/components/dialer"
@ -16,12 +17,23 @@ import (
"github.com/go-gost/gost/pkg/service" "github.com/go-gost/gost/pkg/service"
) )
var (
chains = make(map[string]*chain.Chain)
bypasses = make(map[string]bypass.Bypass)
)
func buildService(cfg *config.Config) (services []*service.Service) { func buildService(cfg *config.Config) (services []*service.Service) {
if cfg == nil || len(cfg.Services) == 0 { if cfg == nil || len(cfg.Services) == 0 {
return return
} }
chains := buildChain(cfg) for _, bypassCfg := range cfg.Bypasses {
bypasses[bypassCfg.Name] = bypassFromConfig(&bypassCfg)
}
for _, chainCfg := range cfg.Chains {
chains[chainCfg.Name] = chainFromConfig(&chainCfg)
}
for _, svc := range cfg.Services { for _, svc := range cfg.Services {
listenerLogger := log.WithFields(map[string]interface{}{ listenerLogger := log.WithFields(map[string]interface{}{
@ -37,21 +49,15 @@ func buildService(cfg *config.Config) (services []*service.Service) {
listenerLogger.Fatal("init:", err) listenerLogger.Fatal("init:", err)
} }
var chain *chain.Chain
for _, ch := range chains {
if svc.Chain == ch.Name {
chain = ch
break
}
}
handlerLogger := log.WithFields(map[string]interface{}{ handlerLogger := log.WithFields(map[string]interface{}{
"kind": "handler", "kind": "handler",
"type": svc.Handler.Type, "type": svc.Handler.Type,
"service": svc.Name, "service": svc.Name,
}) })
h := registry.GetHandler(svc.Handler.Type)( h := registry.GetHandler(svc.Handler.Type)(
handler.ChainOption(chain), handler.ChainOption(chains[svc.Chain]),
handler.BypassOption(bypasses[svc.Bypass]),
handler.LoggerOption(handlerLogger), handler.LoggerOption(handlerLogger),
) )
if err := h.Init(metadata.MapMetadata(svc.Handler.Metadata)); err != nil { if err := h.Init(metadata.MapMetadata(svc.Handler.Metadata)); err != nil {
@ -67,27 +73,23 @@ func buildService(cfg *config.Config) (services []*service.Service) {
return return
} }
func buildChain(cfg *config.Config) (chains []*chain.Chain) { func chainFromConfig(cfg *config.ChainConfig) *chain.Chain {
if cfg == nil || len(cfg.Chains) == 0 { if cfg == nil {
return nil return nil
} }
for _, ch := range cfg.Chains { c := &chain.Chain{}
c := &chain.Chain{
Name: ch.Name,
}
selector := selectorFromConfig(ch.LB) selector := selectorFromConfig(cfg.LB)
for _, hop := range ch.Hops { for _, hop := range cfg.Hops {
group := &chain.NodeGroup{} group := &chain.NodeGroup{}
for _, v := range hop.Nodes { for _, v := range hop.Nodes {
node := chain.NewNode(v.Name, v.Addr)
connectorLogger := log.WithFields(map[string]interface{}{ connectorLogger := log.WithFields(map[string]interface{}{
"kind": "connector", "kind": "connector",
"type": v.Connector.Type, "type": v.Connector.Type,
"hop": hop.Name, "hop": hop.Name,
"node": node.Name(), "node": v.Name,
}) })
cr := registry.GetConnector(v.Connector.Type)( cr := registry.GetConnector(v.Connector.Type)(
connector.LoggerOption(connectorLogger), connector.LoggerOption(connectorLogger),
@ -100,7 +102,7 @@ func buildChain(cfg *config.Config) (chains []*chain.Chain) {
"kind": "dialer", "kind": "dialer",
"type": v.Dialer.Type, "type": v.Dialer.Type,
"hop": hop.Name, "hop": hop.Name,
"node": node.Name(), "node": v.Name,
}) })
d := registry.GetDialer(v.Dialer.Type)( d := registry.GetDialer(v.Dialer.Type)(
dialer.LoggerOption(dialerLogger), dialer.LoggerOption(dialerLogger),
@ -113,7 +115,9 @@ func buildChain(cfg *config.Config) (chains []*chain.Chain) {
WithConnector(cr). WithConnector(cr).
WithDialer(d) WithDialer(d)
node.WithTransport(tr) node := chain.NewNode(v.Name, v.Addr).
WithTransport(tr).
WithBypass(bypasses[v.Bypass])
group.AddNode(node) group.AddNode(node)
} }
@ -125,10 +129,7 @@ func buildChain(cfg *config.Config) (chains []*chain.Chain) {
c.AddNodeGroup(group) c.AddNodeGroup(group)
} }
chains = append(chains, c) return c
}
return
} }
func logFromConfig(cfg *config.LogConfig) logger.Logger { func logFromConfig(cfg *config.LogConfig) logger.Logger {
@ -182,3 +183,11 @@ func selectorFromConfig(cfg *config.LoadbalancingConfig) chain.Selector {
}, },
) )
} }
func bypassFromConfig(cfg *config.BypassConfig) bypass.Bypass {
if cfg == nil {
return nil
}
return bypass.NewBypassPatterns(cfg.Reverse, cfg.Matchers...)
}

View File

@ -22,6 +22,7 @@ services:
metadata: metadata:
keepAlive: 15s keepAlive: 15s
chain: chain01 chain: chain01
# bypass: bypass01
chains: chains:
- name: chain01 - name: chain01
@ -41,6 +42,7 @@ chains:
- name: node01 - name: node01
addr: ":8081" addr: ":8081"
url: "http://gost:gost@:8081" url: "http://gost:gost@:8081"
# bypass: bypass01
connector: connector:
type: http type: http
metadata: metadata:
@ -52,11 +54,12 @@ chains:
- name: node02 - name: node02
addr: ":8082" addr: ":8082"
url: "http://gost:gost@:8082" url: "http://gost:gost@:8082"
# bypass: bypass01
connector: connector:
type: http type: http
metadata: metadata:
userAgent: "gost/3.0" userAgent: "gost/3.0"
auth: user1:pass1 auth: user2:pass2
dialer: dialer:
type: tcp type: tcp
metadata: {} metadata: {}
@ -70,11 +73,42 @@ chains:
- name: node03 - name: node03
addr: ":8083" addr: ":8083"
url: "http://gost:gost@:8083" url: "http://gost:gost@:8083"
# bypass: bypass01
connector: connector:
type: http type: http
metadata: metadata:
userAgent: "gost/3.0" userAgent: "gost/3.0"
auth: user1:pass1 auth: user3:pass3
dialer: dialer:
type: tcp type: tcp
metadata: {} metadata: {}
bypasses:
- name: bypass01
reverse: false
matchers:
- .baidu.com
- "*.example.com" # domain wildcard
- .example.org # will match example.org and *.example.org
# From IANA IPv4 Special-Purpose Address Registry
# http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
- 0.0.0.0/8 # RFC1122: "This host on this network"
- 10.0.0.0/8 # RFC1918: Private-Use
- 100.64.0.0/10 # RFC6598: Shared Address Space
- 127.0.0.0/8 # RFC1122: Loopback
- 169.254.0.0/16 # RFC3927: Link Local
- 172.16.0.0/12 # RFC1918: Private-Use
- 192.0.0.0/24 # RFC6890: IETF Protocol Assignments
- 192.0.2.0/24 # RFC5737: Documentation (TEST-NET-1)
- 192.88.99.0/24 # RFC3068: 6to4 Relay Anycast
- 192.168.0.0/16 # RFC1918: Private-Use
- 198.18.0.0/15 # RFC2544: Benchmarking
- 198.51.100.0/24 # RFC5737: Documentation (TEST-NET-2)
- 203.0.113.0/24 # RFC5737: Documentation (TEST-NET-3)
- 240.0.0.0/4 # RFC1112: Reserved
- 255.255.255.255/32 # RFC0919: Limited Broadcast
# From IANA Multicast Address Space Registry
# http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml
- 224.0.0.0/4 # RFC5771: Multicast/Reserved

1
go.mod
View File

@ -7,6 +7,7 @@ require (
github.com/coreos/go-iptables v0.5.0 // indirect github.com/coreos/go-iptables v0.5.0 // indirect
github.com/ginuerzh/tls-dissector v0.0.2-0.20201202075250-98fa925912da github.com/ginuerzh/tls-dissector v0.0.2-0.20201202075250-98fa925912da
github.com/go-gost/gosocks5 v0.3.0 github.com/go-gost/gosocks5 v0.3.0
github.com/gobwas/glob v0.2.3
github.com/golang/snappy v0.0.3 github.com/golang/snappy v0.0.3
github.com/google/gopacket v1.1.19 // indirect github.com/google/gopacket v1.1.19 // indirect
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2

2
go.sum
View File

@ -113,6 +113,8 @@ github.com/go-gost/gosocks5 v0.3.0 h1:Hkmp9YDRBSCJd7xywW6dBPT6B9aQTkuWd+3WCheJiJ
github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4= github.com/go-gost/gosocks5 v0.3.0/go.mod h1:1G6I7HP7VFVxveGkoK8mnprnJqSqJjdcASKsdUn4Pp4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=

158
pkg/bypass/bypass.go Normal file
View File

@ -0,0 +1,158 @@
package bypass
import (
"net"
"strconv"
"strings"
glob "github.com/gobwas/glob"
)
// Matcher is a generic pattern matcher,
// it gives the match result of the given pattern for specific v.
type Matcher interface {
Match(v string) bool
}
// NewMatcher creates a Matcher for the given pattern.
// The acutal Matcher depends on the pattern:
// IP Matcher if pattern is a valid IP address.
// CIDR Matcher if pattern is a valid CIDR address.
// Domain Matcher if both of the above are not.
func NewMatcher(pattern string) Matcher {
if pattern == "" {
return nil
}
if ip := net.ParseIP(pattern); ip != nil {
return IPMatcher(ip)
}
if _, inet, err := net.ParseCIDR(pattern); err == nil {
return CIDRMatcher(inet)
}
return DomainMatcher(pattern)
}
type ipMatcher struct {
ip net.IP
}
// IPMatcher creates a Matcher for a specific IP address.
func IPMatcher(ip net.IP) Matcher {
return &ipMatcher{
ip: ip,
}
}
func (m *ipMatcher) Match(ip string) bool {
if m == nil {
return false
}
return m.ip.Equal(net.ParseIP(ip))
}
type cidrMatcher struct {
ipNet *net.IPNet
}
// CIDRMatcher creates a Matcher for a specific CIDR notation IP address.
func CIDRMatcher(inet *net.IPNet) Matcher {
return &cidrMatcher{
ipNet: inet,
}
}
func (m *cidrMatcher) Match(ip string) bool {
if m == nil || m.ipNet == nil {
return false
}
return m.ipNet.Contains(net.ParseIP(ip))
}
type domainMatcher struct {
pattern string
glob glob.Glob
}
// DomainMatcher creates a Matcher for a specific domain pattern,
// the pattern can be a plain domain such as 'example.com',
// a wildcard such as '*.exmaple.com' or a special wildcard '.example.com'.
func DomainMatcher(pattern string) Matcher {
p := pattern
if strings.HasPrefix(pattern, ".") {
p = pattern[1:] // trim the prefix '.'
pattern = "*" + p
}
return &domainMatcher{
pattern: p,
glob: glob.MustCompile(pattern),
}
}
func (m *domainMatcher) Match(domain string) bool {
if m == nil || m.glob == nil {
return false
}
if domain == m.pattern {
return true
}
return m.glob.Match(domain)
}
// Bypass is a filter of address (IP or domain).
type Bypass interface {
// Contains reports whether the bypass includes addr.
Contains(addr string) bool
}
type bypass struct {
matchers []Matcher
reversed bool
}
// NewBypass creates and initializes a new Bypass using matchers as its match rules.
// The rules will be reversed if the reversed is true.
func NewBypass(reversed bool, matchers ...Matcher) Bypass {
return &bypass{
matchers: matchers,
reversed: reversed,
}
}
// NewBypassPatterns creates and initializes a new Bypass using matcher patterns as its match rules.
// The rules will be reversed if the reverse is true.
func NewBypassPatterns(reversed bool, patterns ...string) Bypass {
var matchers []Matcher
for _, pattern := range patterns {
if m := NewMatcher(pattern); m != nil {
matchers = append(matchers, m)
}
}
return NewBypass(reversed, matchers...)
}
func (bp *bypass) Contains(addr string) bool {
if addr == "" || bp == nil || len(bp.matchers) == 0 {
return false
}
// try to strip the port
if host, port, _ := net.SplitHostPort(addr); host != "" && port != "" {
if p, _ := strconv.Atoi(port); p > 0 { // port is valid
addr = host
}
}
var matched bool
for _, matcher := range bp.matchers {
if matcher == nil {
continue
}
if matcher.Match(addr) {
matched = true
break
}
}
return !bp.reversed && matched ||
bp.reversed && !matched
}

View File

@ -1,7 +1,6 @@
package chain package chain
type Chain struct { type Chain struct {
Name string
groups []*NodeGroup groups []*NodeGroup
} }
@ -9,7 +8,7 @@ func (c *Chain) AddNodeGroup(group *NodeGroup) {
c.groups = append(c.groups, group) c.groups = append(c.groups, group)
} }
func (c *Chain) GetRoute() (r *Route) { func (c *Chain) GetRouteFor(addr string) (r *Route) {
if c == nil || len(c.groups) == 0 { if c == nil || len(c.groups) == 0 {
return return
} }
@ -20,11 +19,13 @@ func (c *Chain) GetRoute() (r *Route) {
if node == nil { if node == nil {
return return
} }
// TODO: bypass if node.bypass != nil && node.bypass.Contains(addr) {
break
}
if node.Transport().IsMultiplex() { if node.transport.IsMultiplex() {
tr := node.Transport().WithRoute(r) tr := node.transport.Copy().WithRoute(r)
node = node.WithTransport(tr) node = node.Copy().WithTransport(tr)
r = &Route{} r = &Route{}
} }

View File

@ -3,12 +3,15 @@ package chain
import ( import (
"sync" "sync"
"time" "time"
"github.com/go-gost/gost/pkg/bypass"
) )
type Node struct { type Node struct {
name string name string
addr string addr string
transport *Transport transport *Transport
bypass bypass.Bypass
marker *failMarker marker *failMarker
} }
@ -28,15 +31,22 @@ func (node *Node) Addr() string {
return node.addr return node.addr
} }
func (node *Node) Transport() *Transport {
return node.transport
}
func (node *Node) WithTransport(tr *Transport) *Node { func (node *Node) WithTransport(tr *Transport) *Node {
node.transport = tr node.transport = tr
return node return node
} }
func (node *Node) WithBypass(bp bypass.Bypass) *Node {
node.bypass = bp
return node
}
func (node *Node) Copy() *Node {
n := &Node{}
*n = *node
return n
}
type NodeGroup struct { type NodeGroup struct {
nodes []*Node nodes []*Node
selector Selector selector Selector

View File

@ -20,13 +20,13 @@ func (r *Route) Connect(ctx context.Context) (conn net.Conn, err error) {
} }
node := r.nodes[0] node := r.nodes[0]
cc, err := node.Transport().Dial(ctx, r.nodes[0].Addr()) cc, err := node.transport.Dial(ctx, r.nodes[0].Addr())
if err != nil { if err != nil {
node.marker.Mark() node.marker.Mark()
return return
} }
cn, err := node.Transport().Handshake(ctx, cc) cn, err := node.transport.Handshake(ctx, cc)
if err != nil { if err != nil {
cc.Close() cc.Close()
node.marker.Mark() node.marker.Mark()
@ -36,7 +36,7 @@ func (r *Route) Connect(ctx context.Context) (conn net.Conn, err error) {
preNode := node preNode := node
for _, node := range r.nodes[1:] { for _, node := range r.nodes[1:] {
cc, err = preNode.Transport().Connect(ctx, cn, "tcp", node.Addr()) cc, err = preNode.transport.Connect(ctx, cn, "tcp", node.Addr())
if err != nil { if err != nil {
cn.Close() cn.Close()
node.marker.Mark() node.marker.Mark()
@ -68,7 +68,7 @@ func (r *Route) Dial(ctx context.Context, network, address string) (net.Conn, er
return nil, err return nil, err
} }
cc, err := r.Last().Transport().Connect(ctx, conn, network, address) cc, err := r.Last().transport.Connect(ctx, conn, network, address)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, err
@ -93,9 +93,19 @@ func (r *Route) IsEmpty() bool {
return r == nil || len(r.nodes) == 0 return r == nil || len(r.nodes) == 0
} }
func (r Route) Last() *Node { func (r *Route) Last() *Node {
if r.IsEmpty() { if r.IsEmpty() {
return nil return nil
} }
return r.nodes[len(r.nodes)-1] return r.nodes[len(r.nodes)-1]
} }
func (r *Route) Path() (path []*Node) {
for _, node := range r.nodes {
if node.transport != nil && node.transport.route != nil {
path = append(path, node.transport.route.Path()...)
}
path = append(path, node)
}
return
}

View File

@ -14,6 +14,12 @@ type Transport struct {
connector connector.Connector connector connector.Connector
} }
func (tr *Transport) Copy() *Transport {
tr2 := &Transport{}
*tr2 = *tr
return tr
}
func (tr *Transport) WithDialer(dialer dialer.Dialer) *Transport { func (tr *Transport) WithDialer(dialer dialer.Dialer) *Transport {
tr.dialer = dialer tr.dialer = dialer
return tr return tr

View File

@ -21,7 +21,7 @@ func init() {
registry.RegiserConnector("http", NewConnector) registry.RegiserConnector("http", NewConnector)
} }
type Connector struct { type httpConnector struct {
md metadata md metadata
logger logger.Logger logger logger.Logger
} }
@ -32,16 +32,16 @@ func NewConnector(opts ...connector.Option) connector.Connector {
opt(options) opt(options)
} }
return &Connector{ return &httpConnector{
logger: options.Logger, logger: options.Logger,
} }
} }
func (c *Connector) Init(md md.Metadata) (err error) { func (c *httpConnector) Init(md md.Metadata) (err error) {
return c.parseMetadata(md) return c.parseMetadata(md)
} }
func (c *Connector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) { func (c *httpConnector) Connect(ctx context.Context, conn net.Conn, network, address string, opts ...connector.ConnectOption) (net.Conn, error) {
req := &http.Request{ req := &http.Request{
Method: http.MethodConnect, Method: http.MethodConnect,
URL: &url.URL{Host: address}, URL: &url.URL{Host: address},
@ -95,7 +95,7 @@ func (c *Connector) Connect(ctx context.Context, conn net.Conn, network, address
return conn, nil return conn, nil
} }
func (c *Connector) parseMetadata(md md.Metadata) (err error) { func (c *httpConnector) parseMetadata(md md.Metadata) (err error) {
c.md.UserAgent, _ = md.Get(userAgent).(string) c.md.UserAgent, _ = md.Get(userAgent).(string)
if c.md.UserAgent == "" { if c.md.UserAgent == "" {
c.md.UserAgent = defaultUserAgent c.md.UserAgent = defaultUserAgent

View File

@ -14,7 +14,7 @@ func init() {
registry.RegisterDialer("tcp", NewDialer) registry.RegisterDialer("tcp", NewDialer)
} }
type Dialer struct { type tcpDialer struct {
md metadata md metadata
logger logger.Logger logger logger.Logger
} }
@ -25,16 +25,16 @@ func NewDialer(opts ...dialer.Option) dialer.Dialer {
opt(options) opt(options)
} }
return &Dialer{ return &tcpDialer{
logger: options.Logger, logger: options.Logger,
} }
} }
func (d *Dialer) Init(md md.Metadata) (err error) { func (d *tcpDialer) Init(md md.Metadata) (err error) {
return d.parseMetadata(md) return d.parseMetadata(md)
} }
func (d *Dialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) { func (d *tcpDialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOption) (net.Conn, error) {
var options dialer.DialOptions var options dialer.DialOptions
for _, opt := range opts { for _, opt := range opts {
opt(&options) opt(&options)
@ -71,6 +71,6 @@ func (d *Dialer) Dial(ctx context.Context, addr string, opts ...dialer.DialOptio
return conn, err return conn, err
} }
func (d *Dialer) parseMetadata(md md.Metadata) (err error) { func (d *tcpDialer) parseMetadata(md md.Metadata) (err error) {
return return
} }

View File

@ -2,10 +2,12 @@ package http
import ( import (
"bufio" "bufio"
"bytes"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"hash/crc32" "hash/crc32"
"net" "net"
"net/http" "net/http"
@ -16,6 +18,7 @@ import (
"time" "time"
"github.com/go-gost/gost/pkg/auth" "github.com/go-gost/gost/pkg/auth"
"github.com/go-gost/gost/pkg/bypass"
"github.com/go-gost/gost/pkg/chain" "github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/components/handler" "github.com/go-gost/gost/pkg/components/handler"
md "github.com/go-gost/gost/pkg/components/metadata" md "github.com/go-gost/gost/pkg/components/metadata"
@ -27,8 +30,9 @@ func init() {
registry.RegisterHandler("http", NewHandler) registry.RegisterHandler("http", NewHandler)
} }
type Handler struct { type httpHandler struct {
chain *chain.Chain chain *chain.Chain
bypass bypass.Bypass
logger logger.Logger logger logger.Logger
md metadata md metadata
} }
@ -39,17 +43,18 @@ func NewHandler(opts ...handler.Option) handler.Handler {
opt(options) opt(options)
} }
return &Handler{ return &httpHandler{
chain: options.Chain, chain: options.Chain,
bypass: options.Bypass,
logger: options.Logger, logger: options.Logger,
} }
} }
func (h *Handler) Init(md md.Metadata) error { func (h *httpHandler) Init(md md.Metadata) error {
return h.parseMetadata(md) return h.parseMetadata(md)
} }
func (h *Handler) parseMetadata(md md.Metadata) error { func (h *httpHandler) parseMetadata(md md.Metadata) error {
h.md.proxyAgent = md.GetString(proxyAgentKey) h.md.proxyAgent = md.GetString(proxyAgentKey)
if v, _ := md.Get(authsKey).([]interface{}); len(v) > 0 { if v, _ := md.Get(authsKey).([]interface{}); len(v) > 0 {
@ -81,7 +86,7 @@ func (h *Handler) parseMetadata(md md.Metadata) error {
return nil return nil
} }
func (h *Handler) Handle(ctx context.Context, conn net.Conn) { func (h *httpHandler) Handle(ctx context.Context, conn net.Conn) {
defer conn.Close() defer conn.Close()
h.logger = h.logger.WithFields(map[string]interface{}{ h.logger = h.logger.WithFields(map[string]interface{}{
@ -99,7 +104,7 @@ func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
h.handleRequest(ctx, conn, req) h.handleRequest(ctx, conn, req)
} }
func (h *Handler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request) { func (h *httpHandler) handleRequest(ctx context.Context, conn net.Conn, req *http.Request) {
if req == nil { if req == nil {
return return
} }
@ -156,21 +161,18 @@ func (h *Handler) handleRequest(ctx context.Context, conn net.Conn, req *http.Re
} }
*/ */
/* if h.bypass != nil && h.bypass.Contains(host) {
if h.options.Bypass.Contains(host) {
resp.StatusCode = http.StatusForbidden resp.StatusCode = http.StatusForbidden
log.Logf("[http] %s - %s bypass %s", if h.logger.IsLevelEnabled(logger.DebugLevel) {
conn.RemoteAddr(), conn.LocalAddr(), host)
if Debug {
dump, _ := httputil.DumpResponse(resp, false) dump, _ := httputil.DumpResponse(resp, false)
log.Logf("[http] %s <- %s\n%s", conn.RemoteAddr(), conn.LocalAddr(), string(dump)) h.logger.Debug(string(dump))
} }
h.logger.Info("bypass: ", host)
resp.Write(conn) resp.Write(conn)
return return
} }
*/
if !h.authenticate(conn, req, resp) { if !h.authenticate(conn, req, resp) {
return return
@ -200,6 +202,7 @@ func (h *Handler) handleRequest(ctx context.Context, conn net.Conn, req *http.Re
dump, _ := httputil.DumpResponse(resp, false) dump, _ := httputil.DumpResponse(resp, false)
h.logger.Debug(string(dump)) h.logger.Debug(string(dump))
} }
h.logger.Error(err)
return return
} }
defer cc.Close() defer cc.Close()
@ -227,25 +230,21 @@ func (h *Handler) handleRequest(ctx context.Context, conn net.Conn, req *http.Re
handler.Transport(conn, cc) handler.Transport(conn, cc)
} }
func (h *Handler) dial(ctx context.Context, addr string) (conn net.Conn, err error) { func (h *httpHandler) dial(ctx context.Context, addr string) (conn net.Conn, err error) {
count := h.md.retryCount + 1 count := h.md.retryCount + 1
if count <= 0 { if count <= 0 {
count = 1 count = 1
} }
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
route := h.chain.GetRoute() route := h.chain.GetRouteFor(addr)
/*
buf := bytes.Buffer{} buf := bytes.Buffer{}
fmt.Fprintf(&buf, "%s -> %s -> ", for _, node := range route.Path() {
conn.RemoteAddr(), h.options.Node.String()) fmt.Fprintf(&buf, "%s@%s -> ", node.Name(), node.Addr())
for _, nd := range route.route {
fmt.Fprintf(&buf, "%d@%s -> ", nd.ID, nd.String())
} }
fmt.Fprintf(&buf, "%s", host) fmt.Fprintf(&buf, "%s", addr)
log.Log("[route]", buf.String()) h.logger.Infof("route(retry=%d): %s", i, buf.String())
*/
/* /*
// forward http request // forward http request
@ -270,7 +269,7 @@ func (h *Handler) dial(ctx context.Context, addr string) (conn net.Conn, err err
return return
} }
func (h *Handler) decodeServerName(s string) (string, error) { func (h *httpHandler) decodeServerName(s string) (string, error) {
b, err := base64.RawURLEncoding.DecodeString(s) b, err := base64.RawURLEncoding.DecodeString(s)
if err != nil { if err != nil {
return "", err return "", err
@ -288,7 +287,7 @@ func (h *Handler) decodeServerName(s string) (string, error) {
return string(v), nil return string(v), nil
} }
func (h *Handler) basicProxyAuth(proxyAuth string) (username, password string, ok bool) { func (h *httpHandler) basicProxyAuth(proxyAuth string) (username, password string, ok bool) {
if proxyAuth == "" { if proxyAuth == "" {
return return
} }
@ -309,7 +308,7 @@ func (h *Handler) basicProxyAuth(proxyAuth string) (username, password string, o
return cs[:s], cs[s+1:], true return cs[:s], cs[s+1:], true
} }
func (h *Handler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) { func (h *httpHandler) authenticate(conn net.Conn, req *http.Request, resp *http.Response) (ok bool) {
u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization")) u, p, _ := h.basicProxyAuth(req.Header.Get("Proxy-Authorization"))
if h.md.authenticator == nil || h.md.authenticator.Authenticate(u, p) { if h.md.authenticator == nil || h.md.authenticator.Authenticate(u, p) {
return true return true

View File

@ -1 +0,0 @@
package http

View File

@ -1,12 +1,14 @@
package handler package handler
import ( import (
"github.com/go-gost/gost/pkg/bypass"
"github.com/go-gost/gost/pkg/chain" "github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/logger" "github.com/go-gost/gost/pkg/logger"
) )
type Options struct { type Options struct {
Chain *chain.Chain Chain *chain.Chain
Bypass bypass.Bypass
Logger logger.Logger Logger logger.Logger
} }
@ -23,3 +25,9 @@ func ChainOption(chain *chain.Chain) Option {
opts.Chain = chain opts.Chain = chain
} }
} }
func BypassOption(bypass bypass.Bypass) Option {
return func(opts *Options) {
opts.Bypass = bypass
}
}

View File

@ -7,6 +7,8 @@ import (
"time" "time"
"github.com/go-gost/gosocks5" "github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/bypass"
"github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/components/handler" "github.com/go-gost/gost/pkg/components/handler"
md "github.com/go-gost/gost/pkg/components/metadata" md "github.com/go-gost/gost/pkg/components/metadata"
"github.com/go-gost/gost/pkg/logger" "github.com/go-gost/gost/pkg/logger"
@ -19,7 +21,9 @@ func init() {
registry.RegisterHandler("ss", NewHandler) registry.RegisterHandler("ss", NewHandler)
} }
type Handler struct { type ssHandler struct {
chain *chain.Chain
bypass bypass.Bypass
logger logger.Logger logger logger.Logger
md metadata md metadata
} }
@ -30,18 +34,25 @@ func NewHandler(opts ...handler.Option) handler.Handler {
opt(options) opt(options)
} }
return &Handler{ return &ssHandler{
chain: options.Chain,
bypass: options.Bypass,
logger: options.Logger, logger: options.Logger,
} }
} }
func (h *Handler) Init(md md.Metadata) (err error) { func (h *ssHandler) Init(md md.Metadata) (err error) {
return h.parseMetadata(md) return h.parseMetadata(md)
} }
func (h *Handler) Handle(ctx context.Context, conn net.Conn) { func (h *ssHandler) Handle(ctx context.Context, conn net.Conn) {
defer conn.Close() defer conn.Close()
h.logger = h.logger.WithFields(map[string]interface{}{
"src": conn.RemoteAddr().String(),
"local": conn.LocalAddr().String(),
})
if h.md.cipher != nil { if h.md.cipher != nil {
conn = &shadowConn{ conn = &shadowConn{
Conn: h.md.cipher.StreamConn(conn), Conn: h.md.cipher.StreamConn(conn),
@ -61,9 +72,18 @@ func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
conn.SetReadDeadline(time.Time{}) conn.SetReadDeadline(time.Time{})
host := addr.String() h.logger = h.logger.WithFields(map[string]interface{}{
cc, err := net.Dial("tcp", host) "dst": addr.String(),
})
if h.bypass != nil && h.bypass.Contains(addr.String()) {
h.logger.Info("bypass: ", addr.String())
return
}
cc, err := h.dial(ctx, addr.String())
if err != nil { if err != nil {
h.logger.Error(err)
return return
} }
defer cc.Close() defer cc.Close()
@ -71,7 +91,7 @@ func (h *Handler) Handle(ctx context.Context, conn net.Conn) {
handler.Transport(conn, cc) handler.Transport(conn, cc)
} }
func (h *Handler) parseMetadata(md md.Metadata) (err error) { func (h *ssHandler) parseMetadata(md md.Metadata) (err error) {
h.md.cipher, err = h.initCipher( h.md.cipher, err = h.initCipher(
md.GetString(method), md.GetString(method),
md.GetString(password), md.GetString(password),
@ -82,10 +102,41 @@ func (h *Handler) parseMetadata(md md.Metadata) (err error) {
} }
h.md.readTimeout = md.GetDuration(readTimeout) h.md.readTimeout = md.GetDuration(readTimeout)
h.md.retryCount = md.GetInt(retryCount)
return return
} }
func (h *Handler) initCipher(method, password string, key string) (core.Cipher, error) { func (h *ssHandler) dial(ctx context.Context, addr string) (conn net.Conn, err error) {
count := h.md.retryCount + 1
if count <= 0 {
count = 1
}
for i := 0; i < count; i++ {
route := h.chain.GetRouteFor(addr)
/*
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())
*/
conn, err = route.Dial(ctx, "tcp", addr)
if err == nil {
break
}
h.logger.Errorf("route(retry=%d): %s", i, err)
}
return
}
func (h *ssHandler) initCipher(method, password string, key string) (core.Cipher, error) {
if method == "" && password == "" { if method == "" && password == "" {
return nil, nil return nil, nil
} }

View File

@ -11,9 +11,11 @@ const (
password = "password" password = "password"
key = "key" key = "key"
readTimeout = "readTimeout" readTimeout = "readTimeout"
retryCount = "retry"
) )
type metadata struct { type metadata struct {
cipher core.Cipher cipher core.Cipher
readTimeout time.Duration readTimeout time.Duration
retryCount int
} }

View File

@ -30,6 +30,11 @@ type LoadbalancingConfig struct {
FailTimeout time.Duration FailTimeout time.Duration
} }
type BypassConfig struct {
Name string
Reverse bool
Matchers []string
}
type ListenerConfig struct { type ListenerConfig struct {
Type string Type string
Metadata map[string]interface{} Metadata map[string]interface{}
@ -57,6 +62,7 @@ type ServiceConfig struct {
Listener *ListenerConfig Listener *ListenerConfig
Handler *HandlerConfig Handler *HandlerConfig
Chain string Chain string
Bypass string
} }
type ChainConfig struct { type ChainConfig struct {
@ -77,12 +83,14 @@ type NodeConfig struct {
Addr string Addr string
Dialer *DialerConfig Dialer *DialerConfig
Connector *ConnectorConfig Connector *ConnectorConfig
Bypass string
} }
type Config struct { type Config struct {
Log *LogConfig Log *LogConfig
Services []ServiceConfig Services []ServiceConfig
Chains []ChainConfig Chains []ChainConfig
Bypasses []BypassConfig
} }
func (c *Config) Load() error { func (c *Config) Load() error {

View File

@ -85,7 +85,10 @@ func NewLogger(opts ...LoggerOption) Logger {
switch options.Format { switch options.Format {
case JSONFormat: case JSONFormat:
log.SetFormatter(&logrus.JSONFormatter{}) log.SetFormatter(&logrus.JSONFormatter{
DisableHTMLEscape: true,
// PrettyPrint: true,
})
default: default:
log.SetFormatter(&logrus.TextFormatter{ log.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true, FullTimestamp: true,