conniver

package module
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 13, 2026 License: BSD-2-Clause Imports: 6 Imported by: 0

README

Conniver

Conniver is a small Go package that wraps net.Conn sockets and collects detailed event information. On common platforms, the TCP_INFO/TCP_CONNECTION socket options are used to obtained kernel-level statistics for the connection, including round-trip-time, max segment size, and more.

Overview

Conniver is best used by specifying a DialContext with a TCP or HTTP.

If you aren't able to wrap the net.Conn, you can instead call tcpinfo.TCPInfo() directly instead. You can find the documentation for this in the package README.

The following code demonstrates using conniver to collect connection data from the net/http.Client. The wrapped net.Conn should work with most Go packages that allow custom dialers or provide some way to provide a proxy net.Conn.

import (
    "context"
    "encoding/json"
    "net/http"
    "fmt"
    
    "github.com/runZeroInc/conniver"
)
func main() {
	timeout := 15 * time.Second
	d := net.Dialer{Timeout: timeout}
	cl := &http.Client{Transport: &http.Transport{
		TLSHandshakeTimeout: timeout,
		// Set DisableKeepAlives to true to force connection close after each request.
		// Alternatively, we can call client.CloseIdleConnections() manually.
		// DisableKeepAlives:     true,
		DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
			conn, err := d.DialContext(ctx, network, addr)
			if err != nil {
				return nil, err
			}
			return conniver.WrapConn(conn, func(c *conniver.Conn, state int) {
				if state != conniver.Closed {
					return
				}
				jb, _ := json.Marshal(c)
				fmt.Println("[" + conniver.StateMap[state] + "] " + string(jb) + "\n\n")
			}), err
		},
	}}
	resp, err := cl.Get("https://www.golang.org/")
	if err != nil {
		logrus.Fatalf("get: %v", err)
	}
	_ = resp.Body.Close()

	// Use client.CloseIdleConnections() to trigger the closed events for all wrapped connections.
	// Alteratively use `DisableKeepAlives: true`` in the HTTP transport.
	cl.CloseIdleConnections()

	return
}

Operating Systems

The current code supports detailed TCPINFO collection for Linux, macOS, and Windows.

Support for FreeBSD is planned.

Examples

The conniver.Conn struct includes basic socket details in addition to TCPInfo fields.

type Conn struct {
	net.Conn                   // The wrapped net.Conn
	Context    context.Context // The optional context
	OpenedAt   int64           // The opened time in unix nanoseconds
	ClosedAt   int64           // The closed time in unix nanoseconds
	FirstRxAt  int64           // The first successful read time in unix nanoseconds
	FirstTxAt  int64           // The first successful write time in unix nanoseconds
	LastRxAt   int64           // The last successful read time in unix nanoseconds
	LastTxAt   int64           // The last successful write time in unix nanoseconds
	TxBytes    int64           // The number of bytes sent successfully
	RxBytes    int64           // The number of bytes read successfully
	RxErr      error           // The last receive error, if any
	TxErr      error           // The last send error, if any
	InfoErr    error           // The last send error, if any
	Reconnects int             // The number of retries to connect (managed by the caller)
	OpenedInfo *tcpinfo.Info   // An OS-agnostic set of TCP information fields at open time
	ClosedInfo *tcpinfo.Info   // An OS-agnostic set of TCP information fields at close timeß
}

The tcpinfo.Info structure contains OS-normalized fields AND the entire platform-specific TCPINFO structure.

type Info struct {
	State         string        // Connection state
	TxOptions     []Option      // Requesting options
	RxOptions     []Option      // Options requested from peer
	TxMSS         uint64        // Maximum segment size for sender in bytes
	RxMSS         uint64        // Maximum segment size for receiver in bytes
	RTT           time.Duration // Round-trip time in nanoseconds
	RTTVar        time.Duration // Round-trip time variation in nanoseconds
	RTO           time.Duration // Retransmission timeout
	ATO           time.Duration // Delayed acknowledgement timeout [Linux only]
	LastTxAt      time.Duration // Nanoseconds since last data sent [Linux only]
	LastRxAt      time.Duration // Nanoseconds since last data received [FreeBSD and Linux]
	LastTxAckAt   time.Duration // Nanoseconds since last ack sent [Linux only]
	LastRxAckAt   time.Duration // Nanoseconds since last ack received [Linux only]
	RxWindow      uint64        // Advertised receiver window in bytes
	TxSSThreshold uint64        // Slow start threshold for sender in bytes or # of segments
	RxSSThreshold uint64        // Slow start threshold for receiver in bytes [Linux only]
	TxWindowBytes uint64        // Congestion window for sender in bytes [Darwin and FreeBSD]
	TxWindowSegs  uint64        // Congestion window for sender in # of segments [Linux and NetBSD]
	Retransmits   uint64        // Number of retransmissions (segments or packets)
	Sys           *SysInfo      // Platform-specific information
}

