{
"route": {
"rules": [
{
"action": "route",
"outbound": "direct"
}
]
},
"inbounds": [
{
"tag": "socks-in",
"type": "socks",
"listen": "127.0.0.1",
"listen_port": 12000,
"udp_timeout": "3s"
}
],
"outbounds": [
],
"experimental": {
"clash_api": {
"external_controller": "0.0.0.0:9090",
"external_ui": "ui",
"external_ui_download_url": "https://github.com/Zephyruso/zashboard/releases/latest/download/dist-no-fonts.zip",
"secret": "aaa"
}
}
}
//go:build ignore
package main
import (
"fmt"
"net"
"time"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks5"
)
func main() {
proxyAddr := "127.0.0.1:12000"
// Use Cloudflare DNS as UDP target
targetAddr := M.ParseSocksaddr("1.1.1.1:9999")
fmt.Printf("Testing UDP SOCKS5 proxy at %s\n", proxyAddr)
fmt.Printf("Target: %s\n", targetAddr)
// Connect to SOCKS5 proxy
tcpConn, err := net.Dial("tcp", proxyAddr)
if err != nil {
fmt.Printf("❌ Failed to connect to proxy: %v\n", err)
return
}
defer tcpConn.Close()
// SOCKS5 UDP Associate handshake
response, err := socks.ClientHandshake5(tcpConn, socks5.CommandUDPAssociate, M.Socksaddr{}, "", "")
if err != nil {
fmt.Printf("❌ Handshake failed: %v\n", err)
return
}
fmt.Printf("✓ UDP relay address: %s\n", response.Bind)
// Resolve UDP relay address
if response.Bind.Port == 0 {
fmt.Printf("❌ Invalid relay port\n")
return
}
// Create UDP connection to the relay
udpConn, err := net.Dial("udp", response.Bind.String())
if err != nil {
fmt.Printf("❌ Failed to create UDP connection: %v\n", err)
return
}
defer udpConn.Close()
// Build DNS query for google.com (type A)
dnsQuery := buildDNSQuery("google.com")
// Build SOCKS5 UDP packet header + DNS query
// Format: RSV(2) + FRAG(1) + ATYP(1) + DST.ADDR(variable) + DST.PORT(2) + DATA
packet := make([]byte, 0, 512)
packet = append(packet, 0, 0) // RSV
packet = append(packet, 0) // FRAG
packet = append(packet, 1) // ATYP = IPv4
ip := targetAddr.Addr.As4()
packet = append(packet, ip[:]...)
packet = append(packet, byte(targetAddr.Port>>8), byte(targetAddr.Port)) // Port
packet = append(packet, dnsQuery...)
fmt.Printf("Sending DNS query (%d bytes)...\n", len(packet))
// Send the packet
udpConn.SetWriteDeadline(time.Now().Add(5 * time.Second))
_, err = udpConn.Write(packet)
if err != nil {
fmt.Printf("❌ Failed to send packet: %v\n", err)
return
}
// Wait for response
udpConn.SetReadDeadline(time.Now().Add(5 * time.Second))
buf := make([]byte, 4096)
n, err := udpConn.Read(buf)
if err != nil {
fmt.Printf("❌ Failed to receive response: %v\n", err)
return
}
fmt.Printf("\n✅ SUCCESS! Received %d bytes\n", n)
// Parse SOCKS5 UDP header (skip 10 bytes for IPv4)
if n > 10 {
payload := buf[10:n]
fmt.Printf("DNS response bytes: %x\n", payload[:min(32, len(payload))])
// Parse DNS response to show answer count
if len(payload) > 12 {
answerCount := int(payload[6])<<8 | int(payload[7])
fmt.Printf("DNS answer count: %d\n", answerCount)
}
}
}
func buildDNSQuery(domain string) []byte {
// Simple DNS query for type A record
query := make([]byte, 0, 512)
// Transaction ID
query = append(query, 0x00, 0x01)
// Flags: standard query
query = append(query, 0x01, 0x00)
// Questions: 1
query = append(query, 0x00, 0x01)
// Answer RRs: 0
query = append(query, 0x00, 0x00)
// Authority RRs: 0
query = append(query, 0x00, 0x00)
// Additional RRs: 0
query = append(query, 0x00, 0x00)
// Query name
for _, part := range splitDomain(domain) {
query = append(query, byte(len(part)))
query = append(query, []byte(part)...)
}
query = append(query, 0x00) // End of name
// Type A
query = append(query, 0x00, 0x01)
// Class IN
query = append(query, 0x00, 0x01)
return query
}
func splitDomain(domain string) []string {
var parts []string
start := 0
for i, c := range domain {
if c == '.' {
parts = append(parts, domain[start:i])
start = i + 1
}
}
parts = append(parts, domain[start:])
return parts
}
but if we connect to standard port like 53, it will set a default 20 seconds timeout, which is wierd since I don't see any 20 seconds default in code.
Operating system
Others
System version
FreeBSD
Installation type
Original sing-box Command Line
If you are using a graphical client, please provide the version of the client.
No response
Version
Description
When using socks inbound to handle UDP packets,
UDP connections will hang forever
due to UDPTimeout option never applied to them.
Reproduction
singbox config:
test script:
UDP connection hangs forever if we are not hitting any default value at:
sing-box/route/conn.go
Lines 203 to 218 in 4efe70b
but if we connect to standard port like 53, it will set a default 20 seconds timeout, which is wierd since I don't see any 20 seconds default in code.
Logs
Supporter
Integrity requirements