Skip to content

Commit 06d05e8

Browse files
committed
🥓 netio: add packet abstractions
1 parent 3e3fcbf commit 06d05e8

File tree

2 files changed

+392
-0
lines changed

2 files changed

+392
-0
lines changed

netio/packet.go

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
package netio
2+
3+
import (
4+
"context"
5+
"net/netip"
6+
"time"
7+
8+
"github.com/database64128/shadowsocks-go/conn"
9+
)
10+
11+
// PacketClient establishes packet sessions to servers.
12+
type PacketClient interface {
13+
// NewSession opens a new client session and returns the opened session and its information.
14+
//
15+
// connectAddr specifies an optional connect address for the session. If valid, the server
16+
// is expected to drop received packets not sent from this address.
17+
//
18+
// The returned [PacketClientSessionInfo] is always valid, even when session creation fails.
19+
NewSession(ctx context.Context, connectAddr conn.Addr) (PacketClientSession, PacketClientSessionInfo, error)
20+
}
21+
22+
// PacketClientSession handles packing and unpacking of packets for a client session.
23+
type PacketClientSession interface {
24+
// AppendPack packs the payload into a packet ready for sending and appends it to the buffer.
25+
// It returns the extended buffer, the destination address of the packed packet, or an error if packing fails.
26+
//
27+
// The remaining capacity of b must not overlap payload.
28+
//
29+
// If the session is "connected", destAddr must be the zero value.
30+
//
31+
// If the session uses a connected socket, sendAddrPort must be the zero value.
32+
AppendPack(ctx context.Context, b, payload []byte, destAddr conn.Addr) (sendBuf []byte, sendAddrPort netip.AddrPort, err error)
33+
34+
// UnpackInPlace unpacks the received packet in-place and returns the payload, source address,
35+
// or an error if unpacking fails.
36+
//
37+
// If the session uses a connected socket, recvAddrPort will be the zero value.
38+
//
39+
// If the session is "connected", srcAddr must be the zero value.
40+
UnpackInPlace(recvBuf []byte, recvAddrPort netip.AddrPort) (payload []byte, srcAddr conn.Addr, err error)
41+
42+
// Close closes the session.
43+
Close() error
44+
}
45+
46+
// PacketClientSessionInfo contains information about a client session.
47+
type PacketClientSessionInfo struct {
48+
// Name is the name of the client.
49+
Name string
50+
51+
// MaxPacketSize is the maximum size of outgoing packets.
52+
MaxPacketSize int
53+
54+
// ListenConfig is the [conn.ListenConfig] for opening unconnected client sockets.
55+
ListenConfig conn.ListenConfig
56+
57+
// Dialer is the [conn.Dialer] for opening connected client sockets.
58+
Dialer conn.Dialer
59+
60+
// ConnectAddr is the address for the client socket to connect to.
61+
ConnectAddr conn.Addr
62+
}
63+
64+
// PacketServer handles incoming packet sessions from clients.
65+
type PacketServer[SID comparable] interface {
66+
// PacketServerInfo returns information about the packet server.
67+
PacketServerInfo() PacketServerInfo
68+
69+
// HandlePacket extracts the session ID from the received packet.
70+
//
71+
// The returned session ID is then used by the caller to look up the session table.
72+
// If no matching session is found, [NewSession] should be called to create a new session.
73+
HandlePacket(recvBuf []byte, recvAddrPort netip.AddrPort) (sid SID, err error)
74+
75+
// NewSession creates a new session with the given session ID and unpacks the received packet in-place.
76+
// It returns the server session, session information, payload, destination address, or an error
77+
// if session creation or unpacking fails.
78+
NewSession(recvBuf []byte, sid SID) (
79+
serverSession PacketServerSession,
80+
serverSessionInfo PacketServerSessionInfo,
81+
payload []byte, destAddr conn.Addr, err error,
82+
)
83+
}
84+
85+
// PacketServerInfo contains information about a packet server.
86+
type PacketServerInfo struct {
87+
// MinNATTimeout is the minimum NAT timeout allowed by the server.
88+
MinNATTimeout time.Duration
89+
}
90+
91+
// PacketServerSession handles packing and unpacking of packets for a server session.
92+
type PacketServerSession interface {
93+
// UnpackInPlace unpacks the received packet in-place and returns the payload, destination address,
94+
// or an error if unpacking fails.
95+
//
96+
// If the session is "connected", destAddr must be the zero value.
97+
UnpackInPlace(recvBuf []byte, recvAddrPort netip.AddrPort) (payload []byte, destAddr conn.Addr, err error)
98+
99+
// AppendPack packs the payload into a packet ready for sending and appends it to the buffer.
100+
// It returns the extended buffer, or an error if packing fails.
101+
//
102+
// The remaining capacity of b must not overlap payload.
103+
//
104+
// If the session is "connected", srcAddr will be the zero value.
105+
AppendPack(b, payload []byte, srcAddr conn.Addr) (sendBuf []byte, err error)
106+
}
107+
108+
// PacketServerSessionInfo contains information about a server session.
109+
type PacketServerSessionInfo struct {
110+
// Username identifies the initiator of the session.
111+
Username string
112+
113+
// IsConnected indicates whether the session is "connected".
114+
//
115+
// If true, received packets not sent from the connected address are dropped.
116+
IsConnected bool
117+
}
118+
119+
// PacketProxyServerConfig is the configuration for a packet proxy server.
120+
type PacketProxyServerConfig struct {
121+
// Addr is the destination address.
122+
Addr conn.Addr
123+
124+
// IsConnected controls whether to establish "connected" sessions.
125+
//
126+
// If true, received packets not sent from Addr are dropped.
127+
IsConnected bool
128+
}
129+
130+
// NewPacketProxyServer returns a new packet proxy server.
131+
func (c *PacketProxyServerConfig) NewPacketProxyServer() PacketServer[netip.AddrPort] {
132+
if c.IsConnected {
133+
return &PacketProxyServerConnected{
134+
connectAddr: c.Addr,
135+
}
136+
}
137+
return &PacketProxyServer{
138+
session: PacketProxyServerSession{
139+
addr: c.Addr,
140+
},
141+
}
142+
}
143+
144+
// PacketProxyServer proxies all incoming packet sessions to a fixed destination address.
145+
//
146+
// PacketProxyServer implements [PacketServer].
147+
type PacketProxyServer struct {
148+
session PacketProxyServerSession
149+
}
150+
151+
// PacketServerInfo implements [PacketServer.PacketServerInfo].
152+
func (*PacketProxyServer) PacketServerInfo() PacketServerInfo {
153+
return PacketServerInfo{}
154+
}
155+
156+
// HandlePacket implements [PacketServer.HandlePacket].
157+
func (*PacketProxyServer) HandlePacket(_ []byte, recvAddrPort netip.AddrPort) (sid netip.AddrPort, err error) {
158+
return recvAddrPort, nil
159+
}
160+
161+
// NewSession implements [PacketServer.NewSession].
162+
func (s *PacketProxyServer) NewSession(recvBuf []byte, _ netip.AddrPort) (
163+
serverSession PacketServerSession,
164+
serverSessionInfo PacketServerSessionInfo,
165+
payload []byte, destAddr conn.Addr, err error,
166+
) {
167+
return &s.session, PacketServerSessionInfo{}, recvBuf, s.session.addr, nil
168+
}
169+
170+
// PacketProxyServerSession passes packets unmodified to a fixed destination address.
171+
//
172+
// PacketProxyServerSession implements [PacketServerSession].
173+
type PacketProxyServerSession struct {
174+
addr conn.Addr
175+
}
176+
177+
// UnpackInPlace implements [PacketServerSession.UnpackInPlace].
178+
func (s *PacketProxyServerSession) UnpackInPlace(recvBuf []byte, _ netip.AddrPort) (payload []byte, destAddr conn.Addr, err error) {
179+
return recvBuf, s.addr, nil
180+
}
181+
182+
// AppendPack implements [PacketServerSession.AppendPack].
183+
func (*PacketProxyServerSession) AppendPack(b, payload []byte, _ conn.Addr) (sendBuf []byte, err error) {
184+
return append(b, payload...), nil
185+
}
186+
187+
// PacketProxyServerConnected is like [PacketProxyServer] but
188+
// drops packets that are not sent from the destination address.
189+
//
190+
// PacketProxyServerConnected implements [PacketServer].
191+
type PacketProxyServerConnected struct {
192+
connectAddr conn.Addr
193+
}
194+
195+
// PacketServerInfo implements [PacketServer.PacketServerInfo].
196+
func (*PacketProxyServerConnected) PacketServerInfo() PacketServerInfo {
197+
return PacketServerInfo{}
198+
}
199+
200+
// HandlePacket implements [PacketServer.HandlePacket].
201+
func (*PacketProxyServerConnected) HandlePacket(_ []byte, recvAddrPort netip.AddrPort) (sid netip.AddrPort, err error) {
202+
return recvAddrPort, nil
203+
}
204+
205+
// NewSession implements [PacketServer.NewSession].
206+
func (s *PacketProxyServerConnected) NewSession(recvBuf []byte, _ netip.AddrPort) (
207+
serverSession PacketServerSession,
208+
serverSessionInfo PacketServerSessionInfo,
209+
payload []byte, destAddr conn.Addr, err error,
210+
) {
211+
return PacketProxyServerConnectedSession{}, PacketServerSessionInfo{IsConnected: true}, recvBuf, s.connectAddr, nil
212+
}
213+
214+
// PacketProxyServerConnectedSession is like [PacketProxyServerSession] but
215+
// drops packets that are not sent from the destination address.
216+
//
217+
// PacketProxyServerConnectedSession implements [PacketServerSession].
218+
type PacketProxyServerConnectedSession struct{}
219+
220+
// UnpackInPlace implements [PacketServerSession.UnpackInPlace].
221+
func (PacketProxyServerConnectedSession) UnpackInPlace(recvBuf []byte, _ netip.AddrPort) (payload []byte, destAddr conn.Addr, err error) {
222+
return recvBuf, conn.Addr{}, nil
223+
}
224+
225+
// AppendPack implements [PacketServerSession.AppendPack].
226+
func (PacketProxyServerConnectedSession) AppendPack(b, payload []byte, srcAddr conn.Addr) (sendBuf []byte, err error) {
227+
return append(b, payload...), nil
228+
}

