v0.22.0 several enhancements and fixes for security, network, doc, test, packaging
This commit is contained in:
1 parent
9ab8c356d2
commit
71d1b82f83
102 files changed
+7102
-2178
No files matched your search
+23
-1
@@ -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
|
||||
)
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
@@ -24,4 +24,3 @@ type TransportEvent struct {
|
||||
// Structured entry for re-serializing sinks (chain links). Zero Time => absent
|
||||
Entry LogEntry
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user