mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-16 04:42:22 +08:00
draft
This commit is contained in:
parent
794506c42d
commit
dbdb8ef472
|
@ -2,7 +2,6 @@ package adapter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
@ -12,7 +11,3 @@ type MITMService interface {
|
||||||
Service
|
Service
|
||||||
ProcessConnection(ctx context.Context, conn net.Conn, dialer N.Dialer, metadata InboundContext) (net.Conn, error)
|
ProcessConnection(ctx context.Context, conn net.Conn, dialer N.Dialer, metadata InboundContext) (net.Conn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TLSOutbound interface {
|
|
||||||
NewTLSConnection(ctx context.Context, conn net.Conn, tlsConfig *tls.Config, metadata InboundContext) error
|
|
||||||
}
|
|
||||||
|
|
13
mitm/engine.go
Normal file
13
mitm/engine.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package mitm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Engine interface {
|
||||||
|
ProcessConnection(ctx context.Context, clientConn net.Conn, serverConn *tls.Conn, metadata adapter.InboundContext) (net.Conn, error)
|
||||||
|
}
|
135
mitm/http.go
Normal file
135
mitm/http.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package mitm
|
||||||
|
|
||||||
|
import (
|
||||||
|
std_bufio "bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"github.com/sagernet/sing-box/adapter"
|
||||||
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/option"
|
||||||
|
"github.com/sagernet/sing/common/buf"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
"github.com/sagernet/sing/common/logger"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ Engine = (*HTTPEngine)(nil)
|
||||||
|
|
||||||
|
type HTTPEngine struct {
|
||||||
|
logger logger.ContextLogger
|
||||||
|
urlRewriteRules []HTTPHandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTTPEngine(logger logger.ContextLogger, options option.MITMHTTPOptions) (*HTTPEngine, error) {
|
||||||
|
engine := &HTTPEngine{
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
for i, urlRewritePath := range options.URLRewritePath {
|
||||||
|
urlRewriteFile, err := os.Open(C.BasePath(urlRewritePath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read url rewrite configuration[", i, "]")
|
||||||
|
}
|
||||||
|
urlRewriteRules, err := readSurgeURLRewriteRules(urlRewriteFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "read url rewrite configuration[", i, "] at ", urlRewritePath)
|
||||||
|
}
|
||||||
|
engine.urlRewriteRules = append(engine.urlRewriteRules, urlRewriteRules...)
|
||||||
|
}
|
||||||
|
return engine, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HTTPEngine) ProcessConnection(ctx context.Context, clientConn net.Conn, serverConn *tls.Conn, metadata adapter.InboundContext) (net.Conn, error) {
|
||||||
|
buffer := buf.NewPacket()
|
||||||
|
httpRequest, err := http.ReadRequest(std_bufio.NewReader(io.TeeReader(clientConn, buffer)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
e.logger.DebugContext(ctx, "HTTP ", httpRequest.Method, " ", httpRequest.URL.String(), " ", httpRequest.Proto)
|
||||||
|
var httpServer http.Server
|
||||||
|
var handled bool
|
||||||
|
httpConn := &httpMITMConn{Conn: bufio.NewCachedConn(clientConn, buffer.ToOwned()), readOnly: true}
|
||||||
|
processCtx, cancel := context.WithCancel(ctx)
|
||||||
|
httpServer.Handler = http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||||
|
if request.Method == "PRI" && len(request.Header) == 0 && request.URL.Path == "*" && request.Proto == "HTTP/2.0" {
|
||||||
|
httpConn.readOnly = false
|
||||||
|
h2c.NewHandler(httpServer.Handler, new(http2.Server)).ServeHTTP(writer, request)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer cancel()
|
||||||
|
httpConn.readOnly = false
|
||||||
|
url := *request.URL
|
||||||
|
url.Scheme = "https"
|
||||||
|
url.Host = request.Host
|
||||||
|
urlString := url.String()
|
||||||
|
for _, rule := range e.urlRewriteRules {
|
||||||
|
if rule(writer, request, urlString) {
|
||||||
|
handled = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !handled {
|
||||||
|
httpConn.readOnly = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
_ = httpServer.Serve(&fixedListener{conn: httpConn})
|
||||||
|
<-processCtx.Done()
|
||||||
|
if !handled {
|
||||||
|
if !httpConn.readOnly {
|
||||||
|
return nil, E.New("http2 description failed")
|
||||||
|
}
|
||||||
|
return bufio.NewCachedConn(clientConn, buffer), nil
|
||||||
|
}
|
||||||
|
serverConn.Close()
|
||||||
|
buffer.Release()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpMITMConn struct {
|
||||||
|
net.Conn
|
||||||
|
readOnly bool
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpMITMConn) Write(p []byte) (n int, err error) {
|
||||||
|
if c.readOnly {
|
||||||
|
return 0, os.ErrInvalid
|
||||||
|
}
|
||||||
|
return c.Conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpMITMConn) Close() error {
|
||||||
|
c.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpMITMConn) Upstream() any {
|
||||||
|
return c.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
type fixedListener struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *fixedListener) Accept() (net.Conn, error) {
|
||||||
|
conn := l.conn
|
||||||
|
l.conn = nil
|
||||||
|
if conn != nil {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, os.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *fixedListener) Addr() net.Addr {
|
||||||
|
return M.Socksaddr{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *fixedListener) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
74
mitm/http_surge_url_rewrite.go
Normal file
74
mitm/http_surge_url_rewrite.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package mitm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTTPHandlerFunc func(writer http.ResponseWriter, request *http.Request, urlString string) bool
|
||||||
|
|
||||||
|
func readSurgeURLRewriteRules(file *os.File) ([]HTTPHandlerFunc, error) {
|
||||||
|
defer file.Close()
|
||||||
|
reader := bufio.NewReader(file)
|
||||||
|
var handlers []HTTPHandlerFunc
|
||||||
|
for {
|
||||||
|
lineBytes, _, err := reader.ReadLine()
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ruleLine := strings.TrimSpace(string(lineBytes))
|
||||||
|
if ruleLine == "" || ruleLine[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ruleParts := strings.Split(ruleLine, " ")
|
||||||
|
if len(ruleParts) != 3 {
|
||||||
|
return nil, E.New("invalid surge url rewrite line: ", ruleLine)
|
||||||
|
}
|
||||||
|
urlRegex, err := regexp.Compile(ruleParts[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "invalid surge url rewrite line (bad regex): ", ruleLine)
|
||||||
|
}
|
||||||
|
switch ruleParts[2] {
|
||||||
|
case "reject":
|
||||||
|
handlers = append(handlers, surgeURLRewriteReject(urlRegex))
|
||||||
|
case "header":
|
||||||
|
// TODO: support header redirect
|
||||||
|
fallthrough
|
||||||
|
case "302":
|
||||||
|
handlers = append(handlers, surgeURLRewrite302(urlRegex, ruleParts[1]))
|
||||||
|
default:
|
||||||
|
return nil, E.Cause(err, "invalid surge url rewrite line (unknown acton): ", ruleLine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return handlers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func surgeURLRewriteReject(urlRegex *regexp.Regexp) HTTPHandlerFunc {
|
||||||
|
return func(writer http.ResponseWriter, request *http.Request, urlString string) bool {
|
||||||
|
if !urlRegex.MatchString(urlString) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
writer.WriteHeader(404)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func surgeURLRewrite302(urlRegex *regexp.Regexp, rewriteURL string) HTTPHandlerFunc {
|
||||||
|
return func(writer http.ResponseWriter, request *http.Request, urlString string) bool {
|
||||||
|
if !urlRegex.MatchString(urlString) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// use 307 to keep method
|
||||||
|
http.RedirectHandler(rewriteURL, http.StatusTemporaryRedirect).ServeHTTP(writer, request)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ type Service struct {
|
||||||
keyPath string
|
keyPath string
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
insecure bool
|
insecure bool
|
||||||
|
engines []Engine
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(router adapter.Router, logger logger.ContextLogger, options option.MITMServiceOptions) (*Service, error) {
|
func NewService(router adapter.Router, logger logger.ContextLogger, options option.MITMServiceOptions) (*Service, error) {
|
||||||
|
@ -80,6 +81,14 @@ func NewService(router adapter.Router, logger logger.ContextLogger, options opti
|
||||||
insecure: options.Insecure,
|
insecure: options.Insecure,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if options.HTTP != nil && options.HTTP.Enabled {
|
||||||
|
engine, err := NewHTTPEngine(logger, common.PtrValueOrDefault(options.HTTP))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
service.engines = append(service.engines, engine)
|
||||||
|
}
|
||||||
|
|
||||||
return service, nil
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,8 +128,6 @@ func (s *Service) ProcessConnection(ctx context.Context, conn net.Conn, dialer N
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, N.HandshakeFailure(conn, err)
|
return nil, N.HandshakeFailure(conn, err)
|
||||||
}
|
}
|
||||||
clientConn := tls.Server(bufio.NewCachedConn(conn, buffer), &tls.Config{
|
|
||||||
GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
|
||||||
var serverConfig tls.Config
|
var serverConfig tls.Config
|
||||||
serverConfig.Time = s.router.TimeFunc()
|
serverConfig.Time = s.router.TimeFunc()
|
||||||
if serverConn.ConnectionState().NegotiatedProtocol != "" {
|
if serverConn.ConnectionState().NegotiatedProtocol != "" {
|
||||||
|
@ -131,13 +138,20 @@ func (s *Service) ProcessConnection(ctx context.Context, conn net.Conn, dialer N
|
||||||
serverConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
serverConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
return sTLS.GenerateKeyPair(nil, serverConfig.ServerName, s.tlsCertificate)
|
return sTLS.GenerateKeyPair(nil, serverConfig.ServerName, s.tlsCertificate)
|
||||||
}
|
}
|
||||||
return &serverConfig, nil
|
|
||||||
},
|
clientTLSConn := tls.Server(bufio.NewCachedConn(conn, buffer), &serverConfig)
|
||||||
})
|
err = clientTLSConn.HandshakeContext(ctx)
|
||||||
err = clientConn.HandshakeContext(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "mitm TLS handshake")
|
return nil, E.Cause(err, "mitm TLS handshake")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var clientConn net.Conn = clientTLSConn
|
||||||
|
for _, engine := range s.engines {
|
||||||
|
clientConn, err = engine.ProcessConnection(ctx, clientTLSConn, serverConn, metadata)
|
||||||
|
if conn == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
s.logger.DebugContext(ctx, "mitm TLS handshake success")
|
s.logger.DebugContext(ctx, "mitm TLS handshake success")
|
||||||
return nil, bufio.CopyConn(ctx, clientConn, serverConn)
|
return nil, bufio.CopyConn(ctx, clientConn, serverConn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,10 @@ type MITMServiceOptions struct {
|
||||||
CertificatePath string `json:"certificate_path,omitempty"`
|
CertificatePath string `json:"certificate_path,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyPath string `json:"key_path,omitempty"`
|
KeyPath string `json:"key_path,omitempty"`
|
||||||
|
HTTP *MITMHTTPOptions `json:"http,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MITMHTTPOptions struct {
|
||||||
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
|
URLRewritePath []string `json:"url_rewrite_path,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user