mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(launcher): refine console host display
This commit is contained in:
+90
-30
@@ -139,45 +139,105 @@ func advertiseIPForWildcardBindHosts(bindHosts []string) string {
|
||||
return wildcardAdvertiseIP(bindHosts, utils.GetLocalIPv4(), utils.GetLocalIPv6())
|
||||
}
|
||||
|
||||
func launcherConsoleHosts(bindHosts []string, probeHost string) []string {
|
||||
hosts := make([]string, 0, 6)
|
||||
seen := make(map[string]struct{}, 6)
|
||||
func appendLauncherConsoleHostList(hosts []string, seen map[string]struct{}, values []string) []string {
|
||||
for _, value := range values {
|
||||
hosts = appendUniqueHost(hosts, seen, value)
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
hosts = appendUniqueHost(hosts, seen, probeHost)
|
||||
func isConsoleDisplayGlobalIPv6(ip net.IP) bool {
|
||||
if ip == nil || ip.IsLoopback() || ip.To4() != nil {
|
||||
return false
|
||||
}
|
||||
ip = ip.To16()
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
return ip[0]&0xe0 == 0x20
|
||||
}
|
||||
|
||||
for _, bindHost := range bindHosts {
|
||||
switch {
|
||||
case netbind.IsUnspecifiedHost(bindHost):
|
||||
if ip := net.ParseIP(strings.Trim(bindHost, "[]")); ip != nil && ip.To4() != nil {
|
||||
hosts = appendUniqueHost(hosts, seen, "127.0.0.1")
|
||||
} else {
|
||||
hosts = appendUniqueHost(hosts, seen, "::1")
|
||||
}
|
||||
case netbind.IsLoopbackHost(bindHost):
|
||||
hosts = appendUniqueHost(hosts, seen, "localhost")
|
||||
if ip := net.ParseIP(strings.Trim(bindHost, "[]")); ip != nil {
|
||||
if ip.To4() != nil {
|
||||
hosts = appendUniqueHost(hosts, seen, "127.0.0.1")
|
||||
} else {
|
||||
hosts = appendUniqueHost(hosts, seen, "::1")
|
||||
}
|
||||
}
|
||||
default:
|
||||
hosts = appendUniqueHost(hosts, seen, bindHost)
|
||||
func launcherConsoleHostsWithLocalAddrs(
|
||||
hostInput string,
|
||||
public bool,
|
||||
ipv4s []string,
|
||||
globalIPv6s []string,
|
||||
) []string {
|
||||
hosts := make([]string, 0, 8)
|
||||
seen := make(map[string]struct{}, 8)
|
||||
|
||||
hosts = appendUniqueHost(hosts, seen, "localhost")
|
||||
|
||||
normalizedHostInput := strings.TrimSpace(hostInput)
|
||||
if normalizedHostInput == "" {
|
||||
if public {
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, globalIPv6s)
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, ipv4s)
|
||||
}
|
||||
return hosts
|
||||
}
|
||||
|
||||
hasStar := false
|
||||
hasIPv4Any := false
|
||||
hasIPv6Any := false
|
||||
for _, token := range strings.Split(normalizedHostInput, ",") {
|
||||
switch strings.TrimSpace(token) {
|
||||
case "*":
|
||||
hasStar = true
|
||||
case "0.0.0.0":
|
||||
hasIPv4Any = true
|
||||
case "::":
|
||||
hasIPv6Any = true
|
||||
}
|
||||
}
|
||||
|
||||
if hasWildcardBindHosts(bindHosts) {
|
||||
hosts = appendUniqueHost(hosts, seen, "localhost")
|
||||
hosts = appendUniqueHost(hosts, seen, "::1")
|
||||
hosts = appendUniqueHost(hosts, seen, "127.0.0.1")
|
||||
hosts = appendUniqueHost(hosts, seen, utils.GetLocalIPv6())
|
||||
hosts = appendUniqueHost(hosts, seen, utils.GetLocalIPv4())
|
||||
if hasStar {
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, globalIPv6s)
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, ipv4s)
|
||||
return hosts
|
||||
}
|
||||
|
||||
for _, token := range strings.Split(normalizedHostInput, ",") {
|
||||
token = strings.TrimSpace(token)
|
||||
if token == "" || strings.EqualFold(token, "localhost") || netbind.IsLoopbackHost(token) {
|
||||
continue
|
||||
}
|
||||
|
||||
ip := net.ParseIP(strings.Trim(token, "[]"))
|
||||
switch {
|
||||
case token == "::":
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, globalIPv6s)
|
||||
case token == "0.0.0.0":
|
||||
hosts = appendLauncherConsoleHostList(hosts, seen, ipv4s)
|
||||
case ip != nil && ip.To4() != nil:
|
||||
if hasIPv4Any {
|
||||
continue
|
||||
}
|
||||
hosts = appendUniqueHost(hosts, seen, ip.String())
|
||||
case ip != nil:
|
||||
if hasIPv6Any {
|
||||
continue
|
||||
}
|
||||
if isConsoleDisplayGlobalIPv6(ip) {
|
||||
hosts = appendUniqueHost(hosts, seen, ip.String())
|
||||
}
|
||||
default:
|
||||
hosts = appendUniqueHost(hosts, seen, token)
|
||||
}
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
func launcherConsoleHosts(_ []string, hostInput string, public bool) []string {
|
||||
return launcherConsoleHostsWithLocalAddrs(
|
||||
hostInput,
|
||||
public,
|
||||
utils.GetLocalIPv4s(),
|
||||
utils.GetGlobalIPv6s(),
|
||||
)
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
@@ -443,7 +503,7 @@ func main() {
|
||||
|
||||
// Print startup banner and token (console mode only).
|
||||
if enableConsole || debug {
|
||||
consoleHosts := launcherConsoleHosts(openResult.BindHosts, openResult.ProbeHost)
|
||||
consoleHosts := launcherConsoleHosts(openResult.BindHosts, hostInput, effectivePublic)
|
||||
|
||||
fmt.Print(utils.Banner)
|
||||
fmt.Println()
|
||||
|
||||
+92
-18
@@ -7,6 +7,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -123,27 +124,100 @@ func TestResolveLauncherHostInput(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLauncherConsoleHosts(t *testing.T) {
|
||||
t.Run("wildcard exposes local loopback hints", func(t *testing.T) {
|
||||
hosts := launcherConsoleHosts([]string{"::"}, netbind.ResolveAdaptiveLoopbackHost())
|
||||
seen := make(map[string]bool, len(hosts))
|
||||
for _, host := range hosts {
|
||||
seen[host] = true
|
||||
}
|
||||
if !seen["localhost"] {
|
||||
t.Fatalf("expected localhost in %#v", hosts)
|
||||
}
|
||||
if !seen["::1"] {
|
||||
t.Fatalf("expected ::1 in %#v", hosts)
|
||||
}
|
||||
if !seen["127.0.0.1"] {
|
||||
t.Fatalf("expected 127.0.0.1 in %#v", hosts)
|
||||
t.Run("default loopback shows localhost only", func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
"",
|
||||
false,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit ipv6 host remains visible", func(t *testing.T) {
|
||||
hosts := launcherConsoleHosts([]string{"::1"}, "::1")
|
||||
if len(hosts) < 1 || hosts[0] != "::1" {
|
||||
t.Fatalf("hosts = %#v, want probe host first", hosts)
|
||||
t.Run("explicit loopback hosts collapse to localhost", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
hostInput string
|
||||
}{
|
||||
{name: "ipv6 loopback", hostInput: "::1"},
|
||||
{name: "ipv4 loopback", hostInput: "127.0.0.1"},
|
||||
{name: "localhost", hostInput: "localhost"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
tt.hostInput,
|
||||
false,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("public wildcard shows localhost then ipv6 and ipv4", func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
"",
|
||||
true,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost", "2001:db8::1", "2001:db8::2", "192.168.1.2", "10.0.0.8"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit ipv6 any shows localhost then ipv6 variants", func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
"::",
|
||||
false,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost", "2001:db8::1", "2001:db8::2"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
|
||||
for _, host := range hosts {
|
||||
if host == "::1" || host == "127.0.0.1" || strings.HasPrefix(strings.ToLower(host), "fe80:") {
|
||||
t.Fatalf("hosts = %#v, loopback IPs must not be displayed", hosts)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit ipv4 any shows localhost then lan ipv4", func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
"0.0.0.0",
|
||||
false,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost", "192.168.1.2", "10.0.0.8"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("explicit multi-address binding shows all exact ipv4 and global ipv6 addresses", func(t *testing.T) {
|
||||
hosts := launcherConsoleHostsWithLocalAddrs(
|
||||
"192.168.1.2,10.0.0.8,2001:db8::1,2001:db8::2,fe80::1",
|
||||
false,
|
||||
[]string{"192.168.1.2", "10.0.0.8"},
|
||||
[]string{"2001:db8::1", "2001:db8::2"},
|
||||
)
|
||||
want := []string{"localhost", "192.168.1.2", "10.0.0.8", "2001:db8::1", "2001:db8::2"}
|
||||
if strings.Join(hosts, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("hosts = %#v, want %#v", hosts, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
@@ -54,41 +55,88 @@ func FindPicoclawBinary() string {
|
||||
return "picoclaw"
|
||||
}
|
||||
|
||||
// GetLocalIPv4 returns a non-loopback local IPv4 address.
|
||||
func GetLocalIPv4() string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
func appendUniqueIP(addrs []string, seen map[string]struct{}, value string) []string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return addrs
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String()
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
return addrs
|
||||
}
|
||||
return ""
|
||||
seen[value] = struct{}{}
|
||||
return append(addrs, value)
|
||||
}
|
||||
|
||||
// GetLocalIPv6 returns a non-loopback local IPv6 address.
|
||||
func GetLocalIPv6() string {
|
||||
// GetLocalIPv4s returns all non-loopback local IPv4 addresses.
|
||||
func GetLocalIPv4s() []string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
results := make([]string, 0, 4)
|
||||
seen := make(map[string]struct{}, 4)
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok || ipnet.IP == nil || ipnet.IP.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
if ip4 := ipnet.IP.To4(); ip4 != nil {
|
||||
results = appendUniqueIP(results, seen, ip4.String())
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func isDisplayGlobalIPv6(ip net.IP) bool {
|
||||
if ip == nil || ip.IsLoopback() || ip.To4() != nil {
|
||||
return false
|
||||
}
|
||||
ip = ip.To16()
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
// Only show IPv6 global unicast addresses in 2000::/3.
|
||||
return ip[0]&0xe0 == 0x20
|
||||
}
|
||||
|
||||
// GetGlobalIPv6s returns all IPv6 global unicast addresses.
|
||||
func GetGlobalIPv6s() []string {
|
||||
addrs, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
results := make([]string, 0, 4)
|
||||
seen := make(map[string]struct{}, 4)
|
||||
for _, a := range addrs {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok || ipnet.IP == nil {
|
||||
continue
|
||||
}
|
||||
ip := ipnet.IP
|
||||
if ip.IsLoopback() || ip.To4() != nil {
|
||||
if !isDisplayGlobalIPv6(ip) {
|
||||
continue
|
||||
}
|
||||
if ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
|
||||
continue
|
||||
}
|
||||
return ip.String()
|
||||
results = appendUniqueIP(results, seen, ip.String())
|
||||
}
|
||||
return ""
|
||||
return results
|
||||
}
|
||||
|
||||
// GetLocalIPv4 returns the first non-loopback local IPv4 address.
|
||||
func GetLocalIPv4() string {
|
||||
addrs := GetLocalIPv4s()
|
||||
if len(addrs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return addrs[0]
|
||||
}
|
||||
|
||||
// GetLocalIPv6 returns the first IPv6 global unicast address.
|
||||
func GetLocalIPv6() string {
|
||||
addrs := GetGlobalIPv6s()
|
||||
if len(addrs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return addrs[0]
|
||||
}
|
||||
|
||||
// GetLocalIP returns a non-loopback local IPv4 address for backward compatibility.
|
||||
|
||||
Reference in New Issue
Block a user