socks: Add custom udp listener

This commit is contained in:
世界 2025-03-19 20:00:11 +08:00
parent ea82ac275f
commit d39c2c2fdd
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
2 changed files with 171 additions and 2 deletions

View file

@ -25,6 +25,10 @@ type HandlerEx interface {
N.UDPConnectionHandlerEx
}
type PacketListener interface {
ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error)
}
func ClientHandshake4(conn io.ReadWriter, command byte, destination M.Socksaddr, username string) (socks4.Response, error) {
err := socks4.WriteRequest(conn, socks4.Request{
Command: command,
@ -121,6 +125,8 @@ func HandleConnectionEx(
ctx context.Context, conn net.Conn, reader *std_bufio.Reader,
authenticator *auth.Authenticator,
handler HandlerEx,
packetListener PacketListener,
// resolver TorResolver,
source M.Socksaddr,
onClose N.CloseHandlerFunc,
) error {
@ -148,6 +154,11 @@ func HandleConnectionEx(
}
handler.NewConnectionEx(auth.ContextWithUser(ctx, request.Username), NewLazyConn(conn, version), source, request.Destination, onClose)
return nil
/*case CommandTorResolve, CommandTorResolvePTR:
if resolver == nil {
return E.New("socks4: torsocks: commands not implemented")
}
return handleTorSocks4(ctx, conn, request, resolver)*/
default:
err = socks4.WriteResponse(conn, socks4.Response{
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
@ -214,8 +225,15 @@ func HandleConnectionEx(
handler.NewConnectionEx(ctx, NewLazyConn(conn, version), source, request.Destination, onClose)
return nil
case socks5.CommandUDPAssociate:
var udpConn *net.UDPConn
udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNet(conn.LocalAddr()), 0)))
var (
listenConfig net.ListenConfig
udpConn net.PacketConn
)
if packetListener != nil {
udpConn, err = packetListener.ListenPacket(listenConfig, ctx, M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), M.SocksaddrFrom(M.AddrFromNet(conn.LocalAddr()), 0).String())
} else {
udpConn, err = listenConfig.ListenPacket(ctx, M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), M.SocksaddrFrom(M.AddrFromNet(conn.LocalAddr()), 0).String())
}
if err != nil {
return E.Cause(err, "socks5: listen udp")
}
@ -236,6 +254,11 @@ func HandleConnectionEx(
socksPacketConn = bufio.NewCachedPacketConn(socksPacketConn, firstPacket, destination)
handler.NewPacketConnectionEx(ctx, socksPacketConn, source, destination, onClose)
return nil
/*case CommandTorResolve, CommandTorResolvePTR:
if resolver == nil {
return E.New("socks4: torsocks: commands not implemented")
}
return handleTorSocks5(ctx, conn, request, resolver)*/
default:
err = socks5.WriteResponse(conn, socks5.Response{
ReplyCode: socks5.ReplyCodeUnsupported,

View file

@ -0,0 +1,146 @@
package socks
import (
"context"
"net"
"net/netip"
"os"
"strings"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/protocol/socks/socks4"
"github.com/sagernet/sing/protocol/socks/socks5"
)
const (
CommandTorResolve byte = 0xF0
CommandTorResolvePTR byte = 0xF1
)
type TorResolver interface {
LookupIP(ctx context.Context, host string) (netip.Addr, error)
LookupPTR(ctx context.Context, addr netip.Addr) (string, error)
}
func handleTorSocks4(ctx context.Context, conn net.Conn, request socks4.Request, resolver TorResolver) error {
switch request.Command {
case CommandTorResolve:
if !request.Destination.IsFqdn() {
return E.New("socks4: torsocks: invalid destination")
}
ipAddr, err := resolver.LookupIP(ctx, request.Destination.Fqdn)
if err != nil {
err = socks4.WriteResponse(conn, socks4.Response{
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
})
if err != nil {
return err
}
return E.Cause(err, "socks4: torsocks: lookup failed for domain: ", request.Destination.Fqdn)
}
err = socks4.WriteResponse(conn, socks4.Response{
ReplyCode: socks4.ReplyCodeGranted,
Destination: M.SocksaddrFrom(ipAddr, 0),
})
if err != nil {
return E.Cause(err, "socks4: torsocks: write response")
}
return nil
case CommandTorResolvePTR:
var ipAddr netip.Addr
if request.Destination.IsIP() {
ipAddr = request.Destination.Addr
} else if strings.HasSuffix(request.Destination.Fqdn, ".in-addr.arpa") {
ipAddr, _ = netip.ParseAddr(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".in-addr.arpa")])
} else if strings.HasSuffix(request.Destination.Fqdn, ".ip6.arpa") {
ipAddr, _ = netip.ParseAddr(strings.ReplaceAll(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".ip6.arpa")], ".", ":"))
}
if !ipAddr.IsValid() {
return E.New("socks4: torsocks: invalid destination")
}
host, err := resolver.LookupPTR(ctx, ipAddr)
if err != nil {
err = socks4.WriteResponse(conn, socks4.Response{
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
})
if err != nil {
return err
}
return E.Cause(err, "socks4: torsocks: lookup PTR failed for ip: ", ipAddr)
}
err = socks4.WriteResponse(conn, socks4.Response{
ReplyCode: socks4.ReplyCodeGranted,
Destination: M.Socksaddr{
Fqdn: host,
},
})
if err != nil {
return E.Cause(err, "socks4: torsocks: write response")
}
return nil
default:
return os.ErrInvalid
}
}
func handleTorSocks5(ctx context.Context, conn net.Conn, request socks5.Request, resolver TorResolver) error {
switch request.Command {
case CommandTorResolve:
if !request.Destination.IsFqdn() {
return E.New("socks5: torsocks: invalid destination")
}
ipAddr, err := resolver.LookupIP(ctx, request.Destination.Fqdn)
if err != nil {
err = socks5.WriteResponse(conn, socks5.Response{
ReplyCode: socks5.ReplyCodeFailure,
})
if err != nil {
return err
}
return E.Cause(err, "socks5: torsocks: lookup failed for domain: ", request.Destination.Fqdn)
}
err = socks5.WriteResponse(conn, socks5.Response{
ReplyCode: socks5.ReplyCodeSuccess,
Bind: M.SocksaddrFrom(ipAddr, 0),
})
if err != nil {
return E.Cause(err, "socks5: torsocks: write response")
}
return nil
case CommandTorResolvePTR:
var ipAddr netip.Addr
if request.Destination.IsIP() {
ipAddr = request.Destination.Addr
} else if strings.HasSuffix(request.Destination.Fqdn, ".in-addr.arpa") {
ipAddr, _ = netip.ParseAddr(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".in-addr.arpa")])
} else if strings.HasSuffix(request.Destination.Fqdn, ".ip6.arpa") {
ipAddr, _ = netip.ParseAddr(strings.ReplaceAll(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".ip6.arpa")], ".", ":"))
}
if !ipAddr.IsValid() {
return E.New("socks5: torsocks: invalid destination")
}
host, err := resolver.LookupPTR(ctx, ipAddr)
if err != nil {
err = socks5.WriteResponse(conn, socks5.Response{
ReplyCode: socks5.ReplyCodeFailure,
})
if err != nil {
return err
}
return E.Cause(err, "socks5: torsocks: lookup PTR failed for ip: ", ipAddr)
}
err = socks5.WriteResponse(conn, socks5.Response{
ReplyCode: socks5.ReplyCodeSuccess,
Bind: M.Socksaddr{
Fqdn: host,
},
})
if err != nil {
return E.Cause(err, "socks5: torsocks: write response")
}
return nil
default:
return os.ErrInvalid
}
}