Skip to content

Commit b0bd729

Browse files
authored
Merge pull request #1635 from kernelkit/ifaces
Finalize ietf-interfaces.yang: last-change, higher-layer-if, lower-layer-if
2 parents 2597ee6 + 2fd8cec commit b0bd729

43 files changed

Lines changed: 670 additions & 23 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎doc/ChangeLog.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ All notable changes to the project are documented in this file.
4343
editor, show mesh peers on the WiFi and interface status pages, and add
4444
an editor section for access point roaming (802.11k/r/v, band steering,
4545
OKC).
46+
- Add `higher-layer-if` and `lower-layer-if` to interface operational
47+
status, listing the interfaces stacked directly on top of or beneath
48+
each one, e.g., a VLAN interface and its parent, issue #514
49+
- Add `last-change` to interface operational status: the time the
50+
interface entered its current operational state, issue #514
4651

4752
### Fixes
4853

‎src/statd/Makefile.am‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ DISTCLEANFILES = *~ *.d
22
ACLOCAL_AMFLAGS = -I m4
33

44
sbin_PROGRAMS = statd
5-
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h
5+
statd_SOURCES = statd.c shared.c shared.h journal.c journal_retention.c journal.h avahi.c avahi.h iface.c iface.h
66
statd_CPPFLAGS = -D_DEFAULT_SOURCE -D_GNU_SOURCE
77
statd_CPPFLAGS += -DSTATD_VERSION=\"$(PACKAGE_VERSION)\"
88
statd_CFLAGS = -W -Wall -Wextra

‎src/statd/avahi.c‎

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include <srx/common.h>
3737

3838
#include "avahi.h"
39+
#include "shared.h"
3940

