wip: migrate to mono-repo. SPN has already been moved to spn/

This commit is contained in:
Patrick Pacher
2024-03-15 11:55:13 +01:00
parent b30fd00ccf
commit 8579430db9
577 changed files with 35981 additions and 818 deletions

View File

@@ -0,0 +1,60 @@
package netutils
import (
"errors"
"net"
"strconv"
"github.com/safing/portmaster/service/network/packet"
)
var errInvalidIP = errors.New("invalid IP address")
// IPPortFromAddr extracts or parses the IP address and port contained in the given address.
func IPPortFromAddr(addr net.Addr) (ip net.IP, port uint16, err error) {
// Convert addr to IP if needed.
switch v := addr.(type) {
case *net.TCPAddr:
return v.IP, uint16(v.Port), nil
case *net.UDPAddr:
return v.IP, uint16(v.Port), nil
case *net.IPAddr:
return v.IP, 0, nil
case *net.UnixAddr:
return nil, 0, errors.New("unix addresses don't have IPs")
default:
return ParseIPPort(addr.String())
}
}
// ProtocolFromNetwork returns the protocol from the given net, as used in the "net" golang stdlib.
func ProtocolFromNetwork(net string) (protocol packet.IPProtocol) {
switch net {
case "tcp", "tcp4", "tcp6":
return packet.TCP
case "udp", "udp4", "udp6":
return packet.UDP
default:
return 0
}
}
// ParseIPPort parses a <ip>:port formatted address.
func ParseIPPort(address string) (net.IP, uint16, error) {
ipString, portString, err := net.SplitHostPort(address)
if err != nil {
return nil, 0, err
}
ip := net.ParseIP(ipString)
if ip == nil {
return nil, 0, errInvalidIP
}
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, 0, err
}
return ip, uint16(port), nil
}

View File

@@ -0,0 +1,99 @@
package netutils
import (
"fmt"
"net"
"regexp"
"strings"
"github.com/miekg/dns"
)
var (
cleanDomainRegex = regexp.MustCompile(
`^` + // match beginning
`(` + // start subdomain group
`(xn--)?` + // idn prefix
`[a-z0-9_-]{1,63}` + // main chunk
`\.` + // ending with a dot
`)*` + // end subdomain group, allow any number of subdomains
`(xn--)?` + // TLD idn prefix
`[a-z0-9_-]{1,63}` + // TLD main chunk with at least one character (for custom ones)
`\.` + // ending with a dot
`$`, // match end
)
// dnsSDDomainRegex is a lot more lax to better suit the allowed characters in DNS-SD.
// Not all characters have been allowed - some special characters were
// removed to reduce the general attack surface.
dnsSDDomainRegex = regexp.MustCompile(
// Start of charset selection.
`^[` +
// Printable ASCII (character code 32-127), excluding some special characters.
` !#$%&()*+,\-\./0-9:;=?@A-Z[\\\]^_\a-z{|}~` +
// Only latin characters from extended ASCII (character code 128-255).
`ŠŒŽšœžŸ¡¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ` +
// End of charset selection.
`]*$`,
)
)
// IsValidFqdn returns whether the given string is a valid fqdn.
func IsValidFqdn(fqdn string) bool {
// root zone
if fqdn == "." {
return true
}
// check max length
if len(fqdn) > 256 {
return false
}
// IsFqdn checks if a domain name is fully qualified.
if !dns.IsFqdn(fqdn) {
return false
}
// Use special check for .local domains to support DNS-SD.
if strings.HasSuffix(fqdn, ".local.") {
return dnsSDDomainRegex.MatchString(fqdn)
}
// check with regex
if !cleanDomainRegex.MatchString(fqdn) {
return false
}
// IsDomainName checks if s is a valid domain name, it returns the number of
// labels and true, when a domain name is valid. Note that non fully qualified
// domain name is considered valid, in this case the last label is counted in
// the number of labels. When false is returned the number of labels is not
// defined. Also note that this function is extremely liberal; almost any
// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
// label fits in 63 characters and that the entire name will fit into the 255
// octet wire format limit.
_, ok := dns.IsDomainName(fqdn)
return ok
}
// IPsToRRs transforms the given IPs to resource records.
func IPsToRRs(domain string, ips []net.IP) ([]dns.RR, error) {
records := make([]dns.RR, 0, len(ips))
var rr dns.RR
var err error
for _, ip := range ips {
if ip.To4() != nil {
rr, err = dns.NewRR(fmt.Sprintf("%s 17 IN A %s", domain, ip))
} else {
rr, err = dns.NewRR(fmt.Sprintf("%s 17 IN AAAA %s", domain, ip))
}
if err != nil {
return nil, fmt.Errorf("failed to create record for %s: %w", ip, err)
}
records = append(records, rr)
}
return records, nil
}

View File

