connection.scm
1 | ;;;; This file is part of Guile Netlink |
2 | ;;;; |
3 | ;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu> |
4 | ;;;; Copyright (C) 2023 Ludovic Courtès <ludo@gnu.org> |
5 | ;;;; |
6 | ;;;; This library is free software: you can redistribute it and/or modify |
7 | ;;;; it under the terms of the GNU General Public License as published by |
8 | ;;;; the Free Software Foundation, either version 3 of the License, or |
9 | ;;;; (at your option) any later version. |
10 | ;;;; |
11 | ;;;; This library is distributed in the hope that it will be useful, |
12 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | ;;;; GNU General Public License for more details. |
15 | ;;;; |
16 | ;;;; You should have received a copy of the GNU General Public License |
17 | ;;;; along with this library. If not, see <https://www.gnu.org/licenses/>. |
18 | |
19 | (define-module (netlink connection) |
20 | #:use-module (netlink constant) |
21 | #:use-module (netlink data) |
22 | #:use-module (netlink error) |
23 | #:use-module (netlink message) |
24 | #:use-module (rnrs bytevectors) |
25 | #:use-module (system foreign) |
26 | #:use-module (srfi srfi-34) |
27 | #:use-module (srfi srfi-35) |
28 | #:use-module (srfi srfi-71) |
29 | #:autoload (ice-9 suspendable-ports) (current-read-waiter |
30 | current-write-waiter) |
31 | #:export (connect |
32 | connect-route |
33 | add-socket-membership |
34 | close-socket |
35 | send-msg |
36 | receive-msg |
37 | receive-and-decode-msg |
38 | get-addr)) |
39 | |
40 | (define libc (dynamic-link)) |
41 | |
42 | (define* (syscall->procedure return-type function |
43 | argument-types |
44 | #:key waiter) |
45 | "Return a procedure that calls FUNCTION, a syscall wrapper from the C library |
46 | with the given RETURN-TYPE and ARGUMENT-TYPES. When WAITER is true and the |
47 | first argument is a port, call it upon EAGAIN or EWOULDBLOCK." |
48 | (let ((proc (pointer->procedure return-type |
49 | (dynamic-func function libc) |
50 | argument-types |
51 | #:return-errno? #t))) |
52 | (lambda (first . rest) |
53 | (let loop () |
54 | (let ((ret errno (apply proc |
55 | (if (port? first) (fileno first) first) |
56 | rest))) |
57 | (if (< ret 0) |
58 | (if (and (memv errno (list EAGAIN EWOULDBLOCK)) |
59 | (port? first) waiter) |
60 | (begin |
61 | ((waiter) first) |
62 | (loop)) |
63 | (throw 'system-error function "~A" |
64 | (list (strerror errno)) (list errno))) |
65 | ret)))))) |
66 | |
67 | (define ffi-sendto |
68 | (syscall->procedure int "sendto" (list int '* size_t int '* int) |
69 | #:waiter (lambda () (current-write-waiter)))) |
70 | (define ffi-recvmsg |
71 | (syscall->procedure int "recvmsg" (list int '* int) |
72 | #:waiter (lambda () (current-read-waiter)))) |
73 | (define ffi-bind |
74 | (syscall->procedure int "bind" (list int '* int) |
75 | #:waiter (lambda () (current-read-waiter)))) |
76 | |
77 | (define socklen_t uint32) ;per <posix/bits/types.h> |
78 | (define ffi-setsockopt |
79 | (syscall->procedure int "setsockopt" (list int int int '* socklen_t))) |
80 | |
81 | (define SOL_NETLINK 270) |
82 | |
83 | (define NETLINK_ADD_MEMBERSHIP 1) |
84 | (define NETLINK_DROP_MEMBERSHIP 2) |
85 | (define NETLINK_PKTINFO 3) |
86 | (define NETLINK_BROADCAST_ERROR 4) |
87 | (define NETLINK_NO_ENOBUFS 5) |
88 | (define NETLINK_LISTEN_ALL_NSID 8) |
89 | (define NETLINK_LIST_MEMBERSHIPS 9) |
90 | (define NETLINK_CAP_ACK 10) |
91 | (define NETLINK_EXT_ACK 11) |
92 | (define NETLINK_GET_STRICT_CHK 12) |
93 | |
94 | ;; define simple functions to open/close sockets |
95 | (define (open-socket proto flags) |
96 | (socket AF_NETLINK (logior SOCK_RAW SOCK_CLOEXEC flags) proto)) |
97 | |
98 | (define (add-socket-membership sock group) |
99 | "Make @var{sock} a member of @var{group}, an @code{RTNLGRP_} constant, |
100 | meaning that it will be subscribed to events of that group." |
101 | (let ((bv (make-bytevector (sizeof int)))) |
102 | (bytevector-uint-set! bv 0 group (native-endianness) (sizeof int)) |
103 | (ffi-setsockopt sock SOL_NETLINK NETLINK_ADD_MEMBERSHIP |
104 | (bytevector->pointer bv) (bytevector-length bv)))) |
105 | |
106 | (define (close-socket sock) |
107 | (issue-deprecation-warning |
108 | "'close-socket' is deprecated; use 'close-port' instead.") |
109 | (close-port sock)) |
110 | |
111 | (define (get-addr family pid groups) |
112 | "This is a variant of 'make-socket-address' for AF_NETLINK sockets. The |
113 | main difference is that it returns a raw bytevector that libguile procedures |
114 | such as 'bind' cannot handle." |
115 | (let ((addr (make-bytevector 12))) |
116 | (bytevector-u16-set! addr 0 family (native-endianness)) |
117 | (bytevector-u32-set! addr 4 pid (native-endianness)) |
118 | (bytevector-u32-set! addr 8 groups (native-endianness)) |
119 | addr)) |
120 | |
121 | (define (get-msghdr name namelen iov iovlen control controllen flags) |
122 | (make-c-struct |
123 | (list '* size_t '* size_t '* size_t int) |
124 | (list name namelen iov iovlen control controllen flags))) |
125 | |
126 | (define (get-iovec content size) |
127 | (make-c-struct |
128 | (list '* size_t) |
129 | (list content size))) |
130 | |
131 | (define* (connect proto addr #:key (flags 0)) |
132 | (let ((sock (open-socket proto flags))) |
133 | (ffi-bind sock |
134 | (bytevector->pointer addr) |
135 | 12) |
136 | sock)) |
137 | |
138 | (define* (connect-route #:key (groups 0) (flags 0)) |
139 | (connect NETLINK_ROUTE (get-addr AF_NETLINK 0 groups) |
140 | #:flags flags)) |
141 | |
142 | (define* (send-msg msg sock #:key (addr (get-addr AF_NETLINK 0 0))) |
143 | (unless (message? msg) |
144 | (raise (condition (&netlink-message-type-error |
145 | (message msg))))) |
146 | |
147 | (let* ((len (data-size msg)) |
148 | (bv (make-bytevector len))) |
149 | (serialize msg 0 bv) |
150 | (ffi-sendto sock (bytevector->pointer bv) len 0 %null-pointer 0))) |
151 | |
152 | (define* (receive-msg sock #:key (addr (get-addr AF_NETLINK 0 0))) |
153 | (let* ((len (* 1024 32)) |
154 | (bv (make-bytevector len)) |
155 | (iovec (get-iovec (bytevector->pointer bv) len)) |
156 | (msghdr (get-msghdr (bytevector->pointer addr) (bytevector-length addr) |
157 | iovec 1 |
158 | %null-pointer 0 |
159 | 0)) |
160 | (size (ffi-recvmsg sock msghdr 0)) |
161 | (answer (make-bytevector size))) |
162 | (when (> size (* 1024 32)) |
163 | (raise (condition (&netlink-answer-too-big-error (size size))))) |
164 | (when (> size 0) |
165 | (bytevector-copy! bv 0 answer 0 size)) |
166 | answer)) |
167 | |
168 | (define* (receive-and-decode-msg sock decoder |
169 | #:key (addr (get-addr AF_NETLINK 0 0))) |
170 | (let* ((answer (receive-msg sock #:addr addr)) |
171 | (size (bytevector-length answer))) |
172 | (let loop ((messages '()) (pos 0)) |
173 | (if (>= pos size) |
174 | (let ((last-message (car messages))) |
175 | (if (and |
176 | (equal? (logand (message-flags last-message) NLM_F_MULTI) |
177 | NLM_F_MULTI) |
178 | (> (message-kind last-message) NLMSG_OVERUN)) |
179 | (append (reverse messages) |
180 | (receive-and-decode-msg sock decoder #:addr addr)) |
181 | (reverse messages))) |
182 | (let ((message (deserialize 'message decoder answer pos))) |
183 | (loop (cons message messages) |
184 | (+ (data-size message) pos))))))) |
185 |