sockowner

package
v0.22.0 Latest Latest
Warning

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

Go to latest
Published: Jun 18, 2026 License: CC0-1.0 Imports: 13 Imported by: 0

Documentation

Overview

Package sockowner resolves best-effort process ownership for network sockets.

It can look up the owner of TCP and UDP sockets from a flow tuple, and can resolve the peer owner for incoming connections when the platform exposes enough socket metadata. Results are inherently race-prone because sockets can close, be reused, or be shared by multiple processes while lookup is running.

See examples/sockowner for runnable TCP, UDP, and Unix-domain socket demos.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNilIP              = errors.New("nil IP")
	ErrInvIP              = errors.New("mixed or invalid IP addrs")
	ErrAEx                = errors.New("ambiguous exact socket match")
	ErrAUW                = errors.New("ambiguous UDP wildcard socket match")
	ErrConnNil            = errors.New("nil connection")
	ErrAddrNil            = errors.New("nil address")
	ErrAddrTypeUnexpected = errors.New("addr type unexpected")
	ErrNoOwner            = errors.New("no socket owner info")
	ErrProtocol           = errors.New("protocol")
	ErrNotIPPacket        = errors.New("not an IPv4 or IPv6 packet")
	ErrShortPacket        = errors.New("packet is too short")
	ErrNonFirstFragment   = errors.New(
		"non-first fragment has no transport header",
	)
	ErrMalformedPacket = errors.New("malformed IP packet")
)

Functions

This section is empty.

Types

type FlowTuple

type FlowTuple struct {
	Proto string // "tcp" or "udp"

	LocalIP    net.IP
	LocalPort  uint16
	RemoteIP   net.IP
	RemotePort uint16
}

FlowTuple identifies the kernel socket whose owner should be resolved.

Important: LocalIP/LocalPort and RemoteIP/RemotePort are from the perspective of the socket whose owner you want, not necessarily from the perspective of your current net.Conn or from the literal packet direction.

Examples:

  • For an outbound TCP connection created by a local process:

    LocalIP:LocalPort = local ephemeral endpoint RemoteIP:RemotePort = destination endpoint

  • For a TCP connection accepted by your local listener, if you want the owner of the peer process on the same host, use the reversed tuple:

    LocalIP:LocalPort = conn.RemoteAddr() RemoteIP:RemotePort = conn.LocalAddr()

    If you use conn.LocalAddr() as Local* and conn.RemoteAddr() as Remote*, you are asking for the owner of your accepted server-side socket, usually your own process.

  • For an egress IP packet read from a TUN device, where the packet was generated by a local process:

    LocalIP:LocalPort = packet source RemoteIP:RemotePort = packet destination

  • For an ingress packet that you plan to inject into the local stack:

    LocalIP:LocalPort = packet destination RemoteIP:RemotePort = packet source

This tuple is best-effort. It is not a stable socket identity. Between the time you observe a packet/connection and the time GetSockOwner runs, the socket may close, be reused, or be shared by multiple processes.

func FlowTupleFromIncomingIPPacket

func FlowTupleFromIncomingIPPacket(packet []byte) (FlowTuple, error)

FlowTupleFromIncomingIPPacket extracts a FlowTuple from an incoming raw IP packet, oriented from the local socket owner's perspective.

For an incoming packet:

packet source      = remote peer
packet destination = local app socket

GetSockOwner expects:

Local*  = local app socket endpoint
Remote* = remote peer endpoint

So this function reverses packet source/destination.

func FlowTupleFromOutgoingIPPacket

func FlowTupleFromOutgoingIPPacket(packet []byte) (FlowTuple, error)

FlowTupleFromOutgoingIPPacket extracts a FlowTuple from an outgoing raw IP packet generated by a local application.

This function is designed for app-based split routing with a local MITM/TUN setup:

Local apps -> MITM/TUN -> outer networks

It treats the packet as an outgoing packet from the local machine. Therefore:

FlowTuple.LocalIP    = packet source IP
FlowTuple.LocalPort  = packet source TCP/UDP port
FlowTuple.RemoteIP   = packet destination IP
FlowTuple.RemotePort = packet destination TCP/UDP port

This is the correct orientation for GetSockOwner when you want to identify the local process that created the socket.

