github.com/database64128/shadowsocks-go@v1.10.2-0.20240315062903-143a773533f1/conn/conn.go (about) 1 package conn 2 3 import ( 4 "context" 5 "net" 6 "syscall" 7 8 "github.com/database64128/tfo-go/v2" 9 ) 10 11 type setFunc = func(fd int, network string) error 12 13 type setFuncSlice []setFunc 14 15 func (fns setFuncSlice) controlContextFunc() func(ctx context.Context, network, address string, c syscall.RawConn) error { 16 if len(fns) == 0 { 17 return nil 18 } 19 return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) { 20 if cerr := c.Control(func(fd uintptr) { 21 for _, fn := range fns { 22 if err = fn(int(fd), network); err != nil { 23 return 24 } 25 } 26 }); cerr != nil { 27 return cerr 28 } 29 return 30 } 31 } 32 33 func (fns setFuncSlice) controlFunc() func(network, address string, c syscall.RawConn) error { 34 if len(fns) == 0 { 35 return nil 36 } 37 return func(network, address string, c syscall.RawConn) (err error) { 38 if cerr := c.Control(func(fd uintptr) { 39 for _, fn := range fns { 40 if err = fn(int(fd), network); err != nil { 41 return 42 } 43 } 44 }); cerr != nil { 45 return cerr 46 } 47 return 48 } 49 } 50 51 // ListenConfig is [tfo.ListenConfig] but provides a subjectively nicer API. 52 type ListenConfig tfo.ListenConfig 53 54 // ListenTCP wraps [tfo.ListenConfig.Listen] and returns a [*net.TCPListener] directly. 55 func (lc *ListenConfig) ListenTCP(ctx context.Context, network, address string) (*net.TCPListener, error) { 56 l, err := (*tfo.ListenConfig)(lc).Listen(ctx, network, address) 57 if err != nil { 58 return nil, err 59 } 60 return l.(*net.TCPListener), nil 61 } 62 63 // ListenUDP wraps [net.ListenConfig.ListenPacket] and returns a [*net.UDPConn] directly. 64 func (lc *ListenConfig) ListenUDP(ctx context.Context, network, address string) (*net.UDPConn, error) { 65 pc, err := lc.ListenConfig.ListenPacket(ctx, network, address) 66 if err != nil { 67 return nil, err 68 } 69 return pc.(*net.UDPConn), nil 70 } 71 72 // ListenerSocketOptions contains listener-specific socket options. 73 type ListenerSocketOptions struct { 74 // Fwmark sets the listener's fwmark on Linux, or user cookie on FreeBSD. 75 // 76 // Available on Linux and FreeBSD. 77 Fwmark int 78 79 // TrafficClass sets the traffic class of the listener. 80 // 81 // Available on most platforms except Windows. 82 TrafficClass int 83 84 // TCPFastOpenBacklog specifies the maximum number of pending TFO connections on Linux. 85 // If the value is 0, Go std's listen(2) backlog is used. 86 // 87 // On other platforms, a non-negative value is ignored, as they do not have the option to set the TFO backlog. 88 // 89 // On all platforms, a negative value disables TFO. 90 TCPFastOpenBacklog int 91 92 // ReusePort enables SO_REUSEPORT on the listener. 93 // 94 // Available on Linux and the BSDs. 95 ReusePort bool 96 97 // Transparent enables transparent proxy on the listener. 98 // 99 // Only available on Linux. 100 Transparent bool 101 102 // PathMTUDiscovery enables Path MTU Discovery on the listener. 103 // 104 // Available on Linux, macOS, FreeBSD, and Windows. 105 PathMTUDiscovery bool 106 107 // TCPFastOpen enables TCP Fast Open on the listener. 108 // 109 // Available on Linux, macOS, FreeBSD, and Windows. 110 TCPFastOpen bool 111 112 // TCPFastOpenFallback enables runtime detection of TCP Fast Open support on the listener. 113 // 114 // When enabled, the listener will start without TFO if TFO is not available on the system. 115 // When disabled, the listener will abort if TFO cannot be enabled on the socket. 116 // 117 // Available on all platforms. 118 TCPFastOpenFallback bool 119 120 // MultipathTCP enables multipath TCP on the listener. 121 // 122 // Unlike Go std, we make MPTCP strictly opt-in. 123 // That is, if this field is false, MPTCP will be explicitly disabled. 124 // This ensures that if Go std suddenly decides to enable MPTCP by default, 125 // existing configurations won't encounter issues due to missing features in the kernel MPTCP stack, 126 // such as TCP keepalive (as of Linux 6.5), and failed connect attempts won't always be retried once. 127 // 128 // Available on platforms supported by Go std's MPTCP implementation. 129 MultipathTCP bool 130 131 // ReceivePacketInfo enables the reception of packet information control messages on the listener. 132 // 133 // Available on Linux, macOS, and Windows. 134 ReceivePacketInfo bool 135 136 // ReceiveOriginalDestAddr enables the reception of original destination address control messages on the listener. 137 // 138 // Only available on Linux. 139 ReceiveOriginalDestAddr bool 140 } 141 142 // ListenConfig returns a [ListenConfig] with a control function that sets the socket options. 143 func (lso ListenerSocketOptions) ListenConfig() ListenConfig { 144 lc := ListenConfig{ 145 ListenConfig: net.ListenConfig{ 146 Control: lso.buildSetFns().controlFunc(), 147 }, 148 Backlog: lso.TCPFastOpenBacklog, 149 DisableTFO: !lso.TCPFastOpen, 150 Fallback: lso.TCPFastOpenFallback, 151 } 152 lc.SetMultipathTCP(lso.MultipathTCP) 153 return lc 154 } 155 156 var ( 157 // DefaultTCPListenerSocketOptions is the default [ListenerSocketOptions] for TCP servers. 158 DefaultTCPListenerSocketOptions = ListenerSocketOptions{ 159 TCPFastOpen: true, 160 } 161 162 // DefaultTCPListenConfig is the default [ListenConfig] for TCP listeners. 163 DefaultTCPListenConfig = DefaultTCPListenerSocketOptions.ListenConfig() 164 165 // DefaultUDPServerSocketOptions is the default [ListenerSocketOptions] for UDP servers. 166 DefaultUDPServerSocketOptions = ListenerSocketOptions{ 167 PathMTUDiscovery: true, 168 ReceivePacketInfo: true, 169 } 170 171 // DefaultUDPServerListenConfig is the default [ListenConfig] for UDP servers. 172 DefaultUDPServerListenConfig = DefaultUDPServerSocketOptions.ListenConfig() 173 174 // DefaultUDPClientSocketOptions is the default [ListenerSocketOptions] for UDP clients. 175 DefaultUDPClientSocketOptions = ListenerSocketOptions{ 176 PathMTUDiscovery: true, 177 } 178 179 // DefaultUDPClientListenConfig is the default [ListenConfig] for UDP clients. 180 DefaultUDPClientListenConfig = DefaultUDPClientSocketOptions.ListenConfig() 181 ) 182 183 // Dialer is [tfo.Dialer] but provides a subjectively nicer API. 184 type Dialer tfo.Dialer 185 186 // DialTCP wraps [tfo.Dialer.DialContext] and returns a [*net.TCPConn] directly. 187 func (d *Dialer) DialTCP(ctx context.Context, network, address string, b []byte) (*net.TCPConn, error) { 188 c, err := (*tfo.Dialer)(d).DialContext(ctx, network, address, b) 189 if err != nil { 190 return nil, err 191 } 192 return c.(*net.TCPConn), nil 193 } 194 195 // DialUDP wraps [net.Dialer.DialContext] and returns a [*net.UDPConn] directly. 196 func (d *Dialer) DialUDP(ctx context.Context, network, address string) (*net.UDPConn, error) { 197 c, err := d.Dialer.DialContext(ctx, network, address) 198 if err != nil { 199 return nil, err 200 } 201 return c.(*net.UDPConn), nil 202 } 203 204 // DialerSocketOptions contains dialer-specific socket options. 205 type DialerSocketOptions struct { 206 // Fwmark sets the dialer's fwmark on Linux, or user cookie on FreeBSD. 207 // 208 // Available on Linux and FreeBSD. 209 Fwmark int 210 211 // TrafficClass sets the traffic class of the dialer. 212 // 213 // Available on most platforms except Windows. 214 TrafficClass int 215 216 // TCPFastOpen enables TCP Fast Open on the dialer. 217 // 218 // Available on Linux, macOS, FreeBSD, and Windows. 219 TCPFastOpen bool 220 221 // TCPFastOpenFallback enables runtime detection of TCP Fast Open support on the dialer. 222 // 223 // When enabled, the dialer will connect without TFO if TFO is not available on the system. 224 // When disabled, the dialer will abort if TFO cannot be enabled on the socket. 225 // 226 // Available on all platforms. 227 TCPFastOpenFallback bool 228 229 // MultipathTCP enables multipath TCP on the dialer. 230 // 231 // Unlike Go std, we make MPTCP strictly opt-in. 232 // That is, if this field is false, MPTCP will be explicitly disabled. 233 // This ensures that if Go std suddenly decides to enable MPTCP by default, 234 // existing configurations won't encounter issues due to missing features in the kernel MPTCP stack, 235 // such as TCP keepalive (as of Linux 6.5), and failed connect attempts won't always be retried once. 236 // 237 // Available on platforms supported by Go std's MPTCP implementation. 238 MultipathTCP bool 239 } 240 241 // Dialer returns a [Dialer] with a control function that sets the socket options. 242 func (dso DialerSocketOptions) Dialer() Dialer { 243 d := Dialer{ 244 Dialer: net.Dialer{ 245 ControlContext: dso.buildSetFns().controlContextFunc(), 246 }, 247 DisableTFO: !dso.TCPFastOpen, 248 Fallback: dso.TCPFastOpenFallback, 249 } 250 d.SetMultipathTCP(dso.MultipathTCP) 251 return d 252 } 253 254 var ( 255 // DefaultTCPDialerSocketOptions is the default [DialerSocketOptions] for TCP clients. 256 DefaultTCPDialerSocketOptions = DialerSocketOptions{ 257 TCPFastOpen: true, 258 } 259 260 // DefaultTCPDialer is the default [Dialer] for TCP clients. 261 DefaultTCPDialer = DefaultTCPDialerSocketOptions.Dialer() 262 ) 263 264 // ListenConfigCache is a map of [ListenerSocketOptions] to [ListenConfig]. 265 type ListenConfigCache map[ListenerSocketOptions]ListenConfig 266 267 // NewListenConfigCache creates a new cache for [ListenConfig] with a few default entries. 268 func NewListenConfigCache() ListenConfigCache { 269 return ListenConfigCache{ 270 DefaultTCPListenerSocketOptions: DefaultTCPListenConfig, 271 DefaultUDPServerSocketOptions: DefaultUDPServerListenConfig, 272 DefaultUDPClientSocketOptions: DefaultUDPClientListenConfig, 273 } 274 } 275 276 // Get returns a [ListenConfig] for the given [ListenerSocketOptions]. 277 func (cache ListenConfigCache) Get(lso ListenerSocketOptions) (lc ListenConfig) { 278 lc, ok := cache[lso] 279 if ok { 280 return 281 } 282 lc = lso.ListenConfig() 283 cache[lso] = lc 284 return 285 } 286 287 // DialerCache is a map of [DialerSocketOptions] to [Dialer]. 288 type DialerCache map[DialerSocketOptions]Dialer 289 290 // NewDialerCache creates a new cache for [Dialer] with a few default entries. 291 func NewDialerCache() DialerCache { 292 return DialerCache{ 293 DefaultTCPDialerSocketOptions: DefaultTCPDialer, 294 } 295 } 296 297 // Get returns a [Dialer] for the given [DialerSocketOptions]. 298 func (cache DialerCache) Get(dso DialerSocketOptions) (d Dialer) { 299 d, ok := cache[dso] 300 if ok { 301 return 302 } 303 d = dso.Dialer() 304 cache[dso] = d 305 return 306 }