From a81ac2150d0790372b74f1cecd7b967ddb2f11ea Mon Sep 17 00:00:00 2001 From: Markus Stenberg Date: Wed, 5 Dec 2012 18:41:06 +0200 Subject: [PATCH] Converted code to use getaddrinfo/getnameinfo (available ~everywhere like *BSD, Linux, Windows at least), and added various ipv6 socket options' get+set. --- src/inet.c | 127 ++++++++++++++--------------------- src/options.c | 95 +++++++++++++++++++++++++- src/options.h | 9 +++ src/udp.c | 148 ++++++++++++++++------------------------- test/ipv6mcasttest.lua | 116 ++++++++++++++++++++++++++++++++ 5 files changed, 324 insertions(+), 171 deletions(-) create mode 100644 test/ipv6mcasttest.lua diff --git a/src/inet.c b/src/inet.c index dfee700..940c0e0 100644 --- a/src/inet.c +++ b/src/inet.c @@ -231,45 +231,31 @@ static int inet_global_gethostname(lua_State *L) \*-------------------------------------------------------------------------*/ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) { - switch (family) { - case PF_INET: { - struct sockaddr_in peer; - socklen_t peer_len = sizeof(peer); - char name[INET_ADDRSTRLEN]; - if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(errno)); - return 2; - } else { - inet_ntop(family, &peer.sin_addr, name, sizeof(name)); - lua_pushstring(L, name); - lua_pushnumber(L, ntohs(peer.sin_port)); - lua_pushliteral(L, "inet"); - return 3; - } - } - case PF_INET6: { - struct sockaddr_in6 peer; - socklen_t peer_len = sizeof(peer); - char name[INET6_ADDRSTRLEN]; - if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(errno)); - return 2; - } else { - inet_ntop(family, &peer.sin6_addr, name, sizeof(name)); - lua_pushstring(L, name); - lua_pushnumber(L, ntohs(peer.sin6_port)); - lua_pushliteral(L, "inet6"); - return 3; - } - return 2; - } - default: - lua_pushnil(L); - lua_pushfstring(L, "unknown family %d", family); - return 2; - } + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getpeername(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + if ((err=getnameinfo((struct sockaddr *)&peer, peer_len, + name, INET6_ADDRSTRLEN, + port, 6, + NI_NUMERICHOST | NI_NUMERICSERV))) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushstring(L, port); + if (family == PF_INET) + lua_pushliteral(L, "inet"); + else + lua_pushliteral(L, "inet6"); + return 3; } /*-------------------------------------------------------------------------*\ @@ -277,44 +263,31 @@ int inet_meth_getpeername(lua_State *L, p_socket ps, int family) \*-------------------------------------------------------------------------*/ int inet_meth_getsockname(lua_State *L, p_socket ps, int family) { - switch (family) { - case PF_INET: { - struct sockaddr_in local; - socklen_t local_len = sizeof(local); - char name[INET_ADDRSTRLEN]; - if (getsockname(*ps, (SA *) &local, &local_len) < 0) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(errno)); - return 2; - } else { - inet_ntop(family, &local.sin_addr, name, sizeof(name)); - lua_pushstring(L, name); - lua_pushnumber(L, ntohs(local.sin_port)); - lua_pushliteral(L, "inet"); - return 3; - } - } - case PF_INET6: { - struct sockaddr_in6 local; - socklen_t local_len = sizeof(local); - char name[INET6_ADDRSTRLEN]; - if (getsockname(*ps, (SA *) &local, &local_len) < 0) { - lua_pushnil(L); - lua_pushstring(L, socket_strerror(errno)); - return 2; - } else { - inet_ntop(family, &local.sin6_addr, name, sizeof(name)); - lua_pushstring(L, name); - lua_pushnumber(L, ntohs(local.sin6_port)); - lua_pushliteral(L, "inet6"); - return 3; - } - } - default: - lua_pushnil(L); - lua_pushfstring(L, "unknown family %d", family); - return 2; - } + int err; + struct sockaddr_storage peer; + socklen_t peer_len = sizeof(peer); + char name[INET6_ADDRSTRLEN]; + char port[6]; /* 65535 = 5 bytes + 0 to terminate it */ + if (getsockname(*ps, (SA *) &peer, &peer_len) < 0) { + lua_pushnil(L); + lua_pushstring(L, socket_strerror(errno)); + return 2; + } + if ((err=getnameinfo((struct sockaddr *)&peer, peer_len, + name, INET6_ADDRSTRLEN, + port, 6, + NI_NUMERICHOST | NI_NUMERICSERV))) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + lua_pushstring(L, name); + lua_pushstring(L, port); + if (family == PF_INET) + lua_pushliteral(L, "inet"); + else + lua_pushliteral(L, "inet6"); + return 3; } /*=========================================================================*\ diff --git a/src/options.c b/src/options.c index 6cae7ee..d3c347a 100644 --- a/src/options.c +++ b/src/options.c @@ -14,8 +14,11 @@ * Internal functions prototypes \*=========================================================================*/ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name); +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name); static int opt_setboolean(lua_State *L, p_socket ps, int level, int name); static int opt_getboolean(lua_State *L, p_socket ps, int level, int name); +static int opt_setint(lua_State *L, p_socket ps, int level, int name); +static int opt_getint(lua_State *L, p_socket ps, int level, int name); static int opt_set(lua_State *L, p_socket ps, int level, int name, void *val, int len); static int opt_get(lua_State *L, p_socket ps, int level, int name, @@ -106,6 +109,26 @@ int opt_set_broadcast(lua_State *L, p_socket ps) return opt_setboolean(L, ps, SOL_SOCKET, SO_BROADCAST); } +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_UNICAST_HOPS); +} + +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_setint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps) +{ + return opt_getint(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); +} + int opt_set_ip_multicast_loop(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); @@ -116,6 +139,16 @@ int opt_get_ip_multicast_loop(lua_State *L, p_socket ps) return opt_getboolean(L, ps, IPPROTO_IP, IP_MULTICAST_LOOP); } +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); +} + int opt_set_linger(lua_State *L, p_socket ps) { struct linger li; /* obj, name, table */ @@ -150,9 +183,7 @@ int opt_get_linger(lua_State *L, p_socket ps) int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps) { - int val = (int) luaL_checknumber(L, 3); /* obj, name, int */ - return opt_set(L, ps, IPPROTO_IP, IP_MULTICAST_TTL, - (char *) &val, sizeof(val)); + return opt_setint(L, ps, IPPROTO_IP, IP_MULTICAST_TTL); } int opt_set_ip_multicast_if(lua_State *L, p_socket ps) @@ -179,6 +210,8 @@ int opt_get_ip_multicast_if(lua_State *L, p_socket ps) return 1; } + + int opt_set_ip_add_membership(lua_State *L, p_socket ps) { return opt_setmembership(L, ps, IPPROTO_IP, IP_ADD_MEMBERSHIP); @@ -189,6 +222,21 @@ int opt_set_ip_drop_membersip(lua_State *L, p_socket ps) return opt_setmembership(L, ps, IPPROTO_IP, IP_DROP_MEMBERSHIP); } +int opt_set_ip6_add_membership(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP); +} + +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps) +{ + return opt_ip6_setmembership(L, ps, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP); +} + +int opt_get_ip6_v6only(lua_State *L, p_socket ps) +{ + return opt_getboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); +} + int opt_set_ip6_v6only(lua_State *L, p_socket ps) { return opt_setboolean(L, ps, IPPROTO_IPV6, IPV6_V6ONLY); @@ -218,6 +266,31 @@ static int opt_setmembership(lua_State *L, p_socket ps, int level, int name) return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } +static int opt_ip6_setmembership(lua_State *L, p_socket ps, int level, int name) +{ + struct ipv6_mreq val; /* obj, opt-name, table */ + memset(&val, 0, sizeof(val)); + if (!lua_istable(L, 3)) auxiliar_typeerror(L,3,lua_typename(L, LUA_TTABLE)); + lua_pushstring(L, "multiaddr"); + lua_gettable(L, 3); + if (!lua_isstring(L, -1)) + luaL_argerror(L, 3, "string 'multiaddr' field expected"); + if (!inet_pton(AF_INET6, lua_tostring(L, -1), &val.ipv6mr_multiaddr)) + luaL_argerror(L, 3, "invalid 'multiaddr' ip address"); + lua_pushstring(L, "interface"); + lua_gettable(L, 3); + /* By default we listen to all interfaces. However, interface= can + override it. */ + if (!lua_isnil(L, -1)) + { + if (!lua_isnumber(L, -1)) + luaL_argerror(L, 3, "string 'interface' field expected"); + /* XXX - support non-numeric interface specification? */ + } + val.ipv6mr_interface = lua_tonumber(L, -1); + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} + static int opt_get(lua_State *L, p_socket ps, int level, int name, void *val, int* len) { @@ -260,3 +333,19 @@ static int opt_setboolean(lua_State *L, p_socket ps, int level, int name) return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); } +static int opt_getint(lua_State *L, p_socket ps, int level, int name) +{ + int val = 0; + int len = sizeof(val); + int err = opt_get(L, ps, level, name, (char *) &val, &len); + if (err) + return err; + lua_pushnumber(L, val); + return 1; +} + +static int opt_setint(lua_State *L, p_socket ps, int level, int name) +{ + int val = (int) lua_tonumber(L, 3); /* obj, name, int */ + return opt_set(L, ps, level, name, (char *) &val, sizeof(val)); +} diff --git a/src/options.h b/src/options.h index 55447f7..92bd125 100644 --- a/src/options.h +++ b/src/options.h @@ -32,6 +32,11 @@ int opt_set_ip_multicast_ttl(lua_State *L, p_socket ps); int opt_set_ip_multicast_loop(lua_State *L, p_socket ps); int opt_set_ip_add_membership(lua_State *L, p_socket ps); int opt_set_ip_drop_membersip(lua_State *L, p_socket ps); +int opt_set_ip6_unicast_hops(lua_State *L, p_socket ps); +int opt_set_ip6_multicast_hops(lua_State *L, p_socket ps); +int opt_set_ip6_multicast_loop(lua_State *L, p_socket ps); +int opt_set_ip6_add_membership(lua_State *L, p_socket ps); +int opt_set_ip6_drop_membersip(lua_State *L, p_socket ps); int opt_set_ip6_v6only(lua_State *L, p_socket ps); /* supported options for getoption */ @@ -42,6 +47,10 @@ int opt_get_linger(lua_State *L, p_socket ps); int opt_get_reuseaddr(lua_State *L, p_socket ps); int opt_get_ip_multicast_loop(lua_State *L, p_socket ps); int opt_get_ip_multicast_if(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_loop(lua_State *L, p_socket ps); +int opt_get_ip6_multicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_unicast_hops(lua_State *L, p_socket ps); +int opt_get_ip6_v6only(lua_State *L, p_socket ps); /* invokes the appropriate option handler */ int opt_meth_setoption(lua_State *L, p_opt opt, p_socket ps); diff --git a/src/udp.c b/src/udp.c index 4cd9a41..075500e 100644 --- a/src/udp.c +++ b/src/udp.c @@ -77,6 +77,11 @@ static t_opt optset[] = { {"ip-multicast-loop", opt_set_ip_multicast_loop}, {"ip-add-membership", opt_set_ip_add_membership}, {"ip-drop-membership", opt_set_ip_drop_membersip}, + {"ipv6-unicast-hops", opt_set_ip6_unicast_hops}, + {"ipv6-multicast-hops",opt_set_ip6_unicast_hops}, + {"ipv6-multicast-loop", opt_set_ip6_multicast_loop}, + {"ipv6-add-membership", opt_set_ip6_add_membership}, + {"ipv6-drop-membership", opt_set_ip6_drop_membersip}, {"ipv6-v6only", opt_set_ip6_v6only}, {NULL, NULL} }; @@ -85,6 +90,10 @@ static t_opt optset[] = { static t_opt optget[] = { {"ip-multicast-if", opt_get_ip_multicast_if}, {"ip-multicast-loop", opt_get_ip_multicast_loop}, + {"ipv6-unicast-hops", opt_get_ip6_unicast_hops}, + {"ipv6-multicast-hops",opt_get_ip6_unicast_hops}, + {"ipv6-multicast-loop",opt_get_ip6_multicast_loop}, + {"ipv6-v6only", opt_get_ip6_v6only}, {NULL, NULL} }; @@ -151,39 +160,25 @@ static int meth_sendto(lua_State *L) { size_t count, sent = 0; const char *data = luaL_checklstring(L, 2, &count); const char *ip = luaL_checkstring(L, 3); - unsigned short port = (unsigned short) luaL_checknumber(L, 4); + /* unsigned short port = (unsigned short) luaL_checknumber(L, 4); */ p_timeout tm = &udp->tm; int err; - switch (udp->family) { - case PF_INET: { - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - if (!inet_pton(AF_INET, ip, &addr.sin_addr)) - luaL_argerror(L, 3, "invalid ip address"); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - timeout_markstart(tm); - err = socket_sendto(&udp->sock, data, count, &sent, - (SA *) &addr, sizeof(addr), tm); - break; - } - case PF_INET6: { - struct sockaddr_in6 addr; - memset(&addr, 0, sizeof(addr)); - if (!inet_pton(AF_INET6, ip, &addr.sin6_addr)) - luaL_argerror(L, 3, "invalid ip address"); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons(port); - timeout_markstart(tm); - err = socket_sendto(&udp->sock, data, count, &sent, - (SA *) &addr, sizeof(addr), tm); - break; - } - default: - lua_pushnil(L); - lua_pushfstring(L, "unknown family %d", udp->family); - return 2; - } + struct addrinfo aihint; + struct addrinfo *ai; + + memset(&aihint, 0, sizeof(aihint)); + aihint.ai_family = udp->family; + aihint.ai_socktype = SOCK_DGRAM; + if ((err = getaddrinfo(ip, lua_tostring(L, 4), &aihint, &ai))) + { + lua_pushnil(L); + lua_pushstring(L, udp_strerror(err)); + return 2; + } + timeout_markstart(tm); + err = socket_sendto(&udp->sock, data, count, &sent, + ai->ai_addr, ai->ai_addrlen, tm); + freeaddrinfo(ai); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); @@ -220,69 +215,40 @@ static int meth_receive(lua_State *L) { /*-------------------------------------------------------------------------*\ * Receives data and sender from a UDP socket \*-------------------------------------------------------------------------*/ -static int meth_receivefrom(lua_State *L) { - p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); - char buffer[UDP_DATAGRAMSIZE]; - size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); - int err; - p_timeout tm = &udp->tm; - timeout_markstart(tm); - count = MIN(count, sizeof(buffer)); - switch (udp->family) { - case PF_INET: { - struct sockaddr_in addr; - socklen_t addr_len = sizeof(addr); - err = socket_recvfrom(&udp->sock, buffer, count, &got, - (SA *) &addr, &addr_len, tm); - /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ - if (err == IO_CLOSED) - err = IO_DONE; - if (err == IO_DONE) { - char addrstr[INET_ADDRSTRLEN]; - lua_pushlstring(L, buffer, got); - if (!inet_ntop(AF_INET, &addr.sin_addr, - addrstr, sizeof(addrstr))) { - lua_pushnil(L); - lua_pushstring(L, "invalid source address"); - return 2; - } - lua_pushstring(L, addrstr); - lua_pushnumber(L, ntohs(addr.sin_port)); - return 3; - } - break; - } - case PF_INET6: { - struct sockaddr_in6 addr; - socklen_t addr_len = sizeof(addr); - err = socket_recvfrom(&udp->sock, buffer, count, &got, - (SA *) &addr, &addr_len, tm); - /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ - if (err == IO_CLOSED) - err = IO_DONE; - if (err == IO_DONE) { - char addrstr[INET6_ADDRSTRLEN]; - lua_pushlstring(L, buffer, got); - if (!inet_ntop(AF_INET6, &addr.sin6_addr, - addrstr, sizeof(addrstr))) { - lua_pushnil(L); - lua_pushstring(L, "invalid source address"); - return 2; - } - lua_pushstring(L, addrstr); - lua_pushnumber(L, ntohs(addr.sin6_port)); - return 3; - } - break; - } - default: - lua_pushnil(L); - lua_pushfstring(L, "unknown family %d", udp->family); - return 2; - } +static int meth_receivefrom(lua_State *L) +{ + p_udp udp = (p_udp) auxiliar_checkclass(L, "udp{unconnected}", 1); + char buffer[UDP_DATAGRAMSIZE]; + size_t got, count = (size_t) luaL_optnumber(L, 2, sizeof(buffer)); + int err; + p_timeout tm = &udp->tm; + struct sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + char addrstr[INET6_ADDRSTRLEN]; + char portstr[6]; + timeout_markstart(tm); + count = MIN(count, sizeof(buffer)); + err = socket_recvfrom(&udp->sock, buffer, count, &got, + (SA *) &addr, &addr_len, tm); + /* Unlike TCP, recv() of zero is not closed, but a zero-length packet. */ + if (err == IO_CLOSED) + err = IO_DONE; + if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, udp_strerror(err)); return 2; + } + if ((err = getnameinfo((struct sockaddr *)&addr, addr_len, + addrstr, INET6_ADDRSTRLEN, + portstr, 6, + NI_NUMERICHOST | NI_NUMERICSERV))) { + lua_pushnil(L); + lua_pushstring(L, gai_strerror(err)); + return 2; + } + lua_pushstring(L, addrstr); + lua_pushstring(L, portstr); + return 3; } /*-------------------------------------------------------------------------*\ diff --git a/test/ipv6mcasttest.lua b/test/ipv6mcasttest.lua new file mode 100644 index 0000000..8084443 --- /dev/null +++ b/test/ipv6mcasttest.lua @@ -0,0 +1,116 @@ +#!/usr/bin/env lua +-- -*-lua-*- +-- +-- $Id: ipv6mcasttest.lua $ +-- +-- Author: Markus Stenberg +-- +-- Copyright (c) 2012 cisco Systems, Inc. +-- +-- Created: Wed Dec 5 09:55:46 2012 mstenber +-- Last modified: Wed Dec 5 18:33:09 2012 mstenber +-- Edit time: 50 min +-- + +-- exercise the new IPv6 related functionality: + +-- join/leave IPv6 multicast group, send/receive IPv6 message using +-- sendmsg/recvmsg API + +require 'socket' + +local mcast = 'ff02::fb' + +local port = 4242 +local ifname = 'eth2' +--local ifname = 'lo' -- should not work - no multicast on lo in Linux? +local testdata = 'foobar' + +function setup_socket(port) + local s = socket.udp6() + s:setoption('ipv6-v6only', true) + if port + then + s:setoption('reuseaddr', true) + --local r, err = s:setsockname('::1', port) + local r, err = s:setsockname('*', port) + assert(r, 'setsockname failed ' .. tostring(err)) + --print("getsockname", s:getsockname()) + end + assert(tostring(s):find("udp{unconnected}")) + return s +end + +function setup_connected(host, port) + local c = setup_socket() + assert(host and port) + local r, err = c:setpeername(host, port) + assert(r, 'setpeername failed ' .. tostring(err)) + --print('got from setpeername', r, err) + --print("getsockname", c:getsockname()) + assert(tostring(c):find("udp{connected}"), 'not connected', tostring(c)) + --print("getpeername", c:getpeername()) + return c +end + +function my_setoption(s, k, v) + local old = s:getoption(k) + print('before set', k, old) + local r, err = s:setoption(k, v) + assert(r, 'setoption failed ' .. tostring(err)) + local new = s:getoption(k) + print('after set', k, new) + assert(v == new) +end + +-- specific interface +--local ifindex = socket.iface.nametoindex(ifname) + +-- all interfaces +local ifindex = nil + +local s = setup_socket(port) + +local mct = {multiaddr=mcast, interface=ifindex} +local r, err = s:setoption('ipv6-drop-membership', mct) +-- don't check result - it may or may not fail +local r, err = s:setoption('ipv6-add-membership', mct) +assert(r, 'ipv6-add-membership failed', err) + +--local c = setup_connected(mcast .. '%lo', port) +--c:send('foo') +local c = setup_socket() + +-- this seems to be the default anyway? +my_setoption(c, 'ipv6-unicast-hops', 255) +my_setoption(c, 'ipv6-multicast-loop', true) +my_setoption(c, 'ipv6-multicast-hops', 255) +my_setoption(s, 'ipv6-multicast-hops', 255) + +local r, err = c:sendto(testdata, mcast .. '%' .. ifname, port) +assert(r, 'sendto failed') + +-- make sure sending both to mcast address, and link-local +-- address on the chosen interface works +local r, err = c:sendto(testdata, mcast .. '%' .. ifname, port) +assert(r, 'sendto failed (mcast)') + +local data, host, port = s:receivefrom() +assert(data) +print('recvfrom', host, port) + +-- make sure we can send stuff back to link-local addr as well' +local r, err = s:sendto(testdata, host, port) +assert(r, 'sendto failed (ll)') + +-- and it is received too +local data, host, port = c:receivefrom() +assert(data) +print('recvfrom', host, port) + +-- now, finally, we can get rid of mcast group +local r, err = s:setoption('ipv6-drop-membership', mct) +assert(r, 'ipv6-drop-membership failed', err) +--local c = setup_connected(port) + +