Skip to content

Commit 9e49c3e

Browse files
author
Barash Asenov
committed
[fix] fix unix addr resolution
1 parent 6a38036 commit 9e49c3e

File tree

4 files changed

+148
-0
lines changed

4 files changed

+148
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
123123
| udp6_addr | User Datagram Protocol Address UDPv6 |
124124
| udp_addr | User Datagram Protocol Address UDP |
125125
| unix_addr | Unix domain socket end point Address |
126+
| uds_exists | Unix domain socket exists (checks filesystem sockets and Linux abstract sockets) |
126127
| uri | URI String |
127128
| url | URL String |
128129
| http_url | HTTP(s) URL String |

baked_in.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package validator
22

33
import (
4+
"bufio"
45
"bytes"
56
"cmp"
67
"context"
@@ -15,6 +16,7 @@ import (
1516
"net/url"
1617
"os"
1718
"reflect"
19+
"runtime"
1820
"strconv"
1921
"strings"
2022
"sync"
@@ -205,6 +207,7 @@ var (
205207
"ip6_addr": isIP6AddrResolvable,
206208
"ip_addr": isIPAddrResolvable,
207209
"unix_addr": isUnixAddrResolvable,
210+
"uds_exists": isUnixDomainSocketExists,
208211
"mac": isMAC,
209212
"hostname": isHostnameRFC952, // RFC 952
210213
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
@@ -2595,6 +2598,63 @@ func isUnixAddrResolvable(fl FieldLevel) bool {
25952598
return err == nil
25962599
}
25972600

2601+
// isUnixDomainSocketExists is the validation function for validating if the field's value is an existing Unix domain socket.
2602+
// It handles both filesystem-based sockets and Linux abstract sockets.
2603+
func isUnixDomainSocketExists(fl FieldLevel) bool {
2604+
sockpath := fl.Field().String()
2605+
2606+
if sockpath == "" {
2607+
return false
2608+
}
2609+
2610+
// On Linux, check for abstract sockets (prefixed with @)
2611+
if runtime.GOOS == "linux" && strings.HasPrefix(sockpath, "@") {
2612+
return isAbstractSocketExists(sockpath)
2613+
}
2614+
2615+
// For filesystem-based sockets, check if the path exists and is a socket
2616+
stats, err := os.Stat(sockpath)
2617+
if err != nil {
2618+
return false
2619+
}
2620+
2621+
return stats.Mode().Type() == fs.ModeSocket
2622+
}
2623+
2624+
// isAbstractSocketExists checks if a Linux abstract socket exists by reading /proc/net/unix.
2625+
// Abstract sockets are identified by an @ prefix in human-readable form.
2626+
func isAbstractSocketExists(sockpath string) bool {
2627+
file, err := os.Open("/proc/net/unix")
2628+
if err != nil {
2629+
return false
2630+
}
2631+
defer file.Close()
2632+
2633+
scanner := bufio.NewScanner(file)
2634+
2635+
// Skip the header line
2636+
if !scanner.Scan() {
2637+
return false
2638+
}
2639+
2640+
// Abstract sockets in /proc/net/unix are represented with @ prefix
2641+
// The socket path is the last field in each line
2642+
for scanner.Scan() {
2643+
line := scanner.Text()
2644+
fields := strings.Fields(line)
2645+
2646+
// The path is the last field (8th field typically)
2647+
if len(fields) >= 8 {
2648+
path := fields[len(fields)-1]
2649+
if path == sockpath {
2650+
return true
2651+
}
2652+
}
2653+
}
2654+
2655+
return false
2656+
}
2657+
25982658
func isIP4Addr(fl FieldLevel) bool {
25992659
val := fl.Field().String()
26002660

doc.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,15 @@ This validates that a string value contains a valid Unix Address.
12631263
12641264
Usage: unix_addr
12651265
1266+
# Unix Domain Socket Exists
1267+
1268+
This validates that a Unix domain socket file exists at the specified path.
1269+
It checks both filesystem-based sockets and Linux abstract sockets (prefixed with @).
1270+
For filesystem sockets, it verifies the path exists and is a socket file.
1271+
For abstract sockets on Linux, it checks /proc/net/unix.
1272+
1273+
Usage: uds_exists
1274+
12661275
# Media Access Control Address MAC
12671276
12681277
This validates that a string value contains a valid MAC Address.

validator_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ import (
1212
"image"
1313
"image/jpeg"
1414
"image/png"
15+
"net"
1516
"os"
1617
"path/filepath"
1718
"reflect"
19+
"runtime"
1820
"strings"
1921
"testing"
2022
"time"
@@ -2950,6 +2952,82 @@ func TestUnixAddrValidation(t *testing.T) {
29502952
}
29512953
}
29522954

2955+
func TestUnixDomainSocketExistsValidation(t *testing.T) {
2956+
validate := New()
2957+
2958+
// Test with empty string - should fail
2959+
errs := validate.Var("", "uds_exists")
2960+
NotEqual(t, errs, nil)
2961+
AssertError(t, errs, "", "", "", "", "uds_exists")
2962+
2963+
// Test with non-existent path - should fail
2964+
errs = validate.Var("/tmp/nonexistent.sock", "uds_exists")
2965+
NotEqual(t, errs, nil)
2966+
AssertError(t, errs, "", "", "", "", "uds_exists")
2967+
2968+
// Test with a real socket file
2969+
// Create a temporary socket file for testing
2970+
// Use /tmp directly to avoid path length issues on macOS
2971+
sockPath := "/tmp/test_validator.sock"
2972+
2973+
// Create a Unix domain socket
2974+
listener, err := net.Listen("unix", sockPath)
2975+
if err != nil {
2976+
t.Fatalf("Failed to create test socket: %v", err)
2977+
}
2978+
defer listener.Close()
2979+
defer os.Remove(sockPath)
2980+
2981+
// Test with existing socket - should pass
2982+
errs = validate.Var(sockPath, "uds_exists")
2983+
Equal(t, errs, nil)
2984+
2985+
// Test with regular file (not a socket) - should fail
2986+
regularFile := "/tmp/test_validator_regular.txt"
2987+
if err := os.WriteFile(regularFile, []byte("test"), 0644); err != nil {
2988+
t.Fatalf("Failed to create regular file: %v", err)
2989+
}
2990+
defer os.Remove(regularFile)
2991+
2992+
errs = validate.Var(regularFile, "uds_exists")
2993+
NotEqual(t, errs, nil)
2994+
AssertError(t, errs, "", "", "", "", "uds_exists")
2995+
2996+
// Test with directory - should fail
2997+
dirPath := "/tmp/test_validator_dir"
2998+
if err := os.Mkdir(dirPath, 0755); err != nil && !os.IsExist(err) {
2999+
t.Fatalf("Failed to create directory: %v", err)
3000+
}
3001+
defer os.RemoveAll(dirPath)
3002+
3003+
errs = validate.Var(dirPath, "uds_exists")
3004+
NotEqual(t, errs, nil)
3005+
AssertError(t, errs, "", "", "", "", "uds_exists")
3006+
3007+
// Linux-specific test for abstract sockets
3008+
if runtime.GOOS == "linux" {
3009+
// Create an abstract socket (prefixed with @)
3010+
abstractSockName := "@test_abstract_socket_" + fmt.Sprintf("%d", time.Now().UnixNano())
3011+
3012+
// In Go, abstract sockets are created by using a null byte prefix
3013+
// but for testing, we need to use the actual implementation
3014+
abstractListener, err := net.Listen("unix", "\x00"+abstractSockName[1:])
3015+
if err != nil {
3016+
t.Fatalf("Failed to create abstract socket: %v", err)
3017+
}
3018+
defer abstractListener.Close()
3019+
3020+
// Test with existing abstract socket - should pass
3021+
errs = validate.Var(abstractSockName, "uds_exists")
3022+
Equal(t, errs, nil)
3023+
3024+
// Test with non-existent abstract socket - should fail
3025+
errs = validate.Var("@nonexistent_abstract_socket", "uds_exists")
3026+
NotEqual(t, errs, nil)
3027+
AssertError(t, errs, "", "", "", "", "uds_exists")
3028+
}
3029+
}
3030+
29533031
func TestSliceMapArrayChanFuncPtrInterfaceRequiredValidation(t *testing.T) {
29543032
validate := New()
29553033

0 commit comments

Comments
 (0)