@@ -0,0 +1,47 @@
package netutils
import "testing"
func testDomainValidity(t *testing.T, domain string, isValid bool) {
t.Helper()
if IsValidFqdn(domain) != isValid {
t.Errorf("domain %s failed check: was valid=%v, expected valid=%v", domain, IsValidFqdn(domain), isValid)
}
}
func TestDNSValidation(t *testing.T) {
t.Parallel()
// valid
testDomainValidity(t, ".", true)
testDomainValidity(t, "at.", true)
testDomainValidity(t, "orf.at.", true)
testDomainValidity(t, "www.orf.at.", true)
testDomainValidity(t, "a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.x.y.z.example.org.", true)
testDomainValidity(t, "a_a.com.", true)
testDomainValidity(t, "a-a.com.", true)
testDomainValidity(t, "a_a.com.", true)
testDomainValidity(t, "a-a.com.", true)
testDomainValidity(t, "xn--a.com.", true)
testDomainValidity(t, "xn--asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasd.com.", true)
// maybe valid
testDomainValidity(t, "-.com.", true)
testDomainValidity(t, "_.com.", true)
testDomainValidity(t, "a_.com.", true)
testDomainValidity(t, "a-.com.", true)
testDomainValidity(t, "_a.com.", true)
testDomainValidity(t, "-a.com.", true)
// invalid
testDomainValidity(t, ".com.", false)
testDomainValidity(t, ".com.", false)
testDomainValidity(t, "xn--asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf.com.", false)
testDomainValidity(t, "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf.com.", false)
testDomainValidity(t, "asdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdfasdf.com.", false)
testDomainValidity(t, "asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.asdf.as.com.", false)
// real world examples
testDomainValidity(t, "iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com.", true)
}

View File

@@ -0,0 +1,160 @@
package netutils
import "net"
// IPScope is the scope of the IP address.
type IPScope int8
// Defined IP Scopes.
const (
Invalid IPScope = iota - 1
Undefined
HostLocal
LinkLocal
SiteLocal
Global
LocalMulticast
GlobalMulticast
)
// ClassifyIP returns the network scope of the given IP address.
// Deprecated: Please use the new GetIPScope instead.
func ClassifyIP(ip net.IP) IPScope {
return GetIPScope(ip)
}
// GetIPScope returns the network scope of the given IP address.
func GetIPScope(ip net.IP) IPScope { //nolint:gocognit
if ip4 := ip.To4(); ip4 != nil {
// IPv4
switch {
case ip4[0] == 0 && ip4[1] == 0 && ip4[2] == 0 && ip4[3] == 0:
// 0.0.0.0/32
return LocalMulticast // Used as source for L2 based protocols with no L3 addressing.
case ip4[0] == 0:
// 0.0.0.0/8
return Invalid
case ip4[0] == 10:
// 10.0.0.0/8 (RFC1918)
return SiteLocal
case ip4[0] == 100 && ip4[1]&0b11000000 == 64:
// 100.64.0.0/10 (RFC6598)
return SiteLocal
case ip4[0] == 127:
// 127.0.0.0/8 (RFC1918)
return HostLocal
case ip4[0] == 169 && ip4[1] == 254:
// 169.254.0.0/16 (RFC3927)
return LinkLocal
case ip4[0] == 172 && ip4[1]&0b11110000 == 16:
// 172.16.0.0/12 (RFC1918)
return SiteLocal
case ip4[0] == 192 && ip4[1] == 0 && ip4[2] == 2:
// 192.0.2.0/24 (TEST-NET-1, RFC5737)
return Invalid
case ip4[0] == 192 && ip4[1] == 168:
// 192.168.0.0/16 (RFC1918)
return SiteLocal
case ip4[0] == 198 && ip4[1] == 51 && ip4[2] == 100:
// 198.51.100.0/24 (TEST-NET-2, RFC5737)
return Invalid
case ip4[0] == 203 && ip4[1] == 0 && ip4[2] == 113:
// 203.0.113.0/24 (TEST-NET-3, RFC5737)
return Invalid
case ip4[0] == 224:
// 224.0.0.0/8 (RFC5771)
return LocalMulticast
case ip4[0] == 233 && ip4[1] == 252 && ip4[2] == 0:
// 233.252.0.0/24 (MCAST-TEST-NET; RFC5771, RFC6676)
return Invalid
case ip4[0] >= 225 && ip4[0] <= 238:
// 225.0.0.0/8 - 238.0.0.0/8 (RFC5771)
return GlobalMulticast
case ip4[0] == 239:
// 239.0.0.0/8 (RFC2365)
return LocalMulticast
case ip4[0] == 255 && ip4[1] == 255 && ip4[2] == 255 && ip4[3] == 255:
// 255.255.255.255/32
return LocalMulticast
case ip4[0] >= 240:
// 240.0.0.0/8 - 255.0.0.0/8 (minus 255.255.255.255/32)
return Invalid
default:
return Global
}
} else if len(ip) == net.IPv6len {
// IPv6
switch {
case ip.Equal(net.IPv6zero):
return Invalid
case ip.Equal(net.IPv6loopback):
return HostLocal
case ip[0]&0xfe == 0xfc:
// fc00::/7
return SiteLocal
case ip[0] == 0xfe && ip[1]&0xc0 == 0x80:
// fe80::/10
return LinkLocal
case ip[0] == 0xff && ip[1] <= 0x05:
// ff00::/16 - ff05::/16
return LocalMulticast
case ip[0] == 0xff:
// other ff00::/8
return GlobalMulticast
default:
return Global
}
}
return Invalid
}
// IsLocalhost returns whether the IP refers to the host itself.
func (scope IPScope) IsLocalhost() bool {
return scope == HostLocal
}
// IsLAN returns true if the scope is site-local or link-local.
func (scope IPScope) IsLAN() bool {
switch scope { //nolint:exhaustive // Looking for something specific.
case SiteLocal, LinkLocal, LocalMulticast:
return true
default:
return false
}
}
// IsGlobal returns true if the scope is global.
func (scope IPScope) IsGlobal() bool {
switch scope { //nolint:exhaustive // Looking for something specific.
case Global, GlobalMulticast:
return true
default:
return false
}
}
// GetBroadcastAddress returns the broadcast address of the given IP and network mask.
// If a mixed IPv4/IPv6 input is given, it returns nil.
func GetBroadcastAddress(ip net.IP, netMask net.IPMask) net.IP {
// Convert to standard v4.
if ip4 := ip.To4(); ip4 != nil {
ip = ip4
}
mask := net.IP(netMask)
if ip4Mask := mask.To4(); ip4Mask != nil {
mask = ip4Mask
}
// Check for mixed v4/v6 input.
if len(ip) != len(mask) {
return nil
}
// Merge to broadcast address
n := len(ip)
broadcastAddress := make(net.IP, n)
for i := 0; i < n; i++ {
broadcastAddress[i] = ip[i] | ^mask[i]
}
return broadcastAddress
}

