guile-netlink/ip/link.scm

link.scm

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