diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm index ca26824dc5..68f340ce7b 100644 --- a/guix/build/syscalls.scm +++ b/guix/build/syscalls.scm @@ -21,6 +21,8 @@ (define-module (guix build syscalls) #:use-module (system foreign) #:use-module (rnrs bytevectors) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-9 gnu) #:use-module (ice-9 rdelim) #:use-module (ice-9 regex) #:use-module (ice-9 match) @@ -70,7 +72,15 @@ (define-module (guix build syscalls) set-network-interface-flags set-network-interface-address set-network-interface-up - configure-network-interface)) + configure-network-interface + + interface? + interface-name + interface-flags + interface-address + interface-netmask + interface-broadcast-address + network-interfaces)) ;;; Commentary: ;;; @@ -713,4 +723,108 @@ (define* (set-network-interface-up name (lambda () (close-port sock))))) + +;;; +;;; Details about network interfaces---aka. 'getifaddrs'. +;;; + +;; Network interfaces. XXX: We would call it but that +;; would collide with the ioctl wrappers above. +(define-record-type + (make-interface name flags address netmask broadcast-address) + interface? + (name interface-name) ;string + (flags interface-flags) ;or'd IFF_* values + (address interface-address) ;sockaddr | #f + (netmask interface-netmask) ;sockaddr | #f + (broadcast-address interface-broadcast-address)) ;sockaddr | #f + +(define (write-interface interface port) + (match interface + (($ name flags address) + (format port "#" (number->string (object-address interface) 16))))) + +(set-record-type-printer! write-interface) + +(define (values->interface next name flags address netmask + broadcast-address data) + "Given the raw field values passed as arguments, return a pair whose car is +an object, and whose cdr is the pointer NEXT." + (define (maybe-socket-address pointer) + (if (null-pointer? pointer) + #f + (read-socket-address (pointer->bytevector pointer 50)))) ;XXX: size + + (cons (make-interface (if (null-pointer? name) + #f + (pointer->string name)) + flags + (maybe-socket-address address) + (maybe-socket-address netmask) + (maybe-socket-address broadcast-address) + ;; Ignore DATA. + ) + next)) + +(define-c-struct ifaddrs ; + values->interface + read-ifaddrs + write-ifaddrs! + (next '*) + (name '*) + (flags unsigned-int) + (addr '*) + (netmask '*) + (broadcastaddr '*) + (data '*)) + +(define-syntax %struct-ifaddrs-type + (identifier-syntax + `(* * ,unsigned-int * * * *))) + +(define-syntax %sizeof-ifaddrs + (identifier-syntax + (sizeof* %struct-ifaddrs-type))) + +(define (unfold-interface-list ptr) + "Call 'read-ifaddrs' on PTR and all its 'next' fields, recursively, and +return the list of resulting objects." + (let loop ((ptr ptr) + (result '())) + (if (null-pointer? ptr) + (reverse result) + (match (read-ifaddrs (pointer->bytevector ptr %sizeof-ifaddrs) + 0) + ((ifaddr . ptr) + (loop ptr (cons ifaddr result))))))) + +(define network-interfaces + (let* ((ptr (dynamic-func "getifaddrs" (dynamic-link))) + (proc (pointer->procedure int ptr (list '*)))) + (lambda () + "Return a list of objects, each denoting a configured +network interface. This is implemented using the 'getifaddrs' libc function." + (let* ((ptr (bytevector->pointer (make-bytevector (sizeof* '*)))) + (ret (proc ptr)) + (err (errno))) + (if (zero? ret) + (let* ((ptr (dereference-pointer ptr)) + (result (unfold-interface-list ptr))) + (free-ifaddrs ptr) + result) + (throw 'system-error "network-interfaces" "~A" + (list (strerror err)) + (list err))))))) + +(define free-ifaddrs + (let ((ptr (dynamic-func "freeifaddrs" (dynamic-link)))) + (pointer->procedure void ptr '(*)))) + ;;; syscalls.scm ends here diff --git a/tests/syscalls.scm b/tests/syscalls.scm index 3b71cd7b1c..090e1e7858 100644 --- a/tests/syscalls.scm +++ b/tests/syscalls.scm @@ -211,6 +211,29 @@ (define (user-namespace pid) ;; We get EPERM with Linux 3.18ish and EACCES with 2.6.32. (memv (system-error-errno args) (list EPERM EACCES)))))) +(test-equal "network-interfaces returns one or more interfaces" + '(#t #t #t) + (match (network-interfaces) + ((interfaces ..1) + (list (every interface? interfaces) + (every string? (map interface-name interfaces)) + (every vector? (map interface-address interfaces)))))) + +(test-equal "network-interfaces returns \"lo\"" + (list #t (make-socket-address AF_INET (inet-pton AF_INET "127.0.0.1") 0)) + (match (filter (lambda (interface) + (string=? "lo" (interface-name interface))) + (network-interfaces)) + ((loopbacks ..1) + (list (every (lambda (lo) + (not (zero? (logand IFF_LOOPBACK (interface-flags lo))))) + loopbacks) + (match (find (lambda (lo) + (= AF_INET (sockaddr:fam (interface-address lo)))) + loopbacks) + (#f #f) + (lo (interface-address lo))))))) + (test-end)