4041
/* Complete the opaque avahi types declared in avahi-common/watch.h */
4142
struct AvahiWatch {
@@ -336,15 +337,6 @@ static void free_all(struct mdns_ctx *ctx)
336337

337338
#define XPATH_BASE "/infix-services:mdns/neighbors"
338339

339-
static void format_timestamp(char *buf, size_t sz)
340-
{
341-
struct tm tm;
342-
time_t now = time(NULL);
343-
344-
gmtime_r(&now, &tm);
345-
strftime(buf, sz, "%Y-%m-%dT%H:%M:%S+00:00", &tm);
346-
}
347-
348340
static int sr_setstr(sr_session_ctx_t *ses, const char *xpath, const char *val)
349341
{
350342
int err = sr_set_item_str(ses, xpath, val, NULL, 0);
@@ -449,7 +441,7 @@ static void ds_push_resolver(struct mdns_ctx *ctx, struct avahi_service *svc,
449441
}
450442

451443
/* last-seen */
452-
format_timestamp(ts, sizeof(ts));
444+
format_timestamp(time(NULL), ts, sizeof(ts));
453445
snprintf(xpath, sizeof(xpath),
454446
XPATH_BASE "/neighbor[hostname='%s']/last-seen", svc->hostname);
455447
err = err ?: sr_setstr(ctx->sr_ses, xpath, ts);

‎src/statd/iface.c‎

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
/* SPDX-License-Identifier: BSD-3-Clause */
2+
/*
3+
* Track interface state changes over rtnetlink to provide the
4+
* ietf-interfaces last-change leaf, see issue #514.
5+
*/
6+
7+
#include <errno.h>
8+
#include <stdio.h>
9+
#include <stdlib.h>
10+
#include <string.h>
11+
#include <time.h>
12+
#include <unistd.h>
13+
#include <sys/socket.h>
14+
#include <linux/netlink.h>
15+
#include <linux/rtnetlink.h>
16+
17+
#include <srx/common.h>
18+
#include <srx/lyx.h>
19+
20+
#include "iface.h"
21+
#include "shared.h"
22+
23+
static time_t monotonic(void)
24+
{
25+
struct timespec ts;
26+
27+
clock_gettime(CLOCK_MONOTONIC, &ts);
28+
return ts.tv_sec;
29+
}
30+
31+
static struct iface *iface_find(struct iface_ctx *ctx, const char *name)
32+
{
33+
struct iface *l;
34+
35+
TAILQ_FOREACH(l, &ctx->ifaces, entries) {
36+
if (!strcmp(l->name, name))
37+
return l;
38+
}
39+
40+
return NULL;
41+
}
42+
43+
static struct iface *iface_find_index(struct iface_ctx *ctx, int ifindex)
44+
{
45+
struct iface *l;
46+
47+
TAILQ_FOREACH(l, &ctx->ifaces, entries) {
48+
if (l->ifindex == ifindex)
49+
return l;
50+
}
51+
52+
return NULL;
53+
}
54+
55+
static void iface_del(struct iface_ctx *ctx, struct iface *l)
56+
{
57+
TAILQ_REMOVE(&ctx->ifaces, l, entries);
58+
free(l);
59+
}
60+
61+
static void iface_update(struct iface_ctx *ctx, int ifindex, const char *name,
62+
uint8_t operstate, int dump)
63+
{
64+
struct iface *l;
65+
66+
l = iface_find(ctx, name);
67+
if (!l) {
68+
l = iface_find_index(ctx, ifindex);
69+
if (l) {
70+
DEBUG("Link %s renamed %s", l->name, name);
71+
snprintf(l->name, sizeof(l->name), "%s", name);
72+
}
73+
}
74+
75+
if (!l) {
76+
l = calloc(1, sizeof(*l));
77+
if (!l) {
78+
ERRNO("Failed allocating link %s", name);
79+
return;
80+
}
81+
82+
snprintf(l->name, sizeof(l->name), "%s", name);
83+
l->operstate = operstate;
84+
if (!dump)
85+
l->changed = monotonic();
86+
TAILQ_INSERT_TAIL(&ctx->ifaces, l, entries);
87+
DEBUG("Link %s added, operstate %u", name, operstate);
88+
} else if (l->operstate != operstate) {
89+
DEBUG("Link %s operstate %u -> %u", name, l->operstate, operstate);
90+
l->operstate = operstate;
91+
l->changed = monotonic();
92+
}
93+
94+
l->ifindex = ifindex;
95+
}
96+
97+
static void iface_parse(struct iface_ctx *ctx, struct nlmsghdr *nlh, int dump)
98+
{
99+
struct ifinfomsg *ifi = NLMSG_DATA(nlh);
100+
int len = nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi));
101+
uint8_t operstate = IF_OPER_UNKNOWN;
102+
const char *name = NULL;
103+
struct rtattr *rta;
104+
struct iface *l;
105+
106+
for (rta = IFLA_RTA(ifi); RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) {
107+
switch (rta->rta_type & NLA_TYPE_MASK) {
108+
case IFLA_IFNAME:
109+
name = RTA_DATA(rta);
110+
break;
111+
case IFLA_OPERSTATE:
112+
operstate = *(uint8_t *)RTA_DATA(rta);
113+
break;
114+
}
115+
}
116+
117+
if (!name)
118+
return;
119+
120+
if (nlh->nlmsg_type == RTM_DELLINK) {
121+
l = iface_find(ctx, name);
122+
if (l) {
123+
DEBUG("Link %s removed", name);
124+
iface_del(ctx, l);
125+
}
126+
return;
127+
}
128+
129+
iface_update(ctx, ifi->ifi_index, name, operstate, dump);
130+
}
131+
132+
static void iface_purge(struct iface_ctx *ctx)
133+
{
134+
struct iface *l;
135+
136+
while ((l = TAILQ_FIRST(&ctx->ifaces)))
137+
iface_del(ctx, l);
138+
}
139+
140+
/* Request a full link dump, replies are told apart from events by sequence */
141+
static int iface_dump(struct iface_ctx *ctx)
142+
{
143+
struct {
144+
struct nlmsghdr nlh;
145+
struct ifinfomsg ifi;
146+
} req = {
147+
.nlh = {
148+
.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)),
149+
.nlmsg_type = RTM_GETLINK,
150+
.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP,
151+
},
152+
.ifi = {
153+
.ifi_family = AF_UNSPEC,
154+
},
155+
};
156+
157+
if (!++ctx->seq)
158+
ctx->seq = 1;
159+
req.nlh.nlmsg_seq = ctx->seq;
160+
161+
if (send(ctx->sd, &req, req.nlh.nlmsg_len, 0) < 0) {
162+
ERRNO("Failed requesting link dump");
163+
ctx->seq = 0;
164+
return -1;
165+
}
166+
167+
return 0;
168+
}
169+
170+
static void iface_io_cb(struct ev_loop *, ev_io *w, int)
171+
{
172+
struct iface_ctx *ctx = (struct iface_ctx *)w;
173+
static char buf[32768];
174+
struct nlmsghdr *nlh;
175+
int len;
176+
177+
for (;;) {
178+
len = recv(ctx->sd, buf, sizeof(buf), MSG_DONTWAIT);
179+
if (len < 0) {
180+
if (errno == EINTR)
181+
continue;
182+
if (errno == EAGAIN)
183+
break;
184+
if (errno == ENOBUFS) {
185+
WARN("Link event overrun, resyncing");
186+
iface_purge(ctx);
187+
iface_dump(ctx);
188+
continue;
189+
}
190+
191+
ERRNO("Failed reading link events");
192+
break;
193+
}
194+
195+
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
196+
int dump = ctx->seq && nlh->nlmsg_seq == ctx->seq;
197+
198+
switch (nlh->nlmsg_type) {
199+
case RTM_NEWLINK:
200+
case RTM_DELLINK:
201+
iface_parse(ctx, nlh, dump);
202+
break;
203+
case NLMSG_DONE:
204+
if (dump)
205+
ctx->seq = 0;
206+
break;
207+
case NLMSG_ERROR:
208+
if (dump) {
209+
struct nlmsgerr *e = NLMSG_DATA(nlh);
210+
211+
errno = -e->error;
212+
ERRNO("Link dump failed");
213+
ctx->seq = 0;
214+
}
215+
break;
216+
default:
217+
break;
218+
}
219+
}
220+
}
221+
}
222+
223+
int iface_ctx_init(struct iface_ctx *ctx, struct ev_loop *loop)
224+
{
225+
struct sockaddr_nl sa = {
226+
.nl_family = AF_NETLINK,
227+
.nl_groups = RTMGRP_LINK,
228+
};
229+
230+
TAILQ_INIT(&ctx->ifaces);
231+
ctx->loop = loop;
232+
233+
ctx->sd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, NETLINK_ROUTE);
234+
if (ctx->sd < 0) {
235+
ERRNO("Failed opening link event socket");
236+
return -1;
237+
}
238+
239+
if (bind(ctx->sd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
240+
ERRNO("Failed subscribing to link events");
241+
close(ctx->sd);
242+
ctx->sd = -1;
243+
return -1;
244+
}
245+
246+
ev_io_init(&ctx->io, iface_io_cb, ctx->sd, EV_READ);
247+
ev_io_start(loop, &ctx->io);
248+
249+
return iface_dump(ctx);
250+
}
251+
252+
void iface_ctx_exit(struct iface_ctx *ctx)
253+
{
254+
if (ctx->sd >= 0) {
255+
ev_io_stop(ctx->loop, &ctx->io);
256+
close(ctx->sd);
257+
ctx->sd = -1;
258+
}
259+
260+
iface_purge(ctx);
261+
}
262+
263+
/* Add last-change to every interface in tree whose state changed since start */
264+
void iface_annotate(struct iface_ctx *ctx, struct lyd_node *tree)
265+
{
266+
time_t now = time(NULL), mono = monotonic();
267+
struct lyd_node *iface;
268+
269+
if (!tree)
270+
return;
271+
272+
LY_LIST_FOR(lyd_child(tree), iface) {
273+
struct iface *l;
274+
char buf[32];
275+
276+
l = iface_find(ctx, lydx_get_cattr(iface, "name") ?: "");
277+
if (!l || !l->changed)
278+
continue;
279+
280+
format_timestamp(now - (mono - l->changed), buf, sizeof(buf));
281+
if (lyd_new_term(iface, NULL, "last-change", buf, 0, NULL))
282+
WARN("Failed adding last-change to interface %s", l->name);
283+
}
284+
}

