rtnl: add team activebackup support

This patch add team interface activebackup mode support. As linux team use
genl netlink message, when we get a rtnl link change notify, we have to setup
a new genl socket and request the current active port.

v2: check nlmsg_len before copy rta_data
v3: a) Do not make rtnl_buf global as it may be freed by calling rtnl_close()
       while we are using it in rtnl_link_status()
    b) Reorder declarations of variables as reversed Christmas tree for
       function rtnl_link_status()
    c) remove rtnl_len
v4: Remove the first !rtnl_buf check in rtnl_link_status as it's alway true
v5: a) Re-order {nl, rtnl}_open and add function nl_close()
    b) revert the v3_{a,c}, v4 changes, use nl_close to close genl fd
    c) do not use len in get_team_active_iface() as it may mislead reader
v6: Return index at the end to fix fd leak in get_team_active_iface()

Signed-off-by: Hangbin Liu <liuhangbin@gmail.com>
master
Hangbin Liu 2019-03-22 15:02:46 +08:00 committed by Richard Cochran
parent 450b1ed844
commit 268be957d2
3 changed files with 239 additions and 34 deletions

View File

@ -118,6 +118,22 @@ enum {
#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) #define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1)
#endif /*IFLA_BOND_MAX*/ #endif /*IFLA_BOND_MAX*/
#ifndef NLA_TYPE_MAX
enum {
NLA_UNSPEC,
NLA_U8,
NLA_U16,
NLA_U32,
NLA_U64,
NLA_STRING,
NLA_FLAG,
NLA_MSECS,
NLA_NESTED,
__NLA_TYPE_MAX,
};
#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)
#endif /*NLA_TYPE_MAX*/
#ifdef __UCLIBC__ #ifdef __UCLIBC__
#if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && \ #if (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L) && \

View File

@ -108,9 +108,9 @@ together with the
option, the master clock is used only to correct the offset by whole number of option, the master clock is used only to correct the offset by whole number of
seconds, which cannot be fixed with PPS alone. Not compatible with the seconds, which cannot be fixed with PPS alone. Not compatible with the
.B \-a .B \-a
option. This option does not support bonded interface (e.g. bond0). If option. This option does not support bonded interface (e.g. bond0, team0). If
.B ptp4l .B ptp4l
has a port on an active-backup bond interface, the has a port on an active-backup bond or team interface, the
.B \-a .B \-a
option can be used to track the active interface. option can be used to track the active interface.
.TP .TP

253
rtnl.c
View File

@ -20,6 +20,8 @@
#include <sys/socket.h> /* Must come before linux/netlink.h on some systems. */ #include <sys/socket.h> /* Must come before linux/netlink.h on some systems. */
#include <linux/netlink.h> #include <linux/netlink.h>
#include <linux/rtnetlink.h> #include <linux/rtnetlink.h>
#include <linux/genetlink.h>
#include <linux/if_team.h>
#include <net/if.h> #include <net/if.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -30,8 +32,39 @@
#include "print.h" #include "print.h"
#include "rtnl.h" #include "rtnl.h"
#define BUF_SIZE 4096
#define GENLMSG_DATA(glh) ((void *)(NLMSG_DATA(glh) + GENL_HDRLEN))
static int rtnl_len; static int rtnl_len;
static char *rtnl_buf; static char *rtnl_buf;
static int get_team_active_iface(int master_index);
static int nl_close(int fd)
{
return close(fd);
}
static int nl_open(int family)
{
int fd;
struct sockaddr_nl sa;
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTNLGRP_LINK;
fd = socket(AF_NETLINK, SOCK_RAW, family);
if (fd < 0) {
pr_err("failed to open netlink socket: %m");
return -1;
}
if (bind(fd, (struct sockaddr *) &sa, sizeof(sa))) {
pr_err("failed to bind netlink socket: %m");
close(fd);
return -1;
}
return fd;
}
int rtnl_close(int fd) int rtnl_close(int fd)
{ {
@ -40,7 +73,12 @@ int rtnl_close(int fd)
rtnl_buf = NULL; rtnl_buf = NULL;
rtnl_len = 0; rtnl_len = 0;
} }
return close(fd); return nl_close(fd);
}
int rtnl_open(void)
{
return nl_open(NETLINK_ROUTE);
} }
static void rtnl_get_ts_device_callback(void *ctx, int linkup, int ts_index) static void rtnl_get_ts_device_callback(void *ctx, int linkup, int ts_index)
@ -116,14 +154,24 @@ int rtnl_link_query(int fd, char *device)
return 0; return 0;
} }
static inline __u32 rta_getattr_u32(const struct rtattr *rta) static inline __u8 rta_getattr_u8(struct rtattr *rta)
{
return *(__u8 *)RTA_DATA(rta);
}
static inline __u16 rta_getattr_u16(struct rtattr *rta)
{
return *(__u16 *)RTA_DATA(rta);
}
static inline __u32 rta_getattr_u32(struct rtattr *rta)
{ {
return *(__u32 *)RTA_DATA(rta); return *(__u32 *)RTA_DATA(rta);
} }
static inline const char *rta_getattr_str(const struct rtattr *rta) static inline char *rta_getattr_str(struct rtattr *rta)
{ {
return (const char *)RTA_DATA(rta); return (char *)RTA_DATA(rta);
} }
static int rtnl_rtattr_parse(struct rtattr *tb[], int max, struct rtattr *rta, int len) static int rtnl_rtattr_parse(struct rtattr *tb[], int max, struct rtattr *rta, int len)
@ -150,12 +198,12 @@ static inline int rtnl_nested_rtattr_parse(struct rtattr *tb[], int max, struct
return rtnl_rtattr_parse(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta)); return rtnl_rtattr_parse(tb, max, RTA_DATA(rta), RTA_PAYLOAD(rta));
} }
static int rtnl_linkinfo_parse(struct rtattr *rta) static int rtnl_linkinfo_parse(int master_index, struct rtattr *rta)
{ {
int index = -1;
const char *kind;
struct rtattr *linkinfo[IFLA_INFO_MAX]; struct rtattr *linkinfo[IFLA_INFO_MAX];
struct rtattr *bond[IFLA_BOND_MAX]; struct rtattr *bond[IFLA_BOND_MAX];
int index = -1;
char *kind;
if (rtnl_nested_rtattr_parse(linkinfo, IFLA_INFO_MAX, rta) < 0) if (rtnl_nested_rtattr_parse(linkinfo, IFLA_INFO_MAX, rta) < 0)
return -1; return -1;
@ -172,6 +220,8 @@ static int rtnl_linkinfo_parse(struct rtattr *rta)
if (bond[IFLA_BOND_ACTIVE_SLAVE]) { if (bond[IFLA_BOND_ACTIVE_SLAVE]) {
index = rta_getattr_u32(bond[IFLA_BOND_ACTIVE_SLAVE]); index = rta_getattr_u32(bond[IFLA_BOND_ACTIVE_SLAVE]);
} }
} else if (kind && !strncmp(kind, "team", 4)) {
index = get_team_active_iface(master_index);
} }
} }
return index; return index;
@ -179,18 +229,18 @@ static int rtnl_linkinfo_parse(struct rtattr *rta)
int rtnl_link_status(int fd, char *device, rtnl_callback cb, void *ctx) int rtnl_link_status(int fd, char *device, rtnl_callback cb, void *ctx)
{ {
int index, len, link_up;
int slave_index = -1;
struct iovec iov;
struct sockaddr_nl sa;
struct msghdr msg;
struct nlmsghdr *nh;
struct ifinfomsg *info = NULL;
struct rtattr *tb[IFLA_MAX+1]; struct rtattr *tb[IFLA_MAX+1];
struct ifinfomsg *info = NULL;
int index, len, link_up;
struct sockaddr_nl sa;
int slave_index = -1;
struct nlmsghdr *nh;
struct msghdr msg;
struct iovec iov;
index = if_nametoindex(device); index = if_nametoindex(device);
if (!rtnl_buf) { if (!rtnl_buf) {
rtnl_len = 4096; rtnl_len = BUF_SIZE;
rtnl_buf = malloc(rtnl_len); rtnl_buf = malloc(rtnl_len);
if (!rtnl_buf) { if (!rtnl_buf) {
pr_err("rtnl: low memory"); pr_err("rtnl: low memory");
@ -246,7 +296,7 @@ int rtnl_link_status(int fd, char *device, rtnl_callback cb, void *ctx)
IFLA_PAYLOAD(nh)); IFLA_PAYLOAD(nh));
if (tb[IFLA_LINKINFO]) if (tb[IFLA_LINKINFO])
slave_index = rtnl_linkinfo_parse(tb[IFLA_LINKINFO]); slave_index = rtnl_linkinfo_parse(index, tb[IFLA_LINKINFO]);
if (cb) if (cb)
cb(ctx, link_up, slave_index); cb(ctx, link_up, slave_index);
@ -255,24 +305,163 @@ int rtnl_link_status(int fd, char *device, rtnl_callback cb, void *ctx)
return 0; return 0;
} }
int rtnl_open(void) static int genl_send_msg(int fd, int family_id, int genl_cmd, int genl_version,
int rta_type, void *rta_data, int rta_len)
{ {
int fd; struct sockaddr_nl daddr;
struct sockaddr_nl sa; struct genlmsghdr *gnlh;
struct nlmsghdr *nlh;
struct rtattr *attr;
char msg[BUF_SIZE];
memset(&sa, 0, sizeof(sa)); memset(&daddr, 0, sizeof(daddr));
sa.nl_family = AF_NETLINK; daddr.nl_family = AF_NETLINK;
sa.nl_groups = RTNLGRP_LINK;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); memset(&msg, 0, sizeof(msg));
if (fd < 0) { nlh = (struct nlmsghdr *) msg;
pr_err("failed to open netlink socket: %m"); nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
return -1; nlh->nlmsg_type = family_id;
nlh->nlmsg_flags = NLM_F_REQUEST;
gnlh = (struct genlmsghdr *) NLMSG_DATA(nlh);
gnlh->cmd = genl_cmd;
gnlh->version = genl_version;
if (rta_data && rta_len > 0) {
attr = (struct rtattr *) GENLMSG_DATA(msg);
attr->rta_type = rta_type;
attr->rta_len = RTA_LENGTH(rta_len);
nlh->nlmsg_len += NLMSG_ALIGN(attr->rta_len);
if (nlh->nlmsg_len < sizeof(msg))
memcpy(RTA_DATA(attr), rta_data, rta_len);
else
return -1;
} }
if (bind(fd, (struct sockaddr *) &sa, sizeof(sa))) {
pr_err("failed to bind netlink socket: %m"); return sendto(fd, &msg, nlh->nlmsg_len, 0,
close(fd); (struct sockaddr *)&daddr, sizeof(daddr));
return -1; }
}
return fd; static int genl_get_family_id(int fd, void *family_name)
{
struct rtattr *tb[CTRL_ATTR_MAX+1];
struct nlmsghdr *nlh;
struct rtattr *attr;
char msg[BUF_SIZE];
int len, gf_id;
len = genl_send_msg(fd, GENL_ID_CTRL, CTRL_CMD_GETFAMILY, 1,
CTRL_ATTR_FAMILY_NAME, family_name,
strlen(family_name) + 1);
if (len < 0)
return len;
len = recv(fd, &msg, sizeof(msg), 0);
if (len < 0)
return len;
nlh = (struct nlmsghdr *) msg;
if (nlh->nlmsg_type == NLMSG_ERROR || !NLMSG_OK(nlh, len))
return -1;
attr = (struct rtattr *) GENLMSG_DATA(msg);
rtnl_rtattr_parse(tb, CTRL_ATTR_MAX, attr, NLMSG_PAYLOAD(nlh, GENL_HDRLEN));
if (tb[CTRL_ATTR_FAMILY_ID])
gf_id = rta_getattr_u16(tb[CTRL_ATTR_FAMILY_ID]);
else
gf_id = -1;
return gf_id;
}
static int parase_team_list_option(struct rtattr *attr)
{
struct rtattr *tb[TEAM_ATTR_OPTION_MAX+1];
int len = RTA_PAYLOAD(attr);
const char *optname = "";
const char *mode = "";
int active_index = -1;
for (attr = RTA_DATA(attr); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
rtnl_nested_rtattr_parse(tb, TEAM_ATTR_OPTION_MAX, attr);
if (tb[TEAM_ATTR_OPTION_NAME])
optname = rta_getattr_str(tb[TEAM_ATTR_OPTION_NAME]);
if (!strcmp(optname, "mode") && tb[TEAM_ATTR_OPTION_TYPE] &&
rta_getattr_u8(tb[TEAM_ATTR_OPTION_TYPE]) == NLA_STRING)
mode = rta_getattr_str(tb[TEAM_ATTR_OPTION_DATA]);
if (!strcmp(optname, "activeport") && tb[TEAM_ATTR_OPTION_TYPE] &&
rta_getattr_u8(tb[TEAM_ATTR_OPTION_TYPE]) == NLA_U32)
active_index = rta_getattr_u32(tb[TEAM_ATTR_OPTION_DATA]);
}
if (strcmp(mode, "activebackup")) {
pr_err("team supported only in activebackup mode");
return -1;
} else {
return active_index;
}
}
static int get_team_active_iface(int master_index)
{
struct rtattr *tb[TEAM_ATTR_MAX+1];
struct genlmsghdr *gnlh;
struct nlmsghdr *nlh;
char msg[BUF_SIZE];
int fd, gf_id, len;
int index = -1;
fd = nl_open(NETLINK_GENERIC);
if (fd < 0)
return fd;
gf_id = genl_get_family_id(fd, TEAM_GENL_NAME);
if (gf_id < 0) {
pr_err("get genl family failed");
goto no_info;
}
len = genl_send_msg(fd, gf_id, TEAM_CMD_OPTIONS_GET,
TEAM_GENL_VERSION, TEAM_ATTR_TEAM_IFINDEX,
&master_index, sizeof(master_index));
if (len < 0) {
pr_err("send team info request failed: %m");
goto no_info;
}
len = recv(fd, msg, sizeof(msg), 0);
if (len < 0) {
pr_err("recv team info failed: %m");
goto no_info;
}
nlh = (struct nlmsghdr *) msg;
for ( ; NLMSG_OK(nlh, len); nlh = NLMSG_NEXT(nlh, len)) {
if (nlh->nlmsg_type != gf_id)
continue;
gnlh = (struct genlmsghdr *) NLMSG_DATA(nlh);
if (gnlh->cmd != TEAM_CMD_OPTIONS_GET)
continue;
rtnl_rtattr_parse(tb, TEAM_ATTR_MAX, (struct rtattr *)GENLMSG_DATA(msg),
NLMSG_PAYLOAD(nlh, GENL_HDRLEN));
if (tb[TEAM_ATTR_TEAM_IFINDEX] &&
master_index != rta_getattr_u32(tb[TEAM_ATTR_TEAM_IFINDEX]))
continue;
if (tb[TEAM_ATTR_LIST_OPTION]) {
index = parase_team_list_option(tb[TEAM_ATTR_LIST_OPTION]);
break;
}
}
no_info:
nl_close(fd);
return index;
} }