Static networking should wait for interfaces to be up

  • Done
  • quality assurance status badge
Details
3 participants
  • Julien Lepiller
  • Ludovic Courtès
  • Ludovic Courtès
Owner
unassigned
Submitted by
Ludovic Courtès
Severity
important
L
L
Ludovic Courtès wrote on 15 May 2023 11:30
(address . bug-guix@gnu.org)
87pm726iwd.fsf@inria.fr
Hi!

With shepherd 0.10.0 starting services in parallel, I observed
‘networking’ (from ‘static-networking-service-type’) starting too early,
before the corresponding interfaces were available:

Toggle snippet (31 lines)
[ 21.863249] shepherd[1]: Service udev started.
[ 21.863414] shepherd[1]: Service udev running with value 196.
[ 21.865686] shepherd[1]: Starting service networking...
[ 21.865822] shepherd[1]: Starting service file-system-/boot/efi...
[ 21.986025] shepherd[1]: Exception caught while starting #<<service> 7f9bf08434e0>: (no-such-device "eno1"
[ 22.738237] udevd[196]: starting eudev-3.2.11
[ 22.767830] udevd[196]: no sender credentials received, message ignored

[...]

[ 23.562275] tg3 0000:05:00.0 eth0: Tigon3 [partno(BCM95720) rev 5720000] (PCI Express) MAC address b8:cb:2
[ 23.562290] tg3 0000:05:00.0 eth0: attached PHY is 5720C (10/100/1000Base-T Ethernet) (WireSpeed[1], EEE[1
[ 23.562298] tg3 0000:05:00.0 eth0: RXcsums[1] LinkChgREG[0] MIirq[0] ASF[1] TSOcap[1]
[ 23.562305] tg3 0000:05:00.0 eth0: dma_rwctrl[00000001] dma_mask[64-bit]
[ 23.596221] tg3 0000:05:00.1 eth1: Tigon3 [partno(BCM95720) rev 5720000] (PCI Express) MAC address b8:cb:2
[ 23.596234] tg3 0000:05:00.1 eth1: attached PHY is 5720C (10/100/1000Base-T Ethernet) (WireSpeed[1], EEE[1
[ 23.596242] tg3 0000:05:00.1 eth1: RXcsums[1] LinkChgREG[0] MIirq[0] ASF[1] TSOcap[1]
[ 23.596249] tg3 0000:05:00.1 eth1: dma_rwctrl[00000001] dma_mask[64-bit]
[ 23.599725] tg3 0000:05:00.0 eno1: renamed from eth0
[ 23.613468] iTCO_vendor_support: vendor-support=0
[ 23.619114] iTCO_wdt iTCO_wdt: Found a Intel PCH TCO device (Version=6, TCOBASE=0x0400)
[ 23.619327] iTCO_wdt iTCO_wdt: initialized. heartbeat=30 sec (nowayout=0)
[ 23.645196] tg3 0000:05:00.1 eno2: renamed from eth1
[ 23.727153] Error: Driver 'pcspkr' is already registered, aborting...
[ 23.738237] ipmi_si dmi-ipmi-si.0: Removing SMBIOS-specified kcs state machine in favor of ACPI
[ 23.738239] ipmi_si: Adding ACPI-specified kcs state machine
[ 23.738273] ipmi_si: Trying ACPI-specified kcs state machine at i/o address 0xca8, slave address 0x20, irq
[ 23.926348] shepherd[1]: Service file-system-/boot/efi has been started.
[ 23.927164] shepherd[1]: Service networking failed to start.

Before doing ‘addr-add’ in ‘network-set-up/linux’, should we wait for
the interface to show up, by calling ‘get-links’ from Guile-Netlink or
something like that?

Ludo’.
L
L
Ludovic Courtès wrote on 21 May 2023 01:03
(address . 63516@debbugs.gnu.org)(name . Julien Lepiller)(address . julien@lepiller.eu)
871qjawqpt.fsf@gnu.org
Ludovic Courtès <ludovic.courtes@inria.fr> skribis:

Toggle quote (4 lines)
> Before doing ‘addr-add’ in ‘network-set-up/linux’, should we wait for
> the interface to show up, by calling ‘get-links’ from Guile-Netlink or
> something like that?

Below is a simple workaround. How does that sound?

A better fix would be to poll(2) on the underlying AF_NETLINK socket.
In fact, we could also implement something like systemd’s
‘network-online.target’ by doing that. For that we’d need Guile-Netlink
to let us create SOCK_NONBLOCK sockets and to use real ports instead of
raw file descriptors; Fibers would then take care of the rest.

Thoughts?

Ludo’.
Toggle diff (45 lines)
diff --git a/gnu/services/base.scm b/gnu/services/base.scm
index fd79c9e232..5d43d998c3 100644
--- a/gnu/services/base.scm
+++ b/gnu/services/base.scm
@@ -2794,10 +2794,39 @@ (define (network-set-up/linux config)
(scheme-file "set-up-network"
(with-extensions (list guile-netlink)
#~(begin
- (use-modules (ip addr) (ip link) (ip route))
+ (use-modules (ip addr) (ip link) (ip route)
+ (srfi srfi-1))
+
+ (define (wait-for-device device)
+ ;; Wait for DEVICE to show up.
+ ;; XXX: Polling is ridiculous. We should open a
+ ;; SOCK_NONBLOCK netlink socket and wait on it.
+ (let loop ((attempts 0))
+ (unless (find (lambda (link)
+ (string=? (link-name link)
+ device))
+ (get-links))
+ (if (< attempts 30)
+ (begin
+ (format #t
+ "waiting for \
+networking device '~a'...~%"
+ device)
+ ((@ (fibers) sleep) 1)
+ (loop (+ 1 attempts)))
+ (begin
+ (format #t "networking device '~a' \
+did not show up; bailing out~%"
+ device)
+ #f)))))
#$@(map (lambda (address)
#~(begin
+ ;; Before going any further, wait for the
+ ;; device to show up.
+ (wait-for-device
+ #$(network-address-device address))
+
(addr-add #$(network-address-device address)
#$(network-address-value address)
#:ipv6?
L
L
Ludovic Courtès wrote on 21 May 2023 01:03
control message for bug #63516
(address . control@debbugs.gnu.org)
87zg5yvc51.fsf@gnu.org
severity 63516 important
quit
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 00/11] Add 'wait-for-link' and related code
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-1-ludo@gnu.org
Hi Julien,

As a followup to https://issues.guix.gnu.org/63516, here is code that
lets us wait for a link to show up “the right way”—i.e., without polling.
It works over SOCK_NONBLOCK sockets, for use in Fibers programs.

I tested it in a VM created with ‘guix system vm’. If the “ens3” device
is already there, (wait-for-link "ens3") returns immediately. Then I
ran “rmmod e1000” to make the device disappear, and made another
(wait-for-link "ens3") call: that call returns once I’ve run “modprobe e1000”
in another terminal. Wonderful. :-)

Now, it would be good to have a test suite that can run without
complicated setups. We should check the strategy used by libnl, systemd,
and the likes.

Thoughts?

Ludo’.

Ludovic Courtès (11):
connection: Remove unused procedure.
connection: Use Guile's 'socket' procedure to open a socket.
connection: Throw upon errors in FFI bindings.
connection: Add support for suspendable sockets.
connection: Allow users to pass extra SOCK_ flags to 'socket'.
link: Extract 'new-link-message->link'.
addr: Extract 'new-address-message->address'.
connection: Add 'add-socket-membership'.
error: Add 'sub-type' field to '&netlink-decoder-error' and use it.
doc: Add indexes.
link: Add 'wait-for-link'.

doc/guile-netlink.texi | 51 +++++++++++++++--
ip/addr.scm | 46 +++++++--------
ip/link.scm | 122 ++++++++++++++++++++++++++++++---------
ip/route.scm | 6 +-
netlink/connection.scm | 126 +++++++++++++++++++++++++++--------------
netlink/constant.scm | 40 +++++++++++++
netlink/data.scm | 13 +++--
netlink/error.scm | 4 +-
8 files changed, 303 insertions(+), 105 deletions(-)


base-commit: beceb4cfea4739954e558411f46e07425891c774
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 01/11] connection: Remove unused procedure.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-2-ludo@gnu.org
* netlink/connection.scm (ffi-sendmsg): Remove.
---
netlink/connection.scm | 4 ----
1 file changed, 4 deletions(-)

Toggle diff (17 lines)
diff --git a/netlink/connection.scm b/netlink/connection.scm
index 4d2ceca..11f004f 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -40,10 +40,6 @@
(define ffi-close (pointer->procedure void
(dynamic-func "close" libc)
(list int)))
-(define ffi-sendmsg (pointer->procedure int
- (dynamic-func "sendmsg" libc)
- (list int '* int)
- #:return-errno? #t))
(define ffi-sendto (pointer->procedure int
(dynamic-func "sendto" libc)
(list int '* size_t int '* int)
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 02/11] connection: Use Guile's 'socket' procedure to open a socket.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-3-ludo@gnu.org
This gives us a real port, which can then let us benefit from the
suspendable port facilities.

* netlink/connection.scm (ffi-socket, ffi-close): Remove.
(socket): Remove record type.
(open-socket): Use Guile's 'socket' procedure.
(close-socket): Make a deprecated alias for 'close-port'.
(get-addr): Add docstring.
(connect, send-msg, receive-msg): Use 'fileno' instead of 'socket-num'.
* ip/addr.scm (addr-del, addr-add, get-addrs): Use 'close-port' instead
of 'close-socket'.
* ip/link.scm (get-links, link-set, link-add, link-del): Likewise.
* ip/route.scm (route-del, route-add, get-routes): Likewise.
* doc/guile-netlink.texi (Netlink Connections): Remove 'close-socket'.
---
doc/guile-netlink.texi | 4 ----
ip/addr.scm | 6 +++---
ip/link.scm | 8 ++++----
ip/route.scm | 6 +++---
netlink/connection.scm | 35 +++++++++++++----------------------
5 files changed, 23 insertions(+), 36 deletions(-)

Toggle diff (202 lines)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index 548e47b..48ca6d7 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -256,10 +256,6 @@ rtnetlink protocol, binds it to the kernel and returns it. By passing the
optional @var{groups} keyword, you can select broadcast groups to subscribe to.
@end deffn
-@deffn {Scheme Procedure} close-socket @var{socket}
-Closes a netlink socket. The socket cannot be used afterwards.
-@end deffn
-
@deffn {Scheme Procedure} send-msg @var{msg} @var{sock} [#:@var{addr}]
Send @var{msg} (it must be of type message, @xref{Netlink Headers}) to
@var{addr} using @var{sock}. If not passed, @var{addr} is the address of
diff --git a/ip/addr.scm b/ip/addr.scm
index 0976ab9..fcb286f 100644
--- a/ip/addr.scm
+++ b/ip/addr.scm
@@ -100,7 +100,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define* (addr-add device cidr #:key (ipv6? #f) (peer (cidr->addr cidr))
@@ -180,7 +180,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define (get-addrs)
@@ -216,7 +216,7 @@
(get-attr attrs IFA_BROADCAST)
(get-attr attrs IFA_CACHEINFO))))
addrs)))
- (close-socket sock)
+ (close-port sock)
addrs)))
(define print-addr
diff --git a/ip/link.scm b/ip/link.scm
index 0957a5e..814a008 100644
--- a/ip/link.scm
+++ b/ip/link.scm
@@ -94,7 +94,7 @@
(get-attr attrs IFLA_ADDRESS)
(get-attr attrs IFLA_BROADCAST))))
links)))
- (close-socket sock)
+ (close-port sock)
links)))
(define print-link
@@ -246,7 +246,7 @@ criteria."
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
(when netnsfd
(close netnsfd))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define* (bond-type-args #:key (mode #f) (miimon #f) (lacp-active #f) (lacp-rate #f)
@@ -364,7 +364,7 @@ balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb"
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define* (link-del device)
@@ -390,5 +390,5 @@ balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb"
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
diff --git a/ip/route.scm b/ip/route.scm
index bf43c18..d5e1275 100644
--- a/ip/route.scm
+++ b/ip/route.scm
@@ -106,7 +106,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define* (route-add dest
@@ -170,7 +170,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
- (close-socket sock)
+ (close-port sock)
(answer-ok? (last answer)))))
(define (link-ref links id)
@@ -221,7 +221,7 @@
(get-attr attrs RTA_PRIORITY)
(link-ref links (get-attr attrs RTA_OIF)))))
routes)))
- (close-socket sock)
+ (close-port sock)
routes)))
(define print-route
diff --git a/netlink/connection.scm b/netlink/connection.scm
index 11f004f..6f41ef8 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -22,7 +22,6 @@
#:use-module (netlink message)
#:use-module (rnrs bytevectors)
#:use-module (system foreign)
- #:use-module (srfi srfi-9)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:export (connect
@@ -34,12 +33,7 @@
get-addr))
(define libc (dynamic-link))
-(define ffi-socket (pointer->procedure int
- (dynamic-func "socket" libc)
- (list int int int)))
-(define ffi-close (pointer->procedure void
- (dynamic-func "close" libc)
- (list int)))
+
(define ffi-sendto (pointer->procedure int
(dynamic-func "sendto" libc)
(list int '* size_t int '* int)
@@ -51,22 +45,19 @@
(dynamic-func "bind" libc)
(list int '* int)))
-;; define socket type
-(define-record-type socket
- (make-socket num open?)
- socket?
- (num socket-num)
- (open? socket-open?))
-
;; define simple functions to open/close sockets
(define (open-socket proto)
- (make-socket (ffi-socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC) proto) #t))
-(define (close-socket socket)
- (if (socket-open? socket)
- (ffi-close (socket-num socket)))
- (make-socket (socket-num socket) #f))
+ (socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC) proto))
+
+(define (close-socket sock)
+ (issue-deprecation-warning
+ "'close-socket' is deprecated; use 'close-port' instead.")
+ (close-port sock))
(define (get-addr family pid groups)
+ "This is a variant of 'make-socket-address' for AF_NETLINK sockets. The
+main difference is that it returns a raw bytevector that libguile procedures
+such as 'bind' cannot handle."
(let ((addr (make-bytevector 12)))
(bytevector-u16-set! addr 0 family (native-endianness))
(bytevector-u32-set! addr 4 pid (native-endianness))
@@ -85,7 +76,7 @@
(define* (connect proto addr)
(let ((sock (open-socket proto)))
- (ffi-bind (socket-num sock)
+ (ffi-bind (fileno sock)
(bytevector->pointer addr)
12)
sock))
@@ -101,7 +92,7 @@
(let* ((len (data-size msg))
(bv (make-bytevector len)))
(serialize msg 0 bv)
- (ffi-sendto (socket-num sock) (bytevector->pointer bv) len 0 %null-pointer 0)))
+ (ffi-sendto (fileno sock) (bytevector->pointer bv) len 0 %null-pointer 0)))
(define* (receive-msg sock #:key (addr (get-addr AF_NETLINK 0 0)))
(let* ((len (* 1024 32))
@@ -111,7 +102,7 @@
iovec 1
%null-pointer 0
0))
- (size (ffi-recvmsg (socket-num sock) msghdr 0))
+ (size (ffi-recvmsg (fileno sock) msghdr 0))
(answer (make-bytevector size)))
(when (> size (* 1024 32))
(raise (condition (&netlink-answer-too-big-error (size size)))))
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 03/11] connection: Throw upon errors in FFI bindings.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-4-ludo@gnu.org
* netlink/connection.scm (syscall->procedure): New procedure.
(ffi-sendto, ffi-recvmsg, ffi-bind): Use it.
---
netlink/connection.scm | 35 ++++++++++++++++++++++++-----------
1 file changed, 24 insertions(+), 11 deletions(-)

Toggle diff (62 lines)
diff --git a/netlink/connection.scm b/netlink/connection.scm
index 6f41ef8..f4a5cc6 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -1,7 +1,8 @@
;;;; This file is part of Guile Netlink
;;;;
;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu>
-;;;;
+;;;; Copyright (C) 2023 Ludovic Courtès <ludo@gnu.org>
+;;;;
;;;; This library is free software: you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation, either version 3 of the License, or
@@ -24,6 +25,7 @@
#:use-module (system foreign)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
+ #:use-module (srfi srfi-71)
#:export (connect
connect-route
close-socket
@@ -34,16 +36,27 @@
(define libc (dynamic-link))
-(define ffi-sendto (pointer->procedure int
- (dynamic-func "sendto" libc)
- (list int '* size_t int '* int)
- #:return-errno? #t))
-(define ffi-recvmsg (pointer->procedure int
- (dynamic-func "recvmsg" libc)
- (list int '* int)))
-(define ffi-bind (pointer->procedure int
- (dynamic-func "bind" libc)
- (list int '* int)))
+(define (syscall->procedure return-type function
+ argument-types)
+ "Return a procedure that calls FUNCTION, a syscall wrapper from the C library
+with the given RETURN-TYPE and ARGUMENT-TYPES."
+ (let ((proc (pointer->procedure return-type
+ (dynamic-func function libc)
+ argument-types
+ #:return-errno? #t)))
+ (lambda args
+ (let ((ret errno (apply proc args)))
+ (when (< ret 0)
+ (throw 'system-error function "~A"
+ (list (strerror errno)) (list errno)))
+ ret))))
+
+(define ffi-sendto
+ (syscall->procedure int "sendto" (list int '* size_t int '* int)))
+(define ffi-recvmsg
+ (syscall->procedure int "recvmsg" (list int '* int)))
+(define ffi-bind
+ (syscall->procedure int "bind" (list int '* int)))
;; define simple functions to open/close sockets
(define (open-socket proto)
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 04/11] connection: Add support for suspendable sockets.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-5-ludo@gnu.org
* netlink/connection.scm (syscall->procedure): Add #:waiter.
Distinguish first argument and call WAITER upon EWOULDBLOCK or EAGAIN
when the first argument is a port.
(ffi-sendto, ffi-recvmsg, ffi-bind): Pass #:waiter.
(connect, send-msg, receive-msg): Pass SOCK instead of (fileno sock).
---
netlink/connection.scm | 45 ++++++++++++++++++++++++++++--------------
1 file changed, 30 insertions(+), 15 deletions(-)

Toggle diff (95 lines)
diff --git a/netlink/connection.scm b/netlink/connection.scm
index f4a5cc6..42f7dbb 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -26,6 +26,8 @@
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (srfi srfi-71)
+ #:autoload (ice-9 suspendable-ports) (current-read-waiter
+ current-write-waiter)
#:export (connect
connect-route
close-socket
@@ -36,27 +38,40 @@
(define libc (dynamic-link))
-(define (syscall->procedure return-type function
- argument-types)
+(define* (syscall->procedure return-type function
+ argument-types
+ #:key waiter)
"Return a procedure that calls FUNCTION, a syscall wrapper from the C library
-with the given RETURN-TYPE and ARGUMENT-TYPES."
+with the given RETURN-TYPE and ARGUMENT-TYPES. When WAITER is true and the
+first argument is a port, call it upon EAGAIN or EWOULDBLOCK."
(let ((proc (pointer->procedure return-type
(dynamic-func function libc)
argument-types
#:return-errno? #t)))
- (lambda args
- (let ((ret errno (apply proc args)))
- (when (< ret 0)
- (throw 'system-error function "~A"
- (list (strerror errno)) (list errno)))
- ret))))
+ (lambda (first . rest)
+ (let loop ()
+ (let ((ret errno (apply proc
+ (if (port? first) (fileno first) first)
+ rest)))
+ (if (< ret 0)
+ (if (and (memv errno (list EAGAIN EWOULDBLOCK))
+ (port? first) waiter)
+ (begin
+ ((waiter) first)
+ (loop))
+ (throw 'system-error function "~A"
+ (list (strerror errno)) (list errno)))
+ ret))))))
(define ffi-sendto
- (syscall->procedure int "sendto" (list int '* size_t int '* int)))
+ (syscall->procedure int "sendto" (list int '* size_t int '* int)
+ #:waiter (lambda () (current-write-waiter))))
(define ffi-recvmsg
- (syscall->procedure int "recvmsg" (list int '* int)))
+ (syscall->procedure int "recvmsg" (list int '* int)
+ #:waiter (lambda () (current-read-waiter))))
(define ffi-bind
- (syscall->procedure int "bind" (list int '* int)))
+ (syscall->procedure int "bind" (list int '* int)
+ #:waiter (lambda () (current-read-waiter))))
;; define simple functions to open/close sockets
(define (open-socket proto)
@@ -89,7 +104,7 @@ such as 'bind' cannot handle."
(define* (connect proto addr)
(let ((sock (open-socket proto)))
- (ffi-bind (fileno sock)
+ (ffi-bind sock
(bytevector->pointer addr)
12)
sock))
@@ -105,7 +120,7 @@ such as 'bind' cannot handle."
(let* ((len (data-size msg))
(bv (make-bytevector len)))
(serialize msg 0 bv)
- (ffi-sendto (fileno sock) (bytevector->pointer bv) len 0 %null-pointer 0)))
+ (ffi-sendto sock (bytevector->pointer bv) len 0 %null-pointer 0)))
(define* (receive-msg sock #:key (addr (get-addr AF_NETLINK 0 0)))
(let* ((len (* 1024 32))
@@ -115,7 +130,7 @@ such as 'bind' cannot handle."
iovec 1
%null-pointer 0
0))
- (size (ffi-recvmsg (fileno sock) msghdr 0))
+ (size (ffi-recvmsg sock msghdr 0))
(answer (make-bytevector size)))
(when (> size (* 1024 32))
(raise (condition (&netlink-answer-too-big-error (size size)))))
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 05/11] connection: Allow users to pass extra SOCK_ flags to 'socket'.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-6-ludo@gnu.org
In particular, this lets users pass SOCK_NONBLOCK.

* netlink/connection.scm (open-socket): Add 'flags' parameter and honor it.
(connect): Add #:flags and pass it to 'open-socket'.
(connect-route): Add #:flags and pass it to 'connect'.
* doc/guile-netlink.texi (Netlink Connections): Adjust accordingly.
---
doc/guile-netlink.texi | 11 +++++++++--
netlink/connection.scm | 13 +++++++------
2 files changed, 16 insertions(+), 8 deletions(-)

Toggle diff (72 lines)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index 48ca6d7..bdb20c6 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -240,7 +240,8 @@ to communicate or 0 for the kernel. @var{groups} is an integer representing
the set of broadcast groups to which the connection subscribes.
@end deffn
-@deffn {Scheme Procedure} connect @var{proto} @var{addr}
+@cindex non-blocking socket
+@deffn {Scheme Procedure} connect @var{proto} @var{addr} [#:flags 0]
Creates a netlink socket for @var{proto} and binds it to @var{addr}.
@var{proto} is the integer representing the protocol. For instance, rtnetlink
@@ -248,12 +249,18 @@ can be selected by usin @code{NETLINK_ROUTE} (defined in
@code{(netlink constant)}).
@var{addr} is a bytevector, as returned by @code{get-addr}.
+
+@var{flags} is a set of additional flags to pass as the second argument
+to the @code{socket} system call---e.g., @code{SOCK_NONBLOCK}.
@end deffn
-@deffn {Scheme Procedure} connect-route [#:groups @code{0}]
+@deffn {Scheme Procedure} connect-route [#:groups 0] [#:flags 0]
This procedure is a wrapper for @code{connect} that creates a socket for the
rtnetlink protocol, binds it to the kernel and returns it. By passing the
optional @var{groups} keyword, you can select broadcast groups to subscribe to.
+
+@var{flags} is a set of additional flags to pass as the second argument
+to the @code{socket} system call---e.g., @code{SOCK_NONBLOCK}.
@end deffn
@deffn {Scheme Procedure} send-msg @var{msg} @var{sock} [#:@var{addr}]
diff --git a/netlink/connection.scm b/netlink/connection.scm
index 42f7dbb..4ad9b10 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -74,8 +74,8 @@ first argument is a port, call it upon EAGAIN or EWOULDBLOCK."
#:waiter (lambda () (current-read-waiter))))
;; define simple functions to open/close sockets
-(define (open-socket proto)
- (socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC) proto))
+(define (open-socket proto flags)
+ (socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC flags) proto))
(define (close-socket sock)
(issue-deprecation-warning
@@ -102,15 +102,16 @@ such as 'bind' cannot handle."
(list '* size_t)
(list content size)))
-(define* (connect proto addr)
- (let ((sock (open-socket proto)))
+(define* (connect proto addr #:key (flags 0))
+ (let ((sock (open-socket proto flags)))
(ffi-bind sock
(bytevector->pointer addr)
12)
sock))
-(define* (connect-route #:key (groups 0))
- (connect NETLINK_ROUTE (get-addr AF_NETLINK 0 groups)))
+(define* (connect-route #:key (groups 0) (flags 0))
+ (connect NETLINK_ROUTE (get-addr AF_NETLINK 0 groups)
+ #:flags flags))
(define* (send-msg msg sock #:key (addr (get-addr AF_NETLINK 0 0)))
(unless (message? msg)
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 06/11] link: Extract 'new-link-message->link'.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-7-ludo@gnu.org
* ip/link.scm (new-link-message->link): New procedure.
(get-links): Use it, and use 'filter-map' instead of 'filter' followed
by 'map'.
---
ip/link.scm | 42 ++++++++++++++++++++----------------------
1 file changed, 20 insertions(+), 22 deletions(-)

Toggle diff (62 lines)
diff --git a/ip/link.scm b/ip/link.scm
index 814a008..7e0ae6b 100644
--- a/ip/link.scm
+++ b/ip/link.scm
@@ -59,6 +59,25 @@
(addr link-addr)
(brd link-brd))
+(define (new-link-message->link msg)
+ "If MSG has type 'RTM_NEWLINK', return the corresponding <link> object.
+Otherwise return #f."
+ (and (eqv? (message-kind msg) RTM_NEWLINK)
+ (let* ((data (message-data msg))
+ (attrs (link-message-attrs data)))
+ (make-link (get-attr attrs IFLA_IFNAME)
+ (link-message-index data)
+ (link-message-kind data)
+ (map int->device-flags (split-flags (link-message-flags data)))
+ (get-attr attrs IFLA_MTU)
+ (get-attr attrs IFLA_QDISC)
+ (get-attr attrs IFLA_OPERSTATE)
+ (get-attr attrs IFLA_LINKMODE)
+ (get-attr attrs IFLA_GROUP)
+ (get-attr attrs IFLA_TXQLEN)
+ (get-attr attrs IFLA_ADDRESS)
+ (get-attr attrs IFLA_BROADCAST)))))
+
(define (get-links)
(define request-num (random 65535))
(define message
@@ -72,28 +91,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let* ((answer (receive-and-decode-msg sock %default-route-decoder))
- (links (filter
- (lambda (msg) (equal? (message-kind msg) RTM_NEWLINK))
- answer))
- (links
- (map
- (lambda (msg)
- (let* ((data (message-data msg))
- (attrs (link-message-attrs data)))
- (make-link
- (get-attr attrs IFLA_IFNAME)
- (link-message-index data)
- (link-message-kind data)
- (map int->device-flags (split-flags (link-message-flags data)))
- (get-attr attrs IFLA_MTU)
- (get-attr attrs IFLA_QDISC)
- (get-attr attrs IFLA_OPERSTATE)
- (get-attr attrs IFLA_LINKMODE)
- (get-attr attrs IFLA_GROUP)
- (get-attr attrs IFLA_TXQLEN)
- (get-attr attrs IFLA_ADDRESS)
- (get-attr attrs IFLA_BROADCAST))))
- links)))
+ (links (filter-map new-link-message->link answer)))
(close-port sock)
links)))
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 07/11] addr: Extract 'new-address-message->address'.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-8-ludo@gnu.org
* ip/addr.scm (new-address-message->address): New procedure.
(get-addrs): Use it, and use 'filter-map' instead of 'filter' followed
by 'map'.
---
ip/addr.scm | 40 +++++++++++++++++++---------------------
1 file changed, 19 insertions(+), 21 deletions(-)

Toggle diff (60 lines)
diff --git a/ip/addr.scm b/ip/addr.scm
index fcb286f..f82d733 100644
--- a/ip/addr.scm
+++ b/ip/addr.scm
@@ -183,6 +183,24 @@
(close-port sock)
(answer-ok? (last answer)))))
+(define (new-address-message->address msg)
+ "If MSG has type 'RTM_NEWADDR', return the corresponding <addr> object.
+Otherwise return #f."
+ (and (eqv? (message-kind msg) RTM_NEWADDR)
+ (let* ((data (message-data msg))
+ (attrs (addr-message-attrs data)))
+ (make-addr (addr-message-family data)
+ (addr-message-prefix-len data)
+ (map int->ifa-flag
+ (split-flags (logior (addr-message-flags data)
+ (get-attr attrs IFA_FLAGS))))
+ (addr-message-scope data)
+ (addr-message-index data)
+ (get-attr attrs IFA_LABEL)
+ (get-attr attrs IFA_ADDRESS)
+ (get-attr attrs IFA_BROADCAST)
+ (get-attr attrs IFA_CACHEINFO)))))
+
(define (get-addrs)
(define request-num (random 65535))
(define message
@@ -195,27 +213,7 @@
(let ((sock (connect-route)))
(send-msg message sock)
(let* ((answer (receive-and-decode-msg sock %default-route-decoder))
- (addrs (filter
- (lambda (msg) (equal? (message-kind msg) RTM_NEWADDR))
- answer))
- (addrs (map
- (lambda (msg)
- (let* ((data (message-data msg))
- (attrs (addr-message-attrs data)))
- (make-addr
- (addr-message-family data)
- (addr-message-prefix-len data)
- (map
- int->ifa-flag
- (split-flags (logior (addr-message-flags data)
- (get-attr attrs IFA_FLAGS))))
- (addr-message-scope data)
- (addr-message-index data)
- (get-attr attrs IFA_LABEL)
- (get-attr attrs IFA_ADDRESS)
- (get-attr attrs IFA_BROADCAST)
- (get-attr attrs IFA_CACHEINFO))))
- addrs)))
+ (addrs (filter-map new-address-message->address answer)))
(close-port sock)
addrs)))
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 08/11] connection: Add 'add-socket-membership'.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-9-ludo@gnu.org
* netlink/connection.scm (socklen_t, ffi-setsockopt, SOL_NETLINK)
* netlink/connection.scm (NETLINK_ADD_MEMBERSHIP):
(NETLINK_DROP_MEMBERSHIP, NETLINK_PKTINFO)
(NETLINK_BROADCAST_ERROR, NETLINK_NO_ENOBUFS)
(NETLINK_LISTEN_ALL_NSID, NETLINK_LIST_MEMBERSHIPS)
(NETLINK_CAP_ACK, NETLINK_EXT_ACK, NETLINK_GET_STRICT_CHK): New
variables.
(add-socket-membership): New procedure.
* netlink/constant.scm (int->rtnetlink-group): New enum.
* doc/guile-netlink.texi (Netlink Connections): Document it.
---
doc/guile-netlink.texi | 18 ++++++++++++++++++
netlink/connection.scm | 26 ++++++++++++++++++++++++++
netlink/constant.scm | 40 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+)

Toggle diff (127 lines)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index bdb20c6..19db019 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -263,6 +263,24 @@ optional @var{groups} keyword, you can select broadcast groups to subscribe to.
to the @code{socket} system call---e.g., @code{SOCK_NONBLOCK}.
@end deffn
+@cindex subscribing, to an rtnetlink group
+@deffn {Scheme Procedure} add-socket-membership @var{sock} @var{group}
+Make @var{sock} a member of @var{group}, an @code{RTNLGRP_} constant,
+meaning that it will be subscribed to events of that group.
+
+For example, here is how you could create a netlink socket and subscribe
+it to the ``link'' group so that it receives notifications for new and
+removed links:
+
+@lisp
+(let ((sock (connect-route)))
+ (add-socket-membership sock RTNLGRP_LINK)
+ @dots{})
+@end lisp
+
+This procedure is implemented as a @code{setsockopt} call.
+@end deffn
+
@deffn {Scheme Procedure} send-msg @var{msg} @var{sock} [#:@var{addr}]
Send @var{msg} (it must be of type message, @xref{Netlink Headers}) to
@var{addr} using @var{sock}. If not passed, @var{addr} is the address of
diff --git a/netlink/connection.scm b/netlink/connection.scm
index 4ad9b10..1b6e1c5 100644
--- a/netlink/connection.scm
+++ b/netlink/connection.scm
@@ -30,6 +30,7 @@
current-write-waiter)
#:export (connect
connect-route
+ add-socket-membership
close-socket
send-msg
receive-msg
@@ -73,10 +74,35 @@ first argument is a port, call it upon EAGAIN or EWOULDBLOCK."
(syscall->procedure int "bind" (list int '* int)
#:waiter (lambda () (current-read-waiter))))
+(define socklen_t uint32) ;per <posix/bits/types.h>
+(define ffi-setsockopt
+ (syscall->procedure int "setsockopt" (list int int int '* socklen_t)))
+
+(define SOL_NETLINK 270)
+
+(define NETLINK_ADD_MEMBERSHIP 1)
+(define NETLINK_DROP_MEMBERSHIP 2)
+(define NETLINK_PKTINFO 3)
+(define NETLINK_BROADCAST_ERROR 4)
+(define NETLINK_NO_ENOBUFS 5)
+(define NETLINK_LISTEN_ALL_NSID 8)
+(define NETLINK_LIST_MEMBERSHIPS 9)
+(define NETLINK_CAP_ACK 10)
+(define NETLINK_EXT_ACK 11)
+(define NETLINK_GET_STRICT_CHK 12)
+
;; define simple functions to open/close sockets
(define (open-socket proto flags)
(socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC flags) proto))
+(define (add-socket-membership sock group)
+ "Make @var{sock} a member of @var{group}, an @code{RTNLGRP_} constant,
+meaning that it will be subscribed to events of that group."
+ (let ((bv (make-bytevector (sizeof int))))
+ (bytevector-uint-set! bv 0 group (native-endianness) (sizeof int))
+ (ffi-setsockopt sock SOL_NETLINK NETLINK_ADD_MEMBERSHIP
+ (bytevector->pointer bv) (bytevector-length bv))))
+
(define (close-socket sock)
(issue-deprecation-warning
"'close-socket' is deprecated; use 'close-port' instead.")
diff --git a/netlink/constant.scm b/netlink/constant.scm
index e7a681e..02c905a 100644
--- a/netlink/constant.scm
+++ b/netlink/constant.scm
@@ -345,3 +345,43 @@
(define-enum int->link-type
(ARPHRD_ETHER 1)
(ARPHRD_LOOPBACK 772))
+
+;; enum rtnetlink_groups
+(define-enum int->rtnetlink-group
+ (RTNLGRP_NONE 0)
+ RTNLGRP_LINK
+ RTNLGRP_NOTIFY
+ RTNLGRP_NEIGH
+ RTNLGRP_TC
+ RTNLGRP_IPV4_IFADDR
+ RTNLGRP_IPV4_MROUTE
+ RTNLGRP_IPV4_ROUTE
+ RTNLGRP_IPV4_RULE
+ RTNLGRP_IPV6_IFADDR
+ RTNLGRP_IPV6_MROUTE
+ RTNLGRP_IPV6_ROUTE
+ RTNLGRP_IPV6_IFINFO
+ RTNLGRP_DECnet_IFADDR
+ RTNLGRP_NOP2
+ RTNLGRP_DECnet_ROUTE
+ RTNLGRP_DECnet_RULE
+ RTNLGRP_NOP4
+ RTNLGRP_IPV6_PREFIX
+ RTNLGRP_IPV6_RULE
+ RTNLGRP_ND_USEROPT
+ RTNLGRP_PHONET_IFADDR
+ RTNLGRP_PHONET_ROUTE
+ RTNLGRP_DCB
+ RTNLGRP_IPV4_NETCONF
+ RTNLGRP_IPV6_NETCONF
+ RTNLGRP_MDB
+ RTNLGRP_MPLS_ROUTE
+ RTNLGRP_NSID
+ RTNLGRP_MPLS_NETCONF
+ RTNLGRP_IPV4_MROUTE_R
+ RTNLGRP_IPV6_MROUTE_R
+ RTNLGRP_NEXTHOP
+ RTNLGRP_BRVLAN
+ RTNLGRP_MCTP_IFADDR
+ RTNLGRP_TUNNEL
+ RTNLGRP_STATS)
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 09/11] error: Add 'sub-type' field to '&netlink-decoder-error' and use it.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-10-ludo@gnu.org
* netlink/error.scm (&netlink-decoder-error)[sub-type]: New field.
* netlink/data.scm (get-next-deserialize, get-current-deserialize): Fill
it out.
---
netlink/data.scm | 13 +++++++++----
netlink/error.scm | 4 +++-
2 files changed, 12 insertions(+), 5 deletions(-)

Toggle diff (53 lines)
diff --git a/netlink/data.scm b/netlink/data.scm
index c9b5fb8..ac95051 100644
--- a/netlink/data.scm
+++ b/netlink/data.scm
@@ -51,15 +51,20 @@
(match (assoc-ref decoder current-type)
((_ . type-alist)
(or (assoc-ref type-alist target-type)
- (assoc-ref type-alist 'default)))
+ (assoc-ref type-alist 'default)
+ (raise (condition (&netlink-decoder-error
+ (type current-type)
+ (sub-type target-type))))))
(#f (raise (condition (&netlink-decoder-error
- (type current-type)))))))
-
+ (type current-type)
+ (sub-type target-type)))))))
+
(define (get-current-deserialize decoder current-type)
(match (assoc-ref decoder current-type)
((current-deserialize . _) current-deserialize)
(#f (raise (condition (&netlink-decoder-error
- (type current-type)))))))
+ (type current-type)
+ (sub-type #f)))))))
(define (deserialize type decoder bv pos)
(let ((deserialize (get-current-deserialize decoder type)))
diff --git a/netlink/error.scm b/netlink/error.scm
index 3e101ed..fa1dba6 100644
--- a/netlink/error.scm
+++ b/netlink/error.scm
@@ -23,6 +23,7 @@
&netlink-decoder-error
netlink-decoder-error?
netlink-decoder-error-type
+ netlink-decoder-error-sub-type
&netlink-family-error
netlink-family-error?
@@ -57,7 +58,8 @@
;; No decoder for type
(define-condition-type &netlink-decoder-error &netlink-error
netlink-decoder-error?
- (type netlink-decoder-error-type))
+ (type netlink-decoder-error-type)
+ (sub-type netlink-decoder-error-sub-type))
;; Unknown protocol family
(define-condition-type &netlink-family-error &netlink-error
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 10/11] doc: Add indexes.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-11-ludo@gnu.org
* doc/guile-netlink.texi (Concept Index, Programming Index): New nodes.
---
doc/guile-netlink.texi | 14 ++++++++++++++
1 file changed, 14 insertions(+)

Toggle diff (32 lines)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index 19db019..4dbeafe 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -34,6 +34,9 @@ implementation of the netlink protocol.
* API Reference:: Description of the library interface.
* IP Library:: High-level functions for network devices.
+* Concept Index:: Concepts.
+* Programming Index:: Data types, procedures, and variables.
+
@detailmenu
--- The Detailed Node Listing ---
@@ -795,4 +798,15 @@ number of routes displayed, you can specify the family as in this example.
@end example
@end deffn
+@c *********************************************************************
+@node Concept Index
+@unnumbered Concept Index
+@printindex cp
+
+@node Programming Index
+@unnumbered Programming Index
+@syncodeindex tp fn
+@syncodeindex vr fn
+@printindex fn
+
@bye
--
2.40.1
L
L
Ludovic Courtès wrote on 23 May 2023 14:39
[PATCH Guile-Netlink 11/11] link: Add 'wait-for-link'.
(address . 63516@debbugs.gnu.org)(name . Ludovic Courtès)(address . ludo@gnu.org)
20230523123951.6225-12-ludo@gnu.org
* ip/link.scm (message->event+link): New procedure.
(new-link-message->link): Use it.
(monitor-links, wait-for-link): New procedures.
* doc/guile-netlink.texi (Link): Document 'wait-for-link'.
---
doc/guile-netlink.texi | 8 ++++
ip/link.scm | 102 ++++++++++++++++++++++++++++++++++-------
2 files changed, 94 insertions(+), 16 deletions(-)

Toggle diff (161 lines)
diff --git a/doc/guile-netlink.texi b/doc/guile-netlink.texi
index 4dbeafe..3355c27 100644
--- a/doc/guile-netlink.texi
+++ b/doc/guile-netlink.texi
@@ -567,6 +567,14 @@ Returns the list of existing links in the system, as a list of @code{<link>}
objects.
@end deffn
+@deffn {Scheme Procedure} wait-for-link @var{name} [#:blocking? #t]
+Wait until a link called @var{name} (a string such as @code{"ens3"}) shows
+up.
+
+When @var{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers.
+@end deffn
+
@deffn {Sceme Procedure} print-link @var{link}
Display @var{link} on the standard output, using a format similar to
@command{ip link} from @code{iproute2}.
diff --git a/ip/link.scm b/ip/link.scm
index 7e0ae6b..1323444 100644
--- a/ip/link.scm
+++ b/ip/link.scm
@@ -1,7 +1,8 @@
;;;; This file is part of Guile Netlink
;;;;
;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu>
-;;;;
+;;;; Copyright (C) 2023 Ludovic Courtès <ludo@gnu.org>
+;;;;
;;;; This library is free software: you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation, either version 3 of the License, or
@@ -31,12 +32,14 @@
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
+ #:use-module (srfi srfi-71)
#:export (link-add
link-del
link-set
link-show
link-name->index
get-links
+ wait-for-link
print-link
<link> make-link link?
@@ -59,24 +62,35 @@
(addr link-addr)
(brd link-brd))
+(define (message->event+link msg)
+ "If MSG relates to a link event, return two values: its kind (e.g.,
+RTM_NEWLINK) and its associated <link> value. Otherwise return #f and #f."
+ (if (memv (message-kind msg)
+ (list RTM_NEWLINK
+ RTM_DELLINK
+ RTM_SETLINK))
+ (values (message-kind msg)
+ (let* ((data (message-data msg))
+ (attrs (link-message-attrs data)))
+ (make-link (get-attr attrs IFLA_IFNAME)
+ (link-message-index data)
+ (link-message-kind data)
+ (map int->device-flags (split-flags (link-message-flags data)))
+ (get-attr attrs IFLA_MTU)
+ (get-attr attrs IFLA_QDISC)
+ (get-attr attrs IFLA_OPERSTATE)
+ (get-attr attrs IFLA_LINKMODE)
+ (get-attr attrs IFLA_GROUP)
+ (get-attr attrs IFLA_TXQLEN)
+ (get-attr attrs IFLA_ADDRESS)
+ (get-attr attrs IFLA_BROADCAST))))
+ (values #f #f)))
+
(define (new-link-message->link msg)
"If MSG has type 'RTM_NEWLINK', return the corresponding <link> object.
Otherwise return #f."
- (and (eqv? (message-kind msg) RTM_NEWLINK)
- (let* ((data (message-data msg))
- (attrs (link-message-attrs data)))
- (make-link (get-attr attrs IFLA_IFNAME)
- (link-message-index data)
- (link-message-kind data)
- (map int->device-flags (split-flags (link-message-flags data)))
- (get-attr attrs IFLA_MTU)
- (get-attr attrs IFLA_QDISC)
- (get-attr attrs IFLA_OPERSTATE)
- (get-attr attrs IFLA_LINKMODE)
- (get-attr attrs IFLA_GROUP)
- (get-attr attrs IFLA_TXQLEN)
- (get-attr attrs IFLA_ADDRESS)
- (get-attr attrs IFLA_BROADCAST)))))
+ (let ((kind link (message->event+link msg)))
+ (and (eqv? kind RTM_NEWLINK) link)))
(define (get-links)
(define request-num (random 65535))
@@ -390,3 +404,59 @@ balance-rr|active-backup|balance-xor|broadcast|802.3ad|balance-tlb|balance-alb"
(let ((answer (receive-and-decode-msg sock %default-route-decoder)))
(close-port sock)
(answer-ok? (last answer)))))
+
+(define* (monitor-links proc init terminate? ;TODO: Make public?
+ #:key (blocking? #t))
+ "Wait for link events until @var{terminate?} returns true. Call @var{init}
+with the initial list of links; use its result as the initial state. From
+then on, call @code{(@var{proc} @var{event} @var{link} @var{state})} where
+@var{event} is a constant such as @code{RTM_NEWLINK} and @var{link} is the
+corresponding link. Return the final state.
+
+When @code{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers."
+ (define request-num (random 65536))
+ (define message
+ (make-message
+ RTM_GETLINK
+ (logior NLM_F_REQUEST NLM_F_DUMP)
+ request-num
+ 0
+ (make-link-message AF_UNSPEC 0 0 0 0 '())))
+
+ (let ((sock (connect-route #:flags (if blocking? 0 SOCK_NONBLOCK))))
+ ;; Subscribe to the "link" group.
+ (add-socket-membership sock RTNLGRP_LINK)
+
+ (send-msg message sock)
+ (let* ((answer (receive-and-decode-msg sock %default-route-decoder))
+ (links (filter-map new-link-message->link answer)))
+ (let loop ((state (init links)))
+ (if (terminate? state)
+ (begin
+ (close-port sock)
+ state)
+ (loop (fold (lambda (msg state)
+ (let ((event link (message->event+link msg)))
+ (proc event link state)))
+ state
+ (receive-and-decode-msg sock %default-route-decoder))))))))
+
+
+(define* (wait-for-link name #:key (blocking? #t))
+ "Wait until a link called @var{name} (a string such as @code{\"ens3\"}) shows
+up.
+
+When @var{blocking?} is false, use a non-blocking socket and cooperate via
+@code{current-read-waiter}---useful when using Fibers."
+ (monitor-links (lambda (event link result)
+ (and (= RTM_NEWLINK)
+ (string=? (link-name link) name)
+ link))
+ (lambda (links)
+ (find (lambda (link)
+ (string=? (link-name link) name))
+ links))
+ (lambda (link) ;if LINK is true, terminate
+ link)
+ #:blocking? blocking?))
--
2.40.1
J
J
Julien Lepiller wrote on 23 May 2023 20:53
Re: bug#63516: [PATCH Guile-Netlink 00/11] Add 'wait-for-link' and related code
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 63516@debbugs.gnu.org)
20230523205304.52d3c06a@sybil.lepiller.eu
Thanks, I was able to test it simply by doing something like
(wait-for-link "veth0") and from another terminal, "ip l add veth0 type
veth peer veth1" (it doesn't have to be veth, it's the first one I
thought of that I didn't have to reach the manual for).

Pushed to guile-netlink's master :)

Le Tue, 23 May 2023 14:39:40 +0200,
Ludovic Courtès <ludo@gnu.org> a écrit :

Toggle quote (46 lines)
> Hi Julien,
>
> As a followup to <https://issues.guix.gnu.org/63516>, here is code
> that lets us wait for a link to show up “the right way”—i.e., without
> polling. It works over SOCK_NONBLOCK sockets, for use in Fibers
> programs.
>
> I tested it in a VM created with ‘guix system vm’. If the “ens3”
> device is already there, (wait-for-link "ens3") returns immediately.
> Then I ran “rmmod e1000” to make the device disappear, and made
> another (wait-for-link "ens3") call: that call returns once I’ve run
> “modprobe e1000” in another terminal. Wonderful. :-)
>
> Now, it would be good to have a test suite that can run without
> complicated setups. We should check the strategy used by libnl,
> systemd, and the likes.
>
> Thoughts?
>
> Ludo’.
>
> Ludovic Courtès (11):
> connection: Remove unused procedure.
> connection: Use Guile's 'socket' procedure to open a socket.
> connection: Throw upon errors in FFI bindings.
> connection: Add support for suspendable sockets.
> connection: Allow users to pass extra SOCK_ flags to 'socket'.
> link: Extract 'new-link-message->link'.
> addr: Extract 'new-address-message->address'.
> connection: Add 'add-socket-membership'.
> error: Add 'sub-type' field to '&netlink-decoder-error' and use it.
> doc: Add indexes.
> link: Add 'wait-for-link'.
>
> doc/guile-netlink.texi | 51 +++++++++++++++--
> ip/addr.scm | 46 +++++++--------
> ip/link.scm | 122 ++++++++++++++++++++++++++++++---------
> ip/route.scm | 6 +-
> netlink/connection.scm | 126
> +++++++++++++++++++++++++++-------------- netlink/constant.scm |
> 40 +++++++++++++ netlink/data.scm | 13 +++--
> netlink/error.scm | 4 +-
> 8 files changed, 303 insertions(+), 105 deletions(-)
>
>
> base-commit: beceb4cfea4739954e558411f46e07425891c774
L
L
Ludovic Courtès wrote on 24 May 2023 16:55
(name . Julien Lepiller)(address . julien@lepiller.eu)(address . 63516@debbugs.gnu.org)
87jzwxixs3.fsf@gnu.org
Hello,

Julien Lepiller <julien@lepiller.eu> skribis:

Toggle quote (5 lines)
> Thanks, I was able to test it simply by doing something like
> (wait-for-link "veth0") and from another terminal, "ip l add veth0 type
> veth peer veth1" (it doesn't have to be veth, it's the first one I
> thought of that I didn't have to reach the manual for).

Neat (I really need to take modern networking class :-)).

Toggle quote (2 lines)
> Pushed to guile-netlink's master :)

That was fast, thanks a lot!

Are you planning to tag a release soonish? If you do, we could use
‘wait-for-link’ to fix https://issues.guix.gnu.org/63516.

Ludo’.
J
J
Julien Lepiller wrote on 24 May 2023 17:12
Re: bug#63516: [PATCH Guile-Netlink 00/ 11] Add 'wait-for-link' and related code
(name . Ludovic Courtès)(address . ludo@gnu.org)(address . 63516@debbugs.gnu.org)
84FCC1C4-E32C-40C9-BC19-EE309EAC520D@lepiller.eu
I'll probably tag a release this week-end.

Le 24 mai 2023 16:55:56 GMT+02:00, "Ludovic Courtès" <ludo@gnu.org> a écrit :
Toggle quote (19 lines)
>Hello,
>
>Julien Lepiller <julien@lepiller.eu> skribis:
>
>> Thanks, I was able to test it simply by doing something like
>> (wait-for-link "veth0") and from another terminal, "ip l add veth0 type
>> veth peer veth1" (it doesn't have to be veth, it's the first one I
>> thought of that I didn't have to reach the manual for).
>
>Neat (I really need to take modern networking class :-)).
>
>> Pushed to guile-netlink's master :)
>
>That was fast, thanks a lot!
>
>Are you planning to tag a release soonish? If you do, we could use
>‘wait-for-link’ to fix <https://issues.guix.gnu.org/63516>.
>
>Ludo’.
L
L
Ludovic Courtès wrote on 14 Jun 2023 23:53
Re: bug#63516: Static networking should wait for interfaces to be up
(address . 63516-done@debbugs.gnu.org)(name . Julien Lepiller)(address . julien@lepiller.eu)
878rclpv6w.fsf@gnu.org
Ludovic Courtès <ludo@gnu.org> skribis:

Toggle quote (14 lines)
> Ludovic Courtès <ludovic.courtes@inria.fr> skribis:
>
>> Before doing ‘addr-add’ in ‘network-set-up/linux’, should we wait for
>> the interface to show up, by calling ‘get-links’ from Guile-Netlink or
>> something like that?
>
> Below is a simple workaround. How does that sound?
>
> A better fix would be to poll(2) on the underlying AF_NETLINK socket.
> In fact, we could also implement something like systemd’s
> ‘network-online.target’ by doing that. For that we’d need Guile-Netlink
> to let us create SOCK_NONBLOCK sockets and to use real ports instead of
> raw file descriptors; Fibers would then take care of the rest.

Pushed the “better fix” as 26602f4063a6e0c626e8deb3423166bcd0abeb90,
building upon ‘wait-for-link’ from Guile-Netlink 1.2.

Thank you Julien for the Guile-Netlink release!

Ludo’.
Closed
?