🏡 index : quine-global/quintodrome.git

// Package netguard keeps outbound connections driven by untrusted input away from internal addresses.
package netguard

import (
	"errors"
	"fmt"
	"net"
	"net/netip"
	"syscall"
)

var ErrPrivateAddress = errors.New("dial to private/loopback address blocked")

// IsPrivateIP reports whether ip is loopback, private, link-local or unspecified.
func IsPrivateIP(ip net.IP) bool {
	return ip.IsLoopback() || ip.IsUnspecified() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast()
}

// DialControl returns a net.Dialer Control hook that rejects private addresses not covered by allowed.
// It sees the resolved IP, so DNS names, redirects and rebinding cannot get around it.
func DialControl(allowed ...netip.Prefix) func(network, address string, c syscall.RawConn) error {
	return func(_, address string, _ syscall.RawConn) error {
		ap, err := netip.ParseAddrPort(address)
		if err != nil {
			return fmt.Errorf("%w: unparseable address %q: %w", ErrPrivateAddress, address, err)
		}
		addr := ap.Addr().Unmap()
		if !IsPrivateIP(addr.AsSlice()) {
			return nil
		}
		for _, p := range allowed {
			if p.Contains(addr) {
				return nil
			}
		}
		return fmt.Errorf("%w: %s", ErrPrivateAddress, address)
	}
}