[PATCH 0/7] IPv6 scope support and network interface impl

2 views
Skip to first unread message

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:04 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
Adds inet_address and socket_address ipv6 scope awareness.

Also adds the long awaited network interface abstraction
to network stack, which allows introspecting available
network interfaces.
The latter is required to support parsing textual ipv6 scopes,
i.e. "fd00::1::3%eth0", which gives the scope as "index of interface eth0".

Also beware that interface index (on linux) is not as simple as
"first is zero". It is neatly hidden in /proc, and the "scopeid"
reported by ifconfig is not the value you are looking for either.
So interface names are probably easier.
Network interface abstraction is loosely modeled after Java nics,
and expose various properties and most importantly, its addresses.

The current impl does not handle interface changes in runtime,
but the API is designed to do so should the need arise.

Calle Wilund (7):
inet_address/socket_address: Introduce awareness of ipv6 scope
inet_address: break out string parsing code into static helper
net: Add concept of "network interface"
posix_stack: Add network_interface support
native_stack: Add network interface impl
inet_address: Allow %scope ext in parsed address strings
network_interface_test: Small test for network interface functionality

include/seastar/net/api.hh | 32 ++++
include/seastar/net/inet_address.hh | 13 +-
include/seastar/net/posix-stack.hh | 1 +
include/seastar/net/socket_defs.hh | 1 +
include/seastar/net/stack.hh | 16 ++
src/net/dns.cc | 10 +-
src/net/inet_address.cc | 52 ++++--
src/net/native-stack.cc | 63 ++++++++
src/net/posix-stack.cc | 231 +++++++++++++++++++++++++++
src/net/socket_address.cc | 2 +-
src/net/stack.cc | 60 ++++++-
tests/unit/network_interface_test.cc | 81 ++++++++++
tests/unit/CMakeLists.txt | 3 +
13 files changed, 545 insertions(+), 20 deletions(-)
create mode 100644 tests/unit/network_interface_test.cc

--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:05 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
socket_address should ensure to fill this field in in6 struct,
and while not really nice from an abstraction standpoint, we
need to include scope in inet_address as well, as it
conceptually needs to propagate via the non-port part of
address pairs.
---
include/seastar/net/inet_address.hh | 11 +++++++++--
include/seastar/net/socket_defs.hh | 1 +
src/net/inet_address.cc | 18 +++++++++++-------
src/net/socket_address.cc | 2 +-
src/net/stack.cc | 8 +++++++-
5 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/include/seastar/net/inet_address.hh b/include/seastar/net/inet_address.hh
index 79798601..2c0d8931 100644
--- a/include/seastar/net/inet_address.hh
+++ b/include/seastar/net/inet_address.hh
@@ -53,12 +53,15 @@ class inet_address {
::in_addr _in;
::in6_addr _in6;
};
+
+ uint32_t _scope = invalid_scope;
public:
+ static constexpr uint32_t invalid_scope = std::numeric_limits<uint32_t>::max();

