From 0cac9e1e1f2abad5901ab114a666f05bfa397d37 Mon Sep 17 00:00:00 2001 From: Daniel Ding Date: Fri, 14 Mar 2025 14:18:56 +0800 Subject: [PATCH] fea: merge http and socks5 proxy. --- .../openlan/switch/ceci/https.yaml.example | 12 ++ pkg/config/proxy.go | 103 ++++++++++++++---- pkg/proxy/http.go | 52 +++------ pkg/proxy/socks.go | 2 +- pkg/socks5/socks5.go | 61 ++++------- 5 files changed, 134 insertions(+), 96 deletions(-) create mode 100644 dist/rootfs/etc/openlan/switch/ceci/https.yaml.example diff --git a/dist/rootfs/etc/openlan/switch/ceci/https.yaml.example b/dist/rootfs/etc/openlan/switch/ceci/https.yaml.example new file mode 100644 index 0000000..e8904e5 --- /dev/null +++ b/dist/rootfs/etc/openlan/switch/ceci/https.yaml.example @@ -0,0 +1,12 @@ + +listen: 127.0.0.1:1080 +socks: + listen: 127.0.0.1:1081 +cacert: ca.crt +backends: +- protocol: ssl + server: :18000 + socks: + server: + match: + - a.b.com \ No newline at end of file diff --git a/pkg/config/proxy.go b/pkg/config/proxy.go index bafd499..571a68d 100755 --- a/pkg/config/proxy.go +++ b/pkg/config/proxy.go @@ -2,8 +2,10 @@ package config import ( "flag" + "fmt" "os" "path" + "regexp" "github.com/luscis/openlan/pkg/libol" ) @@ -18,11 +20,69 @@ type ShadowProxy struct { Protocol string `json:"protocol,omitempty"` } +type ForwardSocks struct { + Server string `json:"server,omitempty"` +} + +type HttpForward struct { + Protocol string `json:"protocol,omitempty" yaml:"protocol,omitempty"` + Server string `json:"server,omitempty" yaml:"server,omitempty"` + Insecure bool `json:"insecure,omitempty" yaml:"insecure,omitempty"` + Match []string `json:"match,omitempty" yaml:"match,omitempty"` + Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` + Socks ForwardSocks `json:"socks,omitempty" yaml:"socks,omitempty"` +} + +func (f *HttpForward) SocksAddr() string { + if f.Socks.Server != "" { + return f.Socks.Server + } + return f.Server +} + +type HttpBackends []*HttpForward + +func (h HttpBackends) isMatch(value string, rules []string) bool { + if len(rules) == 0 { + return true + } + + for _, rule := range rules { + pattern := fmt.Sprintf(`(^|\.)%s(:\d+)?$`, regexp.QuoteMeta(rule)) + re := regexp.MustCompile(pattern) + if re.MatchString(value) { + return true + } + } + + return false +} + +func (h HttpBackends) FindBackend(host string) *HttpForward { + for _, via := range h { + if via == nil { + continue + } + if via.Server == "" && via.Socks.Server == "" { + continue + } + if h.isMatch(host, via.Match) { + return via + } + } + + return nil +} + +type FindBackend interface { + FindBackend(host string) *HttpForward +} + type SocksProxy struct { - Conf string `json:"-" yaml:"-"` - Listen string `json:"listen,omitempty" yaml:"listen,omitempty"` - Auth *Password `json:"auth,omitempty" yaml:"auth,omitempty"` - Backends []*HttpForward `json:"backends,omitempty" yaml:"backends,omitempty"` + Conf string `json:"-" yaml:"-"` + Listen string `json:"listen,omitempty" yaml:"listen,omitempty"` + Auth *Password `json:"auth,omitempty" yaml:"auth,omitempty"` + Backends HttpBackends `json:"backends,omitempty" yaml:"backends,omitempty"` } func (s *SocksProxy) Initialize() error { @@ -41,24 +101,21 @@ func (s *SocksProxy) Load() error { return libol.UnmarshalLoad(s, s.Conf) } -type HttpForward struct { - Protocol string `json:"protocol,omitempty" yaml:"protocol,omitempty"` - Server string `json:"server,omitempty" yaml:"server,omitempty"` - Insecure bool `json:"insecure,omitempty" yaml:"insecure,omitempty"` - Match []string `json:"match,omitempty" yaml:"match,omitempty"` - Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` +type HttpSocks struct { + Listen string `json:"listen,omitempty"` } type HttpProxy struct { - Conf string `json:"-" yaml:"-"` - ConfDir string `json:"-" yaml:"-"` - Listen string `json:"listen,omitempty"` - Auth *Password `json:"auth,omitempty" yaml:"auth,omitempty"` - Cert *Cert `json:"cert,omitempty" yaml:"cert,omitempty"` - Password string `json:"password,omitempty" yaml:"password,omitempty"` - CaCert string `json:"cacert,omitempty" yaml:"cacert,omitempty"` - Forward *HttpForward `json:"forward,omitempty" yaml:"forward,omitempty"` - Backends []*HttpForward `json:"backends,omitempty" yaml:"backends,omitempty"` + Conf string `json:"-" yaml:"-"` + ConfDir string `json:"-" yaml:"-"` + Listen string `json:"listen,omitempty"` + Auth *Password `json:"auth,omitempty" yaml:"auth,omitempty"` + Cert *Cert `json:"cert,omitempty" yaml:"cert,omitempty"` + Password string `json:"password,omitempty" yaml:"password,omitempty"` + CaCert string `json:"cacert,omitempty" yaml:"cacert,omitempty"` + Backends HttpBackends `json:"backends,omitempty" yaml:"backends,omitempty"` + Socks *HttpSocks `json:"socks,omitempty" yaml:"socks,omitempty"` + SocksProxy *SocksProxy `json:"-" yaml:"-"` } func (h *HttpProxy) Initialize() error { @@ -93,6 +150,11 @@ func (h *HttpProxy) Correct() { h.CaCert = "ca.crt" } h.CaCert = path.Join(h.ConfDir, h.CaCert) + if h.Socks != nil { + h.SocksProxy = &SocksProxy{ + Listen: h.Socks.Listen, + } + } } func (h *HttpProxy) FindMatch(domain string, to *HttpForward) int { @@ -105,9 +167,6 @@ func (h *HttpProxy) FindMatch(domain string, to *HttpForward) int { } func (h *HttpProxy) FindBackend(remote string) *HttpForward { - if remote == "" || remote == "null" { - return h.Forward - } for _, to := range h.Backends { if to.Server == remote { return to diff --git a/pkg/proxy/http.go b/pkg/proxy/http.go index 22fc812..9da1d9b 100755 --- a/pkg/proxy/http.go +++ b/pkg/proxy/http.go @@ -13,7 +13,6 @@ import ( "net/http" "net/http/httputil" "os" - "regexp" "sort" "strings" "sync" @@ -57,6 +56,7 @@ type HttpProxy struct { startat time.Time requests map[string]*HttpRecord lock sync.RWMutex + socks *SocksProxy } var ( @@ -134,7 +134,10 @@ func NewHttpProxy(cfg *co.HttpProxy, px Proxyer) *HttpProxy { if auth != nil && auth.Username != "" { h.pass[auth.Username] = auth.Password } - + if cfg.SocksProxy != nil { + h.socks = NewSocksProxy(cfg.SocksProxy) + h.socks.server.SetBackends(h) + } h.loadUrl() h.loadPass() @@ -271,7 +274,7 @@ func (t *HttpProxy) toTunnel(w http.ResponseWriter, conn net.Conn, update func(b } func (t *HttpProxy) openConn(protocol, remote string, insecure bool) (net.Conn, error) { - if protocol == "https" { + if protocol == "https" || protocol == "tls" { conf := &tls.Config{ InsecureSkipVerify: insecure, } @@ -294,6 +297,12 @@ func (t *HttpProxy) openConn(protocol, remote string, insecure bool) (net.Conn, return net.DialTimeout("tcp", remote, 10*time.Second) } +func (h *HttpProxy) FindBackend(host string) *co.HttpForward { + h.lock.RLock() + defer h.lock.RUnlock() + return h.cfg.Backends.FindBackend(host) +} + func (t *HttpProxy) cloneRequest(r *http.Request, secret string) ([]byte, error) { var err error var b bytes.Buffer @@ -344,36 +353,6 @@ func (t *HttpProxy) cloneRequest(r *http.Request, secret string) ([]byte, error) return b.Bytes(), nil } -func (t *HttpProxy) isMatch(value string, rules []string) bool { - if len(rules) == 0 { - return true - } - for _, rule := range rules { - pattern := fmt.Sprintf(`(^|\.)%s(:\d+)?$`, regexp.QuoteMeta(rule)) - re := regexp.MustCompile(pattern) - if re.MatchString(value) { - return true - } - } - return false -} - -func (t *HttpProxy) findForward(r *http.Request) *co.HttpForward { - t.lock.RLock() - defer t.lock.RUnlock() - - via := t.cfg.Forward - if via != nil && t.isMatch(r.URL.Host, via.Match) { - return via - } - for _, via := range t.cfg.Backends { - if via != nil && t.isMatch(r.URL.Host, via.Match) { - return via - } - } - return nil -} - func (t *HttpProxy) doRecord(r *http.Request, bytes int64) { t.lock.Lock() defer t.lock.Unlock() @@ -401,7 +380,7 @@ func (t *HttpProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) { } t.doRecord(r, 0) - via := t.findForward(r) + via := t.FindBackend(r.URL.Host) if via != nil { t.out.Info("HttpProxy.ServeHTTP %s %s -> %s via %s", r.Method, r.RemoteAddr, r.URL.Host, via.Server) conn, err := t.openConn(via.Protocol, via.Server, via.Insecure) @@ -450,6 +429,11 @@ func (t *HttpProxy) Start() { if t.server == nil || t.cfg == nil { return } + + if t.socks != nil { + t.socks.Start() + } + crt := t.cfg.Cert if crt == nil || crt.KeyFile == "" { t.out.Info("HttpProxy.start http://%s", t.server.Addr) diff --git a/pkg/proxy/socks.go b/pkg/proxy/socks.go index 530929e..0022c25 100755 --- a/pkg/proxy/socks.go +++ b/pkg/proxy/socks.go @@ -50,7 +50,7 @@ func (s *SocksProxy) Start() { return } addr := s.cfg.Listen - s.out.Info("SocksProxy.Start: %s", s.cfg.Listen) + s.out.Info("SocksProxy.Start: socks5://%s", s.cfg.Listen) promise := &libol.Promise{ First: time.Second * 2, diff --git a/pkg/socks5/socks5.go b/pkg/socks5/socks5.go index 6decdfc..6176ae5 100644 --- a/pkg/socks5/socks5.go +++ b/pkg/socks5/socks5.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "net" - "regexp" "time" co "github.com/luscis/openlan/pkg/config" @@ -54,7 +53,7 @@ type Config struct { Dial func(ctx context.Context, network, addr string) (net.Conn, error) // Backends forwarding socks request - Backends []*co.HttpForward + Backends co.FindBackend } // Server is reponsible for accepting connections and handling @@ -121,7 +120,6 @@ func (s *Server) Serve(l net.Listener) error { } go s.ServeConn(conn) } - return nil } // ServeConn is used to serve a single connection. @@ -132,14 +130,14 @@ func (s *Server) ServeConn(conn net.Conn) error { // Read the version byte version := []byte{0} if _, err := bufConn.Read(version); err != nil { - s.config.Logger.Error("socks: Failed to get version byte: %v", err) + s.config.Logger.Error("Socks.ServeConn Failed to get version byte: %v", err) return err } // Ensure we are compatible if version[0] != socks5Version { err := fmt.Errorf("Unsupported SOCKS version: %v", version) - s.config.Logger.Error("socks: %v", err) + s.config.Logger.Error("Socks.ServeConn %v", err) return err } @@ -147,7 +145,7 @@ func (s *Server) ServeConn(conn net.Conn) error { authContext, err := s.authenticate(conn, bufConn) if err != nil { err = fmt.Errorf("Failed to authenticate: %v", err) - s.config.Logger.Error("socks: %v", err) + s.config.Logger.Error("Socks.ServeConn %v", err) return err } @@ -166,20 +164,22 @@ func (s *Server) ServeConn(conn net.Conn) error { } dstAddr := request.DestAddr - via := s.findForward(dstAddr.Address()) - if via != nil { - if err := s.toForward(request, conn, via); err != nil { - s.config.Logger.Error("socks.forward: %v", err) - return err + if s.config.Backends != nil { + via := s.config.Backends.FindBackend(dstAddr.Address()) + if via != nil { + if err := s.toForward(request, conn, via); err != nil { + s.config.Logger.Error("Socks.ServeConn: %v", err) + return err + } + return nil } - return nil } - s.config.Logger.Info("socks.ServeConn: %s", dstAddr.Address()) + s.config.Logger.Info("Socks.ServeConn CONNECT %s", dstAddr.Address()) //Process the client request if err := s.handleRequest(request, conn); err != nil { err = fmt.Errorf("Failed to handle request: %v", err) - s.config.Logger.Error("socks: %v", err) + s.config.Logger.Error("Socks.ServeConn %v", err) } return nil @@ -205,34 +205,13 @@ func (s *Server) openConn(remote string) (net.Conn, error) { return net.DialTimeout("tcp", remote, 10*time.Second) } -func (s *Server) isMatch(value string, rules []string) bool { - if len(rules) == 0 { - return true - } - for _, rule := range rules { - pattern := fmt.Sprintf(`(^|\.)%s(:\d+)?$`, regexp.QuoteMeta(rule)) - re := regexp.MustCompile(pattern) - if re.MatchString(value) { - return true - } - } - return false -} - -func (s *Server) findForward(host string) *co.HttpForward { - for _, via := range s.config.Backends { - if via != nil && s.isMatch(host, via.Match) { - return via - } - } - return nil -} - func (s *Server) toForward(req *Request, local net.Conn, via *co.HttpForward) error { dstAddr := req.DestAddr - s.config.Logger.Info("socks.toForward: %s via %s", dstAddr.Address(), via.Server) + proxy := via.SocksAddr() - target, err := s.openConn(via.Server) + s.config.Logger.Info("Socks.ServeConn CONNECT %s via %s", dstAddr.Address(), proxy) + + target, err := s.openConn(proxy) if err != nil { sendReply(local, networkUnreachable, nil) return err @@ -270,3 +249,7 @@ func (s *Server) toForward(req *Request, local net.Conn, via *co.HttpForward) er s.toTunnel(local, target) return nil } + +func (s *Server) SetBackends(find co.FindBackend) { + s.config.Backends = find +}