summary refs log tree commit diff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2014-12-19 14:26:11 +0100
committerLudovic Courtès <ludo@gnu.org>2014-12-19 17:15:53 +0100
commitc9bf64d6d777baf2603b5d6a52c5c5b9adf649cd (patch)
tree7ec2fb81bebc8d5237df98e7a372fb06ab53985a
parentcdae969ae5191d50375c0cb7182d0ac82558875d (diff)
downloadguix-c9bf64d6d777baf2603b5d6a52c5c5b9adf649cd.tar.gz
syscalls: Add more procedures for network interfaces.
* guix/build/syscalls.scm (sizeof*, type-size, write-type, write-types,
  read-type, read-types, define-c-struct): New macros.
  (SIOCSIFFLAGS, SIOCGIFADDR, SIOCSIFADDR): New variables.
  (sockaddr-in, sockaddr-in6): New C structs.
  (write-socket-address!, read-socket-address,
  set-network-interface-flags, set-network-interface-address,
  network-interface-address, configure-network-interface): New
  procedures.
-rw-r--r--guix/build/syscalls.scm200
-rw-r--r--tests/syscalls.scm34
2 files changed, 232 insertions, 2 deletions
diff --git a/guix/build/syscalls.scm b/guix/build/syscalls.scm
index e1fafe2266..b210f8faa8 100644
--- a/guix/build/syscalls.scm
+++ b/guix/build/syscalls.scm
@@ -42,7 +42,11 @@
             all-network-interfaces
             network-interfaces
             network-interface-flags
-            loopback-network-interface?))
+            loopback-network-interface?
+            network-interface-address
+            set-network-interface-flags
+            set-network-interface-address
+            configure-network-interface))
 
 ;;; Commentary:
 ;;;
@@ -230,6 +234,77 @@ user-land process."
 
 
 ;;;
