Commit d82df10e authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

Merge branch 'trac1237'

Conflicts:
	ChangeLog
	src/bin/dhcp4/dhcp4_srv.cc
	src/bin/dhcp6/dhcp6_srv.cc
	src/lib/dhcp/iface_mgr.cc
	src/lib/dhcp/iface_mgr.h
	src/lib/dhcp/tests/iface_mgr_unittest.cc
parents b362cbe3 8a040737
362. [func] tomek
libdhcp++: Interface detection in Linux implemented. libdhcp++
if now able to detect available network interfaces, its link-layer
addresses, flags and configured IPv4 and IPv6 addresses.
(Trac #1237, git 8a040737426aece7cc92a795f2b712d7c3407513)
361. [func] tomek
libdhcp++: Transmission and reception of DHCPv4 packets is now
implemented. Low-level hacks are not implemented for transmission
......
......@@ -369,6 +369,36 @@ AC_HEADER_STDBOOL
AC_TYPE_SIZE_T
# Detect OS type (it may be used to do OS-specific things, e.g.
# interface detection in DHCP)
AC_MSG_CHECKING(OS family)
system=`uname -s`
case $system in
Linux)
OS_TYPE="Linux"
CPPFLAGS="$CPPFLAGS -DOS_LINUX"
;;
Darwin | FreeBSD | NetBSD | OpenBSD)
OS_TYPE="BSD"
CPPFLAGS="$CPPFLAGS -DOS_BSD"
;;
Solaris)
OS_TYPE="Solaris"
CPPFLAGS="$CPPFLAGS -DOS_SOLARIS"
;;
*)
OS_TYPE="Unknown"
AC_MSG_WARN("Unsupported OS: uname returned $system")
;;
esac
AC_MSG_RESULT($OS_TYPE)
AM_CONDITIONAL(OS_LINUX, test $OS_TYPE = Linux)
AM_COND_IF([OS_LINUX], [AC_DEFINE([OS_LINUX], [1], [Running on Linux?])])
AM_CONDITIONAL(OS_BSD, test $OS_TYPE = BSD)
AM_COND_IF([OS_BSD], [AC_DEFINE([OS_BSD], [1], [Running on BSD?])])
AM_CONDITIONAL(OS_SOLARIS, test $OS_TYPE = Solaris)
AM_COND_IF([OS_SOLARIS], [AC_DEFINE([OS_SOLARIS], [1], [Running on Solaris?])])
AC_MSG_CHECKING(for sa_len in struct sockaddr)
AC_TRY_COMPILE([
......@@ -1144,6 +1174,7 @@ Flags:
CXXFLAGS: $CXXFLAGS
LDFLAGS: $LDFLAGS
B10_CXXFLAGS: $B10_CXXFLAGS
OS Family: $OS_TYPE
dnl includes too
Python: ${PYTHON_INCLUDES}
${PYTHON_CXXFLAGS}
......
......@@ -34,10 +34,10 @@ pkglibexec_PROGRAMS = b10-dhcp6
b10_dhcp6_SOURCES = main.cc dhcp6_srv.cc dhcp6_srv.h
b10_dhcp6_LDADD = $(top_builddir)/src/lib/dhcp/libdhcp++.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libexceptions.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/liblog.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libdhcp++.la
# TODO: config.h.in is wrong because doesn't honor pkgdatadir
# and can't use @datadir@ because doesn't expand default ${prefix}
......
......@@ -27,13 +27,21 @@ using namespace isc::dhcp;
using namespace isc::asiolink;
Dhcpv6Srv::Dhcpv6Srv(uint16_t port) {
//void Dhcpv6Srv::Dhcpv6Srv_impl(uint16_t port) {
cout << "Initialization" << endl;
// First call to instance() will create IfaceMgr (it's a singleton).
// It may throw something if things go wrong.
IfaceMgr::instance();
// first call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
try {
IfaceMgr::instance();
} catch (const std::exception &e) {
cout << "Failed to instantiate InterfaceManager:" << e.what() << ". Aborting." << endl;
shutdown = true;
}
if (IfaceMgr::instance().countIfaces() == 0) {
cout << "Failed to detect any network interfaces. Aborting." << endl;
shutdown = true;
}
// Now try to open IPv6 sockets on detected interfaces.
IfaceMgr::instance().openSockets6(port);
......
......@@ -11,6 +11,8 @@ lib_LTLIBRARIES = libdhcp++.la
libdhcp___la_SOURCES =
libdhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
libdhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libdhcp___la_SOURCES += iface_mgr_linux.cc
libdhcp___la_SOURCES += iface_mgr_bsd.cc
libdhcp___la_SOURCES += option.cc option.h
libdhcp___la_SOURCES += option6_ia.cc option6_ia.h
libdhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
......
......@@ -12,6 +12,7 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#include <sstream>
#include <fstream>
#include <string.h>
......@@ -53,7 +54,9 @@ IfaceMgr::instance() {
}
IfaceMgr::Iface::Iface(const std::string& name, int ifindex)
:name_(name), ifindex_(ifindex), mac_len_(0)
:name_(name), ifindex_(ifindex), mac_len_(0), flag_loopback_(false),
flag_up_(false), flag_running_(false), flag_multicast_(false),
flag_broadcast_(false), flags_(0), hardware_type_(0)
{
memset(mac_, 0, sizeof(mac_));
}
......@@ -72,7 +75,7 @@ IfaceMgr::Iface::getPlainMac() const {
tmp << hex;
for (int i = 0; i < mac_len_; i++) {
tmp.width(2);
tmp << int(mac_[i]);
tmp << static_cast<int>(mac_[i]);
if (i < mac_len_-1) {
tmp << ":";
}
......@@ -153,7 +156,7 @@ IfaceMgr::~IfaceMgr() {
}
void
IfaceMgr::detectIfaces() {
IfaceMgr::stubDetectIfaces() {
string ifaceName, linkLocal;
// TODO do the actual detection. Currently interface detection is faked
......@@ -167,8 +170,8 @@ IfaceMgr::detectIfaces() {
ifstream interfaces("interfaces.txt");
if (!interfaces.good()) {
cout << "Failed to read interfaces.txt file." << endl;
isc_throw(Unexpected, "Failed to read interfaces.txt");
cout << "interfaces.txt file is not available. Stub interface detection skipped." << endl;
return;
}
interfaces >> ifaceName;
interfaces >> linkLocal;
......@@ -192,6 +195,12 @@ IfaceMgr::detectIfaces() {
}
}
#if !defined(OS_LINUX) && !defined(OS_BSD)
void IfaceMgr::detectIfaces() {
stubDetectIfaces();
}
#endif
bool IfaceMgr::openSockets4(uint16_t port) {
int sock;
int count = 0;
......@@ -259,8 +268,8 @@ bool IfaceMgr::openSockets6(uint16_t port) {
return (false);
}
if ( !joinMcast(sock, iface->getName(),
string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
if ( !joinMulticast(sock, iface->getName(),
string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
close(sock);
isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
<< " multicast group.");
......@@ -289,15 +298,25 @@ IfaceMgr::printIfaces(std::ostream& out /*= std::cout*/) {
iface!=ifaces_.end();
++iface) {
const AddressCollection& addrs = iface->getAddresses();
out << "Detected interface " << iface->getFullName()
<< ", mac=" << iface->getPlainMac() << endl;
out << "flags=" << endl;
out << " " << iface->getAddresses().size() << " addr(s):" << endl;
for (AddressCollection::const_iterator addr=iface->getAddresses().begin();
addr != iface->getAddresses().end();
++addr) {
out << " " << addr->toText() << endl;
<< ", hwtype=" << iface->hardware_type_ << ", maclen=" << iface->mac_len_
<< ", mac=" << iface->getPlainMac();
out << ", flags=" << hex << iface->flags_ << dec << "("
<< (iface->flag_loopback_?"LOOPBACK ":"")
<< (iface->flag_up_?"UP ":"")
<< (iface->flag_running_?"RUNNING ":"")
<< (iface->flag_multicast_?"MULTICAST ":"")
<< (iface->flag_broadcast_?"BROADCAST ":"")
<< ")" << endl;
out << " " << addrs.size() << " addr(s):";
for (AddressCollection::const_iterator addr = addrs.begin();
addr != addrs.end(); ++addr) {
out << " " << addr->toText();
}
out << endl;
}
}
......@@ -403,7 +422,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
addr.getAddress().to_v6().to_bytes().data(),
sizeof(addr6.sin6_addr));
#ifdef HAVE_SA_LEN
addr6->sin6_len = sizeof(addr6);
addr6.sin6_len = sizeof(addr6);
#endif
// TODO: use sockcreator once it becomes available
......@@ -450,7 +469,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
// are link and site-scoped, so there is no sense to join those groups
// with global addresses.
if ( !joinMcast( sock, iface.getName(),
if ( !joinMulticast( sock, iface.getName(),
string(ALL_DHCP_RELAY_AGENTS_AND_SERVERS) ) ) {
close(sock);
isc_throw(Unexpected, "Failed to join " << ALL_DHCP_RELAY_AGENTS_AND_SERVERS
......@@ -468,7 +487,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, int port) {
}
bool
IfaceMgr::joinMcast(int sock, const std::string& ifname,
IfaceMgr::joinMulticast(int sock, const std::string& ifname,
const std::string & mcast) {
struct ipv6_mreq mreq;
......
......@@ -87,6 +87,14 @@ public:
/// @return MAC address as a plain text (string)
std::string getPlainMac() const;
/// @brief Sets flag_*_ fields based on bitmask value returned by OS
///
/// Note: Implementation of this method is OS-dependent as bits have
/// different meaning on each OS.
///
/// @param flags bitmask value returned by OS in interface detection
void setFlags(uint32_t flags);
/// @brief Returns interface index.
///
/// @return interface index
......@@ -159,11 +167,35 @@ public:
/// list of assigned addresses
AddressCollection addrs_;
public:
/// link-layer address
uint8_t mac_[MAX_MAC_LEN];
/// length of link-layer address (usually 6)
int mac_len_;
/// specifies if selected interface is loopback
bool flag_loopback_;
/// specifies if selected interface is up
bool flag_up_;
/// flag specifies if selected interface is running
/// (e.g. cable plugged in, wifi associated)
bool flag_running_;
/// flag specifies if selected interface is multicast capable
bool flag_multicast_;
/// flag specifies if selected interface is broadcast capable
bool flag_broadcast_;
/// interface flags (this value is as is returned by OS,
/// it may mean different things on different OSes)
uint32_t flags_;
/// hardware type
uint16_t hardware_type_;
};
// TODO performance improvement: we may change this into
......@@ -316,6 +348,11 @@ public:
/// @return true if any sockets were open
bool openSockets4(uint16_t port = DHCP4_SERVER_PORT);
/// @brief returns number of detected interfaces
///
/// @return number of detected interfaces
uint16_t countIfaces() { return ifaces_.size(); }
// don't use private, we need derived classes in tests
protected:
......@@ -368,6 +405,15 @@ protected:
void
detectIfaces();
/// @brief Stub implementation of network interface detection.
///
/// This implementations reads a single line from interfaces.txt file
/// and pretends to detect such interface. First interface name and
/// link-local IPv6 address or IPv4 address is read from the
/// intefaces.txt file.
void
stubDetectIfaces();
// TODO: having 2 maps (ifindex->iface and ifname->iface would)
// probably be better for performance reasons
......@@ -413,8 +459,8 @@ private:
/// @return true if multicast join was successful
///
bool
joinMcast(int sock, const std::string& ifname,
const std::string& mcast);
joinMulticast(int sock, const std::string& ifname,
const std::string& mcast);
};
......
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#if defined(OS_BSD)
#include <dhcp/iface_mgr.h>
#include <exceptions/exceptions.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
namespace isc {
void
IfaceMgr::detectIfaces() {
// TODO do the actual detection on BSDs. Currently just calling
// stub implementation.
stubDetectIfaces();
}
}
#endif
// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <config.h>
#if defined(OS_LINUX)
#include <dhcp/iface_mgr.h>
#include <exceptions/exceptions.h>
#include <net/if.h>
#include <linux/if_link.h>
#include <linux/rtnetlink.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
/// This is a structure that defines context for netlink connection.
struct rtnl_handle
{
int fd;
struct sockaddr_nl local;
struct sockaddr_nl peer;
__u32 seq;
__u32 dump;
};
struct nlmsg_list
{
struct nlmsg_list *next;
struct nlmsghdr h;
};
const int sndbuf = 32768;
const int rcvbuf = 32768;
namespace isc {
/// @brief Opens netlink socket and initializes handle structure.
///
/// @exception Unexpected Thrown if socket configuration fails.
///
/// @param handle Context will be stored in this structure.
void rtnl_open_socket(struct rtnl_handle& handle) {
// equivalent of rtnl_open
handle.fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (handle.fd < 0) {
isc_throw(Unexpected, "Failed to create NETLINK socket.");
}
if (setsockopt(handle.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) {
isc_throw(Unexpected, "Failed to set send buffer in NETLINK socket.");
}
if (setsockopt(handle.fd, SOL_SOCKET, SO_RCVBUF, &sndbuf, sizeof(rcvbuf)) < 0) {
isc_throw(Unexpected, "Failed to set receive buffer in NETLINK socket.");
}
memset(&handle.local, 0, sizeof(handle.local));
handle.local.nl_family = AF_NETLINK;
handle.local.nl_groups = 0;
if (bind(handle.fd, (struct sockaddr*)&handle.local, sizeof(handle.local)) < 0) {
isc_throw(Unexpected, "Failed to bind netlink socket.");
}
socklen_t addr_len = sizeof(handle.local);
if (getsockname(handle.fd, (struct sockaddr*)&handle.local, &addr_len) < 0) {
isc_throw(Unexpected, "Getsockname for netlink socket failed.");
}
// just 2 sanity checks and we are done
if ( (addr_len != sizeof(handle.local)) ||
(handle.local.nl_family != AF_NETLINK) ) {
isc_throw(Unexpected, "getsockname() returned unexpected data for netlink socket.");
}
}
/// @brief Sends request over NETLINK socket.
///
/// @param handle context structure
/// @param family requested information family
/// @param type request type (RTM_GETLINK or RTM_GETADDR)
void rtnl_send_request(struct rtnl_handle& handle, int family, int type) {
struct {
struct nlmsghdr nlh;
struct rtgenmsg g;
} req;
struct sockaddr_nl nladdr;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
memset(&req, 0, sizeof(req));
req.nlh.nlmsg_len = sizeof(req);
req.nlh.nlmsg_type = type;
req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST;
req.nlh.nlmsg_pid = 0;
req.nlh.nlmsg_seq = handle.dump = ++handle.seq;
req.g.rtgen_family = family;
int status = sendto(handle.fd, (void*)&req, sizeof(req), 0,
(struct sockaddr*)&nladdr, sizeof(nladdr));
if (status<0) {
isc_throw(Unexpected, "Failed to send " << sizeof(nladdr) << " bytes over netlink socket.");
}
}
/// @brief Appends nlmsg to a list
///
/// @param n a message to be added
/// @param link_info a list
void rtnl_store_reply(struct nlmsghdr *n, struct nlmsg_list** link_info)
{
struct nlmsg_list *h;
struct nlmsg_list **lp;
h = (nlmsg_list*)malloc(n->nlmsg_len+sizeof(void*));
if (h == NULL) {
isc_throw(Unexpected, "Failed to allocate " << n->nlmsg_len+sizeof(void*)
<< " bytes.");
}
memcpy(&h->h, n, n->nlmsg_len);
h->next = NULL;
for (lp = link_info; *lp; lp = &(*lp)->next) /* NOTHING */;
*lp = h;
}
void parse_rtattr(struct rtattr *tb[], int max, struct rtattr *rta, int len)
{
memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
while (RTA_OK(rta, len)) {
if (rta->rta_type <= max)
tb[rta->rta_type] = rta;
rta = RTA_NEXT(rta,len);
}
if (len) {
isc_throw(Unexpected, "Failed to parse RTATTR in netlink message.");
}
}
void ipaddrs_get(IfaceMgr::Iface& iface, struct nlmsg_list *addr_info) {
uint8_t addr[16];
struct rtattr * rta_tb[IFA_MAX+1];
for ( ;addr_info ; addr_info = addr_info->next) {
struct nlmsghdr *n = &addr_info->h;
struct ifaddrmsg *ifa = (ifaddrmsg*)NLMSG_DATA(n);
// these are not the addresses you are looking for
if ( ifa->ifa_index != iface.getIndex()) {
continue;
}
if ( ifa->ifa_family == AF_INET6 ) {
memset(rta_tb, 0, sizeof(rta_tb));
parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
if (!rta_tb[IFA_LOCAL])
rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
if (!rta_tb[IFA_ADDRESS])
rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
memcpy(addr,(char*)RTA_DATA(rta_tb[IFLA_ADDRESS]),16);
IOAddress a = IOAddress::from_bytes(AF_INET6, addr);
iface.addAddress(a);
/// TODO: Read lifetimes of configured addresses
}
if ( ifa->ifa_family == AF_INET ) {
memset(rta_tb, 0, sizeof(rta_tb));
parse_rtattr(rta_tb, IFA_MAX, IFA_RTA(ifa), n->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa)));
if (!rta_tb[IFA_LOCAL])
rta_tb[IFA_LOCAL] = rta_tb[IFA_ADDRESS];
if (!rta_tb[IFA_ADDRESS])
rta_tb[IFA_ADDRESS] = rta_tb[IFA_LOCAL];
memcpy(addr,(char*)RTA_DATA(rta_tb[IFLA_ADDRESS]),4);
IOAddress a = IOAddress::from_bytes(AF_INET, addr);
iface.addAddress(a);
}
}
}
void rtnl_process_reply(struct rtnl_handle &rth, struct nlmsg_list *&info) {
struct sockaddr_nl nladdr;
struct iovec iov;
struct msghdr msg;
memset(&msg, 0, sizeof(struct msghdr));
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
char buf[rcvbuf];
iov.iov_base = buf;
while (1) {
int status;
struct nlmsghdr *h;
iov.iov_len = sizeof(buf);
status = recvmsg(rth.fd, &msg, 0);
if (status < 0) {
if (errno == EINTR)
continue;
isc_throw(Unexpected, "Overrun while processing reply from netlink socket.");
}
if (status == 0) {
isc_throw(Unexpected, "EOF while reading netlink socket.");
}
h = (struct nlmsghdr*)buf;
while (NLMSG_OK(h, status)) {
// why we received this anyway?
if (nladdr.nl_pid != 0 ||
h->nlmsg_pid != rth.local.nl_pid ||
h->nlmsg_seq != rth.dump) {
h = NLMSG_NEXT(h, status);
continue;
}
if (h->nlmsg_type == NLMSG_DONE) {
// end of message
return;
}
if (h->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = (struct nlmsgerr*)NLMSG_DATA(h);
if (h->nlmsg_len < NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
// we are really out of luck here. We can't even say what is
// wrong as error message is truncated. D'oh.
isc_throw(Unexpected, "Netlink reply read failed.");
} else {
isc_throw(Unexpected, "Netlink reply read error " << -err->error);