Add route high-level API
Makefile.am
| 14 | 14 | netlink/route/route.scm \ | |
| 15 | 15 | ip/addr.scm \ | |
| 16 | 16 | ip/link.scm \ | |
| 17 | + | ip/route.scm \ | |
| 17 | 18 | ip/utils.scm | |
| 18 | 19 | ||
| 19 | 20 | info_TEXINFOS= doc/guile-netlink.texi |
doc/guile-netlink.texi
| 47 | 47 | IP Library | |
| 48 | 48 | ||
| 49 | 49 | * Link:: Actions on network links. | |
| 50 | + | * Addr:: Actions on network addresses. | |
| 51 | + | * Route:: Actions on network routes. | |
| 50 | 52 | ||
| 51 | 53 | @end detailmenu | |
| 52 | 54 | @end menu | |
… | |||
| 614 | 616 | (addr-add "enp1s0" "192.0.2.15/24") | |
| 615 | 617 | @end example | |
| 616 | 618 | ||
| 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}, | |
| 618 | 620 | as in the following example. | |
| 619 | 621 | ||
| 620 | 622 | @example | |
… | |||
| 653 | 655 | to addresses of that device. | |
| 654 | 656 | @end deffn | |
| 655 | 657 | ||
| 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 | + | ||
| 656 | 734 | @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)) |