mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-10-22 23:51:41 +00:00
fix(socks): prevent panics on DomainAddress, Unix socket, and uninitialized timer in UDP Associate
This commit is contained in:
parent
92ccc939aa
commit
5c7bdfd2d7
4 changed files with 169 additions and 7 deletions
|
|
@ -203,15 +203,27 @@ func (s *ServerSession) handshake5(nMethod byte, reader io.Reader, writer net.Co
|
|||
// Use conn.LocalAddr() IP as remote address in the response by default
|
||||
responseAddress = s.localAddress
|
||||
}
|
||||
udpHub, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: responseAddress.IP(), Port: 0}, nil)
|
||||
var listenIP gonet.IP
|
||||
if responseAddress.Family().IsIP() {
|
||||
listenIP = responseAddress.IP()
|
||||
} else {
|
||||
listenIP = gonet.IPv4zero
|
||||
}
|
||||
udpHub, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{IP: listenIP, Port: 0}, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("failed to create UDP listener").Base(err)
|
||||
}
|
||||
responsePort = net.Port(udpHub.LocalAddr().(*net.UDPAddr).Port)
|
||||
if udpAddr, ok := udpHub.LocalAddr().(*gonet.UDPAddr); ok {
|
||||
responsePort = net.Port(udpAddr.Port)
|
||||
}
|
||||
expectedRemote := &gonet.UDPAddr{}
|
||||
// UDP Associate should not specify a domain as source IP
|
||||
if request.Address.Family().IsDomain() || request.Address.IP().IsUnspecified() {
|
||||
expectedRemote.IP = writer.RemoteAddr().(*net.TCPAddr).IP // unix?
|
||||
if tcpAddr, ok := writer.RemoteAddr().(*gonet.TCPAddr); ok {
|
||||
expectedRemote.IP = tcpAddr.IP
|
||||
} else {
|
||||
expectedRemote.IP = gonet.IPv4zero
|
||||
}
|
||||
} else {
|
||||
expectedRemote.IP = request.Address.IP()
|
||||
expectedRemote.Port = int(request.Port) // 0 is allowed
|
||||
|
|
|
|||
|
|
@ -105,11 +105,16 @@ func (s *Server) processTCP(ctx context.Context, conn stat.Connection, dispatche
|
|||
return errors.New("inbound gateway not specified")
|
||||
}
|
||||
|
||||
localAddr := net.LocalHostIP
|
||||
if tcpAddr, ok := conn.LocalAddr().(*net.TCPAddr); ok {
|
||||
localAddr = net.IPAddress(tcpAddr.IP)
|
||||
}
|
||||
|
||||
svrSession := &ServerSession{
|
||||
config: s.config,
|
||||
address: inbound.Gateway.Address,
|
||||
port: inbound.Gateway.Port,
|
||||
localAddress: net.IPAddress(conn.LocalAddr().(*net.TCPAddr).IP),
|
||||
localAddress: localAddr,
|
||||
}
|
||||
|
||||
// Firstbyte is for forwarded conn from SOCKS inbound
|
||||
|
|
|
|||
|
|
@ -66,7 +66,14 @@ func (c *TempUDPConn) SetTimeout(d time.Duration) {
|
|||
}
|
||||
|
||||
func (c *TempUDPConn) Close() error {
|
||||
c.Timer.SetTimeout(0)
|
||||
c.AssociatedTCPConn.Close()
|
||||
return c.PacketConn.Close()
|
||||
if c.Timer != nil {
|
||||
c.Timer.SetTimeout(0)
|
||||
}
|
||||
if c.AssociatedTCPConn != nil {
|
||||
c.AssociatedTCPConn.Close()
|
||||
}
|
||||
if c.PacketConn != nil {
|
||||
return c.PacketConn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
138
proxy/socks/udp_associate_test.go
Normal file
138
proxy/socks/udp_associate_test.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package socks
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
gonet "net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
)
|
||||
|
||||
type mockPipeConn struct {
|
||||
gonet.Conn
|
||||
readBuf *bytes.Buffer
|
||||
writeBuf *bytes.Buffer
|
||||
local gonet.Addr
|
||||
remote gonet.Addr
|
||||
}
|
||||
|
||||
func (m *mockPipeConn) Read(b []byte) (int, error) {
|
||||
return m.readBuf.Read(b)
|
||||
}
|
||||
|
||||
func (m *mockPipeConn) Write(b []byte) (int, error) {
|
||||
return m.writeBuf.Write(b)
|
||||
}
|
||||
|
||||
func (m *mockPipeConn) LocalAddr() gonet.Addr {
|
||||
if m.local != nil {
|
||||
return m.local
|
||||
}
|
||||
return &gonet.UnixAddr{Name: "/tmp/socks.sock", Net: "unix"}
|
||||
}
|
||||
|
||||
func (m *mockPipeConn) RemoteAddr() gonet.Addr {
|
||||
if m.remote != nil {
|
||||
return m.remote
|
||||
}
|
||||
return &gonet.UnixAddr{Name: "@client", Net: "unix"}
|
||||
}
|
||||
|
||||
func (m *mockPipeConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (m *mockPipeConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (m *mockPipeConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
func (m *mockPipeConn) Close() error { return nil }
|
||||
|
||||
func TestUDPAssociateDomainAddress(t *testing.T) {
|
||||
// Verify that UDP Associate does not panic when config.Address is a domain (e.g. "localhost").
|
||||
// Previously: calling responseAddress.IP() unconditionally panicked on DomainAddress.
|
||||
session := &ServerSession{
|
||||
config: &ServerConfig{
|
||||
AuthType: AuthType_NO_AUTH,
|
||||
UdpEnabled: true,
|
||||
Address: net.NewIPOrDomain(net.DomainAddress("localhost")),
|
||||
},
|
||||
address: net.LocalHostIP,
|
||||
port: net.Port(1080),
|
||||
localAddress: net.LocalHostIP,
|
||||
}
|
||||
|
||||
// SOCKS5 greeting + UDP ASSOCIATE request for 127.0.0.1:1024
|
||||
reqData := []byte{
|
||||
0x05, 0x01, 0x00,
|
||||
0x05, 0x03, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x01, 0x04, 0x00,
|
||||
}
|
||||
|
||||
mockConn := &mockPipeConn{
|
||||
readBuf: bytes.NewBuffer(reqData),
|
||||
writeBuf: &bytes.Buffer{},
|
||||
}
|
||||
|
||||
req, udpConn, err := session.Handshake(mockConn.readBuf, mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("Handshake failed unexpectedly: %v", err)
|
||||
}
|
||||
if udpConn != nil {
|
||||
_ = udpConn.Close()
|
||||
}
|
||||
if req == nil {
|
||||
t.Fatalf("Expected non-nil request header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUDPAssociateUnixSocket(t *testing.T) {
|
||||
// Verify that UDP Associate does not panic when client connects over a Unix domain socket
|
||||
// or any non-TCP connection (writer.RemoteAddr() is not *net.TCPAddr).
|
||||
// Previously: writer.RemoteAddr().(*net.TCPAddr) triggered an unchecked type assertion panic.
|
||||
session := &ServerSession{
|
||||
config: &ServerConfig{
|
||||
AuthType: AuthType_NO_AUTH,
|
||||
UdpEnabled: true,
|
||||
Address: net.NewIPOrDomain(net.LocalHostIP),
|
||||
},
|
||||
address: net.LocalHostIP,
|
||||
port: net.Port(1080),
|
||||
localAddress: net.LocalHostIP,
|
||||
}
|
||||
|
||||
// SOCKS5 greeting + UDP ASSOCIATE request with unspecified IP (0.0.0.0:1024)
|
||||
reqData := []byte{
|
||||
0x05, 0x01, 0x00,
|
||||
0x05, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
}
|
||||
|
||||
mockConn := &mockPipeConn{
|
||||
readBuf: bytes.NewBuffer(reqData),
|
||||
writeBuf: &bytes.Buffer{},
|
||||
remote: &gonet.UnixAddr{Name: "@socks_client", Net: "unix"},
|
||||
}
|
||||
|
||||
req, udpConn, err := session.Handshake(mockConn.readBuf, mockConn)
|
||||
if err != nil {
|
||||
t.Fatalf("Handshake failed unexpectedly: %v", err)
|
||||
}
|
||||
if udpConn != nil {
|
||||
_ = udpConn.Close()
|
||||
}
|
||||
if req == nil {
|
||||
t.Fatalf("Expected non-nil request header")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerProcessNonTCPLocalAddr(t *testing.T) {
|
||||
// Verify that Server.processTCP safely handles non-TCP connections (e.g. Unix socket inbound)
|
||||
// without unchecked type assertion panic on conn.LocalAddr().(*net.TCPAddr).
|
||||
mockConn := &mockPipeConn{
|
||||
local: &gonet.UnixAddr{Name: "/var/run/xray.sock", Net: "unix"},
|
||||
remote: &gonet.UnixAddr{Name: "@client", Net: "unix"},
|
||||
}
|
||||
|
||||
localAddr := net.LocalHostIP
|
||||
if tcpAddr, ok := mockConn.LocalAddr().(*net.TCPAddr); ok {
|
||||
localAddr = net.IPAddress(tcpAddr.IP)
|
||||
}
|
||||
if localAddr != net.LocalHostIP {
|
||||
t.Fatalf("Expected fallback to LocalHostIP, got %v", localAddr)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue