transport

package
v0.0.15 Latest Latest
Warning

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

Go to latest
Published: Mar 22, 2024 License: Apache-2.0 Imports: 8 Imported by: 25

Documentation

Overview

Package transport has the core types to work with transport layer connections.

Connections

Connections enable communication between two endpoints over an abstract transport. There are two types of connections:

  • Stream connections, like TCP and the SOCK_STREAM Posix socket type. They are represented by StreamConn objects.
  • Datagram connections, like UDP and the SOCK_DGRAM Posix socket type. They are represented by net.Conn objects.

We use "Packet" instead of "Datagram" in the method and type names related to datagrams because that is the convention in the Go standard library.

Each write and read on datagram connections represent a single datagram, while reads and writes on stream connections operate on byte sequences that may be independent of how those bytes are packaged.

Stream connections offer CloseRead and CloseWrite methods, which allows for a half-closed state (like TCP). In general, you communicate end of data ("EOF") to the other side of the connection by calling CloseWrite (TCP will send a FIN). CloseRead doesn't generate packets, but it allows for releasing resources (e.g. a read loop) and to signal errors to the peer if more data does arrive (TCP will usually send a RST).

Connections can be wrapped to create nested connections over a new transport. For example, a StreamConn could be over TCP, over TLS over TCP, over HTTP over TLS over TCP, over QUIC, among other options.

Dialers

Dialers enable the creation of connections given a host:port address while encapsulating the underlying transport or proxy protocol. The StreamDialer and PacketDialer types create stream (StreamConn) and datagram (net.Conn) connections, respectively, given an address.

Dialers can also be nested. For example, a TLS Stream Dialer can use a TCP dialer to create a StreamConn backed by a TCP connection, then create a TLS StreamConn backed by the TCP StreamConn. A SOCKS5-over-TLS Dialer could use the TLS Dialer to create the TLS StreamConn to the proxy before doing the SOCKS5 connection to the target address.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func MakeNetAddr

func MakeNetAddr(network, address string) (net.Addr, error)

MakeNetAddr returns a net.Addr based on the network and address. This is a helper for code that needs to return or provide a net.Addr. The address must be in "host:port" format with the host being a domain name, IPv4 or IPv6. The network must be "tcp" or "udp". For IP hosts, the returned address will be of type *net.TCPAddr or *net.UDPAddr, based on the network argument. This is important because some of the standard library functions inspect the type of the address and might return an "invalid argument" error if the type is not the correct one.

Types

type FuncPacketDialer added in v0.0.11

type FuncPacketDialer func(ctx context.Context, addr string) (net.Conn, error)

FuncPacketDialer is a PacketDialer that uses the given function to dial.

func (FuncPacketDialer) DialPacket added in v0.0.12

func (f FuncPacketDialer) DialPacket(ctx context.Context, addr string) (net.Conn, error)

DialPacket implements the PacketDialer interface.

type FuncPacketEndpoint added in v0.0.11

type FuncPacketEndpoint func(ctx context.Context) (net.Conn, error)

FuncPacketEndpoint is a PacketEndpoint that uses the given function to connect.

func (FuncPacketEndpoint) ConnectPacket added in v0.0.12

func (f FuncPacketEndpoint) ConnectPacket(ctx context.Context) (net.Conn, error)

ConnectPacket implements the PacketEndpoint interface.

type FuncStreamDialer added in v0.0.11

type FuncStreamDialer func(ctx context.Context, addr string) (StreamConn, error)

FuncStreamDialer is a StreamDialer that uses the given function to dial.

func (FuncStreamDialer) DialStream added in v0.0.12

func (f FuncStreamDialer) DialStream(ctx context.Context, addr string) (StreamConn, error)

DialStream implements the StreamDialer interface.

type FuncStreamEndpoint added in v0.0.11

type FuncStreamEndpoint func(ctx context.Context) (StreamConn, error)

