Эх сурвалжийг харах

SO_EXCLUSIVEADDRUSE fixes for Windows

- Emit NoticeHttp/SocksProxyPortInUse notices when bind failed with WSAEACCES
- Set SO_EXCLUSIVEADDRUSE when binding to the wildcard address
Rod Hynes 4 жил өмнө
parent
commit
77afea6720

+ 3 - 3
psiphon/httpProxy.go

@@ -100,10 +100,10 @@ func NewHttpProxy(
 	tunneler Tunneler,
 	tunneler Tunneler,
 	listenIP string) (proxy *HttpProxy, err error) {
 	listenIP string) (proxy *HttpProxy, err error) {
 
 
-	listener, err := net.Listen(
-		"tcp", net.JoinHostPort(listenIP, strconv.Itoa(config.LocalHttpProxyPort)))
+	listener, portInUse, err := makeLocalProxyListener(
+		listenIP, config.LocalHttpProxyPort)
 	if err != nil {
 	if err != nil {
-		if IsAddressInUseError(err) {
+		if portInUse {
 			NoticeHttpProxyPortInUse(config.LocalHttpProxyPort)
 			NoticeHttpProxyPortInUse(config.LocalHttpProxyPort)
 		}
 		}
 		return nil, errors.Trace(err)
 		return nil, errors.Trace(err)

+ 11 - 0
psiphon/net_android_linux.go

@@ -1,3 +1,4 @@
+//go:build android || linux
 // +build android linux
 // +build android linux
 
 
 /*
 /*
@@ -22,6 +23,8 @@
 package psiphon
 package psiphon
 
 
 import (
 import (
+	"net"
+	"strconv"
 	"unsafe"
 	"unsafe"
 
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
@@ -49,3 +52,11 @@ func setSocketBPF(BPFProgramInstructions []bpf.RawInstruction, socketFD int) err
 
 
 func setAdditionalSocketOptions(_ int) {
 func setAdditionalSocketOptions(_ int) {
 }
 }
+
+func makeLocalProxyListener(listenIP string, port int) (net.Listener, bool, error) {
+	listener, err := net.Listen("tcp", net.JoinHostPort(listenIP, strconv.Itoa(port)))
+	if err != nil {
+		return nil, IsAddressInUseError(err), errors.Trace(err)
+	}
+	return listener, false, nil
+}

+ 10 - 0
psiphon/net_darwin.go

@@ -20,6 +20,8 @@
 package psiphon
 package psiphon
 
 
 import (
 import (
+	"net"
+	"strconv"
 	"syscall"
 	"syscall"
 
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
@@ -37,3 +39,11 @@ func setSocketBPF(_ []bpf.RawInstruction, _ int) error {
 func setAdditionalSocketOptions(socketFd int) {
 func setAdditionalSocketOptions(socketFd int) {
 	syscall.SetsockoptInt(socketFd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE, 1)
 	syscall.SetsockoptInt(socketFd, syscall.SOL_SOCKET, syscall.SO_NOSIGPIPE, 1)
 }
 }
+
+func makeLocalProxyListener(listenIP string, port int) (net.Listener, bool, error) {
+	listener, err := net.Listen("tcp", net.JoinHostPort(listenIP, strconv.Itoa(port)))
+	if err != nil {
+		return nil, IsAddressInUseError(err), errors.Trace(err)
+	}
+	return listener, false, nil
+}

+ 13 - 1
psiphon/net_other.go

@@ -1,4 +1,5 @@
-// +build !darwin,!android,!linux
+//go:build !darwin && !android && !linux && !windows
+// +build !darwin,!android,!linux,!windows
 
 
 /*
 /*
  * Copyright (c) 2017, Psiphon Inc.
  * Copyright (c) 2017, Psiphon Inc.
@@ -22,6 +23,9 @@
 package psiphon
 package psiphon
 
 
 import (
 import (
+	"net"
+	"strconv"
+
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
 	"golang.org/x/net/bpf"
 	"golang.org/x/net/bpf"
 )
 )
@@ -36,3 +40,11 @@ func setSocketBPF(_ []bpf.RawInstruction, _ int) error {
 
 
 func setAdditionalSocketOptions(_ int) {
 func setAdditionalSocketOptions(_ int) {
 }
 }
+
+func makeLocalProxyListener(listenIP string, port int) (net.Listener, bool, error) {
+	listener, err := net.Listen("tcp", net.JoinHostPort(listenIP, strconv.Itoa(port)))
+	if err != nil {
+		return nil, IsAddressInUseError(err), errors.Trace(err)
+	}
+	return listener, false, nil
+}

+ 88 - 0
psiphon/net_windows.go

@@ -0,0 +1,88 @@
+//go:build windows
+// +build windows
+
+/*
+ * Copyright (c) 2022, Psiphon Inc.
+ * All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package psiphon
+
+import (
+	"context"
+	"net"
+	"strconv"
+	"syscall"
+
+	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
+	"golang.org/x/net/bpf"
+)
+
+func ClientBPFEnabled() bool {
+	return false
+}
+
+func setSocketBPF(_ []bpf.RawInstruction, _ int) error {
+	return errors.TraceNew("BPF not supported")
+}
+
+func setAdditionalSocketOptions(_ int) {
+}
+
+func makeLocalProxyListener(listenIP string, port int) (net.Listener, bool, error) {
+	listenConfig := net.ListenConfig{
+		Control: func(_, _ string, c syscall.RawConn) error {
+			var controlErr error
+			err := c.Control(func(fd uintptr) {
+
+				if listenIP != "0.0.0.0" {
+					return
+				}
+
+				// When binding to the wildcard IP address, 0.0.0.0, set
+				// SO_EXCLUSIVEADDRUSE since Windows, in this case, otherwise
+				// allows other programs to bind to a specific IP address
+				// (e.g., 127.0.0.1) with the same port number and we'll
+				// unexpectedly lose our port binding.
+				//
+				// SO_EXCLUSIVEADDRUSE is not necessary in the non-wildcard
+				// case, as Windows will cause conflicting bind calls to
+				// fail.
+
+				// SO_EXCLUSIVEADDRUSE isn't defined in syscall. This is the
+				// definition from Winsock2.h.
+				SO_EXCLUSIVEADDRUSE := ^syscall.SO_REUSEADDR
+
+				controlErr = syscall.SetsockoptInt(
+					syscall.Handle(fd),
+					syscall.SOL_SOCKET,
+					SO_EXCLUSIVEADDRUSE,
+					1)
+			})
+			if controlErr != nil {
+				return errors.Trace(controlErr)
+			}
+			return errors.Trace(err)
+		},
+	}
+	listener, err := listenConfig.Listen(
+		context.Background(), "tcp", net.JoinHostPort(listenIP, strconv.Itoa(port)))
+	if err != nil {
+		return nil, IsAddressInUseError(err), errors.Trace(err)
+	}
+	return listener, false, nil
+}

+ 4 - 5
psiphon/socksProxy.go

@@ -21,7 +21,6 @@ package psiphon
 
 
 import (
 import (
 	"net"
 	"net"
-	"strconv"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 
 
@@ -53,10 +52,10 @@ func NewSocksProxy(
 	tunneler Tunneler,
 	tunneler Tunneler,
 	listenIP string) (proxy *SocksProxy, err error) {
 	listenIP string) (proxy *SocksProxy, err error) {
 
 
-	listener, err := socks.ListenSocks(
-		"tcp", net.JoinHostPort(listenIP, strconv.Itoa(config.LocalSocksProxyPort)))
+	listener, portInUse, err := makeLocalProxyListener(
+		listenIP, config.LocalSocksProxyPort)
 	if err != nil {
 	if err != nil {
-		if IsAddressInUseError(err) {
+		if portInUse {
 			NoticeSocksProxyPortInUse(config.LocalSocksProxyPort)
 			NoticeSocksProxyPortInUse(config.LocalSocksProxyPort)
 		}
 		}
 		return nil, errors.Trace(err)
 		return nil, errors.Trace(err)
@@ -64,7 +63,7 @@ func NewSocksProxy(
 	proxy = &SocksProxy{
 	proxy = &SocksProxy{
 		config:                 config,
 		config:                 config,
 		tunneler:               tunneler,
 		tunneler:               tunneler,
-		listener:               listener,
+		listener:               socks.NewSocksListener(listener),
 		serveWaitGroup:         new(sync.WaitGroup),
 		serveWaitGroup:         new(sync.WaitGroup),
 		openConns:              common.NewConns(),
 		openConns:              common.NewConns(),
 		stopListeningBroadcast: make(chan struct{}),
 		stopListeningBroadcast: make(chan struct{}),

+ 4 - 2
psiphon/utils.go

@@ -107,9 +107,11 @@ func IsAddressInUseError(err error) bool {
 			if err.Err == syscall.EADDRINUSE {
 			if err.Err == syscall.EADDRINUSE {
 				return true
 				return true
 			}
 			}
-			// Special case for Windows (WSAEADDRINUSE = 10048)
+			// Special case for Windows, WSAEADDRINUSE (10048). In the case
+			// where the socket already bound to the port has set
+			// SO_EXCLUSIVEADDRUSE, the error will instead be WSAEACCES (10013).
 			if errno, ok := err.Err.(syscall.Errno); ok {
 			if errno, ok := err.Err.(syscall.Errno); ok {
-				if int(errno) == 10048 {
+				if int(errno) == 10048 || int(errno) == 10013 {
 					return true
 					return true
 				}
 				}
 			}
 			}