mirror of
https://github.com/SagerNet/sing-box.git
synced 2024-11-16 10:12:21 +08:00
draft
This commit is contained in:
parent
794506c42d
commit
dbdb8ef472
|
@ -2,7 +2,6 @@ package adapter
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
@ -12,7 +11,3 @@ type MITMService interface {
|
|||
Service
|
||||
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
|
||||
watcher *fsnotify.Watcher
|
||||
insecure bool
|
||||
engines []Engine
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -119,25 +128,30 @@ func (s *Service) ProcessConnection(ctx context.Context, conn net.Conn, dialer N
|
|||
if err != nil {
|
||||
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
|
||||
serverConfig.Time = s.router.TimeFunc()
|
||||
if serverConn.ConnectionState().NegotiatedProtocol != "" {
|
||||
serverConfig.NextProtos = []string{serverConn.ConnectionState().NegotiatedProtocol}
|
||||
}
|
||||
serverConfig.ServerName = clientHello.ServerName
|
||||
serverConfig.MinVersion = tls.VersionTLS10
|
||||
serverConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return sTLS.GenerateKeyPair(nil, serverConfig.ServerName, s.tlsCertificate)
|
||||
}
|
||||
return &serverConfig, nil
|
||||
},
|
||||
})
|
||||
err = clientConn.HandshakeContext(ctx)
|
||||
var serverConfig tls.Config
|
||||
serverConfig.Time = s.router.TimeFunc()
|
||||
if serverConn.ConnectionState().NegotiatedProtocol != "" {
|
||||
serverConfig.NextProtos = []string{serverConn.ConnectionState().NegotiatedProtocol}
|
||||
}
|
||||
serverConfig.ServerName = clientHello.ServerName
|
||||
serverConfig.MinVersion = tls.VersionTLS10
|
||||
serverConfig.GetCertificate = func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return sTLS.GenerateKeyPair(nil, serverConfig.ServerName, s.tlsCertificate)
|
||||
}
|
||||
|
||||
clientTLSConn := tls.Server(bufio.NewCachedConn(conn, buffer), &serverConfig)
|
||||
err = clientTLSConn.HandshakeContext(ctx)
|
||||
if err != nil {
|
||||
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")
|
||||
return nil, bufio.CopyConn(ctx, clientConn, serverConn)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package option
|
||||
|
||||
type MITMServiceOptions struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
Insecure bool `json:"insecure,omitempty"`
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
CertificatePath string `json:"certificate_path,omitempty"`
|
||||
Key string `json:"key,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