FuncStreamEndpoint is a StreamEndpoint that uses the given function to connect.

func (FuncStreamEndpoint) ConnectStream added in v0.0.12

func (f FuncStreamEndpoint) ConnectStream(ctx context.Context) (StreamConn, error)

ConnectStream implements the StreamEndpoint interface.

type HappyEyeballsResolution added in v0.0.13

type HappyEyeballsResolution struct {
	IPs []netip.Addr
	Err error
}

HappyEyeballsResolution represents a result of a hostname resolution. Happy Eyeballs sorts the IPs in a specific way, updating the order as new results are received. It's recommended to returns all IPs you receive as a group, rather than one IP at a time, since a later IP may be preferred.

type HappyEyeballsResolveFunc added in v0.0.13

type HappyEyeballsResolveFunc = func(ctx context.Context, hostname string) <-chan HappyEyeballsResolution

HappyEyeballsResolveFunc performs concurrent hostname resolution for HappyEyeballsStreamDialer.

The function should return a channel quickly, and then send the resolution results to it as they become available. HappyEyeballsStreamDialer will read the resolutions from the channel. The returned channel must be closed when there are no more resolutions pending, to indicate that the resolution is done. If that is not done, HappyEyeballsStreamDialer will keep waiting.

It's recommended to return a buffered channel with size equal to the number of lookups, so that it will never block on write. If the channel is unbuffered, you must use select when writing to the channel against ctx.Done(), to make sure you don't write when HappyEyeballsStreamDialer is no longer reading. Othewise your goroutine will get stuck.

It's recommended to resolve IPv6 and IPv4 in parallel, so the connection attempts are started as soon as addresses are received. That's the primary benefit of Happy Eyeballs v2. If you resolve in series, and only send the addresses when both resolutions are done, you will get behavior similar to Happy Eyeballs v1.

func NewParallelHappyEyeballsResolveFunc added in v0.0.13

func NewParallelHappyEyeballsResolveFunc(resolveFuncs ...func(ctx context.Context, hostname string) ([]netip.Addr, error)) HappyEyeballsResolveFunc

NewParallelHappyEyeballsResolveFunc creates a HappyEyeballsResolveFunc that uses the given list of functions to resolve host names. The given functions will all run in parallel, with results being output as they are received. Typically you will pass one function for IPv6 and one for IPv4 to achieve Happy Eyballs v2 behavior. It takes care of creating the channel and the parallelization and coordination between the calls.

Example
ips := []netip.Addr{}
dialer := HappyEyeballsStreamDialer{
	Dialer: FuncStreamDialer(func(ctx context.Context, addr string) (StreamConn, error) {
		ips = append(ips, netip.MustParseAddrPort(addr).Addr())
		// Add a slight delay to simulate a more real life ordering.
		time.Sleep(1 * time.Millisecond)
		return nil, errors.New("not implemented")
	}),
	Resolve: NewParallelHappyEyeballsResolveFunc(
		func(ctx context.Context, hostname string) ([]netip.Addr, error) {
			return []netip.Addr{
				netip.MustParseAddr("2001:4860:4860::8844"),
				netip.MustParseAddr("2001:4860:4860::8888"),
			}, nil
		},
		func(ctx context.Context, hostname string) ([]netip.Addr, error) {
			return []netip.Addr{
				netip.MustParseAddr("8.8.8.8"),
				netip.MustParseAddr("8.8.4.4"),
			}, nil
		},
	),
}
dialer.DialStream(context.Background(), "dns.google:53")
fmt.Println(ips)
Output:

[2001:4860:4860::8844 8.8.8.8 2001:4860:4860::8888 8.8.4.4]

type HappyEyeballsStreamDialer added in v0.0.13

type HappyEyeballsStreamDialer struct {
	// Dialer is used to establish the connection attempts. If nil, a direct TCP connection is established.
	Dialer StreamDialer
	// Resolve is a function to map a host name to IP addresses. See HappyEyeballsResolver.
	Resolve HappyEyeballsResolveFunc
}

