A bug causing broadcast-addr and other fields AFTER the flags field in
'struct getifaddrs' to be accessed incorrectly has been fixed.

On 2 July 2015 at 17:53, Ludovic Courtès <l...@gnu.org> wrote:
> Rohan Prinja <rohan.pri...@gmail.com> skribis:
>
>> PTAL, tests to follow soon.
>
> It’s looking good now, thanks!
>
> Could you send the updated patch against master, that includes a simple
> test in tests/syscalls.scm that makes sure that ‘getifaddrs’ returns a
> possibly empty list of <interface-address>?
>
> TIA,
> Ludo’.
From a4158777607d871d028e6bacc9aeb3a1b8178f9d Mon Sep 17 00:00:00 2001
From: Rohan Prinja <rohan.pri...@gmail.com>
Date: Thu, 16 Jul 2015 13:01:36 +0530
Subject: [PATCH 1/5] guix/build/syscalls.scm: export the new functions

---
 guix/build/syscalls.scm | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index 5afdd47..d155f49 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -44,6 +44,19 @@
             interface-address?
             interface-address-name
             interface-address-flags
+            interface-address-data
+
+            interface-address-addr
+            interface-address-netmask
+            interface-address-broadaddr
+
+            ;; Wrappers around the above three functions. Each
+            ;; of these returns either a socket address or #f.
+            interface-address-address
+            interface-address-broadcast-addr
+            interface-address-netmask-addr
+
+            make-ifaddrs
 
             IFF_UP
             IFF_BROADCAST
-- 
1.9.1

From 1a43f24c5e848f07c94e291637ef30210c87fbe5 Mon Sep 17 00:00:00 2001
From: Rohan Prinja <rohan.pri...@gmail.com>
Date: Thu, 16 Jul 2015 13:06:25 +0530
Subject: [PATCH 2/5] guix/build/syscalls.scm: add wrapper accessors for
 netmask, addr and broadaddr

