v0.22.0 several enhancements and fixes for security, network, doc, test, packaging
Test / test (push) Canceled after 0s
Test / e2e (push) Canceled after 0s

This commit is contained in:
lixen committed 2026-10-03 14:52:16 -04:00
1 parent 9ab8c356d2
commit 71d1b82f83
102 files changed
+7102 -2178

No files matched your search

+23 -1
View File
@@ -1,9 +1,31 @@
package core
import (
"fmt"
"net/netip"
"strings"
"time"
)
// Network is what a listener on, or a dialer to, host uses: tcp4 for an IPv4
// literal or "" (the IPv4 wildcard, as the default 0.0.0.0), tcp6 for an IPv6
// literal, which Go binds IPv6-only, and tcp for a hostname, which resolves.
func Network(host string) (string, error) {
addr, err := netip.ParseAddr(host)
switch {
case host == "" || err == nil && addr.Is4():
return "tcp4", nil
case err == nil && addr.Is4In6():
// tcp6 refuses it at bind or dial, long after the config loaded
return "", fmt.Errorf("%q is an IPv4-mapped address; write it as IPv4", host)
case err == nil:
return "tcp6", nil
case strings.ContainsAny(host, "[]:"):
return "", fmt.Errorf("%q is neither an address nor a hostname (write an IPv6 address without brackets or port)", host)
}
return "tcp", nil
}
const (
MaxLogEntryBytes = 1024 * 1024
@@ -30,4 +52,4 @@ const (
ReloadWatchDebounce = 500 * time.Millisecond
ReloadWatchTimeout = 30 * time.Second
)
)
+53
View File
@@ -0,0 +1,53 @@
package core
import (
"net"
"testing"
"logwisp/internal/testutil"
)
// The host literal picks the family, strictly: an IPv4 literal or the empty
// host is IPv4 only, an IPv6 literal (the wildcard :: too) IPv6 only, and a
// hostname resolves. Brackets and ports belong to addresses, not hosts, and
// an IPv4-mapped address is IPv4 written as IPv6.
func TestNetworkFollowsTheHostLiteral(t *testing.T) {
for _, tc := range []struct{ host, want string }{
{"127.0.0.1", "tcp4"}, {"0.0.0.0", "tcp4"}, {"", "tcp4"},
{"::1", "tcp6"}, {"::", "tcp6"}, {"fe80::1%eth0", "tcp6"},
{"relay.example", "tcp"}, {"localhost", "tcp"},
{"[::1]", ""}, {"::1]", ""}, {"10.0.0.1:80", ""}, {"::ffff:10.0.0.1", ""},
} {
got, err := Network(tc.host)
if got != tc.want || (err != nil) != (tc.want == "") {
t.Errorf("Network(%q) = %q, %v; want %q", tc.host, got, err, tc.want)
}
}
testutil.RequireIPv6(t)
network, _ := Network("::")
ln, err := net.Listen(network, "[::]:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ln.Close() })
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
c.Close()
}
}()
_, port, _ := net.SplitHostPort(ln.Addr().String())
if c, err := net.Dial("tcp4", net.JoinHostPort("127.0.0.1", port)); err == nil {
c.Close()
t.Error(":: accepted an IPv4 connection")
}
c, err := net.Dial("tcp6", net.JoinHostPort("::1", port))
if err != nil {
t.Fatalf(":: refused [::1]: %v", err)
}
c.Close()
}
-1
View File
@@ -24,4 +24,3 @@ type TransportEvent struct {
// Structured entry for re-serializing sinks (chain links). Zero Time => absent
Entry LogEntry
}
+49
View File
@@ -0,0 +1,49 @@
package core
import (
"context"
"fmt"
"net"
)
// lookupNetIP resolves listener hostnames; tests replace it
var lookupNetIP = net.DefaultResolver.LookupNetIP
// Listen is lc.Listen, but a hostname first resolves to one address, IPv4 when
// it has one (as Go picks), listened on in that address's family alone: Go
// would bind a name for a wildcard address in both families.
func Listen(ctx context.Context, lc *net.ListenConfig, network, addr string) (net.Listener, error) {
network, addr, err := pinFamily(ctx, network, addr)
if err != nil {
return nil, err
}
return lc.Listen(ctx, network, addr)
}
func pinFamily(ctx context.Context, network, addr string) (string, string, error) {
if network != "tcp" {
return network, addr, nil
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
return "", "", err
}
ips, err := lookupNetIP(ctx, "ip", host)
if err != nil {
return "", "", err
}
if len(ips) == 0 {
return "", "", fmt.Errorf("%s: no address", host)
}
ip := ips[0].Unmap()
for _, a := range ips {
if a.Unmap().Is4() {
ip = a.Unmap()
break
}
}
if network, err = Network(ip.String()); err != nil {
return "", "", err
}
return network, net.JoinHostPort(ip.String(), port), nil
}
+34
View File
@@ -0,0 +1,34 @@
package core
import (
"context"
"net/netip"
"testing"
)
// A hostname listener binds one of its addresses, IPv4 when it has one, in
// that address's family alone, a name for a wildcard address too.
func TestHostnameListenerKeepsToOneFamily(t *testing.T) {
names := map[string][]netip.Addr{
"wild4": {netip.MustParseAddr("0.0.0.0")},
"wild6": {netip.MustParseAddr("::")},
"both": {netip.MustParseAddr("::1"), netip.MustParseAddr("::ffff:127.0.0.1")},
}
saved := lookupNetIP
t.Cleanup(func() { lookupNetIP = saved })
lookupNetIP = func(_ context.Context, _, host string) ([]netip.Addr, error) { return names[host], nil }
for _, tc := range []struct{ network, addr, wantNetwork, wantAddr string }{
{"tcp", "wild4:80", "tcp4", "0.0.0.0:80"},
{"tcp", "wild6:80", "tcp6", "[::]:80"},
{"tcp", "both:80", "tcp4", "127.0.0.1:80"},
{"tcp6", "[::1]:80", "tcp6", "[::1]:80"},
} {
network, addr, err := pinFamily(t.Context(), tc.network, tc.addr)
if err != nil || network != tc.wantNetwork || addr != tc.wantAddr {
t.Errorf("%s %s: %s %s, %v; want %s %s", tc.network, tc.addr, network, addr, err, tc.wantNetwork, tc.wantAddr)
}
}
if _, _, err := pinFamily(t.Context(), "tcp", "none:80"); err == nil {
t.Error("a name without addresses was accepted")
}
}