The *SysInfo fields vary dramatically by operating system and require OS build tags to use directly. The conniver.Conn, tcpinfoInfo, and SysInfo structs all support a ToMap() function, which returns a map[string]any that can be used to access OS-specific fields dynamically.

The function passed to conniver.WrapConn is called for both the opened and closed states. The opened callback fires right after the connection is established. The closed callback fires right before the connection is closed. Separate *tcpinfo.Info{} stats are recorded for both states.

The following reporting function will report the RTT at connection open and just before close, by catching the closed event and reviewing both fields.

func(c *conniver.Conn, state int) {
    if state != conniver.Closed {
        return
    }
    raw, _ := json.Marshal(c)
	fmt.Printf("Connection %s -> %s took %s, sent:%d/recv:%d bytes, starting RTT %s(%s) and ending RTT %s(%s)\nWarnings:%s\n%s\n\n",
        c.LocalAddr().String(), c.RemoteAddr().String(),
        time.Duration(c.ClosedAt-c.OpenedAt),
        c.SentBytes, c.RecvBytes,
        c.OpenedInfo.RTT, c.OpenedInfo.RTTVar,
        c.ClosedInfo.RTT, c.ClosedInfo.RTTVar,
		strings.Join(c.Warnings(), ","),
        string(raw),
    )
})
$ go run main.go

Connection 192.168.10.23:60032 -> 216.239.36.21:443 took 273.869ms, sent:1725/recv:5897 bytes, starting RTT 6ms(3ms) and ending RTT 6ms(1ms)

{"openedAt":1767404790007006000,"closedAt":1767404790280875000,"firstReadAt":1767404790023466000,"firstWriteAt":1767404790007418000,"sentBytes":1725,"recvBytes":5897,"openedInfo":{"state":"ESTABLISHED","options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"sendMSS":1400,"recvMSS":1400,"rtt":6000000,"rttVar":3000000,"recvWindow":131648,"sendSSThreshold":1073725440,"sendCWindowdBytes":14000,"sendCWindowSegs":65535,"sysInfo":{"state":"ESTABLISHED","sendWScale":8,"recvWScale":6,"options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"mss":1400,"sendSSThreshold":1073725440,"sendCWindowBytes":14000,"sendWnd":65535,"recvWnd":131648,"rttCur":6000000,"rttSmoothed":6000000,"rttVar":3000000}},"closedInfo":{"state":"ESTABLISHED","options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"sendMSS":1400,"recvMSS":1400,"rtt":6000000,"rttVar":1000000,"rto":230000000,"recvWindow":125504,"sendSSThreshold":1073725440,"sendCWindowdBytes":15701,"sendCWindowSegs":267520,"sysInfo":{"state":"ESTABLISHED","sendWScale":8,"recvWScale":6,"options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"rto":230000000,"mss":1400,"sendSSThreshold":1073725440,"sendCWindowBytes":15701,"sendWnd":267520,"sendSBBytes":24,"recvWnd":125504,"rttCur":252000000,"rttSmoothed":6000000,"rttVar":1000000,"txPackets":5,"txBytes":1725,"rxPackets":3,"rxBytes":11497}}}

Connection 192.168.10.23:60031 -> 142.251.116.141:443 took 329.892ms, sent:1707/recv:11868 bytes, starting RTT 6ms(3ms) and ending RTT 6ms(2ms)

