aboutsummaryrefslogtreecommitdiff
path: root/package/network/services/ppp/patches/142-pppd-Add-support-for-registering-ppp-interface-via-L.patch
blob: 9987d3dce9d15e23dfd48e515a77eb5a118abfd3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
From 4a54e34cf5629f9fed61f0b7d69ee3ba4d874bc6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pali=20Roh=C3=A1r?= <pali@kernel.org>
Date: Sat, 9 Jul 2022 13:40:24 +0200
Subject: [PATCH] pppd: Add support for registering ppp interface via Linux
 rtnetlink API
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

pppd currently creates ppp network interface via PPPIOCNEWUNIT ioctl API.
This API creates a new ppp network interface named "ppp<unit_id>". If user
supply option "ifname" with custom network name then pppd calls SIOCSIFNAME
ioctl to rename "ppp<unit_id>" to custom name immediately after successful
PPPIOCNEWUNIT ioctl call. If custom name is already registered then
SIOCSIFNAME ioctl fails and pppd close current channel (which destroy also
network interface).

This has side effect that in the first few miliseconds interface has
different name as what user supplied.

Tools like systemd, udev or NetworkManager are trying to query
interface attributes based on interface name immediately when new
network interface is created.

But if interface is renamed immediately after creation then these tools
fails. For example when running pppd with option "ifname ppp-wan" following
error is reported by systemd / udev into dmesg log:

    [   35.718732] PPP generic driver version 2.4.2
    [   35.793914] NET: Registered protocol family 24
    [   35.889924] systemd-udevd[1852]: link_config: autonegotiation is unset or enabled, the speed and duplex are not writable.
    [   35.901450] ppp-wan: renamed from ppp0
    [   35.930332] systemd-udevd[1852]: link_config: could not get ethtool features for ppp0
    [   35.939473] systemd-udevd[1852]: Could not set offload features of ppp0: No such device

There is an easy way to fix this issue: Use new rtnetlink API.

Via rtnetlink API it is possible to create ppp network interface with
custom ifname atomically. Just it is not possible to specify custom ppp
unit id.

So use new rtnetlink API when user requested custom ifname without custom
ppp unit id. This will avoid system issues with interface renaming as ppp
interface is directly registered with specified final name.

This has also advantage that if requested interface name already exists
then pppd fail during registering of networking interface and not during
renaming network interface which happens after successful registration.

If user supply custom ppp unit id then it is required to use old ioctl API
as currently it is the only API which allows specifying ppp unit id.

When user does not specify custom ifname stay also with old ioctl API.
There is currently a bug in kernel which cause that when empty interface is
specified in rtnetlink message for creating ppp interface then kernel
creates ppp interface but with pseudo-random name, not derived from ppp
unit id. And therefore it is not possible to retrieve what is the name of
newly created network interface. So when user does not specify interface
name via "ifname" option (which means that want from kernel to choose some
"free" interface name) it is needed to use old ioctl API which do it
correctly for now.

Signed-off-by: Pali Rohár <pali@kernel.org>
---
 pppd/sys-linux.c | 194 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 192 insertions(+), 2 deletions(-)

--- a/pppd/sys-linux.c
+++ b/pppd/sys-linux.c
@@ -126,6 +126,11 @@
 #include <linux/netlink.h>
 #include <linux/rtnetlink.h>
 #include <linux/if_link.h>
+
+#ifdef INET6
+#include <linux/if_addr.h>
+#endif
+
 /* Attempt at retaining compile-support with older than 4.7 kernels, or kernels
  * where RTM_NEWSTATS isn't defined for whatever reason.
  */
@@ -135,16 +140,20 @@
 #define IFLA_STATS_LINK_64 1
 #endif
 
-#ifdef INET6
-#include <linux/if_addr.h>
 /* glibc versions prior to 2.24 do not define SOL_NETLINK */
 #ifndef SOL_NETLINK
 #define SOL_NETLINK 270
 #endif
+
 /* linux kernel versions prior to 4.3 do not define/support NETLINK_CAP_ACK */
 #ifndef NETLINK_CAP_ACK
 #define NETLINK_CAP_ACK 10
 #endif