inet_address();
inet_address(family);
inet_address(::in_addr i);
- inet_address(::in6_addr i);
+ inet_address(::in6_addr i, uint32_t scope = invalid_scope);
// NOTE: does _not_ resolve the address. Only parses
// ipv4/ipv6 numerical address
inet_address(const sstring&);
@@ -66,7 +69,7 @@ class inet_address {
inet_address(const inet_address&) = default;

inet_address(const ipv4_address&);
- inet_address(const ipv6_address&);
+ inet_address(const ipv6_address&, uint32_t scope = invalid_scope);

// throws iff ipv6
ipv4_address as_ipv4_address() const;
@@ -89,6 +92,10 @@ class inet_address {
size_t size() const;
const void * data() const;

+ uint32_t scope() const {
+ return _scope;
+ }
+
operator ::in_addr() const;
operator ::in6_addr() const;

diff --git a/include/seastar/net/socket_defs.hh b/include/seastar/net/socket_defs.hh
index 6bc41428..0c792b9a 100644
--- a/include/seastar/net/socket_defs.hh
+++ b/include/seastar/net/socket_defs.hh
@@ -57,6 +57,7 @@ class socket_address {
socket_address(uint16_t);
socket_address(ipv4_addr);
socket_address(const ipv6_addr&);
+ socket_address(const ipv6_addr&, uint32_t scope);
socket_address(const net::inet_address&, uint16_t p = 0);
explicit socket_address(const unix_domain_addr&);
socket_address();
diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
index 6c646acf..b6447e82 100644
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
@@ -42,8 +42,8 @@ seastar::net::inet_address::inet_address(::in_addr i)
: _in_family(family::INET), _in(i) {
}

-seastar::net::inet_address::inet_address(::in6_addr i)
- : _in_family(family::INET6), _in6(i) {
+seastar::net::inet_address::inet_address(::in6_addr i, uint32_t scope)
+ : _in_family(family::INET6), _in6(i), _scope(scope) {
}

seastar::net::inet_address::inet_address(const sstring& addr)
@@ -65,12 +65,12 @@ seastar::net::inet_address::inet_address(const ipv4_address& in)
: inet_address(::in_addr{hton(in.ip)})
{}

-seastar::net::inet_address::inet_address(const ipv6_address& in)
+seastar::net::inet_address::inet_address(const ipv6_address& in, uint32_t scope)
: inet_address([&] {
::in6_addr tmp;
std::copy(in.bytes().begin(), in.bytes().end(), tmp.s6_addr);
return tmp;
- }())
+ }(), scope)
{}

seastar::net::ipv4_address seastar::net::inet_address::as_ipv4_address() const {
@@ -92,7 +92,7 @@ bool seastar::net::inet_address::operator==(const inet_address& o) const {
return _in.s_addr == o._in.s_addr;
case family::INET6:
return std::equal(std::begin(_in6.s6_addr), std::end(_in6.s6_addr),
- std::begin(o._in6.s6_addr));
+ std::begin(o._in6.s6_addr)) && _scope == o._scope;
default:
return false;
}
@@ -250,7 +250,7 @@ seastar::net::inet_address seastar::socket_address::addr() const {
case AF_INET:
return net::inet_address(as_posix_sockaddr_in().sin_addr);
case AF_INET6:
- return net::inet_address(as_posix_sockaddr_in6().sin6_addr);
+ return net::inet_address(as_posix_sockaddr_in6().sin6_addr, as_posix_sockaddr_in6().sin6_scope_id);
default:
return net::inet_address();
}
@@ -278,7 +278,11 @@ bool seastar::socket_address::is_wildcard() const {

std::ostream& seastar::net::operator<<(std::ostream& os, const inet_address& addr) {
char buffer[64];
- return os << inet_ntop(int(addr.in_family()), addr.data(), buffer, sizeof(buffer));
+ os << inet_ntop(int(addr.in_family()), addr.data(), buffer, sizeof(buffer));
+ if (addr.scope() != inet_address::invalid_scope) {
+ os << "%" << addr.scope();
+ }
+ return os;
}

std::ostream& seastar::net::operator<<(std::ostream& os, const inet_address::family& f) {
diff --git a/src/net/socket_address.cc b/src/net/socket_address.cc
index 19de5bda..4018d4b0 100644
--- a/src/net/socket_address.cc
+++ b/src/net/socket_address.cc
@@ -43,7 +43,7 @@ size_t std::hash<seastar::socket_address>::operator()(const seastar::socket_addr
namespace seastar {

socket_address::socket_address(const net::inet_address& a, uint16_t p)
- : socket_address(a.is_ipv6() ? socket_address(ipv6_addr(a, p)) : socket_address(ipv4_addr(a, p)))
+ : socket_address(a.is_ipv6() ? socket_address(ipv6_addr(a, p), a.scope()) : socket_address(ipv4_addr(a, p)))
{}

socket_address::socket_address(const unix_domain_addr& s) {
diff --git a/src/net/stack.cc b/src/net/stack.cc
index 417afdbc..467a79b5 100644
--- a/src/net/stack.cc
+++ b/src/net/stack.cc
@@ -282,14 +282,20 @@ socket_address::socket_address(ipv4_addr addr)
u.in.sin_addr.s_addr = htonl(addr.ip);
}

-socket_address::socket_address(const ipv6_addr& addr)
+socket_address::socket_address(const ipv6_addr& addr, uint32_t scope)
{
addr_length = sizeof(::sockaddr_in6);
u.in6.sin6_family = AF_INET6;
u.in6.sin6_port = htons(addr.port);
+ u.in6.sin6_flowinfo = 0;
+ u.in6.sin6_scope_id = scope;
std::copy(addr.ip.begin(), addr.ip.end(), u.in6.sin6_addr.s6_addr);
}

+socket_address::socket_address(const ipv6_addr& addr)
+ : socket_address(addr, net::inet_address::invalid_scope)
+{}
+
socket_address::socket_address(uint32_t ipv4, uint16_t p)
: socket_address(make_ipv4_address(ipv4, p))
{}
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:05 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
And remove duplicated code from dns.cc
---
include/seastar/net/inet_address.hh | 2 ++
src/net/dns.cc | 10 +++-------
src/net/inet_address.cc | 13 +++++++++++--
3 files changed, 16 insertions(+), 9 deletions(-)

diff --git a/include/seastar/net/inet_address.hh b/include/seastar/net/inet_address.hh
index 2c0d8931..654a8aea 100644
--- a/include/seastar/net/inet_address.hh
+++ b/include/seastar/net/inet_address.hh
@@ -108,6 +108,8 @@ class inet_address {
static future<inet_address> find(const sstring&, family);
static future<std::vector<inet_address>> find_all(const sstring&);
static future<std::vector<inet_address>> find_all(const sstring&, family);
+
+ static compat::optional<inet_address> parse_numerical(const sstring&);
};

std::ostream& operator<<(std::ostream&, const inet_address&);
diff --git a/src/net/dns.cc b/src/net/dns.cc
index d97c0d8d..fa1ea878 100644
--- a/src/net/dns.cc
+++ b/src/net/dns.cc
@@ -240,13 +240,9 @@ class net::dns_resolver::impl
dns_log.debug("Query name {} ({})", name, family);

if (!family) {
- ::in_addr in;
- ::in6_addr in6;
- if (::inet_pton(AF_INET, name.c_str(), &in)) {
- return make_ready_future<hostent>(hostent{ {name}, {net::inet_address(in)}});
- }
- if (::inet_pton(AF_INET6, name.c_str(), &in6)) {
- return make_ready_future<hostent>(hostent{ {name}, {net::inet_address(in6)}});
+ auto res = inet_address::parse_numerical(name);
+ if (res) {
+ return make_ready_future<hostent>(hostent{ {name}, {*res}});
}
}

diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
index b6447e82..ffa43f14 100644
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
@@ -46,8 +46,8 @@ seastar::net::inet_address::inet_address(::in6_addr i, uint32_t scope)
: _in_family(family::INET6), _in6(i), _scope(scope) {
}

-seastar::net::inet_address::inet_address(const sstring& addr)
- : inet_address([&addr] {
+seastar::compat::optional<seastar::net::inet_address>
+seastar::net::inet_address::parse_numerical(const sstring& addr) {
inet_address in;
if (::inet_pton(AF_INET, addr.c_str(), &in._in)) {
in._in_family = family::INET;
@@ -57,6 +57,15 @@ seastar::net::inet_address::inet_address(const sstring& addr)
in._in_family = family::INET6;
return in;
}
+ return {};
+}
+
+seastar::net::inet_address::inet_address(const sstring& addr)
+ : inet_address([&addr] {
+ auto res = parse_numerical(addr);
+ if (res) {
+ return std::move(*res);
+ }
throw std::invalid_argument(addr);
}())
{}
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:06 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
Adds a network_interface type, queryable from network_stack.
Default is to return nothing.

Note: The accessor returns a collection by value. This is
intentional so that the api can eventually support interfaces
being added/removed/changed in runtime. I.e. the returned
values represent a snapshot view of available interfaces.
---
include/seastar/net/api.hh | 32 ++++++++++++++++++++++
include/seastar/net/stack.hh | 16 +++++++++++
src/net/stack.cc | 52 ++++++++++++++++++++++++++++++++++++
3 files changed, 100 insertions(+)

diff --git a/include/seastar/net/api.hh b/include/seastar/net/api.hh
index 4ff17341..a0620ac9 100644
--- a/include/seastar/net/api.hh
+++ b/include/seastar/net/api.hh
@@ -137,6 +137,8 @@ class udp_channel {
void close();
};

+class network_interface_impl;
+
} /* namespace net */

/// \addtogroup networking-module
@@ -336,6 +338,29 @@ struct listen_options {
int listen_backlog = 100;
};

+class network_interface {
+private:
+ shared_ptr<net::network_interface_impl> _impl;
+public:
+ network_interface(shared_ptr<net::network_interface_impl>);
+ network_interface(network_interface&&);
+
+ network_interface& operator=(network_interface&&);
+
+ uint32_t index() const;
+ uint32_t mtu() const;
+
+ const sstring& name() const;
+ const sstring& display_name() const;
+ const std::vector<net::inet_address>& addresses() const;
+ const std::vector<uint8_t> hardware_address() const;
+
+ bool is_loopback() const;
+ bool is_virtual() const;
+ bool is_up() const;
+ bool supports_ipv6() const;
+};
+
class network_stack {
public:
virtual ~network_stack() {}
@@ -354,6 +379,13 @@ class network_stack {
virtual bool supports_ipv6() const {
return false;
}
+
+ /**
+ * Returns available network interfaces. This represents a
+ * snapshot of interfaces available at call time, hence the
+ * return by value.
+ */
+ virtual std::vector<network_interface> network_interfaces();
};

}
diff --git a/include/seastar/net/stack.hh b/include/seastar/net/stack.hh
index 5222d0ce..6d717b09 100644
--- a/include/seastar/net/stack.hh
+++ b/include/seastar/net/stack.hh
@@ -96,6 +96,22 @@ class udp_channel_impl {
virtual void close() = 0;
};

+class network_interface_impl {
+public:
+ virtual uint32_t index() const = 0;
+ virtual uint32_t mtu() const = 0;
+
+ virtual const sstring& name() const = 0;
+ virtual const sstring& display_name() const = 0;
+ virtual const std::vector<net::inet_address>& addresses() const = 0;
+ virtual const std::vector<uint8_t> hardware_address() const = 0;
+
+ virtual bool is_loopback() const = 0;
+ virtual bool is_virtual() const = 0;
+ virtual bool is_up() const = 0;
+ virtual bool supports_ipv6() const = 0;
+};
+
/// \endcond

}
diff --git a/src/net/stack.cc b/src/net/stack.cc
index 467a79b5..ccd04493 100644
--- a/src/net/stack.cc
+++ b/src/net/stack.cc
@@ -343,6 +343,54 @@ bool socket_address::operator==(const socket_address& a) const {
return IN6_ARE_ADDR_EQUAL(&in1, &in2);
}

+network_interface::network_interface(shared_ptr<net::network_interface_impl> impl)
+ : _impl(std::move(impl))
+{}
+
+network_interface::network_interface(network_interface&&) = default;
+network_interface& network_interface::operator=(network_interface&&) = default;
+
+uint32_t network_interface::index() const {
+ return _impl->index();
+}
+
+uint32_t network_interface::mtu() const {
+ return _impl->mtu();
+}
+
+const sstring& network_interface::name() const {
+ return _impl->name();
+}
+
+const sstring& network_interface::display_name() const {
+ return _impl->display_name();
+}
+
+const std::vector<net::inet_address>& network_interface::addresses() const {
+ return _impl->addresses();
+}
+
+const std::vector<uint8_t> network_interface::hardware_address() const {
+ return _impl->hardware_address();
+}
+
+bool network_interface::is_loopback() const {
+ return _impl->is_loopback();
+}
+
+bool network_interface::is_virtual() const {
+ return _impl->is_virtual();
+}
+
+bool network_interface::is_up() const {
+ return _impl->is_up();
+}
+
+bool network_interface::supports_ipv6() const {
+ return _impl->supports_ipv6();
+}
+
+
future<connected_socket>
network_stack::connect(socket_address sa, socket_address local, transport proto) {
if (local == socket_address()) {
@@ -353,4 +401,8 @@ network_stack::connect(socket_address sa, socket_address local, transport proto)
});
}

+std::vector<network_interface> network_stack::network_interfaces() {
+ return {};
+}
+
}
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:07 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
Query interfaces using rtlink.

Note: This is currectly only done once on startup/first query.
Future enhancement would be to subscribe to interface changes
via netlink (with all the sync/async nightmare this brings) and
update the base interface set on changes.
---
include/seastar/net/posix-stack.hh | 1 +
src/net/posix-stack.cc | 231 +++++++++++++++++++++++++++++
2 files changed, 232 insertions(+)

diff --git a/include/seastar/net/posix-stack.hh b/include/seastar/net/posix-stack.hh
index 0ebc89ec..87d23c5f 100644
--- a/include/seastar/net/posix-stack.hh
+++ b/include/seastar/net/posix-stack.hh
@@ -237,6 +237,7 @@ class posix_network_stack : public network_stack {
}
virtual bool has_per_core_namespace() override { return _reuseport; };
bool supports_ipv6() const override;
+ std::vector<network_interface> network_interfaces() override;
};

class posix_ap_network_stack : public posix_network_stack {
diff --git a/src/net/posix-stack.cc b/src/net/posix-stack.cc
index 062dff0d..58021d81 100644
--- a/src/net/posix-stack.cc
+++ b/src/net/posix-stack.cc
@@ -20,10 +20,16 @@
*/

#include <random>
+
+#include <linux/if.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
#include <seastar/net/posix-stack.hh>
#include <seastar/net/net.hh>
#include <seastar/net/packet.hh>
#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
#include <seastar/util/std-compat.hh>
#include <netinet/tcp.h>
#include <netinet/sctp.h>
@@ -758,6 +764,231 @@ void register_posix_stack() {
},
true);
}
+
+// nw interface stuff
+
+std::vector<network_interface> posix_network_stack::network_interfaces() {
+ class posix_network_interface_impl : public network_interface_impl {
+ public:
+ uint32_t _index = 0, _mtu = 0;
+ sstring _name, _display_name;
+ std::vector<net::inet_address> _addresses;
+ std::vector<uint8_t> _hardware_address;
+ bool _loopback = false, _virtual = false, _up = false;
+
+ uint32_t index() const override {
+ return _index;
+ }
+ uint32_t mtu() const override {
+ return _mtu;
+ }
+ const sstring& name() const override {
+ return _name;
+ }
+ const sstring& display_name() const override {
+ return _display_name.empty() ? name() : _display_name;
+ }
+ const std::vector<net::inet_address>& addresses() const override {
+ return _addresses;
+ }
+ const std::vector<uint8_t> hardware_address() const override {
+ return _hardware_address;
+ }
+ bool is_loopback() const override {
+ return _loopback;
+ }
+ bool is_virtual() const override {
+ return _virtual;
+ }
+ bool is_up() const override {
+ // TODO: should be checked on query?
+ return _up;
+ }
+ bool supports_ipv6() const override {
+ // TODO: this is not 100% correct.
+ return std::any_of(_addresses.begin(), _addresses.end(), std::mem_fn(&inet_address::is_ipv6));
+ }
+ };
+
+ // For now, keep an immutable set of interfaces created on start, shared across
+ // shards
+ static const std::vector<posix_network_interface_impl> global_interfaces = [] {
+ auto fd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ throw_system_error_on(fd < 0, "could not open netlink socket");
+
+ std::unique_ptr<int, void(*)(int*)> fd_guard(&fd, [](int* p) { ::close(*p); });
+
+ auto pid = ::getpid();
+
+ sockaddr_nl local = { 0, };
+ local.nl_family = AF_NETLINK;
+ local.nl_pid = pid;
+ local.nl_groups = RTMGRP_IPV6_IFADDR|RTMGRP_IPV4_IFADDR;
+
+ throw_system_error_on(bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0, "could not bind netlink socket");
+
+ /* RTNL socket is ready for use, prepare and send requests */
+
+ std::vector<posix_network_interface_impl> res;
+
+ for (auto msg : { RTM_GETLINK, RTM_GETADDR}) {
+ struct nl_req {
+ nlmsghdr hdr;
+ union {
+ rtgenmsg gen;
+ ifaddrmsg addr;
+ };
+ } req = { 0, };
+
+ sockaddr_nl kernel = { 0, };
+ msghdr rtnl_msg = { 0, };
+
+ kernel.nl_family = AF_NETLINK; /* fill-in kernel address (destination of our message) */
+
+ req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
+ req.hdr.nlmsg_type = msg;
+ req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
+ req.hdr.nlmsg_seq = 1;
+ req.hdr.nlmsg_pid = pid;
+
+ if (msg == RTM_GETLINK) {
+ req.gen.rtgen_family = AF_PACKET; /* no preferred AF, we will get *all* interfaces */
+ } else {
+ req.addr.ifa_family = AF_UNSPEC;
+ }
+
+ iovec io;
+
+ io.iov_base = &req;
+ io.iov_len = req.hdr.nlmsg_len;
+
+ rtnl_msg.msg_iov = &io;
+ rtnl_msg.msg_iovlen = 1;
+ rtnl_msg.msg_name = &kernel;
+ rtnl_msg.msg_namelen = sizeof(kernel);
+
+ throw_system_error_on(::sendmsg(fd, (struct msghdr *) &rtnl_msg, 0) < 0, "could not send netlink request");
+ /* parse reply */
+
+ constexpr size_t reply_buffer_size = 8192;
+ char reply[reply_buffer_size];
+
+ bool done = false;
+
+ while (!done) {
+ msghdr rtnl_reply = { 0, };
+ iovec io_reply = { 0, };
+
+ io_reply.iov_base = reply;
+ io_reply.iov_len = reply_buffer_size;
+ rtnl_reply.msg_iov = &io_reply;
+ rtnl_reply.msg_iovlen = 1;
+ rtnl_reply.msg_name = &kernel;
+ rtnl_reply.msg_namelen = sizeof(kernel);
+
+ auto len = ::recvmsg(fd, &rtnl_reply, 0); /* read as much data as fits in the receive buffer */
+ if (len <= 0) {
+ return res;
+ }
+
+ for (auto* msg_ptr = (struct nlmsghdr *) reply; NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len)) {
+ switch(msg_ptr->nlmsg_type) {
+ case NLMSG_DONE: // that is all
+ done = true;
+ break;
+ case RTM_NEWLINK:
+ {
+ auto* iface = reinterpret_cast<const ifinfomsg*>(NLMSG_DATA(msg_ptr));
+ auto ilen = msg_ptr->nlmsg_len - NLMSG_LENGTH(sizeof(ifinfomsg));
+
+ // todo: filter any non-network interfaces (family)
+
+ posix_network_interface_impl nwif;
+
+ nwif._index = iface->ifi_index;
+ nwif._loopback = (iface->ifi_flags & IFF_LOOPBACK) != 0;
+ nwif._up = (iface->ifi_flags & IFF_UP) != 0;
+ #if defined(IFF_802_1Q_VLAN) && defined(IFF_EBRIDGE) && defined(IFF_SLAVE_INACTIVE)
+ nwif._virtual = (iface->ifi_flags & (IFF_802_1Q_VLAN|IFF_EBRIDGE|IFF_SLAVE_INACTIVE)) != 0;
+ #endif
+ for (auto* attribute = IFLA_RTA(iface); RTA_OK(attribute, ilen); attribute = RTA_NEXT(attribute, ilen)) {
+ switch(attribute->rta_type) {
+ case IFLA_IFNAME:
+ nwif._name = reinterpret_cast<const char *>(RTA_DATA(attribute));
+ break;
+ case IFLA_MTU:
+ nwif._mtu = *reinterpret_cast<const uint32_t *>(RTA_DATA(attribute));
+ break;
+ case IFLA_ADDRESS:
+ nwif._hardware_address.assign(reinterpret_cast<const uint8_t *>(RTA_DATA(attribute)), reinterpret_cast<const uint8_t *>(RTA_DATA(attribute)) + RTA_PAYLOAD(attribute));
+ break;
+ default:
+ break;
+ }
+ }
+
+ res.emplace_back(std::move(nwif));
+
+ break;
+ }
+ case RTM_NEWADDR:
+ {
+ auto* addr = reinterpret_cast<const ifaddrmsg*>(NLMSG_DATA(msg_ptr));
+ auto ilen = msg_ptr->nlmsg_len - NLMSG_LENGTH(sizeof(ifaddrmsg));
+
+ for (auto& nwif : res) {
+ if (nwif._index == addr->ifa_index) {
+ for (auto* attribute = IFA_RTA(addr); RTA_OK(attribute, ilen); attribute = RTA_NEXT(attribute, ilen)) {
+ compat::optional<inet_address> ia;
+
+ switch(attribute->rta_type) {
+ case IFA_LOCAL:
+ case IFA_ADDRESS: // ipv6 addresses are reported only as "ADDRESS"
+
+ if (RTA_PAYLOAD(attribute) == sizeof(::in_addr)) {
+ ia.emplace(*reinterpret_cast<const ::in_addr *>(RTA_DATA(attribute)));
+ } else if (RTA_PAYLOAD(attribute) == sizeof(::in6_addr)) {
+ ia.emplace(*reinterpret_cast<const ::in6_addr *>(RTA_DATA(attribute)), nwif.index());
+ }
+
+ if (ia && std::find(nwif._addresses.begin(), nwif._addresses.end(), *ia) == nwif._addresses.end()) {
+ nwif._addresses.emplace_back(*ia);
+ }
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ return res;
+ }();
+
+ // And a similarly immutable set of shared_ptr to network_interface_impl per shard, ready
+ // to be handed out to callers with minimal overhead
+ static const thread_local std::vector<shared_ptr<posix_network_interface_impl>> thread_local_interfaces = [] {
+ std::vector<shared_ptr<posix_network_interface_impl>> res;
+ res.reserve(global_interfaces.size());
+ std::transform(global_interfaces.begin(), global_interfaces.end(), std::back_inserter(res), [](const posix_network_interface_impl& impl) {
+ return make_shared<posix_network_interface_impl>(impl);
+ });
+ return res;
+ }();
+
+ return std::vector<network_interface>(thread_local_interfaces.begin(), thread_local_interfaces.end());
+}
+
}

}
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:08 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
Not very interesting, but for completeness.
---
src/net/native-stack.cc | 63 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)

diff --git a/src/net/native-stack.cc b/src/net/native-stack.cc
index b6560bbc..10b718cc 100644
--- a/src/net/native-stack.cc
+++ b/src/net/native-stack.cc
@@ -172,6 +172,11 @@ class native_network_stack : public network_stack {
_inet.learn(l2, l3);
}
friend class native_server_socket_impl<tcp4>;
+
+ class native_network_interface;
+ friend class native_network_interface;
+
+ std::vector<network_interface> network_interfaces() override;
};

thread_local promise<std::unique_ptr<network_stack>> native_network_stack::ready_promise;
@@ -339,6 +344,64 @@ boost::program_options::options_description nns_options() {
void register_native_stack() {
register_network_stack("native", nns_options(), native_network_stack::create);
}
+
+class native_network_stack::native_network_interface : public net::network_interface_impl {
+ const native_network_stack& _stack;
+ std::vector<net::inet_address> _addresses;
+ std::vector<uint8_t> _hardware_address;
+public:
+ native_network_interface(const native_network_stack& stack)
+ : _stack(stack)
+ , _addresses(1, _stack._inet.host_address())
+ , _hardware_address(_stack._inet.netif()->hw_address().mac.begin(), _stack._inet.netif()->hw_address().mac.end())
+ {}
+ native_network_interface(const native_network_interface&) = default;
+
+ uint32_t index() const override {
+ return 0;
+ }
+ uint32_t mtu() const override {
+ return _stack._inet.netif()->hw_features().mtu;
+ }
+ const sstring& name() const override {
+ static const sstring name = "if0";
+ return name;
+ }
+ const sstring& display_name() const override {
+ return name();
+ }
+ const std::vector<net::inet_address>& addresses() const override {
+ return _addresses;
+ }
+ const std::vector<uint8_t> hardware_address() const override {
+ return _hardware_address;
+ }
+ bool is_loopback() const override {
+ return false;
+ }
+ bool is_virtual() const override {
+ return false;
+ }
+ bool is_up() const override {
+ return true;
+ }
+ bool supports_ipv6() const override {
+ return false;
+ }
+};
+
+std::vector<network_interface> native_network_stack::network_interfaces() {
+ if (!_inet.netif()) {
+ return {};
+ }
+
+ static const native_network_interface nwif(*this);
+
+ std::vector<network_interface> res;
+ res.emplace_back(make_shared<native_network_interface>(nwif));
+ return res;
+}
+
}

}
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:09 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
Allows including scope in network address string
---
src/net/inet_address.cc | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)

diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
index ffa43f14..093280c8 100644
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
@@ -53,6 +53,27 @@ seastar::net::inet_address::parse_numerical(const sstring& addr) {
in._in_family = family::INET;
return in;
}
+ auto i = addr.find_last_of('%');
+ if (i != sstring::npos) {
+ auto ext = addr.substr(i + 1);
+ auto src = addr.substr(0, i);
+ auto res = parse_numerical(src);
+
+ if (res) {
+ uint32_t index = std::numeric_limits<uint32_t>::max();
+ try {
+ index = std::stoul(ext);
+ } catch (...) {
+ }
+ for (auto& nwif : engine().net().network_interfaces()) {
+ if (nwif.index() == index || nwif.name() == ext || nwif.display_name() == ext) {
+ res->_scope = nwif.index();
+ break;
+ }
+ }
+ return *res;
+ }
+ }
if (::inet_pton(AF_INET6, addr.c_str(), &in._in6)) {
in._in_family = family::INET6;
return in;
--
2.21.0

Calle Wilund

<calle@scylladb.com>
unread,
Oct 21, 2019, 6:01:10 AM10/21/19
to seastar-dev@googlegroups.com, Calle Wilund
---
tests/unit/network_interface_test.cc | 81 ++++++++++++++++++++++++++++
tests/unit/CMakeLists.txt | 3 ++
2 files changed, 84 insertions(+)
create mode 100644 tests/unit/network_interface_test.cc

diff --git a/tests/unit/network_interface_test.cc b/tests/unit/network_interface_test.cc
new file mode 100644
index 00000000..b061e399
--- /dev/null
+++ b/tests/unit/network_interface_test.cc
@@ -0,0 +1,81 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding copyright
+ * ownership. You may not use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/net/ethernet.hh>
+#include <seastar/net/ip.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger niflog("network_interface_test");
+
+SEASTAR_TEST_CASE(list_interfaces) {
+ // just verifying we have something. And can access all the stuff.
+ auto interfaces = engine().net().network_interfaces();
+ BOOST_REQUIRE_GT(interfaces.size(), 0);
+
+ for (auto& nif : interfaces) {
+ niflog.info("Iface: {}, index = {}, mtu = {}, loopback = {}, virtual = {}, up = {}",
+ nif.name(), nif.index(), nif.mtu(), nif.is_loopback(), nif.is_virtual(), nif.is_up()
+ );
+ if (nif.hardware_address().size() >= 6) {
+ niflog.info(" HW: {}", net::ethernet_address(nif.hardware_address().data()));
+ }
+ for (auto& addr : nif.addresses()) {
+ niflog.info(" Addr: {}", addr);
+ }
+ }
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(match_ipv6_scope) {
+ auto interfaces = engine().net().network_interfaces();
+
+ for (auto& nif : interfaces) {
+ if (nif.is_loopback()) {
+ continue;
+ }
+ auto i = std::find_if(nif.addresses().begin(), nif.addresses().end(), std::mem_fn(&net::inet_address::is_ipv6));
+ if (i == nif.addresses().end()) {
+ continue;
+ }
+
+ std::ostringstream ss;
+ ss << net::inet_address(i->as_ipv6_address()) << "%" << nif.name();
+ auto text = ss.str();
+
+ net::inet_address na(text);
+
+ BOOST_REQUIRE_EQUAL(na.as_ipv6_address(), i->as_ipv6_address());
+ BOOST_REQUIRE_EQUAL(na.scope(), nif.index());
+
+ niflog.info("Org: {}, Parsed: {}, Text: {}", *i, na, text);
+
+ }
+
+ return make_ready_future();
+}
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 67be5eed..d2e9c0c4 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -306,6 +306,9 @@ seastar_add_test (httpd
seastar_add_test (ipv6
SOURCES ipv6_test.cc)

+seastar_add_test (network_interface
+ SOURCES network_interface_test.cc)
+
seastar_add_test (json_formatter
SOURCES json_formatter_test.cc)

--
2.21.0

Shlomi Livne

<shlomi@scylladb.com>
unread,
Oct 21, 2019, 2:02:54 PM10/21/19
to Calle Wilund, Gleb Natapov, seastar-dev
Gleb please review 

--
You received this message because you are subscribed to the Google Groups "seastar-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to seastar-dev...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/seastar-dev/20191021100045.18830-1-calle%40scylladb.com.

Gleb Natapov

<gleb@scylladb.com>
unread,
Oct 22, 2019, 10:38:09 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
On Mon, Oct 21, 2019 at 10:00:39AM +0000, Calle Wilund wrote:
> socket_address should ensure to fill this field in in6 struct,
> and while not really nice from an abstraction standpoint, we
> need to include scope in inet_address as well, as it
> conceptually needs to propagate via the non-port part of
> address pairs.
> ---
> include/seastar/net/inet_address.hh | 11 +++++++++--
> include/seastar/net/socket_defs.hh | 1 +
> src/net/inet_address.cc | 18 +++++++++++-------
> src/net/socket_address.cc | 2 +-
> src/net/stack.cc | 8 +++++++-
> 5 files changed, 29 insertions(+), 11 deletions(-)
>
> diff --git a/include/seastar/net/inet_address.hh b/include/seastar/net/inet_address.hh
> index 79798601..2c0d8931 100644
> --- a/include/seastar/net/inet_address.hh
> +++ b/include/seastar/net/inet_address.hh
> @@ -53,12 +53,15 @@ class inet_address {
> ::in_addr _in;
> ::in6_addr _in6;
> };
> +
> + uint32_t _scope = invalid_scope;
How can you use invalid_scope here before it is defined below?
> --
> You received this message because you are subscribed to the Google Groups "seastar-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to seastar-dev...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/seastar-dev/20191021100045.18830-2-calle%40scylladb.com.

--
Gleb.

Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 10:44:35 AM10/22/19
to Gleb Natapov, seastar-dev@googlegroups.com

Den 2019-10-22 kl. 16:38, skrev Gleb Natapov:
> On Mon, Oct 21, 2019 at 10:00:39AM +0000, Calle Wilund wrote:
>> socket_address should ensure to fill this field in in6 struct,
>> and while not really nice from an abstraction standpoint, we
>> need to include scope in inet_address as well, as it
>> conceptually needs to propagate via the non-port part of
>> address pairs.
>> ---
>> include/seastar/net/inet_address.hh | 11 +++++++++--
>> include/seastar/net/socket_defs.hh | 1 +
>> src/net/inet_address.cc | 18 +++++++++++-------
>> src/net/socket_address.cc | 2 +-
>> src/net/stack.cc | 8 +++++++-
>> 5 files changed, 29 insertions(+), 11 deletions(-)
>>
>> diff --git a/include/seastar/net/inet_address.hh b/include/seastar/net/inet_address.hh
>> index 79798601..2c0d8931 100644
>> --- a/include/seastar/net/inet_address.hh
>> +++ b/include/seastar/net/inet_address.hh
>> @@ -53,12 +53,15 @@ class inet_address {
>> ::in_addr _in;
>> ::in6_addr _in6;
>> };
>> +
>> + uint32_t _scope = invalid_scope;
> How can you use invalid_scope here before it is defined below?
statics, unlike regular constexpr are accessible downwards as well. same
as accessing a method declared below the inline using it.

Gleb Natapov

<gleb@scylladb.com>
unread,
Oct 22, 2019, 10:47:57 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
On Mon, Oct 21, 2019 at 10:00:42AM +0000, Calle Wilund wrote:
> Query interfaces using rtlink.
>
Anyone successfully using rtlink gets my respect.
I hope recvmsg never blocks on a netlink socket.
> --
> You received this message because you are subscribed to the Google Groups "seastar-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to seastar-dev...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/seastar-dev/20191021100045.18830-5-calle%40scylladb.com.

--
Gleb.

Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 10:49:46 AM10/22/19
to Gleb Natapov, seastar-dev@googlegroups.com
No, the retrieve is instantaneous, with the added bonus of being a
weird, bordeline type-less api using sockets for buffered ioctl stuff.
Hooray for linux.

Gleb Natapov

<gleb@scylladb.com>
unread,
Oct 22, 2019, 10:53:00 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
On Tue, Oct 22, 2019 at 04:49:42PM +0200, Calle Wilund wrote:
> > > + auto len = ::recvmsg(fd, &rtnl_reply, 0); /* read as much data as fits in the receive buffer */
> > I hope recvmsg never blocks on a netlink socket.
> No, the retrieve is instantaneous, with the added bonus of being a weird,
> bordeline type-less api using sockets for buffered ioctl stuff. Hooray for
> linux.
Blame Alexey Kuznetsov for that.

--
Gleb.

Gleb Natapov

<gleb@scylladb.com>
unread,
Oct 22, 2019, 10:55:15 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
LGTM.

On Mon, Oct 21, 2019 at 10:00:38AM +0000, Calle Wilund wrote:
> Adds inet_address and socket_address ipv6 scope awareness.
>
> Also adds the long awaited network interface abstraction
> to network stack, which allows introspecting available
> network interfaces.
> The latter is required to support parsing textual ipv6 scopes,
> i.e. "fd00::1::3%eth0", which gives the scope as "index of interface eth0".
As far as I see it also supports specifying an actual index instead of a
name.
> --
> You received this message because you are subscribed to the Google Groups "seastar-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to seastar-dev...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/seastar-dev/20191021100045.18830-1-calle%40scylladb.com.

--
Gleb.

Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 10:56:30 AM10/22/19
to Gleb Natapov, seastar-dev@googlegroups.com

Den 2019-10-22 kl. 16:55, skrev Gleb Natapov:
> LGTM.
>
> On Mon, Oct 21, 2019 at 10:00:38AM +0000, Calle Wilund wrote:
>> Adds inet_address and socket_address ipv6 scope awareness.
>>
>> Also adds the long awaited network interface abstraction
>> to network stack, which allows introspecting available
>> network interfaces.
>> The latter is required to support parsing textual ipv6 scopes,
>> i.e. "fd00::1::3%eth0", which gives the scope as "index of interface eth0".
> As far as I see it also supports specifying an actual index instead of a
> name.
Yes, the wording is perhaps unclear. It accepts either, but as I pointed
out below, knowing the interface index is not trivial, since it is
rather well hidden.

Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 11:15:39 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
Why are all these virtual? They may as well be pure data members. In
fact they can be part of network_interface itself. Why would different
stacks implement them differently?


> +
>
> future<connected_socket>
> network_stack::connect(socket_address sa, socket_address local, transport proto) {
> if (local == socket_address()) {
> @@ -353,4 +401,8 @@ network_stack::connect(socket_address sa, socket_address local, transport proto)
> });
> }
>
> +std::vector<network_interface> network_stack::network_interfaces() {
> + return {};
> +}
> +


Let's make it pure virtual and do a minimal-effort native implementation
using native_network_stack::_netif / native_network_stack::_inet.


> }

Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 11:16:29 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
Oh, you did.


Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 11:18:44 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com

On 22/10/2019 18.15, Avi Kivity wrote:
>
In fact network_interface itself can be a plain struct. It precludes
adding operations later, so maybe call it network_interface_description.

Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 11:20:28 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com

On 21/10/2019 13.00, Calle Wilund wrote:
I'm sensing a potential problem with display_name() of on interface
aliasing name() of another, but don't see a huge need to solve it.

Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 11:24:15 AM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com

On 21/10/2019 13.00, Calle Wilund wrote:
> socket_address should ensure to fill this field in in6 struct,
> and while not really nice from an abstraction standpoint, we
> need to include scope in inet_address as well, as it
> conceptually needs to propagate via the non-port part of
> address pairs.
> ---
> include/seastar/net/inet_address.hh | 11 +++++++++--
> include/seastar/net/socket_defs.hh | 1 +
> src/net/inet_address.cc | 18 +++++++++++-------
> src/net/socket_address.cc | 2 +-
> src/net/stack.cc | 8 +++++++-
> 5 files changed, 29 insertions(+), 11 deletions(-)
>
> diff --git a/include/seastar/net/inet_address.hh b/include/seastar/net/inet_address.hh
> index 79798601..2c0d8931 100644
> --- a/include/seastar/net/inet_address.hh
> +++ b/include/seastar/net/inet_address.hh
> @@ -53,12 +53,15 @@ class inet_address {
> ::in_addr _in;
> ::in6_addr _in6;
> };
> +
> + uint32_t _scope = invalid_scope;
> public:
> + static constexpr uint32_t invalid_scope = std::numeric_limits<uint32_t>::max();


Does Linux recognize this invalid_scope and treat it as unspecified scope?


Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 11:45:17 AM10/22/19
to Avi Kivity, seastar-dev@googlegroups.com
No. It is just very unlikely to coincide with an existing, valid scope.
So if you use an address that requires a scope on this machine, it will
probably not accidentally work (which _might_ _maybe_ be the case if we
used default zero or something).

Note that scope is ignored for anything that is not link-local.

Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 11:53:47 AM10/22/19
to Avi Kivity, seastar-dev@googlegroups.com
The idea is that at least "is_up" could change in runtime. Sure, it is
not handled now, but I wanted to define this reasonably future-proof.


> In fact network_interface itself can be a plain struct. It precludes
> adding operations later, so maybe call it network_interface_description.
>
That sort of collides with the reason interfaces are returned by value.
Namely that the set can in truth change during application runtime, and
thus we might want to give back a differing set on each query.

Can obviously be done with pure structs, but this makes the by-value
cheaper, since we only need to copy shared_ptrs, not full object. Not
that it is a hot path, but since it is not, having virtuals does not
disturb either. And follows the general design pattern of our network
objects.

While network_interface -> impl design pattern could work as well having
the impl holding at least some of there as fields, it is not a very high
overhead with the calls.

Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 12:26:38 PM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
Ah, that's what I was missing. I think the patches make the right
choices then.


Avi Kivity

<avi@scylladb.com>
unread,
Oct 22, 2019, 12:29:09 PM10/22/19
to Calle Wilund, seastar-dev@googlegroups.com
Makes sense. But couldn't network_stack write to the property?
Everything would have to be const from the user's perspective.


>
>> In fact network_interface itself can be a plain struct. It precludes
>> adding operations later, so maybe call it network_interface_description.
>>
> That sort of collides with the reason interfaces are returned by
> value. Namely that the set can in truth change during application
> runtime, and thus we might want to give back a differing set on each
> query.
>
> Can obviously be done with pure structs, but this makes the by-value
> cheaper, since we only need to copy shared_ptrs, not full object. Not
> that it is a hot path, but since it is not, having virtuals does not
> disturb either. And follows the general design pattern of our network
> objects.
>
> While network_interface -> impl design pattern could work as well
> having the impl holding at least some of there as fields, it is not a
> very high overhead with the calls.
>

It can stay as is then. With everything wrapped in accessors, we can
change the implementation if/when we want to.




Calle Wilund

<calle@scylladb.com>
unread,
Oct 22, 2019, 12:34:13 PM10/22/19
to Avi Kivity, seastar-dev@googlegroups.com
It can, and in an impl it probably will. But I would prefer to make this
something handled on the stack level, mainly to ensure that the impl
interface still is immutable, while a stack can use mutable objects for
providing the view. (Which is btw exactly how posix stack builds the
interface set).

Shlomi Livne

<shlomi@scylladb.com>
unread,
Oct 23, 2019, 5:36:31 AM10/23/19
to Calle Wilund, Avi Kivity, Gleb Natapov, seastar-dev
Avi 

I chatted with Calle - he is not aware of anything you requested that was not explained or OKd

Are we missing something that you are waiting on ?

Shlomi

Avi Kivity

<avi@scylladb.com>
unread,
Oct 23, 2019, 5:42:03 AM10/23/19
to Calle Wilund, seastar-dev@googlegroups.com
Please supply a git url.

On 21/10/2019 13.00, Calle Wilund wrote:
> Adds inet_address and socket_address ipv6 scope awareness.
>
> Also adds the long awaited network interface abstraction
> to network stack, which allows introspecting available
> network interfaces.
> The latter is required to support parsing textual ipv6 scopes,
> i.e. "fd00::1::3%eth0", which gives the scope as "index of interface eth0".
>

Calle Wilund

<calle@scylladb.com>
unread,
Oct 23, 2019, 5:43:42 AM10/23/19
to Avi Kivity, seastar-dev@googlegroups.com
Are you ok with one with both this and the follow-up patch? (connect->local)

Calle Wilund

<calle@scylladb.com>
unread,
Oct 23, 2019, 5:52:42 AM10/23/19
to Avi Kivity, seastar-dev@googlegroups.com
Never mind, seastar-dev: calle/ipv6_scope

Den 2019-10-23 kl. 11:41, skrev Avi Kivity:

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:16 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

inet_address/socket_address: Introduce awareness of ipv6 scope

socket_address should ensure to fill this field in in6 struct,
and while not really nice from an abstraction standpoint, we
need to include scope in inet_address as well, as it
conceptually needs to propagate via the non-port part of
address pairs.

---
diff --git a/include/seastar/net/inet_address.hh
b/include/seastar/net/inet_address.hh
--- a/include/seastar/net/inet_address.hh
+++ b/include/seastar/net/inet_address.hh
@@ -53,20 +53,23 @@ private:
::in_addr _in;
::in6_addr _in6;
};
+
+ uint32_t _scope = invalid_scope;
public:
+ static constexpr uint32_t invalid_scope =
std::numeric_limits<uint32_t>::max();

inet_address();
inet_address(family);
inet_address(::in_addr i);
- inet_address(::in6_addr i);
+ inet_address(::in6_addr i, uint32_t scope = invalid_scope);
// NOTE: does _not_ resolve the address. Only parses
// ipv4/ipv6 numerical address
inet_address(const sstring&);
inet_address(inet_address&&) = default;
inet_address(const inet_address&) = default;

inet_address(const ipv4_address&);
- inet_address(const ipv6_address&);
+ inet_address(const ipv6_address&, uint32_t scope = invalid_scope);

// throws iff ipv6
ipv4_address as_ipv4_address() const;
@@ -89,6 +92,10 @@ public:
size_t size() const;
const void * data() const;

+ uint32_t scope() const {
+ return _scope;
+ }
+
operator ::in_addr() const;
operator ::in6_addr() const;

diff --git a/include/seastar/net/socket_defs.hh
b/include/seastar/net/socket_defs.hh
--- a/include/seastar/net/socket_defs.hh
+++ b/include/seastar/net/socket_defs.hh
@@ -57,6 +57,7 @@ public:
socket_address(uint16_t);
socket_address(ipv4_addr);
socket_address(const ipv6_addr&);
+ socket_address(const ipv6_addr&, uint32_t scope);
socket_address(const net::inet_address&, uint16_t p = 0);
explicit socket_address(const unix_domain_addr&);
socket_address();
diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
--- a/src/net/socket_address.cc
+++ b/src/net/socket_address.cc
@@ -43,7 +43,7 @@ size_t
std::hash<seastar::socket_address>::operator()(const seastar::socket_addr
namespace seastar {

socket_address::socket_address(const net::inet_address& a, uint16_t p)
- : socket_address(a.is_ipv6() ? socket_address(ipv6_addr(a, p)) :
socket_address(ipv4_addr(a, p)))
+ : socket_address(a.is_ipv6() ? socket_address(ipv6_addr(a, p),
a.scope()) : socket_address(ipv4_addr(a, p)))
{}

socket_address::socket_address(const unix_domain_addr& s) {
diff --git a/src/net/stack.cc b/src/net/stack.cc

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:17 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

inet_address: break out string parsing code into static helper

And remove duplicated code from dns.cc

---
diff --git a/include/seastar/net/inet_address.hh
b/include/seastar/net/inet_address.hh
--- a/include/seastar/net/inet_address.hh
+++ b/include/seastar/net/inet_address.hh
@@ -108,6 +108,8 @@ public:
static future<inet_address> find(const sstring&, family);
static future<std::vector<inet_address>> find_all(const sstring&);
static future<std::vector<inet_address>> find_all(const sstring&,
family);
+
+ static compat::optional<inet_address> parse_numerical(const sstring&);
};

std::ostream& operator<<(std::ostream&, const inet_address&);
diff --git a/src/net/dns.cc b/src/net/dns.cc
--- a/src/net/dns.cc
+++ b/src/net/dns.cc
@@ -240,13 +240,9 @@ class net::dns_resolver::impl
dns_log.debug("Query name {} ({})", name, family);

if (!family) {
- ::in_addr in;
- ::in6_addr in6;
- if (::inet_pton(AF_INET, name.c_str(), &in)) {
- return make_ready_future<hostent>(hostent{ {name},
{net::inet_address(in)}});
- }
- if (::inet_pton(AF_INET6, name.c_str(), &in6)) {
- return make_ready_future<hostent>(hostent{ {name},
{net::inet_address(in6)}});
+ auto res = inet_address::parse_numerical(name);
+ if (res) {
+ return make_ready_future<hostent>(hostent{ {name},
{*res}});
}
}

diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
@@ -46,8 +46,8 @@ seastar::net::inet_address::inet_address(::in6_addr i,
uint32_t scope)
: _in_family(family::INET6), _in6(i), _scope(scope) {
}

-seastar::net::inet_address::inet_address(const sstring& addr)
- : inet_address([&addr] {
+seastar::compat::optional<seastar::net::inet_address>
+seastar::net::inet_address::parse_numerical(const sstring& addr) {
inet_address in;
if (::inet_pton(AF_INET, addr.c_str(), &in._in)) {
in._in_family = family::INET;
@@ -57,6 +57,15 @@ seastar::net::inet_address::inet_address(const sstring&
addr)
in._in_family = family::INET6;
return in;
}
+ return {};
+}
+
+seastar::net::inet_address::inet_address(const sstring& addr)
+ : inet_address([&addr] {
+ auto res = parse_numerical(addr);
+ if (res) {
+ return std::move(*res);
+ }
throw std::invalid_argument(addr);
}())
{}

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:19 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

net: Add concept of "network interface"

Adds a network_interface type, queryable from network_stack.
Default is to return nothing.

Note: The accessor returns a collection by value. This is
intentional so that the api can eventually support interfaces
being added/removed/changed in runtime. I.e. the returned
values represent a snapshot view of available interfaces.

---
diff --git a/include/seastar/net/api.hh b/include/seastar/net/api.hh
--- a/include/seastar/net/api.hh
+++ b/include/seastar/net/api.hh
@@ -137,6 +137,8 @@ public:
@@ -354,6 +379,13 @@ public:
virtual bool supports_ipv6() const {
return false;
}
+
+ /**
+ * Returns available network interfaces. This represents a
+ * snapshot of interfaces available at call time, hence the
+ * return by value.
+ */
+ virtual std::vector<network_interface> network_interfaces();
};

}
diff --git a/include/seastar/net/stack.hh b/include/seastar/net/stack.hh
--- a/include/seastar/net/stack.hh
+++ b/include/seastar/net/stack.hh
@@ -96,6 +96,22 @@ public:
virtual void close() = 0;
};

+class network_interface_impl {
+public:
+ virtual uint32_t index() const = 0;
+ virtual uint32_t mtu() const = 0;
+
+ virtual const sstring& name() const = 0;
+ virtual const sstring& display_name() const = 0;
+ virtual const std::vector<net::inet_address>& addresses() const = 0;
+ virtual const std::vector<uint8_t> hardware_address() const = 0;
+
+ virtual bool is_loopback() const = 0;
+ virtual bool is_virtual() const = 0;
+ virtual bool is_up() const = 0;
+ virtual bool supports_ipv6() const = 0;
+};
+
/// \endcond

}
diff --git a/src/net/stack.cc b/src/net/stack.cc
--- a/src/net/stack.cc
+++ b/src/net/stack.cc
@@ -343,6 +343,54 @@ bool socket_address::operator==(const socket_address&
a) const {
return IN6_ARE_ADDR_EQUAL(&in1, &in2);
}

+network_interface::network_interface(shared_ptr<net::network_interface_impl>
impl)
+ : _impl(std::move(impl))
+{}
+
+network_interface::network_interface(network_interface&&) = default;
+network_interface& network_interface::operator=(network_interface&&) =
default;
+
+uint32_t network_interface::index() const {
+ return _impl->index();
+}
+
+uint32_t network_interface::mtu() const {
+ return _impl->mtu();
+}
+
+const sstring& network_interface::name() const {
+ return _impl->name();
+}
+
+const sstring& network_interface::display_name() const {
+ return _impl->display_name();
+}
+
+const std::vector<net::inet_address>& network_interface::addresses() const
{
+ return _impl->addresses();
+}
+
+const std::vector<uint8_t> network_interface::hardware_address() const {
+ return _impl->hardware_address();
+}
+
+bool network_interface::is_loopback() const {
+ return _impl->is_loopback();
+}
+
+bool network_interface::is_virtual() const {
+ return _impl->is_virtual();
+}
+
+bool network_interface::is_up() const {
+ return _impl->is_up();
+}
+
+bool network_interface::supports_ipv6() const {
+ return _impl->supports_ipv6();
+}
+

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:20 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

posix_stack: Add network_interface support

Query interfaces using rtlink.

Note: This is currectly only done once on startup/first query.
Future enhancement would be to subscribe to interface changes
via netlink (with all the sync/async nightmare this brings) and
update the base interface set on changes.

---
diff --git a/include/seastar/net/posix-stack.hh
b/include/seastar/net/posix-stack.hh
--- a/include/seastar/net/posix-stack.hh
+++ b/include/seastar/net/posix-stack.hh
@@ -237,6 +237,7 @@ public:
}
virtual bool has_per_core_namespace() override { return _reuseport; };
bool supports_ipv6() const override;
+ std::vector<network_interface> network_interfaces() override;
};

class posix_ap_network_stack : public posix_network_stack {
diff --git a/src/net/posix-stack.cc b/src/net/posix-stack.cc
+
+ uint32_t index() const override {
+ return _index;
+ }
+ uint32_t mtu() const override {
+ return _mtu;
+ }
+ const sstring& name() const override {
+ return _name;
+ }
+ const sstring& display_name() const override {
+ return _display_name.empty() ? name() : _display_name;
+ }
+ const std::vector<net::inet_address>& addresses() const override {
+ return _addresses;
+ }
+ const std::vector<uint8_t> hardware_address() const override {
+ return _hardware_address;
+ }
+ auto len = ::recvmsg(fd, &rtnl_reply, 0); /* read as much
data as fits in the receive buffer */
+ break;
+ }
+ }
+
+ res.emplace_back(std::move(nwif));
+
+ break;
+ }
+ case RTM_NEWADDR:
+ {
+ auto* addr = reinterpret_cast<const
ifaddrmsg*>(NLMSG_DATA(msg_ptr));
+ auto ilen = msg_ptr->nlmsg_len -
NLMSG_LENGTH(sizeof(ifaddrmsg));
+
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+ }
+ default:
+ break;
+ }
+ }
+ }

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:21 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

