Skip to content

Commit

Permalink
Merge branch 'master' into staging-client
Browse files Browse the repository at this point in the history
  • Loading branch information
rod-hynes committed Aug 16, 2022
2 parents c2b1377 + b8c5c19 commit f3d1229
Showing 1 changed file with 101 additions and 0 deletions.
101 changes: 101 additions & 0 deletions psiphon/common/quic/quic.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import (
"io"
"net"
"net/http"
"os"
"sync"
"sync/atomic"
"syscall"
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f3d1229

Please sign in to comment.