summary refs log tree commit diff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2015-07-25 13:06:01 +0200
committerLudovic Courtès <ludo@gnu.org>2015-07-25 14:43:45 +0200
commite7f5691d4540e2cbcbc9f22f8b593f15890057b3 (patch)
tree8428f0d6c6d255c684cc99ca8f26d7876f6f98f8
parent573b4c1ff3409fb4417ec676091f6bbc09219f19 (diff)
downloadguix-e7f5691d4540e2cbcbc9f22f8b593f15890057b3.tar.gz
syscalls: Add 'network-interfaces', which wraps libc's 'getifaddrs'.
Based on discussions with Rohan Prinja <rohan.prinja@gmail.com>.

* guix/build/syscalls.scm (<interface>): New record type.
  (write-interface, values->interface, unfold-interface-list,
  network-interfaces, free-ifaddrs): New procedures.
  (ifaddrs): New C struct.
  (%struct-ifaddrs-type, %sizeof-ifaddrs): New macros.
* tests/syscalls.scm ("network-interfaces returns one or more interfaces",
  "network-interfaces returns \"lo\""): New tests.
-rw-r--r--guix/build/syscalls.scm116
-rw-r--r--tests/syscalls.scm23
2 files changed, 138 insertions, 1 deletions
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 @@
   #: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 @@
             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 @@ the same type as that returned by 'make-socket-address'."
       (lambda ()
         (close-port sock)))))
 
+
+;;;
+;;; Details about network interfaces---aka. 'getifaddrs'.
+;;;
+
+;; Network interfaces.  XXX: We would call it <network-interface> but that
+;; would collide with the ioctl wrappers above.
+(define-record-type <interface>
+  (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
+    (($ <interface> name flags address)
+     (format port "#<interface ~s " name)
+     (unless (zero? (logand IFF_UP flags))
+       (display "up " port))
+     (if (member (sockaddr:fam address) (list AF_INET AF_INET6))
+         (format port "~a " (inet-ntop (sockaddr:fam address)
+                                       (sockaddr:addr address)))
+         (format port "family:~a " (sockaddr:fam address)))
+     (format port "~a>" (number->string (object-address interface) 16)))))
+
+(set-record-type-printer! <interface> 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 <interface> 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                          ;<ifaddrs.h>
+  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 <interface> 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 <interface> 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 @@
         ;; 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)