netlink: improve RTM_GETADDR handling.
* Allow filtering by ifa_family & ifa_index. * Add common RTM_<NEW|DEL|GET>ADDR parser * Add tests verifying RTM_GETADDR filtering behaviour & output * Factor out common netlink socket test methods into NetlinkTestTemplate * Add NLMSG_DONE message handler Reviewed By: pauamma Differential Revision: https://reviews.freebsd.org/D37970
This commit is contained in:
parent
4ffe60e683
commit
c1871a3372
@ -403,14 +403,27 @@ Not supported
|
|||||||
.Ss RTM_DELADDR
|
.Ss RTM_DELADDR
|
||||||
Not supported
|
Not supported
|
||||||
.Ss RTM_GETADDR
|
.Ss RTM_GETADDR
|
||||||
|
Fetches interface addresses in the current VNET matching conditions.
|
||||||
|
Each address is reported as a
|
||||||
|
.Dv RTM_NEWADDR
|
||||||
|
message.
|
||||||
|
The following filters are recognised by the kernel:
|
||||||
|
.Pp
|
||||||
|
.Bd -literal -offset indent -compact
|
||||||
|
ifa_family required family or AF_UNSPEC
|
||||||
|
ifa_index matching interface index or 0
|
||||||
|
.Ed
|
||||||
.Ss TLVs
|
.Ss TLVs
|
||||||
.Bl -tag -width indent
|
.Bl -tag -width indent
|
||||||
.It Dv IFA_ADDRESS
|
.It Dv IFA_ADDRESS
|
||||||
(binary) masked interface address or destination address for p2p interfaces.
|
(binary) masked interface address or destination address for p2p interfaces.
|
||||||
.It Dv IFA_LOCAL
|
.It Dv IFA_LOCAL
|
||||||
(binary) local interface address
|
(binary) local interface address.
|
||||||
|
Set for IPv4 and p2p addresses.
|
||||||
|
.It Dv IFA_LABEL
|
||||||
|
(string) interface name.
|
||||||
.It Dv IFA_BROADCAST
|
.It Dv IFA_BROADCAST
|
||||||
(binary) broacast interface address
|
(binary) broacast interface address.
|
||||||
.El
|
.El
|
||||||
.Ss Groups
|
.Ss Groups
|
||||||
The following groups are defined:
|
The following groups are defined:
|
||||||
|
@ -672,6 +672,36 @@ rtnl_handle_newlink(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
|
|||||||
return (modify_link(hdr, &attrs, &bm, nlp, npt));
|
return (modify_link(hdr, &attrs, &bm, nlp, npt));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct nl_parsed_ifa {
|
||||||
|
uint8_t ifa_family;
|
||||||
|
uint8_t ifa_prefixlen;
|
||||||
|
uint8_t ifa_scope;
|
||||||
|
uint32_t ifa_index;
|
||||||
|
uint32_t ifa_flags;
|
||||||
|
struct sockaddr *ifa_address;
|
||||||
|
struct sockaddr *ifa_local;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define _IN(_field) offsetof(struct ifaddrmsg, _field)
|
||||||
|
#define _OUT(_field) offsetof(struct nl_parsed_ifa, _field)
|
||||||
|
static const struct nlfield_parser nlf_p_ifa[] = {
|
||||||
|
{ .off_in = _IN(ifa_family), .off_out = _OUT(ifa_family), .cb = nlf_get_u8 },
|
||||||
|
{ .off_in = _IN(ifa_prefixlen), .off_out = _OUT(ifa_prefixlen), .cb = nlf_get_u8 },
|
||||||
|
{ .off_in = _IN(ifa_scope), .off_out = _OUT(ifa_scope), .cb = nlf_get_u8 },
|
||||||
|
{ .off_in = _IN(ifa_flags), .off_out = _OUT(ifa_flags), .cb = nlf_get_u8_u32 },
|
||||||
|
{ .off_in = _IN(ifa_index), .off_out = _OUT(ifa_index), .cb = nlf_get_u32 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct nlattr_parser nla_p_ifa[] = {
|
||||||
|
{ .type = IFA_ADDRESS, .off = _OUT(ifa_address), .cb = nlattr_get_ip },
|
||||||
|
{ .type = IFA_LOCAL, .off = _OUT(ifa_local), .cb = nlattr_get_ip },
|
||||||
|
{ .type = IFA_FLAGS, .off = _OUT(ifa_flags), .cb = nlattr_get_uint32 },
|
||||||
|
};
|
||||||
|
#undef _IN
|
||||||
|
#undef _OUT
|
||||||
|
NL_DECLARE_PARSER(ifaddrmsg_parser, struct ifaddrmsg, nlf_p_ifa, nla_p_ifa);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
{ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")},
|
{ifa_family=AF_INET, ifa_prefixlen=8, ifa_flags=IFA_F_PERMANENT, ifa_scope=RT_SCOPE_HOST, ifa_index=if_nametoindex("lo")},
|
||||||
@ -826,15 +856,39 @@ dump_iface_addr(struct nl_writer *nw, struct ifnet *ifp, struct ifaddr *ifa,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
|
dump_iface_addrs(struct netlink_walkargs *wa, struct ifnet *ifp)
|
||||||
{
|
{
|
||||||
struct ifaddr *ifa;
|
struct ifaddr *ifa;
|
||||||
|
|
||||||
|
CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
|
||||||
|
if (wa->family != 0 && wa->family != ifa->ifa_addr->sa_family)
|
||||||
|
continue;
|
||||||
|
if (ifa->ifa_addr->sa_family == AF_LINK)
|
||||||
|
continue;
|
||||||
|
wa->count++;
|
||||||
|
if (!dump_iface_addr(wa->nw, ifp, ifa, &wa->hdr))
|
||||||
|
return (ENOMEM);
|
||||||
|
wa->dumped++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *npt)
|
||||||
|
{
|
||||||
struct ifnet *ifp;
|
struct ifnet *ifp;
|
||||||
int error = 0;
|
int error = 0;
|
||||||
|
|
||||||
|
struct nl_parsed_ifa attrs = {};
|
||||||
|
error = nl_parse_nlmsg(hdr, &ifaddrmsg_parser, npt, &attrs);
|
||||||
|
if (error != 0)
|
||||||
|
return (error);
|
||||||
|
|
||||||
struct netlink_walkargs wa = {
|
struct netlink_walkargs wa = {
|
||||||
.so = nlp,
|
.so = nlp,
|
||||||
.nw = npt->nw,
|
.nw = npt->nw,
|
||||||
|
.family = attrs.ifa_family,
|
||||||
.hdr.nlmsg_pid = hdr->nlmsg_pid,
|
.hdr.nlmsg_pid = hdr->nlmsg_pid,
|
||||||
.hdr.nlmsg_seq = hdr->nlmsg_seq,
|
.hdr.nlmsg_seq = hdr->nlmsg_seq,
|
||||||
.hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
|
.hdr.nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI,
|
||||||
@ -843,22 +897,19 @@ rtnl_handle_getaddr(struct nlmsghdr *hdr, struct nlpcb *nlp, struct nl_pstate *n
|
|||||||
|
|
||||||
NL_LOG(LOG_DEBUG2, "Start dump");
|
NL_LOG(LOG_DEBUG2, "Start dump");
|
||||||
|
|
||||||
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
|
if (attrs.ifa_index != 0) {
|
||||||
CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
|
ifp = ifnet_byindex(attrs.ifa_index);
|
||||||
if (wa.family != 0 && wa.family != ifa->ifa_addr->sa_family)
|
if (ifp == NULL)
|
||||||
continue;
|
error = ENOENT;
|
||||||
if (ifa->ifa_addr->sa_family == AF_LINK)
|
else
|
||||||
continue;
|
error = dump_iface_addrs(&wa, ifp);
|
||||||
wa.count++;
|
} else {
|
||||||
if (!dump_iface_addr(wa.nw, ifp, ifa, &wa.hdr)) {
|
CK_STAILQ_FOREACH(ifp, &V_ifnet, if_link) {
|
||||||
error = ENOMEM;
|
error = dump_iface_addrs(&wa, ifp);
|
||||||
break;
|
if (error != 0)
|
||||||
}
|
break;
|
||||||
wa.dumped++;
|
}
|
||||||
}
|
}
|
||||||
if (error != 0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped);
|
NL_LOG(LOG_DEBUG2, "End dump, iterated %d dumped %d", wa.count, wa.dumped);
|
||||||
|
|
||||||
@ -991,7 +1042,7 @@ static const struct rtnl_cmd_handler cmd_handlers[] = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser };
|
static const struct nlhdr_parser *all_parsers[] = { &ifmsg_parser, &ifaddrmsg_parser };
|
||||||
|
|
||||||
void
|
void
|
||||||
rtnl_iface_add_cloner(struct nl_cloner *cloner)
|
rtnl_iface_add_cloner(struct nl_cloner *cloner)
|
||||||
|
@ -52,7 +52,7 @@ enum {
|
|||||||
IFA_UNSPEC,
|
IFA_UNSPEC,
|
||||||
IFA_ADDRESS = 1, /* binary, prefix address (destination for p2p) */
|
IFA_ADDRESS = 1, /* binary, prefix address (destination for p2p) */
|
||||||
IFA_LOCAL = 2, /* binary, interface address */
|
IFA_LOCAL = 2, /* binary, interface address */
|
||||||
IFA_LABEL = 3, /* not supported */
|
IFA_LABEL = 3, /* string, interface name */
|
||||||
IFA_BROADCAST = 4, /* binary, broadcast ifa */
|
IFA_BROADCAST = 4, /* binary, broadcast ifa */
|
||||||
IFA_ANYCAST = 5, /* not supported */
|
IFA_ANYCAST = 5, /* not supported */
|
||||||
IFA_CACHEINFO = 6, /* not supported */
|
IFA_CACHEINFO = 6, /* not supported */
|
||||||
|
@ -49,6 +49,12 @@ class Nlmsghdr(Structure):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Nlmsgdone(Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("error", c_int),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Nlmsgerr(Structure):
|
class Nlmsgerr(Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
("error", c_int),
|
("error", c_int),
|
||||||
@ -961,6 +967,8 @@ def prepare_attrs_map(attrs: List[AttrDescr]) -> Dict[str, Dict]:
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
nldone_attrs = []
|
||||||
|
|
||||||
nlerr_attrs = [
|
nlerr_attrs = [
|
||||||
AttrDescr(NlErrattrType.NLMSGERR_ATTR_MSG, NlAttrStr),
|
AttrDescr(NlErrattrType.NLMSGERR_ATTR_MSG, NlAttrStr),
|
||||||
AttrDescr(NlErrattrType.NLMSGERR_ATTR_OFFS, NlAttrU32),
|
AttrDescr(NlErrattrType.NLMSGERR_ATTR_OFFS, NlAttrU32),
|
||||||
@ -989,6 +997,7 @@ def prepare_attrs_map(attrs: List[AttrDescr]) -> Dict[str, Dict]:
|
|||||||
rtnl_ifa_attrs = [
|
rtnl_ifa_attrs = [
|
||||||
AttrDescr(IfattrType.IFA_ADDRESS, NlAttrIp),
|
AttrDescr(IfattrType.IFA_ADDRESS, NlAttrIp),
|
||||||
AttrDescr(IfattrType.IFA_LOCAL, NlAttrIp),
|
AttrDescr(IfattrType.IFA_LOCAL, NlAttrIp),
|
||||||
|
AttrDescr(IfattrType.IFA_LABEL, NlAttrStr),
|
||||||
AttrDescr(IfattrType.IFA_BROADCAST, NlAttrIp),
|
AttrDescr(IfattrType.IFA_BROADCAST, NlAttrIp),
|
||||||
AttrDescr(IfattrType.IFA_ANYCAST, NlAttrIp),
|
AttrDescr(IfattrType.IFA_ANYCAST, NlAttrIp),
|
||||||
AttrDescr(IfattrType.IFA_FLAGS, NlAttrU32),
|
AttrDescr(IfattrType.IFA_FLAGS, NlAttrU32),
|
||||||
@ -1167,6 +1176,25 @@ def print_message(self):
|
|||||||
nla.print_attr(" ")
|
nla.print_attr(" ")
|
||||||
|
|
||||||
|
|
||||||
|
class NetlinkDoneMessage(StdNetlinkMessage):
|
||||||
|
messages = [NlMsgType.NLMSG_DONE.value]
|
||||||
|
nl_attrs_map = prepare_attrs_map(nldone_attrs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_code(self):
|
||||||
|
return self.base_hdr.error
|
||||||
|
|
||||||
|
def parse_base_header(self, data):
|
||||||
|
if len(data) < sizeof(Nlmsgdone):
|
||||||
|
raise ValueError("length less than nlmsgdone header")
|
||||||
|
done_hdr = Nlmsgdone.from_buffer_copy(data)
|
||||||
|
sz = sizeof(Nlmsgdone)
|
||||||
|
return (done_hdr, sz)
|
||||||
|
|
||||||
|
def print_base_header(self, hdr, prepend=""):
|
||||||
|
print("{}error={}".format(prepend, hdr.error))
|
||||||
|
|
||||||
|
|
||||||
class NetlinkErrorMessage(StdNetlinkMessage):
|
class NetlinkErrorMessage(StdNetlinkMessage):
|
||||||
messages = [NlMsgType.NLMSG_ERROR.value]
|
messages = [NlMsgType.NLMSG_ERROR.value]
|
||||||
nl_attrs_map = prepare_attrs_map(nlerr_attrs)
|
nl_attrs_map = prepare_attrs_map(nlerr_attrs)
|
||||||
@ -1340,6 +1368,7 @@ def build_msgmap(self):
|
|||||||
NetlinkRtMessage,
|
NetlinkRtMessage,
|
||||||
NetlinkIflaMessage,
|
NetlinkIflaMessage,
|
||||||
NetlinkIfaMessage,
|
NetlinkIfaMessage,
|
||||||
|
NetlinkDoneMessage,
|
||||||
NetlinkErrorMessage,
|
NetlinkErrorMessage,
|
||||||
]
|
]
|
||||||
xmap = {}
|
xmap = {}
|
||||||
@ -1476,20 +1505,63 @@ def request_families(self):
|
|||||||
self.write_data(msg_bytes)
|
self.write_data(msg_bytes)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
class NetlinkMultipartIterator(object):
|
||||||
helper = NlHelper()
|
def __init__(self, obj, seq_number: int, msg_type):
|
||||||
if False:
|
self._obj = obj
|
||||||
nl = Nlsock(NlConst.NETLINK_GENERIC, helper)
|
self._seq = seq_number
|
||||||
nl.request_families()
|
self._msg_type = msg_type
|
||||||
else:
|
|
||||||
nl = Nlsock(NlConst.NETLINK_ROUTE, helper)
|
def __iter__(self):
|
||||||
# nl.request_ifaddrs(socket.AF_INET)
|
return self
|
||||||
# nl.request_raw()
|
|
||||||
nl.request_routes(0)
|
def __next__(self):
|
||||||
# nl.request_ifaces()
|
msg = self._obj.read_message()
|
||||||
while True:
|
if self._seq != msg.nl_hdr.nlmsg_seq:
|
||||||
msg = nl.read_message()
|
raise ValueError("bad sequence number")
|
||||||
|
if msg.is_type(NlMsgType.NLMSG_ERROR):
|
||||||
|
raise ValueError(
|
||||||
|
"error while handling multipart msg: {}".format(msg.error_code)
|
||||||
|
)
|
||||||
|
elif msg.is_type(NlMsgType.NLMSG_DONE):
|
||||||
|
if msg.error_code == 0:
|
||||||
|
raise StopIteration
|
||||||
|
raise ValueError(
|
||||||
|
"error listing some parts of the multipart msg: {}".format(
|
||||||
|
msg.error_code
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif not msg.is_type(self._msg_type):
|
||||||
|
raise ValueError("bad message type: {}".format(msg))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
class NetlinkTestTemplate(object):
|
||||||
|
REQUIRED_MODULES = ["netlink"]
|
||||||
|
|
||||||
|
def setup_netlink(self, netlink_family: NlConst):
|
||||||
|
self.helper = NlHelper()
|
||||||
|
self.nlsock = Nlsock(netlink_family, self.helper)
|
||||||
|
|
||||||
|
def write_message(self, msg):
|
||||||
print("")
|
print("")
|
||||||
|
print("============= >> TX MESSAGE =============")
|
||||||
msg.print_message()
|
msg.print_message()
|
||||||
msg.print_as_bytes(msg._orig_data, "-- DATA --")
|
self.nlsock.write_data(bytes(msg))
|
||||||
pass
|
msg.print_as_bytes(bytes(msg), "-- DATA --")
|
||||||
|
|
||||||
|
def read_message(self):
|
||||||
|
msg = self.nlsock.read_message()
|
||||||
|
print("")
|
||||||
|
print("============= << RX MESSAGE =============")
|
||||||
|
msg.print_message()
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def get_reply(self, tx_msg):
|
||||||
|
self.write_message(tx_msg)
|
||||||
|
while True:
|
||||||
|
rx_msg = self.read_message()
|
||||||
|
if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq:
|
||||||
|
return rx_msg
|
||||||
|
|
||||||
|
def read_msg_list(self, seq, msg_type):
|
||||||
|
return list(NetlinkMultipartIterator(self, seq, msg_type))
|
||||||
|
@ -7,6 +7,7 @@ TESTSDIR= ${TESTSBASE}/sys/netlink
|
|||||||
|
|
||||||
ATF_TESTS_C += test_snl
|
ATF_TESTS_C += test_snl
|
||||||
ATF_TESTS_PYTEST += test_rtnl_iface.py
|
ATF_TESTS_PYTEST += test_rtnl_iface.py
|
||||||
|
ATF_TESTS_PYTEST += test_rtnl_ifaddr.py
|
||||||
|
|
||||||
CFLAGS+= -I${.CURDIR:H:H:H}
|
CFLAGS+= -I${.CURDIR:H:H:H}
|
||||||
|
|
||||||
|
@ -6,49 +6,24 @@
|
|||||||
from atf_python.sys.net.netlink import IflinkInfo
|
from atf_python.sys.net.netlink import IflinkInfo
|
||||||
from atf_python.sys.net.netlink import IfLinkInfoDataVlan
|
from atf_python.sys.net.netlink import IfLinkInfoDataVlan
|
||||||
from atf_python.sys.net.netlink import NetlinkIflaMessage
|
from atf_python.sys.net.netlink import NetlinkIflaMessage
|
||||||
|
from atf_python.sys.net.netlink import NetlinkTestTemplate
|
||||||
from atf_python.sys.net.netlink import NlAttrNested
|
from atf_python.sys.net.netlink import NlAttrNested
|
||||||
from atf_python.sys.net.netlink import NlAttrStr
|
from atf_python.sys.net.netlink import NlAttrStr
|
||||||
from atf_python.sys.net.netlink import NlAttrStrn
|
from atf_python.sys.net.netlink import NlAttrStrn
|
||||||
from atf_python.sys.net.netlink import NlAttrU16
|
from atf_python.sys.net.netlink import NlAttrU16
|
||||||
from atf_python.sys.net.netlink import NlAttrU32
|
from atf_python.sys.net.netlink import NlAttrU32
|
||||||
from atf_python.sys.net.netlink import NlConst
|
from atf_python.sys.net.netlink import NlConst
|
||||||
from atf_python.sys.net.netlink import NlHelper
|
|
||||||
from atf_python.sys.net.netlink import NlmBaseFlags
|
from atf_python.sys.net.netlink import NlmBaseFlags
|
||||||
from atf_python.sys.net.netlink import NlmNewFlags
|
from atf_python.sys.net.netlink import NlmNewFlags
|
||||||
from atf_python.sys.net.netlink import NlMsgType
|
from atf_python.sys.net.netlink import NlMsgType
|
||||||
from atf_python.sys.net.netlink import NlRtMsgType
|
from atf_python.sys.net.netlink import NlRtMsgType
|
||||||
from atf_python.sys.net.netlink import Nlsock
|
|
||||||
from atf_python.sys.net.vnet import SingleVnetTestTemplate
|
from atf_python.sys.net.vnet import SingleVnetTestTemplate
|
||||||
|
|
||||||
|
|
||||||
class TestRtNlIface(SingleVnetTestTemplate):
|
class TestRtNlIface(SingleVnetTestTemplate, NetlinkTestTemplate):
|
||||||
REQUIRED_MODULES = ["netlink"]
|
|
||||||
|
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
super().setup_method(method)
|
super().setup_method(method)
|
||||||
self.helper = NlHelper()
|
self.setup_netlink(NlConst.NETLINK_ROUTE)
|
||||||
self.nlsock = Nlsock(NlConst.NETLINK_ROUTE, self.helper)
|
|
||||||
|
|
||||||
def write_message(self, msg):
|
|
||||||
print("")
|
|
||||||
print("============= >> TX MESSAGE =============")
|
|
||||||
msg.print_message()
|
|
||||||
self.nlsock.write_data(bytes(msg))
|
|
||||||
msg.print_as_bytes(bytes(msg), "-- DATA --")
|
|
||||||
|
|
||||||
def read_message(self):
|
|
||||||
msg = self.nlsock.read_message()
|
|
||||||
print("")
|
|
||||||
print("============= << RX MESSAGE =============")
|
|
||||||
msg.print_message()
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def get_reply(self, tx_msg):
|
|
||||||
self.write_message(tx_msg)
|
|
||||||
while True:
|
|
||||||
rx_msg = self.read_message()
|
|
||||||
if tx_msg.nl_hdr.nlmsg_seq == rx_msg.nl_hdr.nlmsg_seq:
|
|
||||||
return rx_msg
|
|
||||||
|
|
||||||
def get_interface_byname(self, ifname):
|
def get_interface_byname(self, ifname):
|
||||||
msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value)
|
msg = NetlinkIflaMessage(self.helper, NlRtMsgType.RTM_GETLINK.value)
|
||||||
@ -236,13 +211,11 @@ def test_delete_iface(self):
|
|||||||
# *
|
# *
|
||||||
# * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0},
|
# * {len=76, type=RTM_NEWLINK, flags=NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, seq=1662892737, pid=0},
|
||||||
# * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
|
# * {ifi_family=AF_UNSPEC, ifi_type=ARPHRD_NETROM, ifi_index=0, ifi_flags=0, ifi_change=0},
|
||||||
# * [
|
|
||||||
# * {{nla_len=8, nla_type=IFLA_LINK}, 2},
|
# * {{nla_len=8, nla_type=IFLA_LINK}, 2},
|
||||||
# * {{nla_len=12, nla_type=IFLA_IFNAME}, "xvlan22"},
|
# * {{nla_len=12, nla_type=IFLA_IFNAME}, "xvlan22"},
|
||||||
# * {{nla_len=24, nla_type=IFLA_LINKINFO},
|
# * {{nla_len=24, nla_type=IFLA_LINKINFO},
|
||||||
# * [
|
|
||||||
# * {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...},
|
# * {{nla_len=8, nla_type=IFLA_INFO_KIND}, "vlan"...},
|
||||||
# * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}]}]}, iov_len=76}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 76
|
# * {{nla_len=12, nla_type=IFLA_INFO_DATA}, "\x06\x00\x01\x00\x16\x00\x00\x00"}
|
||||||
# */
|
# */
|
||||||
@pytest.mark.skip(reason="vlan support needs more work")
|
@pytest.mark.skip(reason="vlan support needs more work")
|
||||||
@pytest.mark.require_user("root")
|
@pytest.mark.require_user("root")
|
||||||
|
144
tests/sys/netlink/test_rtnl_ifaddr.py
Normal file
144
tests/sys/netlink/test_rtnl_ifaddr.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import ipaddress
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from atf_python.sys.net.netlink import IfattrType
|
||||||
|
from atf_python.sys.net.netlink import NetlinkIfaMessage
|
||||||
|
from atf_python.sys.net.netlink import NetlinkTestTemplate
|
||||||
|
from atf_python.sys.net.netlink import NlConst
|
||||||
|
from atf_python.sys.net.netlink import NlHelper
|
||||||
|
from atf_python.sys.net.netlink import NlmBaseFlags
|
||||||
|
from atf_python.sys.net.netlink import NlMsgType
|
||||||
|
from atf_python.sys.net.netlink import NlRtMsgType
|
||||||
|
from atf_python.sys.net.netlink import Nlsock
|
||||||
|
from atf_python.sys.net.netlink import RtScope
|
||||||
|
from atf_python.sys.net.vnet import SingleVnetTestTemplate
|
||||||
|
|
||||||
|
|
||||||
|
class TestRtNlIfaddr(SingleVnetTestTemplate, NetlinkTestTemplate):
|
||||||
|
def setup_method(self, method):
|
||||||
|
method_name = method.__name__
|
||||||
|
if "4" in method_name:
|
||||||
|
self.IPV4_PREFIXES = ["192.0.2.1/24"]
|
||||||
|
if "6" in method_name:
|
||||||
|
self.IPV6_PREFIXES = ["2001:db8::1/64"]
|
||||||
|
super().setup_method(method)
|
||||||
|
self.setup_netlink(NlConst.NETLINK_ROUTE)
|
||||||
|
|
||||||
|
def test_46_nofilter(self):
|
||||||
|
"""Tests that listing outputs both IPv4/IPv6 and interfaces"""
|
||||||
|
msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
|
||||||
|
msg.nl_hdr.nlmsg_flags = (
|
||||||
|
NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
|
||||||
|
)
|
||||||
|
self.write_message(msg)
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
|
||||||
|
ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index)
|
||||||
|
family = rx_msg.base_hdr.ifa_family
|
||||||
|
ret.append((ifname, family, rx_msg))
|
||||||
|
|
||||||
|
ifname = "lo0"
|
||||||
|
assert len([r for r in ret if r[0] == ifname]) > 0
|
||||||
|
|
||||||
|
ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1
|
||||||
|
assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2
|
||||||
|
|
||||||
|
def test_46_filter_iface(self):
|
||||||
|
"""Tests that listing outputs both IPv4/IPv6 for the specific interface"""
|
||||||
|
epair_ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
|
||||||
|
msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
|
||||||
|
msg.nl_hdr.nlmsg_flags = (
|
||||||
|
NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
|
||||||
|
)
|
||||||
|
msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname)
|
||||||
|
self.write_message(msg)
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
|
||||||
|
ifname = socket.if_indextoname(rx_msg.base_hdr.ifa_index)
|
||||||
|
family = rx_msg.base_hdr.ifa_family
|
||||||
|
ret.append((ifname, family, rx_msg))
|
||||||
|
|
||||||
|
ifname = epair_ifname
|
||||||
|
assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET]) == 1
|
||||||
|
assert len([r for r in ret if r[0] == ifname and r[1] == socket.AF_INET6]) == 2
|
||||||
|
assert len(ret) == 3
|
||||||
|
|
||||||
|
def filter_iface_family(self, family, num_items):
|
||||||
|
"""Tests that listing outputs IPv4 for the specific interface"""
|
||||||
|
epair_ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
|
||||||
|
msg = NetlinkIfaMessage(self.helper, NlRtMsgType.RTM_GETADDR.value)
|
||||||
|
msg.nl_hdr.nlmsg_flags = (
|
||||||
|
NlmBaseFlags.NLM_F_ACK.value | NlmBaseFlags.NLM_F_REQUEST.value
|
||||||
|
)
|
||||||
|
msg.base_hdr.ifa_family = family
|
||||||
|
msg.base_hdr.ifa_index = socket.if_nametoindex(epair_ifname)
|
||||||
|
self.write_message(msg)
|
||||||
|
|
||||||
|
ret = []
|
||||||
|
for rx_msg in self.read_msg_list(msg.nl_hdr.nlmsg_seq, NlRtMsgType.RTM_NEWADDR):
|
||||||
|
assert family == rx_msg.base_hdr.ifa_family
|
||||||
|
assert epair_ifname == socket.if_indextoname(rx_msg.base_hdr.ifa_index)
|
||||||
|
ret.append(rx_msg)
|
||||||
|
assert len(ret) == num_items
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def test_4_broadcast(self):
|
||||||
|
"""Tests header/attr output for listing IPv4 ifas on broadcast iface"""
|
||||||
|
ret = self.filter_iface_family(socket.AF_INET, 1)
|
||||||
|
# Should be 192.0.2.1/24
|
||||||
|
msg = ret[0]
|
||||||
|
# Family and ifindex has been checked already
|
||||||
|
assert msg.base_hdr.ifa_prefixlen == 24
|
||||||
|
# Ignore IFA_FLAGS for now
|
||||||
|
assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value
|
||||||
|
|
||||||
|
assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "192.0.2.1"
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LOCAL).addr == "192.0.2.1"
|
||||||
|
assert msg.get_nla(IfattrType.IFA_BROADCAST).addr == "192.0.2.255"
|
||||||
|
|
||||||
|
epair_ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname
|
||||||
|
|
||||||
|
def test_6_broadcast(self):
|
||||||
|
"""Tests header/attr output for listing IPv6 ifas on broadcast iface"""
|
||||||
|
ret = self.filter_iface_family(socket.AF_INET6, 2)
|
||||||
|
# Should be 192.0.2.1/24
|
||||||
|
if ret[0].base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value:
|
||||||
|
(gmsg, lmsg) = ret
|
||||||
|
else:
|
||||||
|
(lmsg, gmsg) = ret
|
||||||
|
# Start with global ( 2001:db8::1/64 )
|
||||||
|
msg = gmsg
|
||||||
|
# Family and ifindex has been checked already
|
||||||
|
assert msg.base_hdr.ifa_prefixlen == 64
|
||||||
|
# Ignore IFA_FLAGS for now
|
||||||
|
assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_UNIVERSE.value
|
||||||
|
|
||||||
|
assert msg.get_nla(IfattrType.IFA_ADDRESS).addr == "2001:db8::1"
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LOCAL) is None
|
||||||
|
assert msg.get_nla(IfattrType.IFA_BROADCAST) is None
|
||||||
|
|
||||||
|
epair_ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname
|
||||||
|
|
||||||
|
# Local: fe80::/64
|
||||||
|
msg = lmsg
|
||||||
|
assert msg.base_hdr.ifa_prefixlen == 64
|
||||||
|
# Ignore IFA_FLAGS for now
|
||||||
|
assert msg.base_hdr.ifa_scope == RtScope.RT_SCOPE_LINK.value
|
||||||
|
|
||||||
|
addr = ipaddress.ip_address(msg.get_nla(IfattrType.IFA_ADDRESS).addr)
|
||||||
|
assert addr.is_link_local
|
||||||
|
# Verify that ifindex is not emmbedded
|
||||||
|
assert struct.unpack("!H", addr.packed[2:4])[0] == 0
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LOCAL) is None
|
||||||
|
assert msg.get_nla(IfattrType.IFA_BROADCAST) is None
|
||||||
|
|
||||||
|
epair_ifname = self.vnet.iface_alias_map["if1"].name
|
||||||
|
assert msg.get_nla(IfattrType.IFA_LABEL).text == epair_ifname
|
Loading…
Reference in New Issue
Block a user