+
+/* linux kernel versions prior to 4.7 do not define/support IFLA_PPP_DEV_FD */
+#ifndef IFLA_PPP_MAX
+/* IFLA_PPP_DEV_FD is declared as enum when IFLA_PPP_MAX is defined */
+#define IFLA_PPP_DEV_FD 1
 #endif
 
 #include "pppd.h"
@@ -657,6 +666,160 @@ void generic_disestablish_ppp(int dev_fd
 }
 
 /*
+ * make_ppp_unit_rtnetlink - register a new ppp network interface for ppp_dev_fd
+ * with specified req_ifname via rtnetlink. Interface name req_ifname must not
+ * be empty. Custom ppp unit id req_unit is ignored and kernel choose some free.
+ */
+static int make_ppp_unit_rtnetlink(void)
+{
+    struct {
+        struct nlmsghdr nlh;
+        struct ifinfomsg ifm;
+        struct {
+            struct rtattr rta;
+            char ifname[IFNAMSIZ];
+        } ifn;
+        struct {
+            struct rtattr rta;
+            struct {
+                struct rtattr rta;
+                char ifkind[sizeof("ppp")];
+            } ifik;
+            struct {
+                struct rtattr rta;
+                struct {
+                    struct rtattr rta;
+                    union {
+                        int ppp_dev_fd;
+                    } ppp;
+                } ifdata[1];
+            } ifid;
+        } ifli;
+    } nlreq;
+    struct {
+        struct nlmsghdr nlh;
+        struct nlmsgerr nlerr;
+    } nlresp;
+    struct sockaddr_nl nladdr;
+    struct iovec iov;
+    struct msghdr msg;
+    ssize_t nlresplen;
+    int one;
+    int fd;
+
+    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (fd < 0) {
+        error("make_ppp_unit_rtnetlink: socket(NETLINK_ROUTE): %m (line %d)", __LINE__);
+        return 0;
+    }
+
+    /* Tell kernel to not send to us payload of acknowledgment error message. */
+    one = 1;
+    setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &one, sizeof(one));
+
+    memset(&nladdr, 0, sizeof(nladdr));
+    nladdr.nl_family = AF_NETLINK;
+
+    if (bind(fd, (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) {
+        error("make_ppp_unit_rtnetlink: bind(AF_NETLINK): %m (line %d)", __LINE__);
+        close(fd);
+        return 0;
+    }
+
+    memset(&nlreq, 0, sizeof(nlreq));
+    nlreq.nlh.nlmsg_len = sizeof(nlreq);
+    nlreq.nlh.nlmsg_type = RTM_NEWLINK;
+    nlreq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE;
+    nlreq.ifm.ifi_family = AF_UNSPEC;
+    nlreq.ifm.ifi_type = ARPHRD_NETROM;
+    nlreq.ifn.rta.rta_len = sizeof(nlreq.ifn);
+    nlreq.ifn.rta.rta_type = IFLA_IFNAME;
+    strlcpy(nlreq.ifn.ifname, req_ifname, sizeof(nlreq.ifn.ifname));
+    nlreq.ifli.rta.rta_len = sizeof(nlreq.ifli);
+    nlreq.ifli.rta.rta_type = IFLA_LINKINFO;
+    nlreq.ifli.ifik.rta.rta_len = sizeof(nlreq.ifli.ifik);
+    nlreq.ifli.ifik.rta.rta_type = IFLA_INFO_KIND;
+    strcpy(nlreq.ifli.ifik.ifkind, "ppp");
+    nlreq.ifli.ifid.rta.rta_len = sizeof(nlreq.ifli.ifid);
+    nlreq.ifli.ifid.rta.rta_type = IFLA_INFO_DATA;
+    nlreq.ifli.ifid.ifdata[0].rta.rta_len = sizeof(nlreq.ifli.ifid.ifdata[0]);
+    nlreq.ifli.ifid.ifdata[0].rta.rta_type = IFLA_PPP_DEV_FD;
+    nlreq.ifli.ifid.ifdata[0].ppp.ppp_dev_fd = ppp_dev_fd;
+
+    memset(&nladdr, 0, sizeof(nladdr));
+    nladdr.nl_family = AF_NETLINK;
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_base = &nlreq;
+    iov.iov_len = sizeof(nlreq);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &nladdr;
+    msg.msg_namelen = sizeof(nladdr);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    if (sendmsg(fd, &msg, 0) < 0) {
+        error("make_ppp_unit_rtnetlink: sendmsg(RTM_NEWLINK/NLM_F_CREATE): %m (line %d)", __LINE__);
+        close(fd);
+        return 0;
+    }
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_base = &nlresp;
+    iov.iov_len = sizeof(nlresp);
+
+    memset(&msg, 0, sizeof(msg));
+    msg.msg_name = &nladdr;
+    msg.msg_namelen = sizeof(nladdr);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    nlresplen = recvmsg(fd, &msg, 0);
+
+    if (nlresplen < 0) {
+        error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): %m (line %d)", __LINE__);
+        close(fd);
+        return 0;
+    }
+
+    close(fd);
+
+    if (nladdr.nl_family != AF_NETLINK) {
+        error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not a netlink packet (line %d)", __LINE__);
+        return 0;
+    }
+
+    if ((size_t)nlresplen < sizeof(nlresp) || nlresp.nlh.nlmsg_len < sizeof(nlresp)) {
+        error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Acknowledgment netlink packet too short (line %d)", __LINE__);
+        return 0;
+    }
+
+    /* acknowledgment packet for NLM_F_ACK is NLMSG_ERROR */
+    if (nlresp.nlh.nlmsg_type != NLMSG_ERROR) {
+        error("make_ppp_unit_rtnetlink: recvmsg(NLM_F_ACK): Not an acknowledgment netlink packet (line %d)", __LINE__);
+        return 0;
+    }
+
+    /* error == 0 indicates success, negative value is errno code */
+    if (nlresp.nlerr.error != 0) {
+        /*
+         * Linux kernel versions prior to 4.7 do not support creating ppp
+         * interfaces via rtnetlink API and therefore error response is
+         * expected. On older kernel versions do not show this error message.
+         * When error is different than EEXIST then pppd tries to fallback to
+         * the old ioctl method.
+         */
+        errno = (nlresp.nlerr.error < 0) ? -nlresp.nlerr.error : EINVAL;
+        if (kernel_version >= KVERSION(4,7,0))
+            error("Couldn't create ppp interface %s: %m", req_ifname);
+        return 0;
+    }
+
+    return 1;
+}
+
+/*
  * make_ppp_unit - make a new ppp unit for ppp_dev_fd.
  * Assumes new_style_driver.
  */
