Add route high-level API

Julien LepillerSun Mar 14 16:50:33+0100 2021

b748606

Add route high-level API

Makefile.am

1414
  netlink/route/route.scm \
1515
  ip/addr.scm \
1616
  ip/link.scm \
17+
  ip/route.scm \
1718
  ip/utils.scm
1819
1920
info_TEXINFOS= doc/guile-netlink.texi

doc/guile-netlink.texi

4747
IP Library
4848
4949
* Link::                   Actions on network links.
50+
* Addr::                   Actions on network addresses.
51+
* Route::                  Actions on network routes.
5052
5153
@end detailmenu
5254
@end menu

614616
(addr-add "enp1s0" "192.0.2.15/24")
615617
@end example
616618
617-
If you wish to remove an IPv6 address instead, set @code{#:ipv6} to @code{#t},
619+
If you wish to add an IPv6 address instead, set @code{#:ipv6} to @code{#t},
618620
as in the following example.
619621
620622
@example

653655
to addresses of that device.
654656
@end deffn
655657
658+
@node Route
659+
@section Route
660+
661+
The @code{(ip route)} module introduces procedures to access and modify the
662+
network routes on your machine.  They are equivalent to the @command{ip route}
663+
family of commands, from @code{iproute2}.
664+
665+
@deffn {Scheme Procedure} route-add @var{dest} [@var{#:ipv6?} #f] @
666+
    [@var{#:device} #f] [@var{#:table} RT_TABLE_MAIN] [@var{#:protocol} #f] @
667+
    [@var{#:scope} RT_SCOPE_LINK] [@var{#:type} RTN_UNICAST] @
668+
    [@var{#:priority} #f] [@var{#:src} #f] [@var{#:via} #f]
669+
Add the route described by the argmuents.  @var{dest} is the destination network,
670+
in cidr notation (@code{addr/prefix}) or the string @code{"default"}.
671+
672+
@var{#:device} is the name or index of a network link. @var{#:table} is the
673+
index of a routing table, one of @code{RT_TABLE_COMPAT}, @code{RT_TABLE_DEFAULT},
674+
@code{RT_TABLE_MAIN} or @code{RT_TABLE_LOCAL}, as defined in
675+
@code{(netlink constant)}.
676+
677+
If it is set, @var{#:protocol} must be the routing protocol, @code{RTPROT_*},
678+
as defined in @code{(netlink constant)}.
679+
680+
@var{#:scope} must be the scope of the route, one of @code{RT_SCOPE_*}, as
681+
defined in @code{(netlink constant)}.
682+
683+
@var{#:type} must be the type of route, one of @code{RTN_*}, as defined in
684+
@code{(netlink constant)}.
685+
686+
If set, @var{#:priority} is a number specifying the priority of the rule
687+
when the kernel is looking for a matching rule.  This is also known as the
688+
metric of the route.
689+
690+
If set, @var{#:src} is the source address in cidr notation, or as a single
691+
address.
692+
693+
If set, @var{#:via} is the gateway address.  This is not in cidr notation, as
694+
the gateway is a single address, not a network.
695+
696+
@example
697+
(route-add "default" #:device "enp1s0" #:via "192.0.2.1")
698+
(route-add "192.0.2.0/24" #:device "enp1s0" #:src "192.0.2.15")
699+
@end example
700+
701+
If you wish to add an IPv6 route instead, set @code{#:ipv6} to @code{#t},
702+
as in the following example.
703+
704+
@example
705+
(addr-add "2001:db8::/64" #:device "enp1s0" #:src "2001:db8::1a4c" #:ipv6? #t)
706+
@end example
707+
708+
Note that using the wrong ip type with the wrong value for the @code{#:ipv6?}
709+
flag will result in a @code{Bad address} exception from inet-pton.
710+
@end deffn
711+
712+
@deffn {Scheme Procedure} route-del @var{dest} [@var{#:ipv6?} #f] @
713+
    [@var{#:device} #f] [@var{#:table} RT_TABLE_MAIN] [@var{#:protocol} #f] @
714+
    [@var{#:scope} #f] [@var{#:type} #f] [@var{#:priority} #f] @
715+
    [@var{#:src} #f] [@var{#:via} #f]
716+
Delete the route given in arguments.  The arguments follow the same structure
717+
as @code{route-add}.  By specifying more arguments, you can narrow down the
718+
search for the rule to delete further.  Each call will only remove one route,
719+
so being more precise ensures you target the rule you wish to delete.  It
720+
is not clear which route is deleted if multiple routes match your query.
721+
@end deffn
722+
723+
@deffn {Scheme Procedure} route-show [@var{#:table} RT_TABLE_MAIN] @
724+
    [@var{#:family} AF_UNSPEC]
725+
Print the list of routes on standard output.  Note that, contrary to
726+
@command{ip route show}, we show both IPv4 and IPv6 routes.  To narrow down the
727+
number of routes displayed, you can specify the family as in this example.
728+
729+
@example
730+
(route-show #:family AF_INET6)
731+
@end example
732+
@end deffn
733+
656734
@bye

ip/route.scm unknown status 1

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 route)
19+
  #:use-module (ice-9 match)
20+
  #:use-module (ip link)
21+
  #:use-module (ip utils)
22+
  #:use-module (netlink route route)
23+
  #:use-module (netlink route attrs)
24+
  #:use-module (netlink connection)
25+
  #:use-module (netlink constant)
26+
  #:use-module (netlink deserialize)
27+
  #:use-module (netlink message)
28+
  #:use-module (netlink standard)
29+
  #:use-module (srfi srfi-1)
30+
  #:use-module (srfi srfi-9)
31+
  #:export (route-add
32+
            route-del
33+
            route-show))
34+
35+
(define-record-type <route>
36+
  (make-route family table dest src gateway proto scope priority device)
37+
  route?
38+
  (family   route-family)
39+
  (table    route-table)
40+
  (dest     route-dest)
41+
  (src      route-src)
42+
  (gateway  route-gateway)
43+
  (proto    route-proto)
44+
  (scope    route-scope)
45+
  (priority route-priority)
46+
  (device   route-device))
47+
48+
(define* (route-del dest
49+
                    #:key (ipv6? #f) (device #f) (table RT_TABLE_MAIN)
50+
                          (protocol #f) (scope RT_SCOPE_NOWHERE) (type #f)
51+
                          (priority #f) (src #f) (via #f))
52+
  (define request-num (random 65535))
53+
54+
  (define index
55+
    (cond
56+
      ((number? device) device)
57+
      ((string? device) (link-name->index device))
58+
      (else #f)))
59+
60+
  (define message
61+
    (make-message
62+
      RTM_DELROUTE
63+
      (logior NLM_F_REQUEST NLM_F_ACK)
64+
      request-num
65+
      0
66+
      (make-route-message
67+
        (if ipv6? AF_INET6 AF_INET)
68+
        (if (equal? dest "default") 0 (cidr->prefix dest))
69+
        (if src (cidr->prefix src) 0)
70+
        0
71+
        table
72+
        (or protocol 0)
73+
        scope
74+
        (or type 0)
75+
        0
76+
        `(,@(if (equal? dest "default")
77+
                '()
78+
                (list (make-route-attr RTA_DST
79+
                        ((if ipv6?
80+
                             make-ipv6-route-attr
81+
                             make-ipv4-route-attr)
82+
                         (cidr->addr dest)))))
83+
          ,@(if index
84+
                (list (make-route-attr RTA_OIF
85+
                        (make-u32-route-attr index)))
86+
                '())
87+
          ,@(if src
88+
                (list (make-route-attr RTA_PREFSRC
89+
                        ((if ipv6?
90+
                             make-ipv6-route-attr
91+
                             make-ipv4-route-attr)
92+
                         (cidr->addr src))))
93+
                '())
94+
          ,@(if via
95+
                (list (make-route-attr RTA_GATEWAY
96+
                        ((if ipv6?
97+
                             make-ipv6-route-attr
98+
                             make-ipv4-route-attr)
99+
                         via)))
100+
                '())
101+
          ,@(if priority
102+
                (list (make-route-attr RTA_PRIORITY
103+
                        (make-u32-route-attr priority)))
104+
                '())))))
105+
106+
  (let ((sock (connect-route)))
107+
    (send-msg message sock)
108+
    (let ((answer (receive-and-decode-msg sock %default-route-decoder)))
109+
      (close-socket sock)
110+
      (answer-ok? (last answer)))))
111+
112+
(define* (route-add dest
113+
                    #:key (ipv6? #f) (device #f) (table RT_TABLE_MAIN)
114+
                          (protocol #f) (scope RT_SCOPE_LINK)
115+
                          (type RTN_UNICAST) (priority #f) (src #f) (via #f))
116+
  (define request-num (random 65535))
117+
118+
  (define index
119+
    (cond
120+
      ((number? device) device)
121+
      ((string? device) (link-name->index device))
122+
      (else #f)))
123+
124+
  (define message
125+
    (make-message
126+
      RTM_NEWROUTE
127+
      (logior NLM_F_REQUEST NLM_F_ACK NLM_F_EXCL NLM_F_CREATE)
128+
      request-num
129+
      0
130+
      (make-route-message
131+
        (if ipv6? AF_INET6 AF_INET)
132+
        (if (equal? dest "default") 0 (cidr->prefix dest))
133+
        (if src (cidr->prefix src) 0)
134+
        0
135+
        table
136+
        (or protocol 0)
137+
        scope
138+
        type
139+
        0
140+
        `(,@(if (equal? dest "default")
141+
                '()
142+
                (list (make-route-attr RTA_DST
143+
                        ((if ipv6?
144+
                             make-ipv6-route-attr
145+
                             make-ipv4-route-attr)
146+
                         (cidr->addr dest)))))
147+
          ,@(if index
148+
                (list (make-route-attr RTA_OIF
149+
                        (make-u32-route-attr index)))
150+
                '())
151+
          ,@(if src
152+
                (list (make-route-attr RTA_PREFSRC
153+
                        ((if ipv6?
154+
                             make-ipv6-route-attr
155+
                             make-ipv4-route-attr)
156+
                         (cidr->addr src))))
157+
                '())
158+
          ,@(if via
159+
                (list (make-route-attr RTA_GATEWAY
160+
                        ((if ipv6?
161+
                             make-ipv6-route-attr
162+
                             make-ipv4-route-attr)
163+
                         via)))
164+
                '())
165+
          ,@(if priority
166+
                (list (make-route-attr RTA_PRIORITY
167+
                        (make-u32-route-attr priority)))
168+
                '())))))
169+
170+
  (let ((sock (connect-route)))
171+
    (send-msg message sock)
172+
    (let ((answer (receive-and-decode-msg sock %default-route-decoder)))
173+
      (close-socket sock)
174+
      (answer-ok? (last answer)))))
175+
176+
(define (link-ref links id)
177+
  (let loop ((links links))
178+
    (match links
179+
      (() #f)
180+
      ((link links ...)
181+
       (if (equal? (link-id link) id)
182+
           link
183+
           (loop links))))))
184+
185+
(define (get-routes links)
186+
  (define request-num (random 65535))
187+
  (define message
188+
    (make-message
189+
      RTM_GETROUTE
190+
      (logior NLM_F_REQUEST NLM_F_DUMP)
191+
      request-num
192+
      0
193+
      (make-route-message AF_UNSPEC 0 0 0 0 0 0 0 0 '())))
194+
  (let ((sock (connect-route)))
195+
    (send-msg message sock)
196+
    (let* ((answer (receive-and-decode-msg sock %default-route-decoder))
197+
           (routes (filter
198+
                     (lambda (msg) (equal? (message-kind msg) RTM_NEWROUTE))
199+
                     answer))
200+
           (routes (map
201+
                     (lambda (msg)
202+
                       (let* ((data (message-data msg))
203+
                              (attrs (route-message-attrs data)))
204+
                         (make-route
205+
                           (route-message-family data)
206+
                           (or (get-attr attrs RTA_TABLE)
207+
                               (route-message-table data))
208+
                           (let ((len (route-message-dest-len data))
209+
                                 (dest (get-attr attrs RTA_DST)))
210+
                             (if (or (equal? len 0) (not dest))
211+
                                 #f
212+
                                 (string-append dest "/" (number->string len))))
213+
                           (let ((len (route-message-src-len data))
214+
                                 (src (get-attr attrs RTA_PREFSRC)))
215+
                             (if (or (equal? len 0) (not src))
216+
                                 #f
217+
                                 (string-append src "/" (number->string len))))
218+
                           (get-attr attrs RTA_GATEWAY)
219+
                           (route-message-protocol data)
220+
                           (route-message-scope data)
221+
                           (get-attr attrs RTA_PRIORITY)
222+
                           (link-ref links (get-attr attrs RTA_OIF)))))
223+
                     routes)))
224+
      (close-socket sock)
225+
      routes)))
226+
227+
(define print-route
228+
  (match-lambda
229+
    (($ <route> family table dest src gateway proto scope priority device)
230+
     (format #t "    ~a"
231+
             (or dest "default"))
232+
     (when gateway
233+
       (format #t " via ~a" gateway))
234+
     (when device
235+
       (format #t " dev ~a" (link-name device)))
236+
     (when (and proto (> proto 0))
237+
       (format #t " proto ~a"
238+
               (string-downcase
239+
                 (substring (symbol->string (int->rtm-protocol proto)) 7))))
240+
     (when (and scope (> scope 0))
241+
       (format #t " scope ~a"
242+
               (string-downcase
243+
                 (substring (symbol->string (int->rtm-scope scope)) 9))))
244+
     (when src
245+
       (format #t " src ~a" src))
246+
     (when priority
247+
       (format #t " metric ~a" priority))
248+
     (format #t "~%"))))
249+
250+
251+
(define* (route-show #:key (table RT_TABLE_MAIN) (family AF_UNSPEC))
252+
  (define links (get-links))
253+
  (define routes (get-routes links))
254+
255+
  (for-each
256+
    (lambda (route)
257+
      (when (and (equal? (route-table route) table)
258+
                 (or (equal? family AF_UNSPEC)
259+
                     (equal? (route-family route) family)))
260+
        (print-route route)))
261+
    routes))