Skip to content

Commit

Permalink
Allow for a connection to check if it's sending bytes
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterZhizhin committed Nov 12, 2024
1 parent 8f91506 commit e3d398e
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 1 deletion.
39 changes: 39 additions & 0 deletions x/sockopt/is_sending_bytes_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build linux

package sockopt

import (
"net"

"golang.org/x/sys/unix"
)

func isSocketFdSendingBytes(fd int) (bool, error) {
tcpInfo, err := unix.GetsockoptTCPInfo(fd, unix.IPPROTO_TCP, unix.TCP_INFO)
if err != nil {
return false, err
}

// 1 == TCP_ESTABLISHED, but for some reason not available in the package
if tcpInfo.State != unix.BPF_TCP_ESTABLISHED {
// If the connection is not established, the socket is not sending bytes
return false, nil
}

return tcpInfo.Notsent_bytes != 0, nil
}

func isConnectionSendingBytesImplemented() bool {
return true
}

func isConnectionSendingBytes(conn *net.TCPConn) (result bool, err error) {
syscallConn, err := conn.SyscallConn()
if err != nil {
return false, err
}
syscallConn.Control(func(fd uintptr) {
result, err = isSocketFdSendingBytes(int(fd))
})
return
}
17 changes: 17 additions & 0 deletions x/sockopt/is_sending_bytes_not_implemented.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !linux

package sockopt

import (
"errors"
"fmt"
"net"
)

func isConnectionSendingBytesImplemented() bool {
return false
}

func isConnectionSendingBytes(_ *net.TCPConn) (bool, error) {
return false, fmt.Errorf("%w: checking if socket is sending bytes is not implemented on this platform", errors.ErrUnsupported)
}
53 changes: 52 additions & 1 deletion x/sockopt/sockopt.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,21 @@ import (
"fmt"
"net"
"net/netip"
"time"

"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)

type HasWaitUntilBytesAreSent interface {
// Wait until all bytes are sent to the socket.
// Returns ErrUnsupported if the platform doesn't support it.
// May return a different error.
WaitUntilBytesAreSent() error
// Checks if the OS supports waiting until the bytes are sent
OsSupportsWaitingUntilBytesAreSent() bool
}

// HasHopLimit enables manipulation of the hop limit option.
type HasHopLimit interface {
// HopLimit returns the hop limit field value for outgoing packets.
Expand All @@ -50,15 +60,51 @@ var _ HasHopLimit = (*hopLimitOption)(nil)

// TCPOptions represents options for TCP connections.
type TCPOptions interface {
HasWaitUntilBytesAreSent
HasHopLimit
}

type tcpOptions struct {
hopLimitOption

conn *net.TCPConn

// Timeout after which we return an error
waitingTimeout time.Duration
// Delay between checking the socket
waitingDelay time.Duration
}

var _ TCPOptions = (*tcpOptions)(nil)

func (o *tcpOptions) SetWaitingTimeout(timeout time.Duration) {
o.waitingTimeout = timeout
}

func (o *tcpOptions) SetWaitingDelay(delay time.Duration) {
o.waitingDelay = delay
}

func (o *tcpOptions) OsSupportsWaitingUntilBytesAreSent() bool {
return isConnectionSendingBytesImplemented()
}

func (o *tcpOptions) WaitUntilBytesAreSent() error {
startTime := time.Now()
for time.Since(startTime) < o.waitingTimeout {
isSendingBytes, err := isConnectionSendingBytes(o.conn)
if err != nil {
return err
}
if !isSendingBytes {
return nil
}

time.Sleep(o.waitingDelay)
}
return fmt.Errorf("waiting for socket to send all bytes: timeout exceeded")
}

// newHopLimit creates a hopLimitOption from a [net.Conn]. Works for both TCP or UDP.
func newHopLimit(conn net.Conn) (*hopLimitOption, error) {
addr, err := netip.ParseAddrPort(conn.LocalAddr().String())
Expand Down Expand Up @@ -87,5 +133,10 @@ func NewTCPOptions(conn *net.TCPConn) (TCPOptions, error) {
if err != nil {
return nil, err
}
return &tcpOptions{hopLimitOption: *hopLimit}, nil
return &tcpOptions{
hopLimitOption: *hopLimit,
conn: conn,
waitingTimeout: 10 * time.Millisecond,
waitingDelay: 100 * time.Microsecond,
}, nil
}

0 comments on commit e3d398e

Please sign in to comment.