1#include <err.h>
2#include <errno.h>
3#include <limits.h>
4#include <stdbool.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <unistd.h>
9#include <semaphore.h>
10#include <pthread.h>
11
12#include <sys/ioctl.h>
13#include <sys/types.h>
14
15#include <net/if.h>
16#include <libmnl/libmnl.h>
17#include <linux/if.h>
18#include <linux/if_link.h>
19#include <linux/rtnetlink.h>
20
21/* Timeout after 30s by default, set to zero to wait indefinitely. */
22#define DEFAULT_TIMEOUT 30
23
24struct context {
25 struct mnl_socket *nl;
26 unsigned int if_idx;
27 sem_t *sema;
28};
29
30static bool
31get_timeout(unsigned int *r)
32{
33 unsigned long delay;
34 const char *timeout;
35
36 if (!(timeout = getenv("IF_WAITIF_TIMEOUT"))) {
37 *r = DEFAULT_TIMEOUT; // no timeout configured
38 return true;
39 }
40
41 errno = 0;
42 delay = strtoul(timeout, NULL, 10);
43 if (!delay && errno) {
44 return false;
45 } else if (delay > UINT_MAX) {
46 errno = EOVERFLOW;
47 return false;
48 }
49
50 *r = (unsigned int)delay;
51 return true;
52}
53
54static int
55data_cb(const struct nlmsghdr *nlh, void *arg)
56{
57 struct ifinfomsg *ifm;
58 struct context *ctx;
59
60 ctx = (struct context *)arg;
61 ifm = mnl_nlmsg_get_payload(nlh);
62
63 if ((unsigned)ifm->ifi_index == ctx->if_idx && ifm->ifi_flags & IFF_RUNNING)
64 return MNL_CB_STOP;
65
66 return MNL_CB_OK;
67}
68
69static void *
70netlink_loop(void *arg)
71{
72 ssize_t ret;
73 struct context *ctx;
74 char buf[MNL_SOCKET_BUFFER_SIZE];
75
76 ctx = (struct context*)arg;
77 ret = mnl_socket_recvfrom(ctx->nl, buf, sizeof(buf));
78 while (ret > 0) {
79 ret = mnl_cb_run(buf, (size_t)ret, 0, 0, data_cb, arg);
80 if (ret <= 0)
81 break;
82 ret = mnl_socket_recvfrom(ctx->nl, buf, sizeof(buf));
83 }
84 if (ret == -1) {
85 warn("netlink_loop failed");
86 return NULL;
87 }
88
89 sem_post(ctx->sema);
90 return NULL;
91}
92
93static int
94iface_is_up(struct mnl_socket *nl, const char *iface)
95{
96 int fd;
97 size_t namelen;
98 struct ifreq req;
99
100 namelen = strlen(iface);
101 if (namelen >= IFNAMSIZ) {
102 errno = ENAMETOOLONG;
103 return -1;
104 }
105 memcpy(req.ifr_name, iface, namelen+1);
106
107 fd = mnl_socket_get_fd(nl);
108 if (ioctl(fd, SIOCGIFFLAGS, &req) < 0)
109 return -1;
110 return req.ifr_flags & IFF_RUNNING;
111}
112
113static bool
114run_nl_thread(pthread_t *thread, sem_t *sema)
115{
116 static struct context ctx;
117 const char *iface;
118 int iface_state_up;
119
120 if (!(iface = getenv("IFACE")) || !(ctx.if_idx = if_nametoindex(iface))) {
121 errno = EINVAL;
122 return false;
123 }
124
125 ctx.nl = mnl_socket_open(NETLINK_ROUTE);
126 if (ctx.nl == NULL)
127 return false;
128 if (mnl_socket_bind(ctx.nl, RTMGRP_LINK, MNL_SOCKET_AUTOPID) == -1)
129 return false;
130
131 iface_state_up = iface_is_up(ctx.nl, iface);
132 if (iface_state_up == -1)
133 return false;
134
135 // Check if the link was up prior to socket creation.
136 if (iface_state_up) {
137 mnl_socket_close(ctx.nl);
138 sem_post(sema);
139 } else {
140 ctx.sema = sema;
141 if ((errno = pthread_create(thread, NULL, netlink_loop, &ctx)))
142 return false;
143 }
144
145 return true;
146}
147
148static bool
149wait_for_iface(sem_t *sema, unsigned int timeout)
150{
151 struct timespec ts;
152
153 // No timeout → block indefinitely
154 if (!timeout) {
155 if (sem_wait(sema) == -1)
156 return false;
157 return true;
158 }
159
160 if (clock_gettime(CLOCK_REALTIME, &ts))
161 return false;
162 ts.tv_sec += timeout;
163 if (sem_timedwait(sema, &ts) == -1)
164 return false; // detect timeout via errno
165
166 return true;
167}
168
169int
170main(void)
171{
172 sem_t sema;
173 pthread_t thread;
174 const char *phase;
175 unsigned int timeout;
176
177 // XXX: The executor doesn't require root privileges but is
178 // started as root by ifupdown-ng. We could drop privileges here.
179
180 // Executor only runs in "up" phase.
181 if (!(phase = getenv("PHASE")))
182 errx(EXIT_FAILURE, "Couldn't determine current phase");
183 if (strcmp(phase, "up"))
184 return EXIT_SUCCESS;
185
186 if (!get_timeout(&timeout))
187 err(EXIT_FAILURE, "get_timeout failed");
188
189 if (getenv("VERBOSE")) {
190 fprintf(stderr, "waitif: Waiting ");
191 if (timeout)
192 fprintf(stderr, "up to %u seconds", timeout);
193 else
194 fprintf(stderr, "indefinitely");
195 fprintf(stderr, " for interface to come up\n");
196 }
197
198 sem_init(&sema, 0, 0);
199 if (!run_nl_thread(&thread, &sema))
200 err(EXIT_FAILURE, "run_nl_thread failed");
201 if (!wait_for_iface(&sema, timeout))
202 err(EXIT_FAILURE, "wait_for_iface failed");
203
204 return EXIT_SUCCESS;
205}