Important usage rules:

  • Pass raw IPv4/IPv6 packets, as normally read from a TUN device. Do not pass Ethernet frames from a TAP device.
  • If your TUN device was created without IFF_NO_PI, Linux may prepend a 4-byte packet information header. Strip that header before calling this.
  • This function only returns flows for TCP and UDP. ICMP, ICMPv6, ESP, GRE, SCTP, and other protocols return ErrProtocol.
  • Non-first IP fragments do not contain TCP/UDP ports, so they return ErrNonFirstFragment.
  • First fragments are accepted if the TCP/UDP ports are present.
  • For incoming packets, do not use this function directly. Incoming packet source/destination are reversed relative to the local socket owner. Use FlowTupleFromIncomingIPPacket instead, or reverse the resulting tuple.

Typical usage:

flow, err := FlowTupleFromOutgoingIPPacket(packet)
if err != nil {
    return nil // unsupported, fragmented, malformed, etc.
}
owner, err := GetSockOwner(flow)

This extractor is packet-level only. It does not verify that the packet belongs to an existing kernel socket. GetSockOwner performs that best-effort socket table lookup later.

func IncomingConnPeerFlow

func IncomingConnPeerFlow(conn net.Conn) (*FlowTuple, error)

IncomingConnPeerFlow converts an incoming TCP/UDP net.Conn into the FlowTuple of the peer-side socket.

For an accepted TCP connection:

conn.LocalAddr()  = server endpoint
conn.RemoteAddr() = client endpoint

The returned tuple is reversed so that Local* describes the client-side socket whose owner we want to resolve.

func (*FlowTuple) FlowFamily

func (f *FlowTuple) FlowFamily() (int, error)

type SocketOwner

type SocketOwner struct {
	PIDs     []int   // nil when unknown; can contain multiple visible owners
	UID      *uint32 // nil when unknown
	GID      *uint32 // nil when unknown
	Comm     string  // "" when unknown
	ProcName string  // "" when unknown
}

SocketOwner describes the best-effort owner of a socket matched by FlowTuple.

PIDs may be nil when the platform can identify the socket UID but cannot map the socket inode back to visible processes. PIDs may contain more than one process because sockets can be inherited across fork, duplicated, or passed over Unix sockets.

UID is the socket UID when available. On Linux this comes from /proc/net/*. This is usually the effective UID of the socket creator, not necessarily the current effective UID of every process that still has the socket open.

GID is best-effort. Linux /proc/net/* does not expose socket GID, so the Linux backend fills this from the first visible owning PID's effective GID.

Comm is the kernel process name from /proc/<pid>/comm for the first visible owning PID. On Linux this name is short and may be truncated.

ProcName is the executable basename resolved from /proc/<pid>/exe for the first visible owning PID. It may be empty due to permissions, process exit, zombies, or non-standard procfs behavior.

func GetIncomingConnOwner

func GetIncomingConnOwner(conn net.Conn) (*SocketOwner, error)

GetIncomingConnOwner returns the best-effort owner of the peer side of an incoming connection accepted by a local listener.

This function is intentionally opportunistic:

  • For Unix-domain sockets, platform backends may use direct peer credential APIs such as Linux SO_PEERCRED. This is the most reliable case.
  • For TCP, this function reverses conn.LocalAddr()/conn.RemoteAddr() and asks GetSockOwner for the owner of the client-side socket.
  • For UDP, this does the same when conn exposes *net.UDPAddr endpoints. Plain UDP listeners usually do not produce accepted net.Conn values, but custom wrappers or connected UDP sockets may.
  • For virtual net.Conn implementations without real OS sockets, or wrappers that do not expose usable endpoint addresses or raw socket access, this returns ErrNoOwner.

Important:

If conn is a TCP connection accepted by your server, conn.LocalAddr() is the server-side endpoint and conn.RemoteAddr() is the client-side endpoint. To find the local client process, this function intentionally builds:

LocalIP:LocalPort     = conn.RemoteAddr()
RemoteIP:RemotePort  = conn.LocalAddr()

That asks "who owns the peer-side socket?", not "who owns my accepted server socket?".

If the peer is remote, in another network namespace, already closed, hidden by procfs permissions, or not representable by the platform backend, nil is returned.

func GetSockOwner

func GetSockOwner(flow FlowTuple) (*SocketOwner, error)

GetSockOwner returns the best-effort owner of the socket matching flow.

Caveats:

  • The result is race-prone.
  • PID ownership is not unique: several processes can share one socket.
  • UID may be known even when PID is not.
  • UDP is weaker than TCP: unconnected UDP sockets often do not have a remote endpoint in kernel socket tables, so backends may fall back to local-address/local-port matching.
  • Network namespaces matter. A backend usually sees sockets only in the network namespace visible to the current process.
  • Permissions, hidepid, containers, pid namespaces, and procfs policy can hide owning processes.

Jump to

Keyboard shortcuts

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