@@ -676,6 +839,33 @@ static int make_ppp_unit(void)
 	    || fcntl(ppp_dev_fd, F_SETFL, flags | O_NONBLOCK) == -1)
 		warn("Couldn't set /dev/ppp to nonblock: %m");
 
+	/*
+	 * Via rtnetlink it is possible to create ppp network interface with
+	 * custom ifname atomically. But it is not possible to specify custom
+	 * ppp unit id.
+	 *
+	 * Tools like systemd, udev or NetworkManager are trying to query
+	 * interface attributes based on interface name immediately when new
+	 * network interface is created. And therefore immediate interface
+	 * renaming is causing issues.
+	 *
+	 * So use rtnetlink API only when user requested custom ifname. It will
+	 * avoid system issues with interface renaming.
+	 */
+	if (req_unit == -1 && req_ifname[0] != '\0' && kernel_version >= KVERSION(2,1,16)) {
+	    if (make_ppp_unit_rtnetlink()) {
+		if (ioctl(ppp_dev_fd, PPPIOCGUNIT, &ifunit))
+		    fatal("Couldn't retrieve PPP unit id: %m");
+		return 0;
+	    }
+	    /*
+	     * If interface with requested name already exist return error
+	     * otherwise fallback to old ioctl method.
+	     */
+	    if (errno == EEXIST)
+		return -1;
+	}
+
 	ifunit = req_unit;
 	x = ioctl(ppp_dev_fd, PPPIOCNEWUNIT, &ifunit);
 	if (x < 0 && req_unit >= 0 && errno == EEXIST) {