+;;; Packed structures.
+;;;
+
+(define-syntax sizeof*
+  ;; XXX: This duplicates 'compile-time-value'.
+  (syntax-rules (int128)
+    ((_ int128)
+     16)
+    ((_ type)
+     (let-syntax ((v (lambda (s)
+                       (let ((val (sizeof type)))
+                         (syntax-case s ()
+                           (_ val))))))
+       v))))
+
+(define-syntax type-size
+  (syntax-rules (~)
+    ((_ (type ~ order))
+     (sizeof* type))
+    ((_ type)
+     (sizeof* type))))
+
+(define-syntax write-type
+  (syntax-rules (~)
+    ((_ bv offset (type ~ order) value)
+     (bytevector-uint-set! bv offset value
+                           (endianness order) (sizeof* type)))
+    ((_ bv offset type value)
+     (bytevector-uint-set! bv offset value
+                           (native-endianness) (sizeof* type)))))
+
+(define-syntax write-types
+  (syntax-rules ()
+    ((_ bv offset () ())
+     #t)
+    ((_ bv offset (type0 types ...) (field0 fields ...))
+     (begin
+       (write-type bv offset type0 field0)
+       (write-types bv (+ offset (type-size type0))
+                    (types ...) (fields ...))))))
+
+(define-syntax read-type
+  (syntax-rules (~)
+    ((_ bv offset (type ~ order))
+     (bytevector-uint-ref bv offset
+                          (endianness order) (sizeof* type)))
+    ((_ bv offset type)
+     (bytevector-uint-ref bv offset
+                          (native-endianness) (sizeof* type)))))
+
+(define-syntax read-types
+  (syntax-rules ()
+    ((_ bv offset ())
+     '())
+    ((_ bv offset (type0 types ...))
+     (cons (read-type bv offset type0)
+           (read-types bv (+ offset (type-size type0)) (types ...))))))
+
+(define-syntax define-c-struct
+  (syntax-rules ()
+    "Define READ as an optimized serializer and WRITE! as a deserializer for
+the C structure with the given TYPES."
+    ((_ name read write! (fields types) ...)
+     (begin
+       (define (write! bv offset fields ...)
+         (write-types bv offset (types ...) (fields ...)))
+       (define (read bv offset)
+         (read-types bv offset (types ...)))))))
+
+
+;;;
 ;;; Network interfaces.
 ;;;
 
@@ -241,6 +316,18 @@ user-land process."
   (if (string-contains %host-type "linux")
       #x8913                                      ;GNU/Linux
       #xc4804191))                                ;GNU/Hurd
+(define SIOCSIFFLAGS
+  (if (string-contains %host-type "linux")
+      #x8914                                      ;GNU/Linux
+      -1))                                        ;FIXME: GNU/Hurd?
+(define SIOCGIFADDR
+  (if (string-contains %host-type "linux")
+      #x8915                                      ;GNU/Linux
+      -1))                                        ;FIXME: GNU/Hurd?
+(define SIOCSIFADDR
+  (if (string-contains %host-type "linux")
+      #x8916                                      ;GNU/Linux
+      -1))                                        ;FIXME: GNU/Hurd?
 
 ;; Flags and constants from <net/if.h>.
 
@@ -263,6 +350,56 @@ user-land process."
       40
       32))
 
+(define-c-struct sockaddr-in                      ;<linux/in.h>
+  read-sockaddr-in
+  write-sockaddr-in!
+  (family    unsigned-short)
+  (port      (int16 ~ big))
+  (address   (int32 ~ big)))
+
+(define-c-struct sockaddr-in6                     ;<linux/in6.h>
+  read-sockaddr-in6
+  write-sockaddr-in6!
+  (family    unsigned-short)
+  (port      (int16 ~ big))
+  (flowinfo  (int32 ~ big))
+  (address   (int128 ~ big))
+  (scopeid   int32))
+
+(define (write-socket-address! sockaddr bv index)
+  "Write SOCKADDR, a socket address as returned by 'make-socket-address', to
+bytevector BV at INDEX."
+  (let ((family (sockaddr:fam sockaddr)))
+    (cond ((= family AF_INET)
+           (write-sockaddr-in! bv index
+                               family
+                               (sockaddr:port sockaddr)
+                               (sockaddr:addr sockaddr)))
+          ((= family AF_INET6)
+           (write-sockaddr-in6! bv index
+                                family
+                                (sockaddr:port sockaddr)
+                                (sockaddr:flowinfo sockaddr)
+                                (sockaddr:addr sockaddr)
+                                (sockaddr:scopeid sockaddr)))
+          (else
+           (error "unsupported socket address" sockaddr)))))
+
+(define (read-socket-address bv index)
+  "Read a socket address from bytevector BV at INDEX."
+  (let ((family (bytevector-u16-native-ref bv index)))
+    (cond ((= family AF_INET)
+           (match (read-sockaddr-in bv index)
+             ((family port address)
+              (make-socket-address family address port))))
+          ((= family AF_INET6)
+           (match (read-sockaddr-in6 bv index)
+             ((family port flowinfo address scopeid)
+              (make-socket-address family address port
+                                   flowinfo scopeid))))
+          (else
+           "unsupported socket address family" family))))
+
 (define %ioctl
   ;; The most terrible interface, live from Scheme.
   (pointer->procedure int
@@ -354,4 +491,65 @@ interface NAME."
     (close-port sock)
     (not (zero? (logand flags IFF_LOOPBACK)))))
 
+(define (set-network-interface-flags socket name flags)
+  "Set the flag of network interface NAME to FLAGS."
+  (let ((req (make-bytevector ifreq-struct-size)))
+    (bytevector-copy! (string->utf8 name) 0 req 0
+                      (min (string-length name) (- IF_NAMESIZE 1)))
+    ;; Set the 'ifr_flags' field.
+    (bytevector-uint-set! req IF_NAMESIZE flags (native-endianness)
+                          (sizeof short))
+    (let* ((ret (%ioctl (fileno socket) SIOCSIFFLAGS
+                        (bytevector->pointer req)))
+           (err (errno)))
+      (unless (zero? ret)
+        (throw 'system-error "set-network-interface-flags"
+               "set-network-interface-flags on ~A: ~A"
+               (list name (strerror err))
+               (list err))))))
+
+(define (set-network-interface-address socket name sockaddr)
+  "Set the address of network interface NAME to SOCKADDR."
+  (let ((req (make-bytevector ifreq-struct-size)))
+    (bytevector-copy! (string->utf8 name) 0 req 0
+                      (min (string-length name) (- IF_NAMESIZE 1)))
+    ;; Set the 'ifr_addr' field.
+    (write-socket-address! sockaddr req IF_NAMESIZE)
+    (let* ((ret (%ioctl (fileno socket) SIOCSIFADDR
+                        (bytevector->pointer req)))
+           (err (errno)))
+      (unless (zero? ret)
+        (throw 'system-error "set-network-interface-address"
+               "set-network-interface-address on ~A: ~A"
+               (list name (strerror err))
+               (list err))))))
+
+(define (network-interface-address socket name)
+  "Return the address of network interface NAME.  The result is an object of
+the same type as that returned by 'make-socket-address'."
+  (let ((req (make-bytevector ifreq-struct-size)))
+    (bytevector-copy! (string->utf8 name) 0 req 0
+                      (min (string-length name) (- IF_NAMESIZE 1)))
+    (let* ((ret (%ioctl (fileno socket) SIOCGIFADDR
+                        (bytevector->pointer req)))
+           (err (errno)))
+      (if (zero? ret)
+          (read-socket-address req IF_NAMESIZE)
+          (throw 'system-error "network-interface-address"
+                 "network-interface-address on ~A: ~A"
+                 (list name (strerror err))
+                 (list err))))))
+
+(define (configure-network-interface name sockaddr flags)
+  "Configure network interface NAME to use SOCKADDR, an address as returned by
+'make-socket-address', and FLAGS, a bitwise-or of IFF_* constants."
+  (let ((sock (socket (sockaddr:fam sockaddr) SOCK_STREAM 0)))
+    (dynamic-wind
+      (const #t)
+      (lambda ()
+        (set-network-interface-address sock name sockaddr)
+        (set-network-interface-flags sock name flags))
+      (lambda ()
+        (close-port sock)))))
+
 ;;; syscalls.scm ends here
diff --git a/tests/syscalls.scm b/tests/syscalls.scm
index d65ec82740..21d6637ff6 100644
--- a/tests/syscalls.scm
+++ b/tests/syscalls.scm
@@ -74,7 +74,7 @@
      (lset<= string=? names (all-network-interfaces)))))
 
 (test-assert "network-interface-flags"
-  (let* ((sock  (socket SOCK_STREAM AF_INET 0))
+  (let* ((sock  (socket AF_INET SOCK_STREAM 0))
          (flags (network-interface-flags sock "lo")))
     (close-port sock)
     (and (not (zero? (logand flags IFF_LOOPBACK)))
@@ -90,6 +90,38 @@
          (lambda args
            (system-error-errno args)))))
 
+(test-skip (if (zero? (getuid)) 1 0))
+(test-equal "set-network-interface-flags"
+  EPERM
+  (let ((sock (socket AF_INET SOCK_STREAM 0)))
+    (catch 'system-error
+      (lambda ()
+        (set-network-interface-flags sock "lo" IFF_UP))
+      (lambda args
+        (close-port sock)
+        (system-error-errno args)))))
+
+(test-equal "network-interface-address lo"
+  (make-socket-address AF_INET (inet-pton AF_INET "127.0.0.1") 0)
+  (let* ((sock (socket AF_INET SOCK_STREAM 0))
+         (addr (network-interface-address sock "lo")))
+    (close-port sock)
+    addr))
+
+(test-equal "set-network-interface-address"
+  EPERM
+  (let ((sock (socket AF_INET SOCK_STREAM 0)))
+    (catch 'system-error
+      (lambda ()
+        (set-network-interface-address sock "nonexistent"
+                                       (make-socket-address
+                                        AF_INET
+                                        (inet-pton AF_INET "127.12.14.15")
+                                        0)))
+      (lambda args
+        (close-port sock)
+        (system-error-errno args)))))
+
 (test-end)