dialguard

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package dialguard holds the address policy a dialer consults before it connects.

It stands on its own so a service can name a network policy without importing the client, and so the deny list is testable on addresses rather than through a socket.

The policy belongs at the dialer rather than at the URL for two reasons. Measured on go1.25.13, the spellings 127.1, 2130706433, 0x7f000001 and 017700000001 are all rejected by net.ParseIP and netip.ParseAddr, and all resolved to 127.0.0.1 by the resolver — "failed to parse" is not "will not be reached", and any check on the URL string lives in that gap. More importantly, a name may resolve differently when it is fetched than when it was stored, so only a check made at connect time closes DNS rebinding.

Index

Constants

This section is empty.

Variables

View Source
var ErrBlockedAddress = errors.New("blocked address")

ErrBlockedAddress is wrapped by every denial, so a caller can tell a blocked dial from a network failure:

if errors.Is(err, dialguard.ErrBlockedAddress) { ... }

It survives the wrapping http.Client applies on the way out — *net.OpError and then *url.Error — and so is still recognizable on the error returned from Do.

Functions

func InternalPrefixes

func InternalPrefixes() []netip.Prefix

InternalPrefixes returns the default deny list: loopback, private, link-local and reserved ranges, plus the IPv6 forms that carry an IPv4 target inside them.

It is a function rather than a variable, and returns a fresh slice with len == cap, so a caller can build their own policy from it — append(InternalPrefixes(), mine) — without writing into a buffer the library also handed to someone else.

func IsInternal

func IsInternal(addr netip.Addr) bool

IsInternal reports whether addr belongs to any range in InternalPrefixes.

It canonicalizes addr first, so a caller holding an address from any source — building an allow-list, validating a form — gets the same answer the guard would give. Both steps are load-bearing: netip.Prefix.Contains matches on address family, so 127.0.0.0/8 does not contain ::ffff:127.0.0.1 until it is unmapped, and never contains a zoned address such as fe80::1%en0 until the zone is dropped.

Types

type Guard

type Guard func(network, address string) error

Guard reports whether a connection to address may proceed, returning a non-nil error to refuse it.

It is deliberately narrower than net.Dialer.Control, whose third parameter is a syscall.RawConn: that type is platform-dependent and no address policy needs it. The client adapts this shape to Control's.

address arrives already resolved, in ip:port form. A Guard runs on every connection attempt — including redirects, retries, and once per address the resolver returned — and must be safe for concurrent use.

A denial rejects one address, not the whole dial. Measured on go1.25.13: when a name resolves to several addresses, net.Dialer treats a refusal as a failed attempt and moves on to the next, so denying [::1] still connects to 127.0.0.1. A policy must therefore be closed over every address a hostile name might return — denying one family while permitting the other blocks nothing. InternalPrefixes covers both families of every range it lists for this reason; a hand-built list that does not is bypassable.

It also runs per connection, not per request. A connection the policy approved stays in the client's pool and serves later requests without consulting the guard again.

func Blocking

func Blocking(prefixes ...netip.Prefix) Guard

Blocking returns a Guard denying any address inside prefixes.

The prefixes are copied at construction, so a later change to the caller's slice cannot loosen a guard already in use.

An address that does not parse is denied whatever the list contains, since the parse happens first: a guard that fails open on input it does not understand is one an attacker only has to confuse. Note this also denies unix-socket dials, whose address is a filesystem path — the intended outcome for a guard installed against an attacker-influenced destination.

Called with no prefixes it denies nothing that parses, which is what an empty deny list means.

Jump to

Keyboard shortcuts

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