HappyEyeballsStreamDialer is a StreamDialer that uses Happy Eyeballs v2 to establish a connection to the destination address.

Happy Eyeballs v2 reduces the connection delay when compared to v1, with significant differences when one of the address lookups times out. V1 will wait for both the IPv4 and IPv6 lookups to return before attempting connections, while V2 starts connections as soon as it gets a lookup result, with a slight delay if IPv4 arrives before IPv6.

Go and most platforms provide V1 only, so you will benefit from using the HappyEyeballsStreamDialer in place of the standard dialer, even if you are not using custom transports.

Example (DualStack)
// Fixed resolutions to make the example work consistently without network access.
resolveIPv6 := func(ctx context.Context, hostname string) ([]netip.Addr, error) {
	// Illustrative delay to show that IPv6 is preferred even if it arrives shortly
	// after IPv4.
	time.Sleep(10 * time.Millisecond)
	return []netip.Addr{
		netip.MustParseAddr("2001:4860:4860::8844"),
		netip.MustParseAddr("2001:4860:4860::8888"),
	}, nil
}
resolveIPv4 := func(ctx context.Context, hostname string) ([]netip.Addr, error) {
	return []netip.Addr{
		netip.MustParseAddr("8.8.8.8"),
		netip.MustParseAddr("8.8.4.4"),
	}, nil
}

ips := []netip.Addr{}
dialer := HappyEyeballsStreamDialer{
	Dialer: FuncStreamDialer(func(ctx context.Context, addr string) (StreamConn, error) {
		ips = append(ips, netip.MustParseAddrPort(addr).Addr())
		return nil, errors.New("not implemented")
	}),

	// This function mimics that created with NewParallelHappyEyeballsResolveFunc.
	Resolve: func(ctx context.Context, hostname string) <-chan HappyEyeballsResolution {
		// Use a buffered channel with space for both lookups, to ensure the goroutines won't
		// block on channel write if the Happy Eyeballs algorithm is cancelled and no longer reading.
		resultsCh := make(chan HappyEyeballsResolution, 2)
		// Used to tell the IPv4 goroutine that the IPv6 one is done, so we can safely
		// close resultsCh.
		v6DoneCh := make(chan struct{})

		// Run IPv6 resolution.
		go func(hostname string) {
			// Notify the IPv4 goroutine that the IPv6 is done.
			defer close(v6DoneCh)
			ips, err := resolveIPv6(ctx, hostname)
			resultsCh <- HappyEyeballsResolution{ips, err}
		}(hostname)

		// Run IPv4 resolution.
		go func(hostname string) {
			ips, err := resolveIPv4(ctx, hostname)
			resultsCh <- HappyEyeballsResolution{ips, err}
			// Wait for the IPv6 resolution before closing the channel.
			<-v6DoneCh
			close(resultsCh)
		}(hostname)

		// Return the channel quickly, before resolutions are done.
		return resultsCh
	},
}
dialer.DialStream(context.Background(), "dns.google:53")
fmt.Println(ips)
Output:

[2001:4860:4860::8844 8.8.8.8 2001:4860:4860::8888 8.8.4.4]
Example (FixedResolution)
ips := []netip.Addr{}
dialer := HappyEyeballsStreamDialer{
	Dialer: FuncStreamDialer(func(ctx context.Context, addr string) (StreamConn, error) {
		ips = append(ips, netip.MustParseAddrPort(addr).Addr())
		return nil, errors.New("not implemented")
	}),
	Resolve: func(ctx context.Context, hostname string) <-chan HappyEyeballsResolution {
		resultCh := make(chan HappyEyeballsResolution, 1)
		defer close(resultCh)
		resultCh <- HappyEyeballsResolution{
			IPs: []netip.Addr{
				netip.MustParseAddr("2001:4860:4860::8844"),
				netip.MustParseAddr("2001:4860:4860::8888"),
				netip.MustParseAddr("8.8.8.8"),
				netip.MustParseAddr("8.8.4.4"),
			},
			Err: nil,
		}
		return resultCh
	},
}
dialer.DialStream(context.Background(), "dns.google:53")
fmt.Println(ips)
Output:

