Browse Source

Socks inbound: Support HTTP inbound by default (#3682)

Co-authored-by: RPRX <[email protected]>
风扇滑翔翼 1 year ago
parent
commit
b612da26eb
3 changed files with 121 additions and 5 deletions
  1. 17 2
      proxy/http/server.go
  2. 22 3
      proxy/socks/server.go
  3. 82 0
      testing/scenarios/socks_test.go

+ 17 - 2
proxy/http/server.go

@@ -2,6 +2,7 @@ package http
 
 import (
 	"bufio"
+	"bytes"
 	"context"
 	"encoding/base64"
 	"io"
@@ -83,14 +84,28 @@ type readerOnly struct {
 }
 
 func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher) error {
+	return s.ProcessWithFirstbyte(ctx, network, conn, dispatcher)
+}
+
+// Firstbyte is for forwarded conn from SOCKS inbound
+// Because it needs first byte to choose protocol
+// We need to add it back
+// Other parts are the same as the process function
+func (s *Server) ProcessWithFirstbyte(ctx context.Context, network net.Network, conn stat.Connection, dispatcher routing.Dispatcher, firstbyte ...byte) error {
 	inbound := session.InboundFromContext(ctx)
 	inbound.Name = "http"
 	inbound.CanSpliceCopy = 2
 	inbound.User = &protocol.MemoryUser{
 		Level: s.config.UserLevel,
 	}
-
-	reader := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
+	var reader *bufio.Reader
+	if len(firstbyte) > 0 {
+		readerWithoutFirstbyte := bufio.NewReaderSize(readerOnly{conn}, buf.Size)
+		multiReader := io.MultiReader(bytes.NewReader(firstbyte), readerWithoutFirstbyte)
+		reader = bufio.NewReaderSize(multiReader, buf.Size)
+	} else {
+		reader = bufio.NewReaderSize(readerOnly{conn}, buf.Size)
+	}
 
 Start:
 	if err := conn.SetReadDeadline(time.Now().Add(s.policy().Timeouts.Handshake)); err != nil {

+ 22 - 3
proxy/socks/server.go

@@ -19,6 +19,7 @@ import (
 	"github.com/xtls/xray-core/features"
 	"github.com/xtls/xray-core/features/policy"
 	"github.com/xtls/xray-core/features/routing"
+	"github.com/xtls/xray-core/proxy/http"
 	"github.com/xtls/xray-core/transport/internet/stat"
 	"github.com/xtls/xray-core/transport/internet/udp"
 )
@@ -29,6 +30,7 @@ type Server struct {
 	policyManager policy.Manager
 	cone          bool
 	udpFilter     *UDPFilter
+	httpServer    *http.Server
 }
 
 // NewServer creates a new Server object.
@@ -39,9 +41,14 @@ func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
 		policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
 		cone:          ctx.Value("cone").(bool),
 	}
+	httpConfig := &http.ServerConfig{
+		UserLevel: config.UserLevel,
+	}
 	if config.AuthType == AuthType_PASSWORD {
+		httpConfig.Accounts = config.Accounts
 		s.udpFilter = new(UDPFilter) // We only use this when auth is enabled
 	}
+	s.httpServer, _ = http.NewServer(ctx, httpConfig)
 	return s, nil
 }
 
@@ -77,7 +84,13 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
 
 	switch network {
 	case net.Network_TCP:
-		return s.processTCP(ctx, conn, dispatcher)
+		firstbyte := make([]byte, 1)
+		conn.Read(firstbyte)
+		if firstbyte[0] != 5 && firstbyte[0] != 4 { // Check if it is Socks5/4/4a
+			errors.LogDebug(ctx, "Not Socks request, try to parse as HTTP request")
+			return s.httpServer.ProcessWithFirstbyte(ctx, network, conn, dispatcher, firstbyte...)
+		}
+		return s.processTCP(ctx, conn, dispatcher, firstbyte)
 	case net.Network_UDP:
 		return s.handleUDPPayload(ctx, conn, dispatcher)
 	default:
@@ -85,7 +98,7 @@ func (s *Server) Process(ctx context.Context, network net.Network, conn stat.Con
 	}
 }
 
-func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher) error {
+func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatcher routing.Dispatcher, firstbyte []byte) error {
 	plcy := s.policy()
 	if err := conn.SetReadDeadline(time.Now().Add(plcy.Timeouts.Handshake)); err != nil {
 		errors.LogInfoInner(ctx, err, "failed to set deadline")
@@ -103,7 +116,13 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
 		localAddress: net.IPAddress(conn.LocalAddr().(*net.TCPAddr).IP),
 	}
 
-	reader := &buf.BufferedReader{Reader: buf.NewReader(conn)}
+	// Firstbyte is for forwarded conn from SOCKS inbound
+	// Because it needs first byte to choose protocol
+	// We need to add it back
+	reader := &buf.BufferedReader{
+		Reader: buf.NewReader(conn),
+		Buffer: buf.MultiBuffer{buf.FromBytes(firstbyte)},
+	}
 	request, err := svrSession.Handshake(reader, conn)
 	if err != nil {
 		if inbound.Source.IsValid() {

+ 82 - 0
testing/scenarios/socks_test.go

@@ -14,6 +14,7 @@ import (
 	"github.com/xtls/xray-core/proxy/blackhole"
 	"github.com/xtls/xray-core/proxy/dokodemo"
 	"github.com/xtls/xray-core/proxy/freedom"
+	"github.com/xtls/xray-core/proxy/http"
 	"github.com/xtls/xray-core/proxy/socks"
 	"github.com/xtls/xray-core/testing/servers/tcp"
 	"github.com/xtls/xray-core/testing/servers/udp"
@@ -102,6 +103,87 @@ func TestSocksBridgeTCP(t *testing.T) {
 	}
 }
 
+func TestSocksWithHttpRequest(t *testing.T) {
+	tcpServer := tcp.Server{
+		MsgProcessor: xor,
+	}
+	dest, err := tcpServer.Start()
+	common.Must(err)
+	defer tcpServer.Close()
+
+	serverPort := tcp.PickPort()
+	serverConfig := &core.Config{
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(serverPort)}},
+					Listen:   net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&socks.ServerConfig{
+					AuthType: socks.AuthType_PASSWORD,
+					Accounts: map[string]string{
+						"Test Account": "Test Password",
+					},
+					Address:    net.NewIPOrDomain(net.LocalHostIP),
+					UdpEnabled: false,
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
+			},
+		},
+	}
+
+	clientPort := tcp.PickPort()
+	clientConfig := &core.Config{
+		Inbound: []*core.InboundHandlerConfig{
+			{
+				ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
+					PortList: &net.PortList{Range: []*net.PortRange{net.SinglePortRange(clientPort)}},
+					Listen:   net.NewIPOrDomain(net.LocalHostIP),
+				}),
+				ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
+					Address: net.NewIPOrDomain(dest.Address),
+					Port:    uint32(dest.Port),
+					NetworkList: &net.NetworkList{
+						Network: []net.Network{net.Network_TCP},
+					},
+				}),
+			},
+		},
+		Outbound: []*core.OutboundHandlerConfig{
+			{
+				ProxySettings: serial.ToTypedMessage(&http.ClientConfig{
+					Server: []*protocol.ServerEndpoint{
+						{
+							Address: net.NewIPOrDomain(net.LocalHostIP),
+							Port:    uint32(serverPort),
+							User: []*protocol.User{
+								{
+									Account: serial.ToTypedMessage(&http.Account{
+										Username: "Test Account",
+										Password: "Test Password",
+									}),
+								},
+							},
+						},
+					},
+				}),
+			},
+		},
+	}
+
+	servers, err := InitializeServerConfigs(serverConfig, clientConfig)
+	common.Must(err)
+	defer CloseAllServers(servers)
+
+	if err := testTCPConn(clientPort, 1024, time.Second*2)(); err != nil {
+		t.Error(err)
+	}
+}
+
 func TestSocksBridageUDP(t *testing.T) {
 	udpServer := udp.Server{
 		MsgProcessor: xor,