{"openedAt":1767404789950983000,"closedAt":1767404790280875000,"firstReadAt":1767404789958865000,"firstWriteAt":1767404789951608000,"sentBytes":1707,"recvBytes":11868,"openedInfo":{"state":"ESTABLISHED","options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"sendMSS":1400,"recvMSS":1400,"rtt":6000000,"rttVar":3000000,"recvWindow":131648,"sendSSThreshold":1073725440,"sendCWindowdBytes":14000,"sendCWindowSegs":65535,"sysInfo":{"state":"ESTABLISHED","sendWScale":8,"recvWScale":6,"options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"mss":1400,"sendSSThreshold":1073725440,"sendCWindowBytes":14000,"sendWnd":65535,"recvWnd":131648,"rttCur":6000000,"rttSmoothed":6000000,"rttVar":3000000}},"closedInfo":{"state":"ESTABLISHED","options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"sendMSS":1400,"recvMSS":1400,"rtt":6000000,"rttVar":2000000,"rto":230000000,"recvWindow":131072,"sendSSThreshold":1073725440,"sendCWindowdBytes":15683,"sendCWindowSegs":267520,"sysInfo":{"state":"ESTABLISHED","sendWScale":8,"recvWScale":6,"options":["Timestamps","SACK","WindowScale:08"],"peerOptions":["Timestamps","SACK","WindowScale:06"],"rto":230000000,"mss":1400,"sendSSThreshold":1073725440,"sendCWindowBytes":15683,"sendWnd":267520,"sendSBBytes":24,"recvWnd":131072,"rttCur":28000000,"rttSmoothed":6000000,"rttVar":2000000,"txPackets":5,"txBytes":1707,"rxPackets":3,"rxBytes":11868}}}

History

The tcpinfo package was bootstrapped from the following sources:

The httpstat command was derived from httpstat (MIT License).

Documentation

Index

Constants

View Source
const (
	Opened = 0
	Closed = 1
)

Variables

View Source
var StateMap = map[int]string{
	Opened: "open",
	Closed: "close",
}

Functions

func WrapConn

func WrapConn(ncon net.Conn, reportStatsFn ReportStatsFn) net.Conn

WrapConn wraps the given net.Conn, triggers an immediate report in Open state, and returns the wrapped connection. Reads and writes are tracked and the final report is triggered on Close. Separate tcpinfo stats are gathered on open and close events.

func WrapConnWithContext

func WrapConnWithContext(ctx context.Context, ncon net.Conn, reportStatsFn ReportStatsFn) net.Conn

WrapConnWithContext wraps the given net.Conn, triggers an immediate report in Open state, and returns the wrapped connection. Reads and writes are tracked and the final report is triggered on Close. Separate tcpinfo stats are gathered on open and close events.

Types

type Conn

type Conn struct {
	net.Conn `json:"-"`
	Context  context.Context `json:"-"`

	OpenedAt   int64         `json:"openedAt,omitempty"`
	ClosedAt   int64         `json:"closedAt,omitempty"`
	FirstRxAt  int64         `json:"firstRxAt,omitempty"`
	FirstTxAt  int64         `json:"firstTxAt,omitempty"`
	LastRxAt   int64         `json:"lastRxAt,omitempty"`
	LastTxAt   int64         `json:"lastTxAt,omitempty"`
	TxBytes    int64         `json:"txBytes"`
	RxBytes    int64         `json:"rxBytes"`
	RxErr      error         `json:"rxErr,omitempty"`
	TxErr      error         `json:"txErr,omitempty"`
	InfoErr    error         `json:"infoErr,omitempty"`
	Reconnects int           `json:"reconnects,omitempty"`
	OpenedInfo *tcpinfo.Info `json:"openedInfo,omitempty"`
	ClosedInfo *tcpinfo.Info `json:"closedInfo,omitempty"`

	sync.Mutex
	// contains filtered or unexported fields
}

func (*Conn) Close

func (w *Conn) Close() error

Close invokes the reportWrapper with a close event before closing the connection.

func (*Conn) Read

func (w *Conn) Read(b []byte) (int, error)

Read wraps the underlying Read method and tracks the bytes received

func (*Conn) SetReconnects

func (w *Conn) SetReconnects(reconnects int)

SetReconnects stores the number of additional connection attempts that were needed to open this connection. This is managed externally by the caller, but reported in the final stats.

func (*Conn) ToMap

func (w *Conn) ToMap() map[string]any

func (*Conn) Warnings

func (w *Conn) Warnings() []string

func (*Conn) Write

func (w *Conn) Write(b []byte) (int, error)

Write wraps the underlying Write method and tracks the bytes sent

type ReportStatsFn

type ReportStatsFn func(tic *Conn, state int)

Directories

Path Synopsis
cmd
get command
httpstat command
pkg
kernel
Package kernel provides helper function to get, parse and compare kernel versions for different platforms.
Package kernel provides helper function to get, parse and compare kernel versions for different platforms.
os
package os provides helper function to get the operating system name for different platforms.
package os provides helper function to get the operating system name for different platforms.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL