Skip to content

Commit

Permalink
☠️ windows: gate dial support behind build tag
Browse files Browse the repository at this point in the history
  • Loading branch information
database64128 committed Jun 8, 2024
1 parent 676234f commit 8097f14
Show file tree
Hide file tree
Showing 22 changed files with 159 additions and 86 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test_gotip.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ jobs:
- name: Test
run: gotip test -v

- name: Test with tag tfogo_checklinkname0
run: gotip test -v -ldflags '-checklinkname=0' -tags tfogo_checklinkname0
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
go get github.com/database64128/tfo-go/v2
```

### Windows support with Go 1.23 and later

`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:

- Windows support is gated behind the build tag `tfogo_checklinkname0` when building with Go 1.23 and later.
- With Go 1.21 and 1.22, `tfo-go` still provides full Windows support, with or without the build tag.
- 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.

## License

[MIT](LICENSE)
2 changes: 2 additions & 0 deletions netpoll_windows.go → netpoll_windows_checklinkname0.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build windows && (!go1.23 || (go1.23 && tfogo_checklinkname0))

package tfo

import (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build windows && go1.23
//go:build windows && go1.23 && tfogo_checklinkname0

package tfo

Expand Down
2 changes: 1 addition & 1 deletion sockopt_connect_generic.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build freebsd || windows
//go:build freebsd || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))

package tfo

Expand Down
7 changes: 7 additions & 0 deletions sockopt_connect_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))

package tfo

func setTFODialer(_ uintptr) error {
return ErrPlatformUnsupported
}
2 changes: 1 addition & 1 deletion sockopt_listen_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ func setTFOListener(fd uintptr) error {
return setTFO(int(fd), 1)
}