netio/udp.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
package netio
2+
3+
import (
4+
"context"
5+
"net/netip"
6+
7+
"github.com/database64128/shadowsocks-go/cache"
8+
"github.com/database64128/shadowsocks-go/conn"
9+
)
10+
11+
// MaxUDPPayloadSizeForAddr calculates the maximum unfragmented UDP payload size for the given address
12+
// based on the MTU and address family.
13+
func MaxUDPPayloadSizeForAddr(mtu int, addr netip.Addr) int {
14+
return MaxUDPPayloadSize(mtu, addr.Is4() || addr.Is4In6())
15+
}
16+
17+
// MaxUDPPayloadSize calculates the maximum unfragmented UDP payload size for the MTU and address family.
18+
func MaxUDPPayloadSize(mtu int, is4 bool) int {
19+
const (
20+
IPv4HeaderLength = 20
21+
IPv6HeaderLength = 40
22+
UDPHeaderLength = 8
23+
24+
// Next Header (1) + Hdr Ext Len (1) + Option Type (1) + Opt Data Len (1) + Jumbo Payload Length (u32be)
25+
//
26+
// 1. RFC 2675 - IPv6 Jumbograms
27+
// 2. RFC 8200 - IPv6 Specification
28+
JumboPayloadOptionLength = 1 + 1 + 1 + 1 + 4
29+
)
30+
if is4 {
31+
return mtu - IPv4HeaderLength - UDPHeaderLength
32+
}
33+
if mtu > 65575 {
34+
return mtu - IPv6HeaderLength - JumboPayloadOptionLength - UDPHeaderLength
35+
}
36+
return mtu - IPv6HeaderLength - UDPHeaderLength
37+
}
38+
39+
// UDPClientConfig is the configuration for a UDP client.
40+
type UDPClientConfig struct {
41+
// Name is the name of the client.
42+
Name string
43+
44+
// Network controls the address family when resolving domain name destination addresses.
45+
//
46+
// - "ip": System default, likely dual-stack.
47+
// - "ip4": Resolve to IPv4 addresses.
48+
// - "ip6": Resolve to IPv6 addresses.
49+
Network string
50+
51+
// MTU is the MTU of the client's designated network path.
52+
// It serves as a hint for calculating buffer sizes.
53+
MTU int
54+
55+
// ListenConfig is the [conn.ListenConfig] for opening unconnected client sockets.
56+
ListenConfig conn.ListenConfig
57+
58+
// Dialer is the [conn.Dialer] for opening connected client sockets.
59+
Dialer conn.Dialer
60+
}
61+
62+
// NewUDPClient returns a new UDP client.
63+
func (c *UDPClientConfig) NewUDPClient() *UDPClient {
64+
is4 := c.Network == "ip" || c.Network == "ip4"
65+
return &UDPClient{
66+
name: c.Name,
67+
network: c.Network,
68+
maxPacketSize: MaxUDPPayloadSize(c.MTU, is4),
69+
listenConfig: c.ListenConfig,
70+
dialer: c.Dialer,
71+
}
72+
}
73+
74+
// UDPClient establishes UDP sessions to servers.
75+
//
76+
// UDPClient implements [PacketClient].
77+
type UDPClient struct {
78+
name string
79+
network string
80+
maxPacketSize int
81+
listenConfig conn.ListenConfig
82+
dialer conn.Dialer
83+
}
84+
85+
var _ PacketClient = (*UDPClient)(nil)
86+
87+
// NewSession implements [PacketClient.NewSession].
88+
func (c *UDPClient) NewSession(ctx context.Context, connectAddr conn.Addr) (PacketClientSession, PacketClientSessionInfo, error) {
89+
if connectAddr.IsValid() {
90+
return UDPClientConnectedSession{}, PacketClientSessionInfo{
91+
Name: c.name,
92+
MaxPacketSize: c.maxPacketSize,
93+
Dialer: c.dialer,
94+
ConnectAddr: connectAddr,
95+
}, nil
96+
}
97+
return &UDPClientSession{network: c.network}, PacketClientSessionInfo{
98+
Name: c.name,
99+
MaxPacketSize: c.maxPacketSize,
100+
ListenConfig: c.listenConfig,
101+
}, nil
102+
}
103+
104+
// UDPClientSession passes UDP packets unmodified.
105+
//
106+
// UDPClientSession implements [PacketClientSession].
107+
type UDPClientSession struct {
108+
ipByDomain *cache.BoundedCache[string, netip.Addr]
109+
network string
110+
}
111+
112+
// AppendPack implements [PacketClientSession.AppendPack].
113+
func (s *UDPClientSession) AppendPack(ctx context.Context, b, payload []byte, destAddr conn.Addr) (sendBuf []byte, sendAddrPort netip.AddrPort, err error) {
114+
if destAddr.IsIP() {
115+
sendAddrPort = destAddr.IPPort()
116+
} else {
117+
if s.ipByDomain == nil {
118+
// Initialize the cache with a reasonable size.
119+
const domainCacheSize = 32
120+
s.ipByDomain = cache.NewBoundedCache[string, netip.Addr](domainCacheSize)
121+
}
122+
domain := destAddr.Domain()
123+
ip, ok := s.ipByDomain.Get(domain)
124+
if !ok {
125+
ip, err = destAddr.ResolveIP(ctx, s.network)
126+
if err != nil {
127+
return nil, netip.AddrPort{}, err
128+
}
129+
s.ipByDomain.InsertUnchecked(domain, ip)
130+
}
131+
sendAddrPort = netip.AddrPortFrom(ip, destAddr.Port())
132+
}
133+
return append(b, payload...), sendAddrPort, nil
134+
}
135+
136+
// UnpackInPlace implements [PacketClientSession.UnpackInPlace].
137+
func (*UDPClientSession) UnpackInPlace(recvBuf []byte, recvAddrPort netip.AddrPort) (payload []byte, srcAddr conn.Addr, err error) {
138+
return recvBuf, conn.AddrFromIPPort(recvAddrPort), nil
139+
}
140+
141+
// Close implements [PacketClientSession.Close].
142+
func (*UDPClientSession) Close() error {
143+
return nil
144+
}
145+
146+
// UDPClientConnectedSession is like [UDPClientSession] but for "connected" sessions.
147+
//
148+
// UDPClientConnectedSession implements [PacketClientSession].
149+
type UDPClientConnectedSession struct{}
150+
151+
// AppendPack implements [PacketClientSession.AppendPack].
152+
func (UDPClientConnectedSession) AppendPack(_ context.Context, b, payload []byte, _ conn.Addr) (sendBuf []byte, sendAddrPort netip.AddrPort, err error) {
153+
return append(b, payload...), netip.AddrPort{}, nil
154+
}
155+
156+
// UnpackInPlace implements [PacketClientSession.UnpackInPlace].
157+
func (UDPClientConnectedSession) UnpackInPlace(recvBuf []byte, _ netip.AddrPort) (payload []byte, srcAddr conn.Addr, err error) {
158+
return recvBuf, conn.Addr{}, nil
159+
}
160+
161+
// Close implements [PacketClientSession.Close].
162+
func (UDPClientConnectedSession) Close() error {
163+
return nil
164+
}

0 commit comments

Comments
 (0)