Skip to content

Commit 8097f14

Browse files
committedJun 8, 2024··
☠️ windows: gate dial support behind build tag
For golang/go#67401.
1 parent 676234f commit 8097f14

22 files changed

+159
-86
lines changed
 

‎.github/workflows/test_gotip.yml

+3
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,6 @@ jobs:
2727
2828
- name: Test
2929
run: gotip test -v
30+
31+
- name: Test with tag tfogo_checklinkname0
32+
run: gotip test -v -ldflags '-checklinkname=0' -tags tfogo_checklinkname0

‎README.md

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
go get github.com/database64128/tfo-go/v2
1010
```
1111

12+
### Windows support with Go 1.23 and later
13+
14+
`tfo-go`'s Windows support requires extensive usage of `//go:linkname` to access Go runtime internals, as there's currently no public API for Windows async IO in the standard library. Unfortunately, the Go team has decided to [lock down future uses of linkname](https://github.com/golang/go/issues/67401), starting with Go 1.23. And our bid to get the linknames we need exempted was [partially rejected](https://github.com/golang/go/issues/67401#issuecomment-2126175774). Therefore, we had to make the following changes:
15+
16+
- Windows support is gated behind the build tag `tfogo_checklinkname0` when building with Go 1.23 and later.
17+
- With Go 1.21 and 1.22, `tfo-go` still provides full Windows support, with or without the build tag.
18+
- With Go 1.23 and later, when the build tag is not specified, `tfo-go` only supports `listen` with TFO on Windows. To get full TFO support on Windows, the build tag `tfogo_checklinkname0` must be specified along with linker flag `-checklinkname=0` to disable the linkname check.
19+
1220
## License
1321

1422
[MIT](LICENSE)

‎netpoll_windows.go ‎netpoll_windows_checklinkname0.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build windows && (!go1.23 || (go1.23 && tfogo_checklinkname0))
2+
13
package tfo
24

35
import (

‎netpoll_windows_go123.go ‎netpoll_windows_go123_checklinkname0.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build windows && go1.23
1+
//go:build windows && go1.23 && tfogo_checklinkname0
22

33
package tfo
44

‎sockopt_connect_generic.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build freebsd || windows
1+
//go:build freebsd || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))
22

33
package tfo
44

‎sockopt_connect_stub.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))
2+
3+
package tfo
4+
5+
func setTFODialer(_ uintptr) error {
6+
return ErrPlatformUnsupported
7+
}

‎sockopt_listen_generic.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ func setTFOListener(fd uintptr) error {
66
return setTFO(int(fd), 1)
77
}
88

