link.scm
1 | ;;;; Copyright (C) 2021 Julien Lepiller <julien@lepiller.eu> |
2 | ;;;; |
3 | ;;;; This library is free software; you can redistribute it and/or |
4 | ;;;; modify it under the terms of the GNU Lesser General Public |
5 | ;;;; License as published by the Free Software Foundation; either |
6 | ;;;; version 3 of the License, or (at your option) any later version. |
7 | ;;;; |
8 | ;;;; This library is distributed in the hope that it will be useful, |
9 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
11 | ;;;; Lesser General Public License for more details. |
12 | ;;;; |
13 | ;;;; You should have received a copy of the GNU Lesser General Public |
14 | ;;;; License along with this library; if not, write to the Free Software |
15 | ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
16 | ;;;; |
17 | |
18 | (define-module (ip link) |
19 | #:use-module (ice-9 match) |
20 | #:use-module (netlink route attrs) |
21 | #:use-module (netlink route link) |
22 | #:use-module (netlink connection) |
23 | #:use-module (netlink constant) |
24 | #:use-module (netlink data) |
25 | #:use-module (netlink deserialize) |
26 | #:use-module (netlink message) |
27 | #:use-module (netlink standard) |
28 | #:use-module (srfi srfi-1) |
29 | #:use-module (srfi srfi-9) |
30 | #:export (link-add |
31 | link-del |
32 | link-set |
33 | link-show)) |
34 | |
35 | (define-record-type <link> |
36 | (make-link name id type flags mtu qdisc state mode group qlen addr brd) |
37 | link? |
38 | (name link-name) |
39 | (id link-id) |
40 | (type link-type) |
41 | (flags link-flags) |
42 | (mtu link-mtu) |
43 | (qdisc link-qdisc) |
44 | (state link-state) |
45 | (mode link-mode) |
46 | (group link-group) |
47 | (qlen link-qlen) |
48 | (addr link-addr) |
49 | (brd link-brd)) |
50 | |
51 | (define (get-attr attrs type) |
52 | (let ((attrs (filter (lambda (attr) (equal? (route-attr-kind attr) type)) attrs))) |
53 | (match attrs |
54 | (() #f) |
55 | ((attr) (nl-data-data (route-attr-data attr)))))) |
56 | |
57 | (define (split-flags flags) |
58 | (let loop ((max-flag 262144) (flags flags) (result '())) |
59 | (cond |
60 | ((equal? max-flag 1) |
61 | (if (equal? flags 1) |
62 | (cons (int->device-flags 1) result) |
63 | result)) |
64 | ((< flags max-flag) |
65 | (loop (/ max-flag 2) flags result)) |
66 | (else |
67 | (loop (/ max-flag 2) (- flags max-flag) |
68 | (cons |
69 | (int->device-flags max-flag) |
70 | result)))))) |
71 | |
72 | (define (get-links) |
73 | (define request-num (random 65535)) |
74 | (define message |
75 | (make-message |
76 | RTM_GETLINK |
77 | (logior NLM_F_REQUEST NLM_F_DUMP) |
78 | request-num |
79 | 0 |
80 | (make-link-message AF_UNSPEC 0 0 0 0 '()))) |
81 | |
82 | (let ((sock (connect-route))) |
83 | (send-msg message sock) |
84 | (let* ((answer (receive-and-decode-msg sock %default-route-decoder)) |
85 | (links (filter |
86 | (lambda (msg) (equal? (message-kind msg) RTM_NEWLINK)) |
87 | answer)) |
88 | (links |
89 | (map |
90 | (lambda (msg) |
91 | (let* ((data (message-data msg)) |
92 | (attrs (link-message-attrs data))) |
93 | (make-link |
94 | (get-attr attrs IFLA_IFNAME) |
95 | (link-message-index data) |
96 | (link-message-kind data) |
97 | (split-flags (link-message-flags data)) |
98 | (get-attr attrs IFLA_MTU) |
99 | (get-attr attrs IFLA_QDISC) |
100 | (get-attr attrs IFLA_OPERSTATE) |
101 | (get-attr attrs IFLA_LINKMODE) |
102 | (get-attr attrs IFLA_GROUP) |
103 | (get-attr attrs IFLA_TXQLEN) |
104 | (get-attr attrs IFLA_ADDRESS) |
105 | (get-attr attrs IFLA_BROADCAST)))) |
106 | links))) |
107 | (close-socket sock) |
108 | links))) |
109 | |
110 | (define* (link-show #:key (device #f) (group #f) (up #f) (master #f) (vrf #f) |
111 | (type #f)) |
112 | "Return a list whose elements represent the data about the links. If a key |
113 | is given, the resulting list is limited to those elements that match the given |
114 | criteria." |
115 | (define print-link |
116 | (match-lambda |
117 | (($ <link> name id type flags mtu qdisc state mode group qlen addr brd) |
118 | (format #t "~a: ~a: <~a>" id name |
119 | (string-join |
120 | (map |
121 | (lambda (s) |
122 | ;; IFF_UP -> UP |
123 | (substring (symbol->string s) 4)) |
124 | flags) |
125 | ",")) |
126 | (when mtu |
127 | (format #t " mtu ~a" mtu)) |
128 | (when qdisc |
129 | (format #t " qdisc ~a" qdisc)) |
130 | (when state |
131 | (format #t " state ~a" |
132 | (substring (symbol->string (int->operstate state)) 8))) |
133 | (when mode |
134 | (format #t " mode ~a" (match mode (0 "DEFAULT") (1 "DORMANT")))) |
135 | (when group |
136 | (format #t " group ~a" (match group (0 "DEFAULT")))) |
137 | (when qlen |
138 | (format #t " qlen ~a" qlen)) |
139 | (newline) |
140 | (cond |
141 | ((equal? type ARPHRD_ETHER) |
142 | (format #t " link/ether ~a brd ~a~%" addr brd)) |
143 | ((equal? type ARPHRD_LOOPBACK) |
144 | (format #t " link/loopback ~a brd ~a~%" addr brd)))))) |
145 | |
146 | (for-each |
147 | (lambda (link) |
148 | (match link |
149 | (($ <link> lname lid ltype lflags lmtu lqdisc lstate lmode lgroup lqlen laddr lbrd) |
150 | (when (and (or (not device) (equal? device lname)) |
151 | (or (not group) (equal? group lgroup)) |
152 | (or (not up) (member 'IFF_UP lflags)) |
153 | ;(or (not master) ()) |
154 | ;(or (not vrf) ()) |
155 | (or (not type) (equal? type ltype))) |
156 | (print-link link))))) |
157 | (get-links))) |
158 | |
159 | (define (link-name->index device) |
160 | (let loop ((links (get-links))) |
161 | (match links |
162 | (() (throw 'no-such-device device)) |
163 | ((link links ...) |
164 | (if (equal? (link-name link) device) |
165 | (link-id link) |
166 | (loop links)))))) |
167 | |
168 | (define (answer-ok? answer) |
169 | (cond |
170 | ((equal? (message-kind answer) NLMSG_DONE) |
171 | #t) |
172 | ((equal? (message-kind answer) NLMSG_ERROR) |
173 | (let ((data (message-data answer))) |
174 | (if (nl-data-data data) |
175 | (let ((err (error-message-err data))) |
176 | (if (equal? err 0) |
177 | #t |
178 | (begin |
179 | (format #t "RTNETLINK answers: ~a~%" (strerror (- err))) |
180 | #f))) |
181 | #f))))) |
182 | |
183 | (define* (link-set device #:key (up #f) (down #f) (type #f) |
184 | (arp-on #f) (arp-off #f) |
185 | (dynamic-on #f) (dynamic-off #f) |
186 | (multicast-on #f) (multicast-off #f) |
187 | (allmulticast-on #f) (allmulticast-off #f) |
188 | (promisc-on #f) (promisc-off #f) |
189 | (trailers-on #f) (trailers-off #f) |
190 | (carrier-on #f) (carrier-off #f) |
191 | (txqueuelen #f) (name #f) (address #f) |
192 | (broadcast #f) (mtu #f) (netns #f)) |
193 | (define request-num (random 65535)) |
194 | (define id (if (number? device) device (link-name->index device))) |
195 | (define netnsfd (cond |
196 | ((string? netns) |
197 | (open (string-append "/var/run/netns/" netns) O_RDONLY)) |
198 | ((number? netns) |
199 | (open (string-append "/var/run/netns/" (number->string netns)) |
200 | O_RDONLY)) |
201 | (else |
202 | #f))) |
203 | (define message |
204 | (make-message |
205 | RTM_NEWLINK |
206 | (logior NLM_F_REQUEST NLM_F_ACK) |
207 | request-num |
208 | 0 |
209 | (make-link-message |
210 | AF_UNSPEC |
211 | (or type 0) |
212 | id |
213 | (+ (if up IFF_UP 0) |
214 | (if arp-off IFF_NOARP 0) |
215 | (if dynamic-on IFF_DYNAMIC 0) |
216 | (if multicast-on IFF_MULTICAST 0) |
217 | (if allmulticast-on IFF_ALLMULTI 0) |
218 | (if promisc-on IFF_PROMISC 0) |
219 | (if trailers-off IFF_NOTRAILERS 0)) |
220 | (+ (if (or up down) IFF_UP 0) |
221 | (if (or arp-on arp-off) IFF_NOARP 0) |
222 | (if (or dynamic-on dynamic-off) IFF_DYNAMIC 0) |
223 | (if (or multicast-on multicast-off) IFF_MULTICAST 0) |
224 | (if (or allmulticast-on allmulticast-off) IFF_ALLMULTI 0) |
225 | (if (or promisc-on promisc-off) IFF_PROMISC 0) |
226 | (if (or trailers-on trailers-off) IFF_NOTRAILERS 0)) |
227 | `(,@(if (or carrier-on carrier-off) |
228 | (list |
229 | (make-route-attr IFLA_CARRIER |
230 | (make-u32-route-attr (if carrier-on 1 0)))) |
231 | '()) |
232 | ,@(if txqueuelen |
233 | (list |
234 | (make-route-attr IFLA_TXQLEN |
235 | (make-u32-route-attr txqueuelen))) |
236 | '()) |
237 | ,@(if name |
238 | (list |
239 | (make-route-attr IFLA_TXQLEN |
240 | (make-u32-route-attr txqueuelen))) |
241 | '()) |
242 | ,@(if address |
243 | (list |
244 | (make-route-attr IFLA_ADDRESS |
245 | (make-ethernet-route-attr address))) |
246 | '()) |
247 | ,@(if broadcast |
248 | (list |
249 | (make-route-attr IFLA_BROADCAST |
250 | (make-ethernet-route-attr broadcast))) |
251 | '()) |
252 | ,@(if mtu |
253 | (list |
254 | (make-route-attr IFLA_MTU |
255 | (make-u32-route-attr mtu))) |
256 | '()) |
257 | ,@(if netns |
258 | (list |
259 | (make-route-attr IFLA_NET_NS_FD |
260 | (make-u32-route-attr |
261 | (fileno netnsfd)))) |
262 | '()))))) |
263 | (let ((sock (connect-route))) |
264 | (send-msg message sock) |
265 | (let ((answer (receive-and-decode-msg sock %default-route-decoder))) |
266 | (when netnsfd |
267 | (close netnsfd)) |
268 | (close-socket sock) |
269 | (answer-ok? (last answer))))) |
270 | |
271 | (define* (link-add name type #:key (type-args '())) |
272 | (define request-num (random 65535)) |
273 | (define type-data |
274 | (match type |
275 | ("vlan" |
276 | `(,@(if (assoc-ref type-args 'id) |
277 | (list (make-route-attr IFLA_VLAN_ID |
278 | (make-u16-route-attr (assoc-ref type-args 'id)))) |
279 | '()))) |
280 | ("veth" |
281 | `(,@(if (assoc-ref type-args 'peer) |
282 | (list (make-route-attr VETH_INFO_PEER |
283 | (make-link-message |
284 | AF_UNSPEC 0 0 0 0 |
285 | (list |
286 | (make-route-attr IFLA_IFNAME |
287 | (make-string-route-attr |
288 | (assoc-ref type-args 'peer))))))) |
289 | '()))) |
290 | ;; TODO: unsupported for now |
291 | (_ '()))) |
292 | (define message |
293 | (make-message |
294 | RTM_NEWLINK |
295 | (logior NLM_F_REQUEST NLM_F_ACK NLM_F_EXCL NLM_F_CREATE) |
296 | request-num |
297 | 0 |
298 | (make-link-message |
299 | AF_UNSPEC |
300 | 0 |
301 | 0 |
302 | 0 |
303 | 0 |
304 | (list |
305 | (make-route-attr IFLA_IFNAME |
306 | (make-string-route-attr name)) |
307 | (make-route-attr IFLA_LINKINFO |
308 | (make-nested-route-attr |
309 | (list |
310 | (make-route-attr IFLA_INFO_KIND |
311 | (make-string-route-attr type)) |
312 | (make-route-attr IFLA_INFO_DATA |
313 | (make-nested-route-attr type-data))))))))) |
314 | (let ((sock (connect-route))) |
315 | (send-msg message sock) |
316 | (let ((answer (receive-and-decode-msg sock %default-route-decoder))) |
317 | (close-socket sock) |
318 | (answer-ok? (last answer))))) |
319 | |
320 | (define* (link-del device) |
321 | (define request-num (random 65535)) |
322 | |
323 | (define message |
324 | (make-message |
325 | RTM_DELLINK |
326 | (logior NLM_F_REQUEST NLM_F_ACK) |
327 | request-num |
328 | 0 |
329 | (make-link-message |
330 | AF_UNSPEC |
331 | 0 |
332 | (cond |
333 | ((number? device) device) |
334 | ((string? device) (link-name->index device))) |
335 | 0 |
336 | 0 |
337 | '()))) |
338 | |
339 | |
340 | (let ((sock (connect-route))) |
341 | (send-msg message sock) |
342 | (let ((answer (receive-and-decode-msg sock %default-route-decoder))) |
343 | (close-socket sock) |
344 | (answer-ok? (last answer))))) |
345 |