Bladeren bron

Merge branch 'master' into staging-client

Rod Hynes 3 jaren geleden
bovenliggende
commit
f3d12292ed
1 gewijzigde bestanden met toevoegingen van 101 en 0 verwijderingen
  1. 101 0
      psiphon/common/quic/quic.go

+ 101 - 0
psiphon/common/quic/quic.go

@@ -50,6 +50,7 @@ import (
 	"io"
 	"net"
 	"net/http"
+	"os"
 	"sync"
 	"sync/atomic"
 	"syscall"
@@ -69,6 +70,7 @@ const (
 	SERVER_HANDSHAKE_TIMEOUT = 30 * time.Second
 	SERVER_IDLE_TIMEOUT      = 5 * time.Minute
 	CLIENT_IDLE_TIMEOUT      = 30 * time.Second
+	UDP_PACKET_WRITE_TIMEOUT = 1 * time.Second
 )
 
 // Enabled indicates if QUIC functionality is enabled.
@@ -383,6 +385,16 @@ func Dial(
 		return nil, errors.Tracef("invalid destination port: %d", remoteAddr.Port)
 	}
 
+	udpConn, ok := packetConn.(udpConn)
+	if !ok {
+		return nil, errors.TraceNew("packetConn must implement net.UDPConn functions")
+	}
+
+	// Ensure blocked packet writes eventually timeout.
+	packetConn = &writeTimeoutUDPConn{
+		udpConn: udpConn,
+	}
+
 	maxPacketSizeAdjustment := 0
 
 	if isObfuscated(quicVersion) {
@@ -488,6 +500,95 @@ func Dial(
 	return conn, nil
 }
 
+// udpConn matches net.UDPConn, which implements both net.Conn and
+// net.PacketConn. udpConn enables handling of Dial packetConn inputs that
+// are not concrete *net.UDPConn types but which still implement all the
+// required functions. A udpConn instance can be passed to quic-go; various
+// quic-go code paths check that the input conn implements net.Conn and/or
+// net.PacketConn.
+//
+// TODO: add *AddrPort functions introduced in Go 1.18
+type udpConn interface {
+	Close() error
+	File() (f *os.File, err error)
+	LocalAddr() net.Addr
+	Read(b []byte) (int, error)
+	ReadFrom(b []byte) (int, net.Addr, error)
+	ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
+	ReadMsgUDP(b, oob []byte) (n, oobn, flags int, addr *net.UDPAddr, err error)
+	RemoteAddr() net.Addr
+	SetDeadline(t time.Time) error
+	SetReadBuffer(bytes int) error
+	SetReadDeadline(t time.Time) error
+	SetWriteBuffer(bytes int) error
+	SetWriteDeadline(t time.Time) error
+	SyscallConn() (syscall.RawConn, error)
+	Write(b []byte) (int, error)
+	WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oobn int, err error)
+	WriteTo(b []byte, addr net.Addr) (int, error)
+	WriteToUDP(b []byte, addr *net.UDPAddr) (int, error)
+}
+
+// writeTimeoutUDPConn sets write deadlines before each UDP packet write.
+//
+// Generally, a UDP packet write doesn't block. However, Go's
+// internal/poll.FD.WriteMsg continues to loop when syscall.SendmsgN fails
+// with EAGAIN, which indicates that an OS socket buffer is currently full;
+// in certain OS states this may cause WriteMsgUDP/etc. to block
+// indefinitely. In this scenario, we want to instead behave as if the packet
+// were dropped, so we set a write deadline which will eventually interrupt
+// any EAGAIN loop.
+//
+// Note that quic-go manages read deadlines; we set only the write deadline
+// here.
+type writeTimeoutUDPConn struct {
+	udpConn
+}
+
+func (conn *writeTimeoutUDPConn) Write(b []byte) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.udpConn.Write(b)
+}
+
+func (conn *writeTimeoutUDPConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (int, int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.udpConn.WriteMsgUDP(b, oob, addr)
+}
+
+func (conn *writeTimeoutUDPConn) WriteTo(b []byte, addr net.Addr) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.udpConn.WriteTo(b, addr)
+}
+
+func (conn *writeTimeoutUDPConn) WriteToUDP(b []byte, addr *net.UDPAddr) (int, error) {
+
+	err := conn.SetWriteDeadline(time.Now().Add(UDP_PACKET_WRITE_TIMEOUT))
+	if err != nil {
+		return 0, errors.Trace(err)
+	}
+
+	// Do not wrap any I/O err returned by udpConn
+	return conn.udpConn.WriteToUDP(b, addr)
+}
+
 // Conn is a net.Conn and psiphon/common.Closer.
 type Conn struct {
 	packetConn net.PacketConn