9-
func setTFOListenerWithBacklog(fd uintptr, backlog int) error {
9+
func setTFOListenerWithBacklog(fd uintptr, _ int) error {
1010
return setTFOListener(fd)
1111
}

‎sockopt_listen_stub.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build !darwin && !freebsd && !linux && !windows
2+
3+
package tfo
4+
5+
func setTFOListener(_ uintptr) error {
6+
return ErrPlatformUnsupported
7+
}
8+
9+
func setTFOListenerWithBacklog(_ uintptr, _ int) error {
10+
return ErrPlatformUnsupported
11+
}

‎sockopt_stub.go

-15
This file was deleted.

‎tfo.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func (lc *ListenConfig) tfoDisabled() bool {
6161
}
6262

6363
func (lc *ListenConfig) tfoNeedsFallback() bool {
64-
return lc.Fallback && (comptimeNoTFO || runtimeListenNoTFO.Load())
64+
return lc.Fallback && (comptimeListenNoTFO || runtimeListenNoTFO.Load())
6565
}
6666

6767
// Listen is like [net.ListenConfig.Listen] but enables TFO whenever possible,
@@ -70,7 +70,7 @@ func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (ne
7070
if lc.tfoDisabled() || !networkIsTCP(network) || lc.tfoNeedsFallback() {
7171
return lc.ListenConfig.Listen(ctx, network, address)
7272
}
73-
return lc.listenTFO(ctx, network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_unsupported.go
73+
return lc.listenTFO(ctx, network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_listen_stub.go
7474
}
7575

7676
// ListenContext is like [net.ListenContext] but enables TFO whenever possible.
@@ -94,7 +94,7 @@ func ListenTCP(network string, laddr *net.TCPAddr) (*net.TCPListener, error) {
9494
address = laddr.String()
9595
}
9696
var lc ListenConfig
97-
ln, err := lc.listenTFO(context.Background(), network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_unsupported.go
97+
ln, err := lc.listenTFO(context.Background(), network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_listen_stub.go
9898
if err != nil {
9999
return nil, err
100100
}
@@ -172,7 +172,11 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string, b []b
172172
if d.DisableTFO || !networkIsTCP(network) {
173173
return d.dialAndWrite(ctx, network, address, b)
174174
}
175-
return d.dialTFO(ctx, network, address, b) // tfo_bsd+windows.go, tfo_linux.go, tfo_unsupported.go
175+
tc, err := d.dialTFO(ctx, network, address, b) // tfo_bsd+windows.go, tfo_connect_stub.go, tfo_linux.go
176+
if err != nil {
177+
return nil, err // return nil [net.Conn] instead of non-nil [net.Conn] with nil [*net.TCPConn] pointer
178+
}
179+
return tc, nil
176180
}
177181

178182
// Dial is like [net.Dialer.Dial] but enables TFO whenever possible,
@@ -205,7 +209,7 @@ func DialTCP(network string, laddr, raddr *net.TCPAddr, b []byte) (*net.TCPConn,
205209
if raddr == nil {
206210
return nil, &net.OpError{Op: "dial", Net: network, Source: opAddr(laddr), Addr: nil, Err: errMissingAddress}
207211
}
208-
return dialTCPAddr(network, laddr, raddr, b) // tfo_bsd+windows.go, tfo_linux.go, tfo_unsupported.go
212+
return dialTCPAddr(network, laddr, raddr, b) // tfo_bsd+windows.go, tfo_connect_stub.go, tfo_linux.go
209213
}
210214

211215
func networkIsTCP(network string) bool {

‎tfo_bsd+windows.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build darwin || freebsd || windows
1+
//go:build darwin || freebsd || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))
22

33
package tfo
44

‎tfo_bsd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ func setTFODialerFromSocket(fd uintptr) error {
77
}
88

99
// doConnectCanFallback returns whether err from [doConnect] indicates lack of TFO support.
10-
func doConnectCanFallback(err error) bool {
10+
func doConnectCanFallback(_ error) bool {
1111
return false
1212
}

‎tfo_supported.go ‎tfo_connect_generic.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build darwin || freebsd || linux || windows
1+
//go:build darwin || freebsd || linux || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))
22

33
package tfo
44

@@ -11,7 +11,7 @@ import (
1111
_ "unsafe"
1212
)
1313

14-
const comptimeNoTFO = false
14+
const comptimeDialNoTFO = false
1515

1616
const (
1717
defaultTCPKeepAlive = 15 * time.Second

‎tfo_connect_stub.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))
2+
3+
package tfo
4+
5+
import (
6+
"context"
7+
"net"
8+
)
9+
10+
const comptimeDialNoTFO = true
11+
12+
func (d *Dialer) dialTFO(ctx context.Context, network, address string, b []byte) (*net.TCPConn, error) {
13+
if d.Fallback {
14+
return d.dialAndWriteTCPConn(ctx, network, address, b)
15+
}
16+
return nil, ErrPlatformUnsupported
17+
}
18+
19+
func dialTCPAddr(_ string, _, _ *net.TCPAddr, _ []byte) (*net.TCPConn, error) {
20+
return nil, ErrPlatformUnsupported
21+
}

‎tfo_unsupported_test.go ‎tfo_connect_stub_test.go

+1-19
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build !darwin && !freebsd && !linux && !windows
1+
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))
22

33
package tfo
44

@@ -7,24 +7,6 @@ import (
77
"testing"
88
)
99

10-
func TestListenTFO(t *testing.T) {
11-
ln, err := Listen("tcp", "")
12-
if ln != nil {
13-
t.Error("Expected nil listener")
14-
}
15-
if err != ErrPlatformUnsupported {
16-
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
17-
}
18-
19-
lntcp, err := ListenTCP("tcp", nil)
20-
if lntcp != nil {
21-
t.Error("Expected nil listener")
22-
}
23-
if err != ErrPlatformUnsupported {
24-
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
25-
}
26-
}
27-
2810
func TestDialTFO(t *testing.T) {
2911
s, err := newDiscardTCPServer(context.Background())
3012
if err != nil {

‎tfo_listen_stub.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//go:build !darwin && !freebsd && !linux && !windows
2+
3+
package tfo
4+
5+
import (
6+
"context"
7+
"net"
8+
)
9+
10+
const comptimeListenNoTFO = true
11+
12+
func (*ListenConfig) listenTFO(_ context.Context, _, _ string) (net.Listener, error) {
13+
return nil, ErrPlatformUnsupported
14+
}

‎tfo_listen_stub_test.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//go:build !darwin && !freebsd && !linux && !windows
2+
3+
package tfo
4+
5+
import (
6+
"testing"
7+
)
8+
9+
func TestListenTFO(t *testing.T) {
10+
ln, err := Listen("tcp", "")
11+
if ln != nil {
12+
t.Error("Expected nil listener")
13+
}
14+
if err != ErrPlatformUnsupported {
15+
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
16+
}
17+
18+
lntcp, err := ListenTCP("tcp", nil)
19+
if lntcp != nil {
20+
t.Error("Expected nil listener")
21+
}
22+
if err != ErrPlatformUnsupported {
23+
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
24+
}
25+
}

‎tfo_listen_supported.go

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build darwin || freebsd || linux || windows
2+
3+
package tfo
4+
5+
const comptimeListenNoTFO = false

‎tfo_supported_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build darwin || freebsd || linux || windows
1+
//go:build darwin || freebsd || linux || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))
22

33
package tfo
44

‎tfo_test.go

+43-14
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,24 @@ func runtimeFallbackSetDialLinuxSendto(t *testing.T) {
5050
}
5151
}
5252

53-
var listenConfigCases = []struct {
53+
type listenConfigTestCase struct {
5454
name string
5555
listenConfig ListenConfig
5656
mptcp mptcpStatus
5757
setRuntimeFallback runtimeFallbackHelperFunc
58-
}{
58+
}
59+
60+
func (c listenConfigTestCase) shouldSkip() bool {
61+
return comptimeListenNoTFO && !c.listenConfig.tfoDisabled()
62+
}
63+
64+
func (c listenConfigTestCase) checkSkip(t *testing.T) {
65+
if c.shouldSkip() {
66+
t.Skip("not applicable to the current platform")
67+
}
68+
}
69+
70+
var listenConfigCases = []listenConfigTestCase{
5971
{"TFO", ListenConfig{}, mptcpUseDefault, runtimeFallbackAsIs},
6072
{"TFO+RuntimeNoTFO", ListenConfig{}, mptcpUseDefault, runtimeFallbackSetListenNoTFO},
6173
{"TFO+MPTCPEnabled", ListenConfig{}, mptcpEnabled, runtimeFallbackAsIs},
@@ -79,13 +91,35 @@ var listenConfigCases = []struct {
7991
{"NoTFO+MPTCPDisabled", ListenConfig{DisableTFO: true}, mptcpDisabled, runtimeFallbackAsIs},
8092
}
8193

82-
var dialerCases = []struct {
94+
type dialerTestCase struct {
8395
name string
8496
dialer Dialer
8597
mptcp mptcpStatus
8698
setRuntimeFallback runtimeFallbackHelperFunc
8799
linuxOnly bool
88-
}{
100+
}
101+
102+
func (c dialerTestCase) shouldSkip() bool {
103+
if comptimeDialNoTFO && !c.dialer.DisableTFO {
104+
return true
105+
}
106+
switch runtime.GOOS {
107+
case "linux", "android":
108+
default:
109+
if c.linuxOnly {
110+
return true
111+
}
112+
}
113+
return false
114+
}
115+
116+
func (c dialerTestCase) checkSkip(t *testing.T) {
117+
if c.shouldSkip() {
118+
t.Skip("not applicable to the current platform")
119+
}
120+
}
121+
122+
var dialerCases = []dialerTestCase{
89123
{"TFO", Dialer{}, mptcpUseDefault, runtimeFallbackAsIs, false},
90124
{"TFO+RuntimeNoTFO", Dialer{}, mptcpUseDefault, runtimeFallbackSetDialNoTFO, false},
91125
{"TFO+RuntimeLinuxSendto", Dialer{}, mptcpUseDefault, runtimeFallbackSetDialLinuxSendto, true},
@@ -160,20 +194,13 @@ func init() {
160194
// Generate [cases].
161195
cases = make([]testCase, 0, len(listenConfigCases)*len(dialerCases))
162196
for _, lc := range listenConfigCases {
163-
if comptimeNoTFO && !lc.listenConfig.tfoDisabled() {
197+
if lc.shouldSkip() {
164198
continue
165199
}
166200
for _, d := range dialerCases {
167-
if comptimeNoTFO && !d.dialer.DisableTFO {
201+
if d.shouldSkip() {
168202
continue
169203
}
170-
switch runtime.GOOS {
171-
case "linux", "android":
172-
default:
173-
if d.linuxOnly {
174-
continue
175-
}
176-
}
177204
cases = append(cases, testCase{
178205
name: lc.name + "/" + d.name,
179206
listenConfig: lc.listenConfig,
@@ -193,7 +220,7 @@ type discardTCPServer struct {
193220

194221
// newDiscardTCPServer creates a new [discardTCPServer] that listens on a random port.
195222
func newDiscardTCPServer(ctx context.Context) (*discardTCPServer, error) {
196-
lc := ListenConfig{DisableTFO: comptimeNoTFO}
223+
lc := ListenConfig{DisableTFO: comptimeListenNoTFO}
197224
ln, err := lc.Listen(ctx, "tcp", "[::1]:")
198225
if err != nil {
199226
return nil, err
@@ -293,6 +320,7 @@ func TestListenDialUDP(t *testing.T) {
293320
func TestListenCtrlFn(t *testing.T) {
294321
for _, c := range listenConfigCases {
295322
t.Run(c.name, func(t *testing.T) {
323+
c.checkSkip(t)
296324
c.setRuntimeFallback(t)
297325
testListenCtrlFn(t, c.listenConfig)
298326
})
@@ -312,6 +340,7 @@ func TestDialCtrlFn(t *testing.T) {
312340

313341
for _, c := range dialerCases {
314342
t.Run(c.name, func(t *testing.T) {
343+
c.checkSkip(t)
315344
c.setRuntimeFallback(t)
316345
testDialCtrlFn(t, c.dialer, address)
317346
testDialCtrlCtxFn(t, c.dialer, address)

‎tfo_unsupported.go

-25
This file was deleted.

‎tfo_windows.go ‎tfo_windows_checklinkname0.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//go:build windows && (!go1.23 || (go1.23 && tfogo_checklinkname0))
2+
13
package tfo
24

35
import (

0 commit comments

Comments
 (0)
Please sign in to comment.