Skip to content

Commit f34dd44

Browse files
committed
socks: Add custom udp listener
1 parent b946c92 commit f34dd44

File tree

2 files changed

+171
-2
lines changed

2 files changed

+171
-2
lines changed

protocol/socks/handshake.go

+25-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ type HandlerEx interface {
2525
N.UDPConnectionHandlerEx
2626
}
2727

28+
type PacketListener interface {
29+
ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error)
30+
}
31+
2832
func ClientHandshake4(conn io.ReadWriter, command byte, destination M.Socksaddr, username string) (socks4.Response, error) {
2933
err := socks4.WriteRequest(conn, socks4.Request{
3034
Command: command,
@@ -121,6 +125,8 @@ func HandleConnectionEx(
121125
ctx context.Context, conn net.Conn, reader *std_bufio.Reader,
122126
authenticator *auth.Authenticator,
123127
handler HandlerEx,
128+
packetListener PacketListener,
129+
// resolver TorResolver,
124130
source M.Socksaddr,
125131
onClose N.CloseHandlerFunc,
126132
) error {
@@ -148,6 +154,11 @@ func HandleConnectionEx(
148154
}
149155
handler.NewConnectionEx(auth.ContextWithUser(ctx, request.Username), NewLazyConn(conn, version), source, request.Destination, onClose)
150156
return nil
157+
/*case CommandTorResolve, CommandTorResolvePTR:
158+
if resolver == nil {
159+
return E.New("socks4: torsocks: commands not implemented")
160+
}
161+
return handleTorSocks4(ctx, conn, request, resolver)*/
151162
default:
152163
err = socks4.WriteResponse(conn, socks4.Response{
153164
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
@@ -214,8 +225,15 @@ func HandleConnectionEx(
214225
handler.NewConnectionEx(ctx, NewLazyConn(conn, version), source, request.Destination, onClose)
215226
return nil
216227
case socks5.CommandUDPAssociate:
217-
var udpConn *net.UDPConn
218-
udpConn, err = net.ListenUDP(M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), net.UDPAddrFromAddrPort(netip.AddrPortFrom(M.AddrFromNet(conn.LocalAddr()), 0)))
228+
var (
229+
listenConfig net.ListenConfig
230+
udpConn net.PacketConn
231+
)
232+
if packetListener != nil {
233+
udpConn, err = packetListener.ListenPacket(listenConfig, ctx, M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), M.SocksaddrFrom(M.AddrFromNet(conn.LocalAddr()), 0).String())
234+
} else {
235+
udpConn, err = listenConfig.ListenPacket(ctx, M.NetworkFromNetAddr("udp", M.AddrFromNet(conn.LocalAddr())), M.SocksaddrFrom(M.AddrFromNet(conn.LocalAddr()), 0).String())
236+
}
219237
if err != nil {
220238
return E.Cause(err, "socks5: listen udp")
221239
}
@@ -236,6 +254,11 @@ func HandleConnectionEx(
236254
socksPacketConn = bufio.NewCachedPacketConn(socksPacketConn, firstPacket, destination)
237255
handler.NewPacketConnectionEx(ctx, socksPacketConn, source, destination, onClose)
238256
return nil
257+
/*case CommandTorResolve, CommandTorResolvePTR:
258+
if resolver == nil {
259+
return E.New("socks4: torsocks: commands not implemented")
260+
}
261+
return handleTorSocks5(ctx, conn, request, resolver)*/
239262
default:
240263
err = socks5.WriteResponse(conn, socks5.Response{
241264
ReplyCode: socks5.ReplyCodeUnsupported,

protocol/socks/handshake_tor.go

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package socks
2+
3+
import (
4+
"context"
5+
"net"
6+
"net/netip"
7+
"os"
8+
"strings"
9+
10+
E "github.com/sagernet/sing/common/exceptions"
11+
M "github.com/sagernet/sing/common/metadata"
12+
"github.com/sagernet/sing/protocol/socks/socks4"
13+
"github.com/sagernet/sing/protocol/socks/socks5"
14+
)
15+
16+
const (
17+
CommandTorResolve byte = 0xF0
18+
CommandTorResolvePTR byte = 0xF1
19+
)
20+
21+
type TorResolver interface {
22+
LookupIP(ctx context.Context, host string) (netip.Addr, error)
23+
LookupPTR(ctx context.Context, addr netip.Addr) (string, error)
24+
}
25+
26+
func handleTorSocks4(ctx context.Context, conn net.Conn, request socks4.Request, resolver TorResolver) error {
27+
switch request.Command {
28+
case CommandTorResolve:
29+
if !request.Destination.IsFqdn() {
30+
return E.New("socks4: torsocks: invalid destination")
31+
}
32+
ipAddr, err := resolver.LookupIP(ctx, request.Destination.Fqdn)
33+
if err != nil {
34+
err = socks4.WriteResponse(conn, socks4.Response{
35+
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
36+
})
37+
if err != nil {
38+
return err
39+
}
40+
return E.Cause(err, "socks4: torsocks: lookup failed for domain: ", request.Destination.Fqdn)
41+
}
42+
err = socks4.WriteResponse(conn, socks4.Response{
43+
ReplyCode: socks4.ReplyCodeGranted,
44+
Destination: M.SocksaddrFrom(ipAddr, 0),
45+
})
46+
if err != nil {
47+
return E.Cause(err, "socks4: torsocks: write response")
48+
}
49+
return nil
50+
case CommandTorResolvePTR:
51+
var ipAddr netip.Addr
52+
if request.Destination.IsIP() {
53+
ipAddr = request.Destination.Addr
54+
} else if strings.HasSuffix(request.Destination.Fqdn, ".in-addr.arpa") {
55+
ipAddr, _ = netip.ParseAddr(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".in-addr.arpa")])
56+
} else if strings.HasSuffix(request.Destination.Fqdn, ".ip6.arpa") {
57+
ipAddr, _ = netip.ParseAddr(strings.ReplaceAll(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".ip6.arpa")], ".", ":"))
58+
}
59+
if !ipAddr.IsValid() {
60+
return E.New("socks4: torsocks: invalid destination")
61+
}
62+
host, err := resolver.LookupPTR(ctx, ipAddr)
63+
if err != nil {
64+
err = socks4.WriteResponse(conn, socks4.Response{
65+
ReplyCode: socks4.ReplyCodeRejectedOrFailed,
66+
})
67+
if err != nil {
68+
return err
69+
}
70+
return E.Cause(err, "socks4: torsocks: lookup PTR failed for ip: ", ipAddr)
71+
}
72+
err = socks4.WriteResponse(conn, socks4.Response{
73+
ReplyCode: socks4.ReplyCodeGranted,
74+
Destination: M.Socksaddr{
75+
Fqdn: host,
76+
},
77+
})
78+
if err != nil {
79+
return E.Cause(err, "socks4: torsocks: write response")
80+
}
81+
return nil
82+
default:
83+
return os.ErrInvalid
84+
}
85+
}
86+
87+
func handleTorSocks5(ctx context.Context, conn net.Conn, request socks5.Request, resolver TorResolver) error {
88+
switch request.Command {
89+
case CommandTorResolve:
90+
if !request.Destination.IsFqdn() {
91+
return E.New("socks5: torsocks: invalid destination")
92+
}
93+
ipAddr, err := resolver.LookupIP(ctx, request.Destination.Fqdn)
94+
if err != nil {
95+
err = socks5.WriteResponse(conn, socks5.Response{
96+
ReplyCode: socks5.ReplyCodeFailure,
97+
})
98+
if err != nil {
99+
return err
100+
}
101+
return E.Cause(err, "socks5: torsocks: lookup failed for domain: ", request.Destination.Fqdn)
102+
}
103+
err = socks5.WriteResponse(conn, socks5.Response{
104+
ReplyCode: socks5.ReplyCodeSuccess,
105+
Bind: M.SocksaddrFrom(ipAddr, 0),
106+
})
107+
if err != nil {
108+
return E.Cause(err, "socks5: torsocks: write response")
109+
}
110+
return nil
111+
case CommandTorResolvePTR:
112+
var ipAddr netip.Addr
113+
if request.Destination.IsIP() {
114+
ipAddr = request.Destination.Addr
115+
} else if strings.HasSuffix(request.Destination.Fqdn, ".in-addr.arpa") {
116+
ipAddr, _ = netip.ParseAddr(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".in-addr.arpa")])
117+
} else if strings.HasSuffix(request.Destination.Fqdn, ".ip6.arpa") {
118+
ipAddr, _ = netip.ParseAddr(strings.ReplaceAll(request.Destination.Fqdn[:len(request.Destination.Fqdn)-len(".ip6.arpa")], ".", ":"))
119+
}
120+
if !ipAddr.IsValid() {
121+
return E.New("socks5: torsocks: invalid destination")
122+
}
123+
host, err := resolver.LookupPTR(ctx, ipAddr)
124+
if err != nil {
125+
err = socks5.WriteResponse(conn, socks5.Response{
126+
ReplyCode: socks5.ReplyCodeFailure,
127+
})
128+
if err != nil {
129+
return err
130+
}
131+
return E.Cause(err, "socks5: torsocks: lookup PTR failed for ip: ", ipAddr)
132+
}
133+
err = socks5.WriteResponse(conn, socks5.Response{
134+
ReplyCode: socks5.ReplyCodeSuccess,
135+
Bind: M.Socksaddr{
136+
Fqdn: host,
137+
},
138+
})
139+
if err != nil {
140+
return E.Cause(err, "socks5: torsocks: write response")
141+
}
142+
return nil
143+
default:
144+
return os.ErrInvalid
145+
}
146+
}

0 commit comments

Comments
 (0)