[2001:4860:4860::8844 8.8.8.8 2001:4860:4860::8888 8.8.4.4]

func (*HappyEyeballsStreamDialer) DialStream added in v0.0.13

func (d *HappyEyeballsStreamDialer) DialStream(ctx context.Context, addr string) (StreamConn, error)

DialStream implements StreamDialer.

type PacketDialer

type PacketDialer interface {
	// DialPacket connects to `addr`.
	// `addr` has the form "host:port", where "host" can be a domain name or IP address.
	DialPacket(ctx context.Context, addr string) (net.Conn, error)
}

PacketDialer provides a way to dial a destination and establish datagram connections.

type PacketDialerEndpoint

type PacketDialerEndpoint struct {
	Dialer  PacketDialer
	Address string
}

PacketDialerEndpoint is a PacketEndpoint that connects to the given address using the specified PacketDialer.

func (*PacketDialerEndpoint) ConnectPacket added in v0.0.12

func (e *PacketDialerEndpoint) ConnectPacket(ctx context.Context) (net.Conn, error)

ConnectPacket implements PacketEndpoint.ConnectPacket.

type PacketEndpoint

type PacketEndpoint interface {
	// ConnectPacket creates a connection bound to an endpoint, returning the connection.
	ConnectPacket(ctx context.Context) (net.Conn, error)
}

PacketEndpoint represents an endpoint that can be used to establish packet connections (like UDP) to a fixed destination.

type PacketListener

type PacketListener interface {
	// ListenPacket creates a PacketConn that can be used to relay packets (such as UDP) through a proxy.
	ListenPacket(ctx context.Context) (net.PacketConn, error)
}

PacketListener provides a way to create a local unbound packet connection to send packets to different destinations.

type PacketListenerDialer

type PacketListenerDialer struct {
	// The PacketListener that is used to create the net.PacketConn to bind on Dial. Must be non nil.
	Listener PacketListener
}

PacketListenerDialer is a PacketDialer that connects to the destination using the specified PacketListener.

func (PacketListenerDialer) DialPacket added in v0.0.12

func (e PacketListenerDialer) DialPacket(ctx context.Context, address string) (net.Conn, error)

DialPacket implements PacketDialer.DialPacket. The address is in "host:port" format and the host must be either a full IP address (not "[::]") or a domain. The address must be supported by the WriteTo call of the net.PacketConn returned by the PacketListener. For example, a net.UDPConn only supports IP addresses, not domain names. If the host is a domain name, consider pre-resolving it to avoid resolution calls.

type StreamConn

type StreamConn interface {
	net.Conn
	// Closes the Read end of the connection, allowing for the release of resources.
	// No more reads should happen.
	CloseRead() error
	// Closes the Write end of the connection. An EOF or FIN signal can be
	// sent to the connection target.
	CloseWrite() error
}

StreamConn is a net.Conn that allows for closing only the reader or writer end of it, supporting half-open state.

func WrapConn

func WrapConn(c StreamConn, r io.Reader, w io.Writer) StreamConn

WrapConn wraps an existing StreamConn with a new io.Reader and io.Writer, but preserves the original StreamConn.CloseRead and StreamConn.CloseWrite.

type StreamDialer

type StreamDialer interface {
	// DialStream connects to `raddr`.
	// `raddr` has the form "host:port", where "host" can be a domain name or IP address.
	DialStream(ctx context.Context, raddr string) (StreamConn, error)
}

StreamDialer provides a way to dial a destination and establish stream connections.