func setTFOListenerWithBacklog(fd uintptr, backlog int) error {
func setTFOListenerWithBacklog(fd uintptr, _ int) error {
return setTFOListener(fd)
}
11 changes: 11 additions & 0 deletions sockopt_listen_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//go:build !darwin && !freebsd && !linux && !windows

package tfo

func setTFOListener(_ uintptr) error {
return ErrPlatformUnsupported
}

func setTFOListenerWithBacklog(_ uintptr, _ int) error {
return ErrPlatformUnsupported
}
15 changes: 0 additions & 15 deletions sockopt_stub.go

This file was deleted.

14 changes: 9 additions & 5 deletions tfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (lc *ListenConfig) tfoDisabled() bool {
}

func (lc *ListenConfig) tfoNeedsFallback() bool {
return lc.Fallback && (comptimeNoTFO || runtimeListenNoTFO.Load())
return lc.Fallback && (comptimeListenNoTFO || runtimeListenNoTFO.Load())
}

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

// ListenContext is like [net.ListenContext] but enables TFO whenever possible.
Expand All @@ -94,7 +94,7 @@ func ListenTCP(network string, laddr *net.TCPAddr) (*net.TCPListener, error) {
address = laddr.String()
}
var lc ListenConfig
ln, err := lc.listenTFO(context.Background(), network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_unsupported.go
ln, err := lc.listenTFO(context.Background(), network, address) // tfo_darwin.go, tfo_listen_generic.go, tfo_listen_stub.go
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -172,7 +172,11 @@ func (d *Dialer) DialContext(ctx context.Context, network, address string, b []b
if d.DisableTFO || !networkIsTCP(network) {
return d.dialAndWrite(ctx, network, address, b)
}
return d.dialTFO(ctx, network, address, b) // tfo_bsd+windows.go, tfo_linux.go, tfo_unsupported.go
tc, err := d.dialTFO(ctx, network, address, b) // tfo_bsd+windows.go, tfo_connect_stub.go, tfo_linux.go
if err != nil {
return nil, err // return nil [net.Conn] instead of non-nil [net.Conn] with nil [*net.TCPConn] pointer
}
return tc, nil
}

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

func networkIsTCP(network string) bool {
Expand Down
2 changes: 1 addition & 1 deletion tfo_bsd+windows.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build darwin || freebsd || windows
//go:build darwin || freebsd || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))

package tfo

Expand Down
2 changes: 1 addition & 1 deletion tfo_bsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ func setTFODialerFromSocket(fd uintptr) error {
}

// doConnectCanFallback returns whether err from [doConnect] indicates lack of TFO support.
func doConnectCanFallback(err error) bool {
func doConnectCanFallback(_ error) bool {
return false
}
4 changes: 2 additions & 2 deletions tfo_supported.go → tfo_connect_generic.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))

package tfo

Expand All @@ -11,7 +11,7 @@ import (
_ "unsafe"
)

const comptimeNoTFO = false
const comptimeDialNoTFO = false

const (
defaultTCPKeepAlive = 15 * time.Second
Expand Down
21 changes: 21 additions & 0 deletions tfo_connect_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))

package tfo

import (
"context"
"net"
)

const comptimeDialNoTFO = true

func (d *Dialer) dialTFO(ctx context.Context, network, address string, b []byte) (*net.TCPConn, error) {
if d.Fallback {
return d.dialAndWriteTCPConn(ctx, network, address, b)
}
return nil, ErrPlatformUnsupported
}

func dialTCPAddr(_ string, _, _ *net.TCPAddr, _ []byte) (*net.TCPConn, error) {
return nil, ErrPlatformUnsupported
}
20 changes: 1 addition & 19 deletions tfo_unsupported_test.go → tfo_connect_stub_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build !darwin && !freebsd && !linux && !windows
//go:build !darwin && !freebsd && !linux && (!windows || (windows && go1.23 && !tfogo_checklinkname0))

package tfo

Expand All @@ -7,24 +7,6 @@ import (
"testing"
)

func TestListenTFO(t *testing.T) {
ln, err := Listen("tcp", "")
if ln != nil {
t.Error("Expected nil listener")
}
if err != ErrPlatformUnsupported {
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
}

lntcp, err := ListenTCP("tcp", nil)
if lntcp != nil {
t.Error("Expected nil listener")
}
if err != ErrPlatformUnsupported {
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
}
}

func TestDialTFO(t *testing.T) {
s, err := newDiscardTCPServer(context.Background())
if err != nil {
Expand Down
14 changes: 14 additions & 0 deletions tfo_listen_stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !darwin && !freebsd && !linux && !windows

package tfo

import (
"context"
"net"
)

const comptimeListenNoTFO = true

func (*ListenConfig) listenTFO(_ context.Context, _, _ string) (net.Listener, error) {
return nil, ErrPlatformUnsupported
}
25 changes: 25 additions & 0 deletions tfo_listen_stub_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build !darwin && !freebsd && !linux && !windows

package tfo

import (
"testing"
)

func TestListenTFO(t *testing.T) {
ln, err := Listen("tcp", "")
if ln != nil {
t.Error("Expected nil listener")
}
if err != ErrPlatformUnsupported {
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
}

lntcp, err := ListenTCP("tcp", nil)
if lntcp != nil {
t.Error("Expected nil listener")
}
if err != ErrPlatformUnsupported {
t.Errorf("Expected ErrPlatformUnsupported, got %v", err)
}
}
5 changes: 5 additions & 0 deletions tfo_listen_supported.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build darwin || freebsd || linux || windows

package tfo

const comptimeListenNoTFO = false
2 changes: 1 addition & 1 deletion tfo_supported_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build darwin || freebsd || linux || windows
//go:build darwin || freebsd || linux || (windows && (!go1.23 || (go1.23 && tfogo_checklinkname0)))

package tfo

Expand Down
57 changes: 43 additions & 14 deletions tfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,24 @@ func runtimeFallbackSetDialLinuxSendto(t *testing.T) {
}
}

var listenConfigCases = []struct {
type listenConfigTestCase struct {
name string
listenConfig ListenConfig
mptcp mptcpStatus
setRuntimeFallback runtimeFallbackHelperFunc
}{
}

func (c listenConfigTestCase) shouldSkip() bool {
return comptimeListenNoTFO && !c.listenConfig.tfoDisabled()
}

func (c listenConfigTestCase) checkSkip(t *testing.T) {
if c.shouldSkip() {
t.Skip("not applicable to the current platform")
}
}

var listenConfigCases = []listenConfigTestCase{
{"TFO", ListenConfig{}, mptcpUseDefault, runtimeFallbackAsIs},
{"TFO+RuntimeNoTFO", ListenConfig{}, mptcpUseDefault, runtimeFallbackSetListenNoTFO},
{"TFO+MPTCPEnabled", ListenConfig{}, mptcpEnabled, runtimeFallbackAsIs},
Expand All @@ -79,13 +91,35 @@ var listenConfigCases = []struct {
{"NoTFO+MPTCPDisabled", ListenConfig{DisableTFO: true}, mptcpDisabled, runtimeFallbackAsIs},
}

var dialerCases = []struct {
type dialerTestCase struct {
name string
dialer Dialer
mptcp mptcpStatus
setRuntimeFallback runtimeFallbackHelperFunc
linuxOnly bool
}{
}

func (c dialerTestCase) shouldSkip() bool {
if comptimeDialNoTFO && !c.dialer.DisableTFO {
return true
}
switch runtime.GOOS {
case "linux", "android":
default:
if c.linuxOnly {
return true
}
}
return false
}

func (c dialerTestCase) checkSkip(t *testing.T) {
if c.shouldSkip() {
t.Skip("not applicable to the current platform")
}
}

var dialerCases = []dialerTestCase{
{"TFO", Dialer{}, mptcpUseDefault, runtimeFallbackAsIs, false},
{"TFO+RuntimeNoTFO", Dialer{}, mptcpUseDefault, runtimeFallbackSetDialNoTFO, false},
{"TFO+RuntimeLinuxSendto", Dialer{}, mptcpUseDefault, runtimeFallbackSetDialLinuxSendto, true},
Expand Down Expand Up @@ -160,20 +194,13 @@ func init() {
// Generate [cases].
cases = make([]testCase, 0, len(listenConfigCases)*len(dialerCases))
for _, lc := range listenConfigCases {
if comptimeNoTFO && !lc.listenConfig.tfoDisabled() {
if lc.shouldSkip() {
continue
}
for _, d := range dialerCases {
if comptimeNoTFO && !d.dialer.DisableTFO {
if d.shouldSkip() {
continue
}
switch runtime.GOOS {
case "linux", "android":
default:
if d.linuxOnly {
continue
}
}
cases = append(cases, testCase{
name: lc.name + "/" + d.name,
listenConfig: lc.listenConfig,
Expand All @@ -193,7 +220,7 @@ type discardTCPServer struct {

// newDiscardTCPServer creates a new [discardTCPServer] that listens on a random port.
func newDiscardTCPServer(ctx context.Context) (*discardTCPServer, error) {
lc := ListenConfig{DisableTFO: comptimeNoTFO}
lc := ListenConfig{DisableTFO: comptimeListenNoTFO}
ln, err := lc.Listen(ctx, "tcp", "[::1]:")
if err != nil {
return nil, err
Expand Down Expand Up @@ -293,6 +320,7 @@ func TestListenDialUDP(t *testing.T) {
func TestListenCtrlFn(t *testing.T) {
for _, c := range listenConfigCases {
t.Run(c.name, func(t *testing.T) {
c.checkSkip(t)
c.setRuntimeFallback(t)
testListenCtrlFn(t, c.listenConfig)
})
Expand All @@ -312,6 +340,7 @@ func TestDialCtrlFn(t *testing.T) {

for _, c := range dialerCases {
t.Run(c.name, func(t *testing.T) {
c.checkSkip(t)
c.setRuntimeFallback(t)
testDialCtrlFn(t, c.dialer, address)
testDialCtrlCtxFn(t, c.dialer, address)
Expand Down
Loading

0 comments on commit 8097f14

Please sign in to comment.