---
 guix/build/syscalls.scm | 108 +++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 103 insertions(+), 5 deletions(-)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index d155f49..800a3a8 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -435,6 +435,16 @@ index START + LEN - 1"
 (define %struct-ifaddrs-type
   `(* * ,unsigned-int * * * *))
 
+;; Size of 'struct sockaddr' in bytes.
+;; See also: bind (2).
+(define %sizeof-struct-sockaddr
+  (+ 14 (sizeof unsigned-short)))
+
+(define (ifaddrs-pointer->bv ptr)
+  "Return a bytevector aliasing the memory pointed to by a
+'struct ifaddrs' pointer, passed as a pointer object PTR."
+  (pointer->bytevector ptr (sizeof %struct-ifaddrs-type)))
+
 ;; Initializer for 'struct ifaddrs'.
 (define %struct-ifaddrs-init
   (list %null-pointer
@@ -445,6 +455,22 @@ index START + LEN - 1"
         %null-pointer
         %null-pointer))
 
+(define (next-ifaddr-ptr bv)
+  "Return a bytevector aliasing the memory pointed to by the
+ifa_next field of a struct ifaddrs* pointer passed as a
+bytevector BV."
+  (let* ((ptr-size (sizeof '*))
+         (address (cond ((= ptr-size 4) (bytevector-u32-native-ref bv 0))
+                        ((= ptr-size 8) (bytevector-u64-native-ref bv 0)))))
+    (make-pointer address)))
+
+;; Return the bytevector aliasing the memory pointed to by
+;; the ifa-next field in a 'struct ifaddrs' pointer passed in
+;; as a bytevector.
+(define next-ifaddr
+  (compose ifaddrs-pointer->bv
+           next-ifaddr-ptr))
+
 (define %getifaddrs
   (let* ((func-ptr (dynamic-func "getifaddrs" (dynamic-link)))
          (proc (pointer->procedure int func-ptr (list '*))))
@@ -468,25 +494,97 @@ index START + LEN - 1"
      (make-interface-address (pointer->string (make-pointer name-ptr))
                              flags
                              (make-pointer addr)
-                             netmask
+                             (make-pointer netmask)
                              (make-pointer broadaddr)
                              (make-pointer data)))))
 
+;; Is an interface the last in the intrusive linked list of struct ifaddrs?
+;; Here, the only argument is a bytevector aliasing the memory pointed to by
+;; a 'struct ifaddrs' pointer.
+(define last-interface?
+  (compose null-pointer? next-ifaddr-ptr))
+
+(define (pack-ifaddrs bv)
+  "Strip out the needless 4-byte padding after the
+unsigned-int ifa-flags field"
+  (if (and (= 8 (sizeof '*))
+           (= 4 (sizeof unsigned-int)))
+      (let* ((res (make-bytevector 52 0)))
+        (bytevector-copy! bv 0 res 0 20)
+        (bytevector-copy! bv 24 res 20 32)
+        res)
+      bv))
+
 (define (getifaddrs)
   "Return the list of network interfaces on the local system."
   (let ((ifaddrs (%getifaddrs)))
     (let loop ((curr ifaddrs) (res '()))
       (if (last-interface? curr)
-          (map make-ifaddrs (reverse res))
+          (map (compose make-ifaddrs pack-ifaddrs)
+               (reverse res))
           (loop (next-ifaddr curr)
                 (cons curr res))))))
 
-;; Retrieve the ifa-name field from a 'struct ifaddrs'
+;; Given a bytevector aliasing the memory pointed to by
+;; a 'struct sockaddr' pointer, return a socket address.
+(define-syntax-rule (bytevector->sockaddr bv)
+  (match (read-sockaddr-in bv 0)
+      ((family port address)
+       (if (member family (list AF_INET AF_INET6 AF_UNIX))
+           (inet-ntop family address)
+           #f))))
+
+;; Note: address fields in 'struct getifaddrs' are pointers to
+;; 'struct sockaddr'. In 'interface-address-broadcast-addr' we are
+;; implicitly typecasting this 'sockaddr' pointer to a
+;; 'sockaddr_in' pointer.
+
+;; Note: getifaddrs returns multiple interfaces with the same
+;; e.g. on my system I see multiple "eth0"s. The difference is
+;; that for one of the eth0's, the family of the address
+;; pointed to by the ifu.ifa-broadaddr field is 17, which is
+;; not an AF_* constant. Hence the check for "(member family ...)".
+
+(define (extract-address-field iface field)
+  "Extract a field corresponding to an IPv4 address from a 'struct
+sockaddr' from an <interface-address> record type."
+  (let* ((addr (field iface))
+         (bv (pointer->bytevector addr %sizeof-struct-sockaddr)))
+    (bytevector->sockaddr bv)))
+
+;; Given an <interface-address> record IFACE, return its
+;; address field as a sockaddr if it exists, otherwise return #f.
+(define (interface-address-address iface)
+  (extract-address-field iface interface-address-addr))
+
+;; Given an <interface-address> record IFACE, return its broadcast
+;; address field as a sockaddr if it exists, otherwise return #f.
+(define (interface-address-broadcast-addr iface)
+  (extract-address-field iface interface-address-broadaddr))
+
+;; Given an <interface-address> record IFACE, return its netmask
+;; address field as a sockaddr if it exists, otherwise return #f.
+(define (interface-address-netmask-addr iface)
+  (extract-address-field iface interface-address-netmask))
+
+;; Retrieve the ifa-next-ptr field from a 'struct ifaddrs'
 ;; pointer passed in as a bytevector BV.
-(define-syntax-rule (ifaddr-name bv)
+(define-syntax-rule (ifaddr-next-ptr bv)
   (match (read-ifaddrs bv 0)
     ((next name-ptr flags addr netmask broadaddr data)
-     (pointer->string (make-pointer name-ptr)))))
+     next)))
+
+;; Retrieve the bytes corresponding to the ifa-name field
+;; from a 'struct ifaddrs' pointer passed in as a bytevector BV.
+(define-syntax-rule (ifaddr-name-bytes bv)
+  (match (read-ifaddrs bv 0)
+    ((next name-ptr flags addr netmask broadaddr data)
+     name-ptr)))
+
+;; Retrieve the string pointed to by the ifa-name field
+;; from a 'struct ifaddrs' pointer passed in as a bytevector BV.
+(define-syntax-rule (ifaddr-name bv)
+  (pointer->string (make-pointer (ifaddr-name-bytes bv))))
 
 ;; Retrieve the ifa-flags field from a 'struct ifaddrs'
 ;; pointer passed in as a bytevector BV.
-- 
1.9.1

From 881412da8f41ea4f4c7c621ea840feaba6fec919 Mon Sep 17 00:00:00 2001
From: Rohan Prinja <rohan.pri...@gmail.com>
Date: Thu, 16 Jul 2015 13:26:48 +0530
Subject: [PATCH 3/5] guix/build/syscalls.scm: remove unneeded low-level
 accessor

---
 guix/build/syscalls.scm | 27 ---------------------------
 1 file changed, 27 deletions(-)

diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index 800a3a8..a50a9bf 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -593,33 +593,6 @@ sockaddr' from an <interface-address> record type."
     ((next name-ptr flags addr netmask broadaddr data)
      flags)))
 
-(define (ifaddrs-pointer->bv ptr)
-  "Return a bytevector aliasing the memory pointed to by a
-'struct ifaddrs' pointer, passed as a pointer object PTR."
-  (pointer->bytevector ptr (sizeof %struct-ifaddrs-type)))
-
-;; Return the bytevector aliasing the memory pointed to by
-;; the ifa-next field in a 'struct ifaddrs' pointer passed in
-;; as a bytevector.
-(define next-ifaddr
-  (compose ifaddrs-pointer->bv
-           next-ifaddr-ptr))
-
-(define (next-ifaddr-ptr bv)
-  "Return a bytevector aliasing the memory pointed to by the
-ifa_next field of a struct ifaddrs* pointer passed as a
-bytevector BV."
-  (let* ((ptr-size (sizeof '*))
-         (address (cond ((= ptr-size 4) (bytevector-u32-native-ref bv 0))
-                        ((= ptr-size 8) (bytevector-u64-native-ref bv 0)))))
-    (make-pointer address)))
-
-;; Is an interface the last in the intrusive linked list of struct ifaddrs?
-;; Here, IFADDRS is a bytevector aliasing the memory pointed to by
-;; a 'struct ifaddrs' pointer.
-(define-syntax-rule (last-interface? ifaddrs)
-  (null-pointer? (next-ifaddr-ptr ifaddrs)))
-
 (define (write-socket-address! sockaddr bv index)
   "Write SOCKADDR, a socket address as returned by 'make-socket-address', to
 bytevector BV at INDEX."
-- 
1.9.1

From 4a718e6f2f76616e06848d94022ee8d05a17bea1 Mon Sep 17 00:00:00 2001
From: Rohan Prinja <rohan.pri...@gmail.com>
Date: Thu, 16 Jul 2015 13:27:13 +0530
Subject: [PATCH 4/5] tests/syscalls.scm: add utility functions for testing

---
 tests/syscalls.scm | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 1 deletion(-)

diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index 706f3df..bf4f604 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -21,11 +21,98 @@
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-64)
-  #:use-module (ice-9 match))
+  #:use-module (ice-9 match)
+  #:use-module ((ice-9 popen) #:select (open-pipe*))
+  #:use-module ((ice-9 rdelim) #:select (read-line))
+  #:use-module ((ice-9 regex) #:select (string-match match:substring))
+  #:use-module (rnrs bytevectors)
+  #:use-module (system foreign)
+  #:use-module ((rnrs io ports) #:select (port-eof?)))
 
 ;; Test the (guix build syscalls) module, although there's not much that can
 ;; actually be tested without being root.
 
+;; Is the first character of a string #\space?
+(define-syntax-rule (first-char-is-space? string)
+  (eq? #\space (string-ref string 0)))
+
+;; In the output produced by ifconfig (8), is a line
+;; one that starts a new interface description?
+(define-syntax-rule (line-contains-iface-name? line)
+    (not (or (string-null? line)
+	     (first-char-is-space? line))))
+
+(define (ifconfig-find-all-interfaces)
+  "List all the network interfaces as identified
+by ifconfig (8)."
+  (let ((pipe (open-pipe* OPEN_READ "ifconfig")))
+    (let lp ((line (read-line pipe))
+	     (res '()))
+      (cond ((port-eof? pipe) (reverse res))
+	    ((line-contains-iface-name? line)
+	     (let* ((trimmed-line (string-trim-both line))
+		    (split-line (string-split trimmed-line #\space))
+		    (iface-name (car split-line)))
+	       (lp (read-line pipe)
+		   (cons iface-name res))))
+	    (else (lp (read-line pipe) res))))))
+
+(define (extract-iface-name line)
+  "Extract the name of the interface from a line in the output of
+ifconfig (8) which is known to be the first line describing said
+interface."
+  (let ((str-ls (string->list line)))
+    (let lp ((ls str-ls) (res '()))
+      (if (eq? #\space (car ls))
+          (apply string (reverse res))
+          (lp (cdr ls) (cons (car ls) res))))))
+
+(define (ifconfig-extract-addr-of iface-name type)
+  "Call ifconfig (8) to find out the broadcast address of the
+interface whose name is a prefix of the string IFACE-NAME. The
+broadcast address is returned as a printable string."
+  (let ((pipe (open-pipe* OPEN_READ "ifconfig")))
+    (let lp ((line (read-line pipe)))
+      (if (eof-object? line)
+          #f
+          (if (and (line-contains-iface-name? line)
+                   (string-prefix? iface-name
+                                   (extract-iface-name line)))
+              (let* ((next-line (read-line pipe))
+                     (search-string (cond ((eq? type 'broadcast) "Bcast:")
+                                          ((eq? type 'netmask) "Mask:")
+                                          (else "inet addr:")))
+                     (str-byte "[0-9]([0-9][0-9])?")
+                     (ipaddr-regex (string-append search-string
+                                                   str-byte "\\."
+                                                   str-byte "\\."
+                                                   str-byte "\\."
+                                                   str-byte))
+                     (match (string-match ipaddr-regex next-line)))
+                (if match
+                    (string-drop (match:substring match) (cond ((eq? type 'broadcast) 6)
+                                                               ((eq? type 'netmask) 5)
+                                                               (else 10)))
+                    (lp (read-line pipe))))
+              (lp (read-line pipe)))))))
+
+(define (prefix? ls1 ls2)
+  "Is list LS1 a prefix of list LS2?. This procedure
+assumes that (length ls1) <= (length ls2)."
+  (or (null? ls1)
+      (and (equal? (car ls1) (car ls2))
+	   (prefix? (cdr ls1) (cdr ls2)))))
+
+(define (remove-duplicates ls)
+  "Remove consecutive duplicate elements from a list LS.
+For example, (4 2 2 2 2 1 3 3) => (4 2 1 3)."
+  (cond ((< (length ls) 2)
+         ls)
+        ((equal? (car ls) (cadr ls))
+         (remove-duplicates (cdr ls)))
+        (else
+         (cons (car ls) (remove-duplicates (cdr ls))))))
+
 (test-begin "syscalls")
 
 (test-equal "mount, ENOENT"
-- 
1.9.1

From a60e8083de3a6cec3b610be54f6bef77ce18a36d Mon Sep 17 00:00:00 2001
From: Rohan Prinja <rohan.pri...@gmail.com>
Date: Thu, 16 Jul 2015 13:27:34 +0530
Subject: [PATCH 5/5] tests/syscalls.scm: add getifaddrs tests

---
 tests/syscalls.scm | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index bf4f604..3665575 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -213,6 +213,58 @@ For example, (4 2 2 2 2 1 3 3) => (4 2 1 3)."
         ;; We get EPERM with Linux 3.18ish and EACCES with 2.6.32.
         (memv (system-error-errno args) (list EPERM EACCES))))))
 
+(test-assert "getifaddrs"
+             (let* ((ifaddrs (getifaddrs))
+                    (names (map interface-address-name ifaddrs)))
+               (member "lo" names)))
+
+(test-assert "ifconfig-result-is-subset-of-getifaddrs-result"
+             (let* ((ifaddrs (getifaddrs))
+                    (names (map interface-address-name ifaddrs))
+                    (sorted-names (sort names string<?))
+                    (unique-names (remove-duplicates sorted-names))
+                    (ifconfig (ifconfig-find-all-interfaces)))
+               (prefix?
+                (sort (ifconfig-find-all-interfaces) string<?)
+                unique-names)))
+
+(test-assert "getifaddrs-address"
+             (let* ((is-eth-iface? (lambda (i)
+                                     (string-prefix? "eth"
+                                                     (interface-address-name i))))
+                    (ifaddrs (getifaddrs))
+                    (eth-ifaces (filter is-eth-iface? ifaddrs))
+                    (getifaddrs-tmp (map interface-address-address eth-ifaces))
+                    (getifaddrs-result (remove not getifaddrs-tmp))
+                    (ifconfig-result (ifconfig-extract-addr-of "eth" 'address)))
+               (member ifconfig-result getifaddrs-result)))
+
+(test-assert "getifaddrs-broadcast-address"
+             (let* ((is-eth-iface? (lambda (i)
+                                     (string-prefix? "eth"
+                                                     (interface-address-name i))))
+                    (ifaddrs (getifaddrs))
+                    (eth-ifaces (filter is-eth-iface? ifaddrs))
+                    (getifaddrs-tmp (map interface-address-broadcast-addr eth-ifaces))
+                    (getifaddrs-result (remove not getifaddrs-tmp))
+                    (ifconfig-result (ifconfig-extract-addr-of "eth" 'broadcast)))
+               (member ifconfig-result getifaddrs-result)))
+
+(test-assert "getifaddrs-netmask-address"
+             (let* ((is-eth-iface? (lambda (i)
+                                     (string-prefix? "eth"
+                                                     (interface-address-name i))))
+                    (ifaddrs (getifaddrs))
+                    (eth-ifaces (filter is-eth-iface? ifaddrs))
+                    (getifaddrs-tmp (remove (lambda (i)
+                                               (null-pointer?
+                                                (interface-address-netmask i)))
+                                             eth-ifaces))
+                    (getifaddrs-tmp (map interface-address-netmask-addr getifaddrs-tmp))
+                    (getifaddrs-result (remove not getifaddrs-tmp))
+                    (ifconfig-result (ifconfig-extract-addr-of "eth" 'netmask)))
+               (member ifconfig-result getifaddrs-result)))
+
 (test-end)
 
 
-- 
1.9.1

Reply via email to