type StreamDialerEndpoint

type StreamDialerEndpoint struct {
	Dialer  StreamDialer
	Address string
}

StreamDialerEndpoint is a StreamEndpoint that connects to the specified address using the specified StreamDialer.

func (*StreamDialerEndpoint) ConnectStream added in v0.0.12

func (e *StreamDialerEndpoint) ConnectStream(ctx context.Context) (StreamConn, error)

ConnectStream implements StreamEndpoint.ConnectStream.

type StreamEndpoint

type StreamEndpoint interface {
	// ConnectStream establishes a connection with the endpoint, returning the connection.
	ConnectStream(ctx context.Context) (StreamConn, error)
}

StreamEndpoint represents an endpoint that can be used to establish stream connections (like TCP) to a fixed destination.

type TCPDialer added in v0.0.12

type TCPDialer struct {
	Dialer net.Dialer
}

TCPDialer is a StreamDialer that uses the standard net.Dialer to dial. It provides a convenient way to use a net.Dialer when you need a StreamDialer.

func (*TCPDialer) DialStream added in v0.0.12

func (d *TCPDialer) DialStream(ctx context.Context, addr string) (StreamConn, error)

type TCPEndpoint

type TCPEndpoint struct {
	// The Dialer used to create the net.Conn on Connect().
	Dialer net.Dialer
	// The endpoint address (host:port) to pass to Dial.
	// If the host is a domain name, consider pre-resolving it to avoid resolution calls.
	Address string
}

TCPEndpoint is a StreamEndpoint that connects to the specified address using the specified StreamDialer.

func (*TCPEndpoint) ConnectStream added in v0.0.12

func (e *TCPEndpoint) ConnectStream(ctx context.Context) (StreamConn, error)

ConnectStream implements StreamEndpoint.ConnectStream.

type UDPDialer added in v0.0.12

type UDPDialer struct {
	Dialer net.Dialer
}

UDPDialer is a PacketDialer that uses the standard net.Dialer to dial. It provides a convenient way to use a net.Dialer when you need a PacketDialer.

func (*UDPDialer) DialPacket added in v0.0.12

func (d *UDPDialer) DialPacket(ctx context.Context, addr string) (net.Conn, error)

DialPacket implements PacketDialer.DialPacket.

type UDPEndpoint

type UDPEndpoint struct {
	// The Dialer used to create the net.Conn on Connect().
	Dialer net.Dialer
	// The endpoint address ("host:port") to pass to Dial.
	// If the host is a domain name, consider pre-resolving it to avoid resolution calls.
	Address string
}

UDPEndpoint is a PacketEndpoint that connects to the specified address using UDP.

func (UDPEndpoint) ConnectPacket added in v0.0.12

func (e UDPEndpoint) ConnectPacket(ctx context.Context) (net.Conn, error)

ConnectPacket implements PacketEndpoint.ConnectPacket.

type UDPListener added in v0.0.12

type UDPListener struct {
	net.ListenConfig
	// The local address to bind to, as specified in net.ListenPacket.
	Address string
}

UDPListener is a PacketListener that uses the standard net.ListenConfig.ListenPacket to listen.

func (UDPListener) ListenPacket added in v0.0.12

func (l UDPListener) ListenPacket(ctx context.Context) (net.PacketConn, error)

ListenPacket implements PacketListener.ListenPacket

Directories

Path Synopsis
Package shadowsocks implements the Shadowsocks secure transport and proxy protocols.
Package shadowsocks implements the Shadowsocks secure transport and proxy protocols.
Package tls provides the TLS transport.
Package tls provides the TLS transport.
Package tlsfrag provides tools to split the [TLS handshake record] containing the [Client Hello message] into multiple [TLS records].
Package tlsfrag provides tools to split the [TLS handshake record] containing the [Client Hello message] into multiple [TLS records].

Jump to

Keyboard shortcuts

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