diff mbox series

[PATCHi,iproute2] mptcp: add support for event monitoring

Message ID 20210114173811.7854-1-fw@strlen.de
State Superseded, archived
Delegated to: Mat Martineau
Headers show
Series [PATCHi,iproute2] mptcp: add support for event monitoring | expand

Commit Message

Florian Westphal Jan. 14, 2021, 5:38 p.m. UTC
Output will look similar to this:

[       CREATED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
[   ESTABLISHED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
[SF_ESTABLISHED] token=83f3a692 remid=0 locid=1 saddr4=10.0.2.2 daddr4=10.0.1.1 sport=40195 dport=10011 backup=0
[        CLOSED] token=83f3a692

Signed-off-by: Florian Westphal <fw@strlen.de>
---
 include/libgenl.h          |   1 +
 include/uapi/linux/mptcp.h |  76 ++++++++++++++++++++++++++
 ip/ipmptcp.c               | 106 +++++++++++++++++++++++++++++++++++++
 lib/libgenl.c              |  66 +++++++++++++++++++++++
 4 files changed, 249 insertions(+)

Comments

Matthieu Baerts Jan. 16, 2021, 1:51 p.m. UTC | #1
Hi Florian,

On 14/01/2021 18:38, Florian Westphal wrote:
> Output will look similar to this:
> 
> [       CREATED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
> [   ESTABLISHED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
> [SF_ESTABLISHED] token=83f3a692 remid=0 locid=1 saddr4=10.0.2.2 daddr4=10.0.1.1 sport=40195 dport=10011 backup=0
> [        CLOSED] token=83f3a692

Thank you for this new feature, that's a good idea!

It looks good! Just the usage/man page to update I think! I just asked a 
few questions below if you don't mind looking :)

(...)

> diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h
> index 321812307718..199b0c46ba70 100644
> --- a/include/uapi/linux/mptcp.h
> +++ b/include/uapi/linux/mptcp.h
> @@ -36,6 +36,7 @@ enum {
>   /* netlink interface */
>   #define MPTCP_PM_NAME		"mptcp_pm"
>   #define MPTCP_PM_CMD_GRP_NAME	"mptcp_pm_cmds"
> +#define MPTCP_PM_EV_GRP_NAME	"mptcp_events"

(just to be sure: we don't want to prefix with "_pm" to keep 
compatibility with mptcp.org kernel, right?)

>   #define MPTCP_PM_VER		0x1

I guess we don't need to change that, on older version of the kernel we 
can just check if "mptcp_events" has been added.

(...)

> diff --git a/ip/ipmptcp.c b/ip/ipmptcp.c
> index e1ffafb3c658..4716aa9e62e8 100644
> --- a/ip/ipmptcp.c
> +++ b/ip/ipmptcp.c
> @@ -385,6 +385,104 @@ static int mptcp_limit_get_set(int argc, char **argv, int cmd)
>   	return 0;
>   }

(...)

> +static void print_addr(const char *key, int af, struct rtattr *value)
> +{
> +	void *data = RTA_DATA(value);
> +	char str[INET6_ADDRSTRLEN];
> +
> +	if (inet_ntop(af, data, str, sizeof(str)))
> +		printf(" %s=%s", key, str);

Should we display an error in case of issue to parse the address? Or no 
need because it should not happen?

> +}
> +
> +static int mptcp_monitor_msg(struct rtnl_ctrl_data *ctrl,
> +			     struct nlmsghdr *n, void *arg)
> +{
> +	const struct genlmsghdr *ghdr = NLMSG_DATA(n);
> +	struct rtattr *tb[MPTCP_ATTR_MAX + 1];
> +	int len = n->nlmsg_len;
> +
> +	len -= NLMSG_LENGTH(GENL_HDRLEN);
> +	if (len < 0)
> +		return -1;
> +
> +	if (n->nlmsg_type != genl_family)
> +		return 0;
> +
> +	if (ghdr->cmd >= ARRAY_SIZE(event_to_str)) {
> +		printf("[UNKNOWN %u]\n", ghdr->cmd);
> +		fflush(stdout);
> +		return 0;

(detail: maybe good to have a goto here and below to do the fflush + 
return (+puts?) mainly for later not to forget that? Up to you, a detail)

(...)

 > +	printf(" token=%08x", rta_getattr_u32(tb[MPTCP_ATTR_TOKEN]));

If we are not the user ID 0, we cannot do the monitoring, right?

>   	if (argc == 0)
> @@ -429,6 +527,14 @@ int do_mptcp(int argc, char **argv)
>   						   MPTCP_PM_CMD_GET_LIMITS);
>   	}
>   
> +	if (matches(*argv, "monitor") == 0) {

May you update the "usage()" function here above and the man page to 
reflect this new parameter please?

(...)

> diff --git a/lib/libgenl.c b/lib/libgenl.c
> index f2ce698fc711..4c51d47af46b 100644
> --- a/lib/libgenl.c
> +++ b/lib/libgenl.c
> @@ -67,6 +67,72 @@ int genl_resolve_family(struct rtnl_handle *grth, const char *family)
>   	return fnum;
>   }
>   
> +static int genl_parse_grps(struct rtattr *attr, const char *name, unsigned int *id)
> +{
> +	const struct rtattr *pos;
> +
> +	rtattr_for_each_nested(pos, attr) {
> +		struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];
> +
> +		parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, pos);
> +
> +		if (tb[CTRL_ATTR_MCAST_GRP_NAME] && tb[CTRL_ATTR_MCAST_GRP_ID]) {
> +			if (strcmp(name, rta_getattr_str(tb[CTRL_ATTR_MCAST_GRP_NAME])) == 0) {

Should we use strncmp()? (But not sure with which limit so probably no)

Cheers,
Matt
Florian Westphal Jan. 16, 2021, 5:57 p.m. UTC | #2
Matthieu Baerts <matthieu.baerts@tessares.net> wrote:
> Hi Florian,
> 
> On 14/01/2021 18:38, Florian Westphal wrote:
> > Output will look similar to this:
> > 
> > [       CREATED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
> > [   ESTABLISHED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
> > [SF_ESTABLISHED] token=83f3a692 remid=0 locid=1 saddr4=10.0.2.2 daddr4=10.0.1.1 sport=40195 dport=10011 backup=0
> > [        CLOSED] token=83f3a692
> 
> Thank you for this new feature, that's a good idea!
> 
> It looks good! Just the usage/man page to update I think! I just asked a few
> questions below if you don't mind looking :)

Right, will update this for the netdev submission.

> > diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h
> > index 321812307718..199b0c46ba70 100644
> > --- a/include/uapi/linux/mptcp.h
> > +++ b/include/uapi/linux/mptcp.h
> > @@ -36,6 +36,7 @@ enum {
> >   /* netlink interface */
> >   #define MPTCP_PM_NAME		"mptcp_pm"
> >   #define MPTCP_PM_CMD_GRP_NAME	"mptcp_pm_cmds"
> > +#define MPTCP_PM_EV_GRP_NAME	"mptcp_events"
> 
> (just to be sure: we don't want to prefix with "_pm" to keep compatibility
> with mptcp.org kernel, right?)

Only change needed on userspace side is use of "mptcp_pm" instead of
plain "mptcp" for the genl name.

Mandation use of "mptcp_pm_events" would mean a second change and I do
not see a good reason for it.

> >   #define MPTCP_PM_VER		0x1
> 
> I guess we don't need to change that, on older version of the kernel we can
> just check if "mptcp_events" has been added.

On old kernel the subscription request will fail.
I would only increment this in case of incompatible changes, which
should be avoided anyway.


> > +static void print_addr(const char *key, int af, struct rtattr *value)
> > +{
> > +	void *data = RTA_DATA(value);
> > +	char str[INET6_ADDRSTRLEN];
> > +
> > +	if (inet_ntop(af, data, str, sizeof(str)))
> > +		printf(" %s=%s", key, str);
> 
> Should we display an error in case of issue to parse the address? Or no need
> because it should not happen?

I don't think it makes sense to handle errors here.
The only error message we could display here would be something like
"kernel netlink abi is broken".

I can do that (plus abort()) in the next iteration.

> > +	if (ghdr->cmd >= ARRAY_SIZE(event_to_str)) {
> > +		printf("[UNKNOWN %u]\n", ghdr->cmd);
> > +		fflush(stdout);
> > +		return 0;
> 
> (detail: maybe good to have a goto here and below to do the fflush + return
> (+puts?) mainly for later not to forget that? Up to you, a detail)

Ok, I can change it.

> > +	printf(" token=%08x", rta_getattr_u32(tb[MPTCP_ATTR_TOKEN]));
> 
> If we are not the user ID 0, we cannot do the monitoring, right?

No, netlink mcast subscription will fail with -EPERM in that case.

> >   	if (argc == 0)
> > @@ -429,6 +527,14 @@ int do_mptcp(int argc, char **argv)
> >   						   MPTCP_PM_CMD_GET_LIMITS);
> >   	}
> > +	if (matches(*argv, "monitor") == 0) {
> 
> May you update the "usage()" function here above and the man page to reflect
> this new parameter please?

Yes, sorry I forgot.

> > +static int genl_parse_grps(struct rtattr *attr, const char *name, unsigned int *id)
> > +{
> > +	const struct rtattr *pos;
> > +
> > +	rtattr_for_each_nested(pos, attr) {
> > +		struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];
> > +
> > +		parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, pos);
> > +
> > +		if (tb[CTRL_ATTR_MCAST_GRP_NAME] && tb[CTRL_ATTR_MCAST_GRP_ID]) {
> > +			if (strcmp(name, rta_getattr_str(tb[CTRL_ATTR_MCAST_GRP_NAME])) == 0) {
> 
> Should we use strncmp()? (But not sure with which limit so probably no)

No, the string is part of the abi, so it will always match "mptcp_event"
in case the feature is present.
Matthieu Baerts Jan. 16, 2021, 7:25 p.m. UTC | #3
Hi Florian,

On 16/01/2021 18:57, Florian Westphal wrote:
> Matthieu Baerts <matthieu.baerts@tessares.net> wrote:
>> On 14/01/2021 18:38, Florian Westphal wrote:
>>> Output will look similar to this:
>>>
>>> [       CREATED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
>>> [   ESTABLISHED] token=83f3a692 remid=0 locid=0 saddr4=10.0.1.2 daddr4=10.0.1.1 sport=58710 dport=10011
>>> [SF_ESTABLISHED] token=83f3a692 remid=0 locid=1 saddr4=10.0.2.2 daddr4=10.0.1.1 sport=40195 dport=10011 backup=0
>>> [        CLOSED] token=83f3a692
>>
>> Thank you for this new feature, that's a good idea!
>>
>> It looks good! Just the usage/man page to update I think! I just asked a few
>> questions below if you don't mind looking :)
> 
> Right, will update this for the netdev submission.

Thanks!

And thank you for the other answers!

Cheers,
Matt
diff mbox series

Patch

diff --git a/include/libgenl.h b/include/libgenl.h
index 656493a2c3c4..97281cc1103f 100644
--- a/include/libgenl.h
+++ b/include/libgenl.h
@@ -21,6 +21,7 @@  struct {								\
 	},								\
 }
 
+int genl_add_mcast_grp(struct rtnl_handle *grth, __u16 genl_family, const char *group);
 int genl_resolve_family(struct rtnl_handle *grth, const char *family);
 int genl_init_handle(struct rtnl_handle *grth, const char *family,
 		     int *genl_family);
diff --git a/include/uapi/linux/mptcp.h b/include/uapi/linux/mptcp.h
index 321812307718..199b0c46ba70 100644
--- a/include/uapi/linux/mptcp.h
+++ b/include/uapi/linux/mptcp.h
@@ -36,6 +36,7 @@  enum {
 /* netlink interface */
 #define MPTCP_PM_NAME		"mptcp_pm"
 #define MPTCP_PM_CMD_GRP_NAME	"mptcp_pm_cmds"
+#define MPTCP_PM_EV_GRP_NAME	"mptcp_events"
 #define MPTCP_PM_VER		0x1
 
 /*
@@ -86,6 +87,81 @@  enum {
 	__MPTCP_PM_CMD_AFTER_LAST
 };
 
+/*
+ * Events generated by MPTCP:
+ *   - MPTCP_EVENT_CREATED: token, family, saddr4 | saddr6, daddr4 | daddr6,
+ *                          sport, dport
+ *       A new connection has been created. It is the good time to allocate
+ *       memory and send ADD_ADDR if needed. Depending on the traffic-patterns
+ *       it can take a long time until the MPTCP_EVENT_ESTABLISHED is sent.
+ *
+ *   - MPTCP_EVENT_ESTABLISHED: token, family, saddr4 | saddr6, daddr4 | daddr6,
+ *                              sport, dport
+ *       A connection is established (can start new subflows).
+ *
+ *   - MPTCP_EVENT_CLOSED: token
+ *       A connection has stopped.
+ *
+ *   - MPTCP_EVENT_ANNOUNCED: token, rem_id, family, daddr4 | daddr6 [, dport]
+ *       A new address has been announced by the peer.
+ *
+ *   - MPTCP_EVENT_REMOVED: token, rem_id
+ *       An address has been lost by the peer.
+ *
+ *   - MPTCP_EVENT_SUB_ESTABLISHED: token, family, saddr4 | saddr6,
+ *                                  daddr4 | daddr6, sport, dport, backup,
+ *                                  if_idx [, error]
+ *       A new subflow has been established. 'error' should not be set.
+ *
+ *   - MPTCP_EVENT_SUB_CLOSED: token, family, saddr4 | saddr6, daddr4 | daddr6,
+ *                             sport, dport, backup, if_idx [, error]
+ *       A subflow has been closed. An error (copy of sk_err) could be set if an
+ *       error has been detected for this subflow.
+ *
+ *   - MPTCP_EVENT_SUB_PRIORITY: token, family, saddr4 | saddr6, daddr4 | daddr6,
+ *                               sport, dport, backup, if_idx [, error]
+ *       The priority of a subflow has changed. 'error' should not be set.
+*/
+enum mptcp_event {
+	MPTCP_EVENT_UNSPEC = 0,
+
+	MPTCP_EVENT_CREATED = 1,
+	MPTCP_EVENT_ESTABLISHED,
+	MPTCP_EVENT_CLOSED,
+
+	MPTCP_EVENT_ANNOUNCED = 6,
+	MPTCP_EVENT_REMOVED,
+
+	MPTCP_EVENT_SUB_ESTABLISHED = 10,
+	MPTCP_EVENT_SUB_CLOSED,
+
+	MPTCP_EVENT_SUB_PRIORITY = 13,
+
+	__MPTCP_EVENT_AFTER_LAST = 16,
+};
+
+enum mptcp_event_attr {
+	MPTCP_ATTR_UNSPEC = 0,
+
+	MPTCP_ATTR_TOKEN,	/* u32 */
+	MPTCP_ATTR_FAMILY,	/* u16 */
+	MPTCP_ATTR_LOC_ID,	/* u8 */
+	MPTCP_ATTR_REM_ID,	/* u8 */
+	MPTCP_ATTR_SADDR4,	/* be32 */
+	MPTCP_ATTR_SADDR6,	/* struct in6_addr */
+	MPTCP_ATTR_DADDR4,	/* be32 */
+	MPTCP_ATTR_DADDR6,	/* struct in6_addr */
+	MPTCP_ATTR_SPORT,	/* be16 */
+	MPTCP_ATTR_DPORT,	/* be16 */
+	MPTCP_ATTR_BACKUP,	/* u8 */
+	MPTCP_ATTR_ERROR,	/* u8 */
+	MPTCP_ATTR_FLAGS,	/* u16 */
+	MPTCP_ATTR_TIMEOUT,	/* u32 */
+	MPTCP_ATTR_IF_IDX,	/* s32 */
+	__MPTCP_ATTR_AFTER_LAST
+};
+#define MPTCP_ATTR_MAX (__MPTCP_ATTR_AFTER_LAST - 1)
+
 #define MPTCP_INFO_FLAG_FALLBACK		_BITUL(0)
 #define MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED	_BITUL(1)
 
diff --git a/ip/ipmptcp.c b/ip/ipmptcp.c
index e1ffafb3c658..4716aa9e62e8 100644
--- a/ip/ipmptcp.c
+++ b/ip/ipmptcp.c
@@ -385,6 +385,104 @@  static int mptcp_limit_get_set(int argc, char **argv, int cmd)
 	return 0;
 }
 
+static const char * const event_to_str[] = {
+	[MPTCP_EVENT_CREATED] = "CREATED",
+	[MPTCP_EVENT_ESTABLISHED] = "ESTABLISHED",
+	[MPTCP_EVENT_CLOSED] = "CLOSED",
+	[MPTCP_EVENT_ANNOUNCED] = "ANNOUNCED",
+	[MPTCP_EVENT_REMOVED] = "REMOVED",
+	[MPTCP_EVENT_SUB_ESTABLISHED] = "SF_ESTABLISHED",
+	[MPTCP_EVENT_SUB_CLOSED] = "SF_CLOSED",
+	[MPTCP_EVENT_SUB_PRIORITY] = "SF_PRIO",
+};
+
+static void print_addr(const char *key, int af, struct rtattr *value)
+{
+	void *data = RTA_DATA(value);
+	char str[INET6_ADDRSTRLEN];
+
+	if (inet_ntop(af, data, str, sizeof(str)))
+		printf(" %s=%s", key, str);
+}
+
+static int mptcp_monitor_msg(struct rtnl_ctrl_data *ctrl,
+			     struct nlmsghdr *n, void *arg)
+{
+	const struct genlmsghdr *ghdr = NLMSG_DATA(n);
+	struct rtattr *tb[MPTCP_ATTR_MAX + 1];
+	int len = n->nlmsg_len;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		return -1;
+
+	if (n->nlmsg_type != genl_family)
+		return 0;
+
+	if (ghdr->cmd >= ARRAY_SIZE(event_to_str)) {
+		printf("[UNKNOWN %u]\n", ghdr->cmd);
+		fflush(stdout);
+		return 0;
+	}
+
+	if (event_to_str[ghdr->cmd] == NULL) {
+		printf("[UNKNOWN %u]\n", ghdr->cmd);
+		fflush(stdout);
+		return 0;
+	}
+
+	printf("[%14s]", event_to_str[ghdr->cmd]);
+
+	parse_rtattr(tb, MPTCP_ATTR_MAX, (void *) ghdr + GENL_HDRLEN, len);
+
+	printf(" token=%08x", rta_getattr_u32(tb[MPTCP_ATTR_TOKEN]));
+
+	if (tb[MPTCP_ATTR_REM_ID])
+		printf(" remid=%u", rta_getattr_u16(tb[MPTCP_ATTR_REM_ID]));
+	if (tb[MPTCP_ATTR_LOC_ID])
+		printf(" locid=%u", rta_getattr_u16(tb[MPTCP_ATTR_LOC_ID]));
+
+	if (tb[MPTCP_ATTR_SADDR4])
+		print_addr("saddr4", AF_INET, tb[MPTCP_ATTR_SADDR4]);
+	if (tb[MPTCP_ATTR_DADDR4])
+		print_addr("daddr4", AF_INET, tb[MPTCP_ATTR_DADDR4]);
+	if (tb[MPTCP_ATTR_SADDR6])
+		print_addr("saddr6", AF_INET6, tb[MPTCP_ATTR_SADDR6]);
+	if (tb[MPTCP_ATTR_DADDR6])
+		print_addr("daddr6", AF_INET6, tb[MPTCP_ATTR_DADDR6]);
+	if (tb[MPTCP_ATTR_SPORT])
+		printf(" sport=%u", rta_getattr_be16(tb[MPTCP_ATTR_SPORT]));
+	if (tb[MPTCP_ATTR_DPORT])
+		printf(" dport=%u", rta_getattr_be16(tb[MPTCP_ATTR_DPORT]));
+	if (tb[MPTCP_ATTR_BACKUP])
+		printf(" backup=%d", rta_getattr_u8(tb[MPTCP_ATTR_BACKUP]));
+	if (tb[MPTCP_ATTR_ERROR])
+		printf(" error=%d", rta_getattr_u8(tb[MPTCP_ATTR_ERROR]));
+	if (tb[MPTCP_ATTR_FLAGS])
+		printf(" flags=%x", rta_getattr_u16(tb[MPTCP_ATTR_FLAGS]));
+	if (tb[MPTCP_ATTR_TIMEOUT])
+		printf(" timeout=%u", rta_getattr_u32(tb[MPTCP_ATTR_TIMEOUT]));
+	if (tb[MPTCP_ATTR_IF_IDX])
+		printf(" ifindex=%d", rta_getattr_s32(tb[MPTCP_ATTR_IF_IDX]));
+
+	puts("");
+	fflush(stdout);
+	return 0;
+}
+
+static int mptcp_monitor(void)
+{
+	if (genl_add_mcast_grp(&genl_rth, genl_family, MPTCP_PM_EV_GRP_NAME) < 0) {
+		perror("can't subscribe to mptcp events");
+		return 1;
+	}
+
+	if (rtnl_listen(&genl_rth, mptcp_monitor_msg, stdout) < 0)
+		return 2;
+
+	return 0;
+}
+
 int do_mptcp(int argc, char **argv)
 {
 	if (argc == 0)
@@ -429,6 +527,14 @@  int do_mptcp(int argc, char **argv)
 						   MPTCP_PM_CMD_GET_LIMITS);
 	}
 
+	if (matches(*argv, "monitor") == 0) {
+		NEXT_ARG_FWD();
+		if (argc == 0)
+			return mptcp_monitor();
+
+		goto unknown;
+	}
+
 unknown:
 	fprintf(stderr, "Command \"%s\" is unknown, try \"ip mptcp help\".\n",
 		*argv);
diff --git a/lib/libgenl.c b/lib/libgenl.c
index f2ce698fc711..4c51d47af46b 100644
--- a/lib/libgenl.c
+++ b/lib/libgenl.c
@@ -67,6 +67,72 @@  int genl_resolve_family(struct rtnl_handle *grth, const char *family)
 	return fnum;
 }
 
+static int genl_parse_grps(struct rtattr *attr, const char *name, unsigned int *id)
+{
+	const struct rtattr *pos;
+
+	rtattr_for_each_nested(pos, attr) {
+		struct rtattr *tb[CTRL_ATTR_MCAST_GRP_MAX + 1];
+
+		parse_rtattr_nested(tb, CTRL_ATTR_MCAST_GRP_MAX, pos);
+
+		if (tb[CTRL_ATTR_MCAST_GRP_NAME] && tb[CTRL_ATTR_MCAST_GRP_ID]) {
+			if (strcmp(name, rta_getattr_str(tb[CTRL_ATTR_MCAST_GRP_NAME])) == 0) {
+				*id = rta_getattr_u32(tb[CTRL_ATTR_MCAST_GRP_ID]);
+				return 0;
+			}
+		}
+	}
+
+	return -1;
+}
+
+int genl_add_mcast_grp(struct rtnl_handle *grth, __u16 fnum, const char *group)
+{
+	GENL_REQUEST(req, 1024, GENL_ID_CTRL, 0, 0, CTRL_CMD_GETFAMILY,
+		     NLM_F_REQUEST);
+	struct rtattr *tb[CTRL_ATTR_MAX + 1];
+	struct nlmsghdr *answer = NULL;
+	struct genlmsghdr *ghdr;
+	struct rtattr *attrs;
+	int len, ret = -1;
+	unsigned int id;
+
+	addattr16(&req.n, sizeof(req), CTRL_ATTR_FAMILY_ID, fnum);
+
+	if (rtnl_talk(grth, &req.n, &answer) < 0) {
+		fprintf(stderr, "Error talking to the kernel\n");
+		return -2;
+	}
+
+	ghdr = NLMSG_DATA(answer);
+	len = answer->nlmsg_len;
+
+	if (answer->nlmsg_type != GENL_ID_CTRL)
+		goto err_free;
+
+	len -= NLMSG_LENGTH(GENL_HDRLEN);
+	if (len < 0)
+		goto err_free;
+
+	attrs = (struct rtattr *) ((char *) ghdr + GENL_HDRLEN);
+	parse_rtattr(tb, CTRL_ATTR_MAX, attrs, len);
+
+	if (tb[CTRL_ATTR_MCAST_GROUPS] == NULL) {
+		fprintf(stderr, "Missing mcast groups TLV\n");
+		goto err_free;
+	}
+
+	if (genl_parse_grps(tb[CTRL_ATTR_MCAST_GROUPS], group, &id) < 0)
+		goto err_free;
+
+	ret = rtnl_add_nl_group(grth, id);
+
+err_free:
+	free(answer);
+	return ret;
+}
+
 int genl_init_handle(struct rtnl_handle *grth, const char *family,
 		     int *genl_family)
 {