ifupdown-ng-waitif

An ifupdown-ng executor which blocks until the interface is actually running

git clone https://git.8pit.net/ifupdown-ng-waitif.git

  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}