diff --git a/sandbox-api/src/handler/drive/mount.go b/sandbox-api/src/handler/drive/mount.go index 8a96275..b9ad200 100644 --- a/sandbox-api/src/handler/drive/mount.go +++ b/sandbox-api/src/handler/drive/mount.go @@ -2,6 +2,7 @@ package drive import ( "fmt" + "net/netip" "os" "os/exec" "path/filepath" @@ -113,7 +114,7 @@ func MountDrive(driveName, mountPath, drivePath string, readOnly bool, uidMap, g // Build blfs mount command args := []string{ "mount", - fmt.Sprintf("-filer=%s:49200.49201", filerAddress), + fmt.Sprintf("-filer=%s", formatFilerServerAddress(filerAddress)), "-asyncDio=true", "-cacheSymlink=true", fmt.Sprintf("-auth.tokenFile=%s", getAuthTokenPath()), @@ -252,28 +253,35 @@ func getFilerAddress() (string, error) { if err != nil { return "", fmt.Errorf("failed to read /etc/resolv.conf: %w", err) } + return parseFilerAddress(resolvConf) +} +func parseFilerAddress(resolvConf []byte) (string, error) { lines := strings.Split(string(resolvConf), "\n") for _, line := range lines { - line = strings.TrimSpace(line) - // Look for nameserver lines - if strings.HasPrefix(line, "nameserver") { - fields := strings.Fields(line) - if len(fields) >= 2 { - filerIP := fields[1] - // Validate it's an IPv4 address - parts := strings.Split(filerIP, ".") - if len(parts) == 4 { - logrus.WithField("filer_ip", filerIP).Debug("Found filer IP from resolv.conf") - return filerIP, nil - } - } + fields := strings.Fields(line) + if len(fields) < 2 || fields[0] != "nameserver" { + continue + } + + address, err := netip.ParseAddr(fields[1]) + if err == nil { + filerAddress := address.String() + logrus.WithField("filer_address", filerAddress).Debug("Found filer address from resolv.conf") + return filerAddress, nil } } return "", fmt.Errorf("no valid nameserver found in /etc/resolv.conf") } +// formatFilerServerAddress preserves SeaweedFS's host:http.grpc address format. +// Its parser splits on the final colon, so an IPv6 literal must remain unbracketed +// here; SeaweedFS adds brackets when constructing the HTTP and gRPC endpoints. +func formatFilerServerAddress(address string) string { + return fmt.Sprintf("%s:49200.49201", address) +} + // isMountPoint checks if a directory is a mount point by checking /proc/mounts func isMountPoint(path string) bool { // Clean the path for comparison diff --git a/sandbox-api/src/handler/drive/mount_test.go b/sandbox-api/src/handler/drive/mount_test.go new file mode 100644 index 0000000..0033903 --- /dev/null +++ b/sandbox-api/src/handler/drive/mount_test.go @@ -0,0 +1,82 @@ +package drive + +import "testing" + +func TestParseFilerAddress(t *testing.T) { + tests := []struct { + name string + content string + want string + wantErr bool + }{ + { + name: "IPv4 nameserver", + content: "nameserver 172.16.1.126\n", + want: "172.16.1.126", + }, + { + name: "IPv6 nameserver", + content: "# DNS Configuration\nnameserver 2600:1f14:c75:3900::301\n", + want: "2600:1f14:c75:3900::301", + }, + { + name: "IPv6 nameserver with zone", + content: "nameserver fe80::1%eth0\n", + want: "fe80::1%eth0", + }, + { + name: "skips malformed nameserver", + content: "nameserver not-an-ip\nnameserver 10.0.0.2\n", + want: "10.0.0.2", + }, + { + name: "requires exact nameserver directive", + content: "nameserver-proxy 10.0.0.2\n", + wantErr: true, + }, + { + name: "missing nameserver", + content: "options edns0 trust-ad\n", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseFilerAddress([]byte(tt.content)) + if (err != nil) != tt.wantErr { + t.Fatalf("parseFilerAddress() error = %v, wantErr %v", err, tt.wantErr) + } + if got != tt.want { + t.Fatalf("parseFilerAddress() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestFormatFilerServerAddress(t *testing.T) { + tests := []struct { + name string + address string + want string + }{ + { + name: "IPv4", + address: "172.16.1.126", + want: "172.16.1.126:49200.49201", + }, + { + name: "IPv6", + address: "2600:1f14:c75:3900::301", + want: "2600:1f14:c75:3900::301:49200.49201", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := formatFilerServerAddress(tt.address); got != tt.want { + t.Fatalf("formatFilerServerAddress(%q) = %q, want %q", tt.address, got, tt.want) + } + }) + } +}