native_stack: Add network interface impl

Not very interesting, but for completeness.

---
diff --git a/src/net/native-stack.cc b/src/net/native-stack.cc
--- a/src/net/native-stack.cc
+++ b/src/net/native-stack.cc
@@ -172,6 +172,11 @@ class native_network_stack : public network_stack {
_inet.learn(l2, l3);
}
friend class native_server_socket_impl<tcp4>;
+
+ class native_network_interface;
+ friend class native_network_interface;
+
+ std::vector<network_interface> network_interfaces() override;
};

thread_local promise<std::unique_ptr<network_stack>>
native_network_stack::ready_promise;
@@ -339,6 +344,64 @@ boost::program_options::options_description
nns_options() {
void register_native_stack() {
register_network_stack("native", nns_options(),
native_network_stack::create);
}
+
+class native_network_stack::native_network_interface : public
net::network_interface_impl {
+ const native_network_stack& _stack;
+ std::vector<net::inet_address> _addresses;
+ std::vector<uint8_t> _hardware_address;
+public:
+ native_network_interface(const native_network_stack& stack)
+ : _stack(stack)
+ , _addresses(1, _stack._inet.host_address())
+ ,
_hardware_address(_stack._inet.netif()->hw_address().mac.begin(),
_stack._inet.netif()->hw_address().mac.end())
+ {}
+ native_network_interface(const native_network_interface&) = default;
+
+ uint32_t index() const override {
+ return 0;
+ }
+ uint32_t mtu() const override {
+ return _stack._inet.netif()->hw_features().mtu;
+ }
+ const sstring& name() const override {
+ static const sstring name = "if0";
+ return name;
+ }
+ const sstring& display_name() const override {
+ return name();
+ }
+ const std::vector<net::inet_address>& addresses() const override {
+ return _addresses;
+ }
+ const std::vector<uint8_t> hardware_address() const override {
+ return _hardware_address;
+ }
+ bool is_loopback() const override {
+ return false;
+ }
+ bool is_virtual() const override {
+ return false;
+ }
+ bool is_up() const override {
+ return true;
+ }
+ bool supports_ipv6() const override {
+ return false;
+ }
+};
+
+std::vector<network_interface> native_network_stack::network_interfaces() {
+ if (!_inet.netif()) {
+ return {};
+ }
+
+ static const native_network_interface nwif(*this);
+
+ std::vector<network_interface> res;
+ res.emplace_back(make_shared<native_network_interface>(nwif));

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:23 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

network_interface_test: Small test for network interface functionality

---
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -306,6 +306,9 @@ seastar_add_test (httpd
seastar_add_test (ipv6
SOURCES ipv6_test.cc)

+seastar_add_test (network_interface
+ SOURCES network_interface_test.cc)
+
seastar_add_test (json_formatter
SOURCES json_formatter_test.cc)

diff --git a/tests/unit/network_interface_test.cc
b/tests/unit/network_interface_test.cc
--- a/tests/unit/network_interface_test.cc
+++ b/tests/unit/network_interface_test.cc
@@ -0,0 +1,81 @@
+/*
+ * This file is open source software, licensed to you under the terms
+ * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
+ * distributed with this work for additional information regarding
copyright
+ * ownership. You may not use this file except in compliance with the
License.
+ *
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/*
+ * Copyright (C) 2019 Cloudius Systems, Ltd.
+ */
+
+#include <seastar/testing/test_case.hh>
+#include <seastar/net/api.hh>
+#include <seastar/net/inet_address.hh>
+#include <seastar/net/ethernet.hh>
+#include <seastar/net/ip.hh>
+#include <seastar/core/reactor.hh>
+#include <seastar/core/thread.hh>
+#include <seastar/util/log.hh>
+
+using namespace seastar;
+
+static logger niflog("network_interface_test");
+
+SEASTAR_TEST_CASE(list_interfaces) {
+ // just verifying we have something. And can access all the stuff.
+ auto interfaces = engine().net().network_interfaces();
+ BOOST_REQUIRE_GT(interfaces.size(), 0);
+
+ for (auto& nif : interfaces) {
+ niflog.info("Iface: {}, index = {}, mtu = {}, loopback = {},
virtual = {}, up = {}",
+ nif.name(), nif.index(), nif.mtu(), nif.is_loopback(),
nif.is_virtual(), nif.is_up()
+ );
+ if (nif.hardware_address().size() >= 6) {
+ niflog.info(" HW: {}",
net::ethernet_address(nif.hardware_address().data()));
+ }
+ for (auto& addr : nif.addresses()) {
+ niflog.info(" Addr: {}", addr);
+ }
+ }
+
+ return make_ready_future();
+}
+
+SEASTAR_TEST_CASE(match_ipv6_scope) {
+ auto interfaces = engine().net().network_interfaces();
+
+ for (auto& nif : interfaces) {
+ if (nif.is_loopback()) {
+ continue;
+ }
+ auto i = std::find_if(nif.addresses().begin(),
nif.addresses().end(), std::mem_fn(&net::inet_address::is_ipv6));
+ if (i == nif.addresses().end()) {
+ continue;
+ }
+
+ std::ostringstream ss;
+ ss << net::inet_address(i->as_ipv6_address()) << "%" << nif.name();
+ auto text = ss.str();
+
+ net::inet_address na(text);
+
+ BOOST_REQUIRE_EQUAL(na.as_ipv6_address(), i->as_ipv6_address());
+ BOOST_REQUIRE_EQUAL(na.scope(), nif.index());
+
+ niflog.info("Org: {}, Parsed: {}, Text: {}", *i, na, text);
+
+ }
+
+ return make_ready_future();
+}

Commit Bot

<bot@cloudius-systems.com>
unread,
Oct 23, 2019, 5:56:23 AM10/23/19
to seastar-dev@googlegroups.com, Calle Wilund
From: Calle Wilund <ca...@scylladb.com>
Committer: Calle Wilund <ca...@scylladb.com>
Branch: master

inet_address: Allow %scope ext in parsed address strings

Allows including scope in network address string

---
diff --git a/src/net/inet_address.cc b/src/net/inet_address.cc
--- a/src/net/inet_address.cc
+++ b/src/net/inet_address.cc
@@ -53,6 +53,27 @@ seastar::net::inet_address::parse_numerical(const
sstring& addr) {
in._in_family = family::INET;
return in;
}
+ auto i = addr.find_last_of('%');
+ if (i != sstring::npos) {
+ auto ext = addr.substr(i + 1);
+ auto src = addr.substr(0, i);
+ auto res = parse_numerical(src);
+
+ if (res) {
+ uint32_t index = std::numeric_limits<uint32_t>::max();
+ try {
+ index = std::stoul(ext);
+ } catch (...) {
+ }
+ for (auto& nwif : engine().net().network_interfaces()) {
+ if (nwif.index() == index || nwif.name() == ext ||
nwif.display_name() == ext) {
+ res->_scope = nwif.index();
+ break;
+ }
+ }
+ return *res;
+ }
+ }
if (::inet_pton(AF_INET6, addr.c_str(), &in._in6)) {
Reply all
Reply to author
Forward
0 new messages