View File

@@ -0,0 +1,51 @@
package netutils
import (
"net"
"testing"
)
func TestIPScope(t *testing.T) {
t.Parallel()
testScope(t, net.IPv4(71, 87, 113, 211), Global)
testScope(t, net.IPv4(127, 0, 0, 1), HostLocal)
testScope(t, net.IPv4(127, 255, 255, 1), HostLocal)
testScope(t, net.IPv4(192, 168, 172, 24), SiteLocal)
testScope(t, net.IPv4(172, 15, 1, 1), Global)
testScope(t, net.IPv4(172, 16, 1, 1), SiteLocal)
testScope(t, net.IPv4(172, 31, 1, 1), SiteLocal)
testScope(t, net.IPv4(172, 32, 1, 1), Global)
}
func testScope(t *testing.T, ip net.IP, expectedScope IPScope) {
t.Helper()
c := GetIPScope(ip)
if c != expectedScope {
t.Errorf("%s is %s, expected %s", ip, scopeName(c), scopeName(expectedScope))
}
}
func scopeName(c IPScope) string {
switch c {
case Invalid:
return "invalid"
case Undefined:
return "undefined"
case HostLocal:
return "hostLocal"
case LinkLocal:
return "linkLocal"
case SiteLocal:
return "siteLocal"
case Global:
return "global"
case LocalMulticast:
return "localMulticast"
case GlobalMulticast:
return "globalMulticast"
default:
return "undefined"
}
}

View File

@@ -0,0 +1,51 @@
package netutils
import (
"sync"
"github.com/google/gopacket"
"github.com/google/gopacket/tcpassembly"
)
// SimpleStreamAssemblerManager is a simple manager for github.com/google/gopacket/tcpassembly.
type SimpleStreamAssemblerManager struct {
InitLock sync.Mutex
lastAssembler *SimpleStreamAssembler
}
// New returns a new stream assembler.
func (m *SimpleStreamAssemblerManager) New(net, transport gopacket.Flow) tcpassembly.Stream {
assembler := new(SimpleStreamAssembler)
m.lastAssembler = assembler
return assembler
}
// GetLastAssembler returns the newest created stream assembler.
func (m *SimpleStreamAssemblerManager) GetLastAssembler() *SimpleStreamAssembler {
return m.lastAssembler
}
// SimpleStreamAssembler is a simple assembler for github.com/google/gopacket/tcpassembly.
type SimpleStreamAssembler struct {
Cumulated []byte
CumulatedLen int
Complete bool
}
// NewSimpleStreamAssembler returns a new SimpleStreamAssembler.
func NewSimpleStreamAssembler() *SimpleStreamAssembler {
return &SimpleStreamAssembler{}
}
// Reassembled implements tcpassembly.Stream's Reassembled function.
func (a *SimpleStreamAssembler) Reassembled(reassembly []tcpassembly.Reassembly) {
for _, entry := range reassembly {
a.Cumulated = append(a.Cumulated, entry.Bytes...)
}
a.CumulatedLen = len(a.Cumulated)
}
// ReassemblyComplete implements tcpassembly.Stream's ReassemblyComplete function.
func (a *SimpleStreamAssembler) ReassemblyComplete() {
a.Complete = true
}