guile-netlink/netlink/connection.scm

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