‎src/statd/iface.h‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* SPDX-License-Identifier: BSD-3-Clause */
2+
3+
#ifndef STATD_IFACE_H_
4+
#define STATD_IFACE_H_
5+
6+
#include <stdint.h>
7+
#include <time.h>
8+
#include <sys/queue.h>
9+
#include <linux/if.h>
10+
11+
#include <ev.h>
12+
#include <libyang/libyang.h>
13+
14+
/*
15+
* In-memory link state, keyed by interface name, tracked over rtnetlink.
16+
* Renames are recognized by ifindex, which the kernel keeps across them.
17+
*/
18+
19+
struct iface {
20+
char name[IFNAMSIZ];
21+
int ifindex;
22+
uint8_t operstate; /* IF_OPER_* */
23+
time_t changed; /* CLOCK_MONOTONIC, 0: state predates statd */
24+
TAILQ_ENTRY(iface) entries;
25+
};
26+
27+
struct iface_ctx {
28+
ev_io io; /* MUST be first (cast from ev_io *) */
29+
struct ev_loop *loop;
30+
int sd;
31+
uint32_t seq; /* sequence of ongoing dump, 0: none */
32+
TAILQ_HEAD(, iface) ifaces;
33+
};
34+
35+
int iface_ctx_init(struct iface_ctx *ctx, struct ev_loop *loop);
36+
void iface_ctx_exit(struct iface_ctx *ctx);
37+
38+
void iface_annotate(struct iface_ctx *ctx, struct lyd_node *tree);
39+
40+
#endif

0 commit comments

Comments
 (0)