diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 11:36:04 +0000 |
commit | 040eee1aa49b49df4698d83a05af57c220127fd1 (patch) | |
tree | f635435954e6ccde5eee9893889e24f30ca68346 /src/lib/dhcp/iface_mgr.h | |
parent | Initial commit. (diff) | |
download | isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.tar.xz isc-kea-040eee1aa49b49df4698d83a05af57c220127fd1.zip |
Adding upstream version 2.2.0.upstream/2.2.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lib/dhcp/iface_mgr.h')
-rw-r--r-- | src/lib/dhcp/iface_mgr.h | 1605 |
1 files changed, 1605 insertions, 0 deletions
diff --git a/src/lib/dhcp/iface_mgr.h b/src/lib/dhcp/iface_mgr.h new file mode 100644 index 0000000..c0ee4f0 --- /dev/null +++ b/src/lib/dhcp/iface_mgr.h @@ -0,0 +1,1605 @@ +// Copyright (C) 2011-2022 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef IFACE_MGR_H +#define IFACE_MGR_H + +#include <asiolink/io_address.h> +#include <dhcp/dhcp4.h> +#include <dhcp/dhcp6.h> +#include <dhcp/pkt4.h> +#include <dhcp/pkt6.h> +#include <dhcp/packet_queue_mgr4.h> +#include <dhcp/packet_queue_mgr6.h> +#include <dhcp/pkt_filter.h> +#include <dhcp/pkt_filter6.h> +#include <util/optional.h> +#include <util/watch_socket.h> +#include <util/watched_thread.h> + +#include <boost/multi_index/hashed_index.hpp> +#include <boost/multi_index/mem_fun.hpp> +#include <boost/multi_index/sequenced_index.hpp> +#include <boost/multi_index_container.hpp> +#include <boost/noncopyable.hpp> +#include <boost/scoped_array.hpp> +#include <boost/shared_ptr.hpp> + +#include <functional> +#include <list> +#include <vector> +#include <mutex> + +namespace isc { + +namespace dhcp { + +/// @brief IfaceMgr exception thrown thrown when interface detection fails. +class IfaceDetectError : public Exception { +public: + IfaceDetectError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown when it is not allowed to set new Packet Filter. +class PacketFilterChangeDenied : public Exception { +public: + PacketFilterChangeDenied(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Exception thrown when a call to select is interrupted by a signal. +class SignalInterruptOnSelect : public Exception { +public: + SignalInterruptOnSelect(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when socket opening +/// or configuration failed. +class SocketConfigError : public Exception { +public: + SocketConfigError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when error occurred during +/// reading data from socket. +class SocketReadError : public Exception { +public: + SocketReadError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown thrown when error occurred during +/// sending data through socket. +class SocketWriteError : public Exception { +public: + SocketWriteError(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown when there is no suitable interface. +class IfaceNotFound : public Exception { +public: + IfaceNotFound(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief IfaceMgr exception thrown when there is no suitable socket found. +class SocketNotFound : public Exception { +public: + SocketNotFound(const char* file, size_t line, const char* what) : + isc::Exception(file, line, what) { }; +}; + +/// @brief Represents a single network interface +/// +/// Iface structure represents network interface with all useful +/// information, like name, interface index, MAC address and +/// list of assigned addresses +/// +/// This class also holds the pointer to the socket read buffer. +/// Functions reading from the socket may utilize this buffer to store the +/// data being read from the socket. The advantage of using the +/// pre-allocated buffer is that the buffer is allocated only once, rather +/// than on every read. In addition, some OS specific code (e.g. BPF) +/// may require use of fixed-size buffers. The size of such a buffer is +/// returned by the OS kernel when the socket is opened. Hence, it is +/// convenient to allocate the buffer when the socket is being opened and +/// utilize it throughout the lifetime of the socket. +/// +/// In order to avoid potentially expensive copies of the @c Iface objects +/// holding pre-allocated buffers and multiple containers, this class is +/// noncopyable. +class Iface : public boost::noncopyable { +public: + + /// Maximum MAC address length (Infiniband uses 20 bytes) + static const unsigned int MAX_MAC_LEN = 20; + + /// @brief Address type. + typedef util::Optional<asiolink::IOAddress> Address; + + /// Type that defines list of addresses + typedef std::list<Address> AddressCollection; + + /// @brief Type that holds a list of socket information. + /// + /// @warning The type of the container used here must guarantee + /// that the iterators do not invalidate when erase() is called. + /// This is because, the \ref closeSockets function removes + /// elements selectively by calling erase on the element to be + /// removed and further iterates through remaining elements. + /// + /// @todo: Add SocketCollectionConstIter type + typedef std::list<SocketInfo> SocketCollection; + + /// @brief Type definition for a list of error messages + using ErrorBuffer = std::vector<std::string>; + + /// @brief Iface constructor. + /// + /// Creates Iface object that represents network interface. + /// + /// @param name name of the interface + /// @param ifindex interface index (unique integer identifier) + /// @throw BadValue when name is empty. + Iface(const std::string& name, unsigned int ifindex); + + /// @brief Destructor. + ~Iface() { } + + /// @brief Closes all open sockets on interface. + void closeSockets(); + + /// @brief Closes all IPv4 or IPv6 sockets. + /// + /// This function closes sockets of the specific 'type' and closes them. + /// The 'type' of the socket indicates whether it is used to send IPv4 + /// or IPv6 packets. The allowed values of the parameter are AF_INET and + /// AF_INET6 for IPv4 and IPv6 packets respectively. It is important + /// to realize that the actual types of sockets may be different than + /// AF_INET for IPv4 packets. This is because, historically the IfaceMgr + /// always used AF_INET sockets for IPv4 traffic. This is no longer the + /// case when the Direct IPv4 traffic must be supported. In order to support + /// direct traffic, the IfaceMgr operates on raw sockets, e.g. AF_PACKET + /// family sockets on Linux. + /// + /// @todo Replace the AF_INET and AF_INET6 values with an enum + /// which will not be confused with the actual socket type. + /// + /// @param family type of the sockets to be closed (AF_INET or AF_INET6) + /// + /// @throw BadValue if family value is different than AF_INET or AF_INET6. + void closeSockets(const uint16_t family); + + /// @brief Returns full interface name as "ifname/ifindex" string. + /// + /// @return string with interface name + std::string getFullName() const; + + /// @brief Returns link-layer address a plain text. + /// + /// @return MAC address as a plain text (string) + std::string getPlainMac() const; + + /// @brief Sets MAC address of the interface. + /// + /// @param mac pointer to MAC address buffer + /// @param macLen length of mac address + void setMac(const uint8_t* mac, size_t macLen); + + /// @brief Returns MAC length. + /// + /// @return length of MAC address + size_t getMacLen() const { return mac_len_; } + + /// @brief Returns pointer to MAC address. + /// + /// Note: Returned pointer is only valid as long as the interface object + /// that returned it. + const uint8_t* getMac() const { return mac_; } + + /// @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. + /// We need to make it 64 bits, because Solaris uses 64, not 32 bits. + /// + /// @param flags bitmask value returned by OS in interface detection + void setFlags(uint64_t flags); + + /// @brief Returns interface index. + /// + /// @return interface index + int getIndex() const { return ifindex_; } + + /// @brief Returns interface name. + /// + /// @return interface name + std::string getName() const { return name_; }; + + /// @brief Sets up hardware type of the interface. + /// + /// @param type hardware type + void setHWType(uint16_t type ) { hardware_type_ = type; } + + /// @brief Returns hardware type of the interface. + /// + /// @return hardware type + uint16_t getHWType() const { return hardware_type_; } + + /// @brief Returns all addresses available on an interface. + /// + /// The returned addresses are encapsulated in the @c util::Optional + /// class to be able to selectively flag some of the addresses as active + /// (when optional value is specified) or inactive (when optional value + /// is specified). If the address is marked as active, the + /// @c IfaceMgr::openSockets4 method will open socket and bind to this + /// address. Otherwise, it will not bind any socket to this address. + /// This is useful when an interface has multiple IPv4 addresses assigned. + /// + /// Care should be taken to not use this collection after Iface object + /// ceases to exist. That is easy in most cases as Iface objects are + /// created by IfaceMgr that is a singleton an is expected to be + /// available at all time. We may revisit this if we ever decide to + /// implement dynamic interface detection, but such fancy feature would + /// mostly be useful for clients with wifi/vpn/virtual interfaces. + /// + /// @return collection of addresses + const AddressCollection& getAddresses() const { return addrs_; } + + /// @brief Returns IPv4 address assigned to the interface. + /// + /// This function looks for an IPv4 address assigned to the interface + /// and returns it through the argument. + /// + /// @param [out] address IPv4 address assigned to the interface. + /// + /// @return Boolean value which informs whether IPv4 address has been found + /// for the interface (if true), or not (false). + bool getAddress4(isc::asiolink::IOAddress& address) const; + + /// @brief Check if the interface has the specified address assigned. + /// + /// @param address Address to be checked. + /// @return true if address is assigned to the interface, false otherwise. + bool hasAddress(const isc::asiolink::IOAddress& address) const; + + /// @brief Adds an address to an interface. + /// + /// This only adds an address to collection, it does not physically + /// configure address on actual network interface. + /// + /// @param addr address to be added + void addAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Activates or deactivates address for the interface. + /// + /// This method marks a specified address on the interface active or + /// inactive. If the address is marked inactive, the + /// @c IfaceMgr::openSockets4 method will NOT open socket for this address. + /// + /// @param address An address which should be activated, deactivated. + /// @param active A boolean flag which indicates that the specified address + /// should be active (if true) or inactive (if false). + /// + /// @throw BadValue if specified address doesn't exist for the interface. + void setActive(const isc::asiolink::IOAddress& address, const bool active); + + /// @brief Activates or deactivates all addresses for the interface. + /// + /// This method marks all addresses on the interface active or inactive. + /// If the address is marked inactive, the @c IfaceMgr::openSockets4 + /// method will NOT open socket for this address. + /// + /// @param active A boolean flag which indicates that the addresses + /// should be active (if true) or inactive (if false). + void setActive(const bool active); + + /// @brief Returns a number of activated IPv4 addresses on the interface. + unsigned int countActive4() const; + + /// @brief Deletes an address from an interface. + /// + /// This only deletes address from collection, it does not physically + /// remove address configuration from actual network interface. + /// + /// @param addr address to be removed. + /// + /// @return true if removal was successful (address was in collection), + /// false otherwise + bool delAddress(const isc::asiolink::IOAddress& addr); + + /// @brief Adds socket descriptor to an interface. + /// + /// @param sock SocketInfo structure that describes socket. + void addSocket(const SocketInfo& sock) { + sockets_.push_back(sock); + } + + /// @brief Closes socket. + /// + /// Closes socket and removes corresponding SocketInfo structure + /// from an interface. + /// + /// @param sockfd socket descriptor to be closed/removed. + /// @return true if there was such socket, false otherwise + bool delSocket(uint16_t sockfd); + + /// @brief Returns collection of all sockets added to interface. + /// + /// When new socket is created with @ref IfaceMgr::openSocket + /// it is added to sockets collection on particular interface. + /// If socket is opened by other means (e.g. function that does + /// not use @ref IfaceMgr::openSocket) it will not be available + /// in this collection. Note that functions like + /// @ref IfaceMgr::openSocketFromIface use + /// @ref IfaceMgr::openSocket internally. + /// The returned reference is only valid during the lifetime of + /// the IfaceMgr object that returned it. + /// + /// @return collection of sockets added to interface + const SocketCollection& getSockets() const { return sockets_; } + + /// @brief Removes any unicast addresses + /// + /// Removes any unicast addresses that the server was configured to + /// listen on + void clearUnicasts() { + unicasts_.clear(); + } + + /// @brief Adds unicast the server should listen on + /// + /// @throw BadValue if specified address is already defined on interface + /// @param addr unicast address to listen on + void addUnicast(const isc::asiolink::IOAddress& addr); + + /// @brief Returns a container of addresses the server should listen on + /// + /// @return address collection (may be empty) + const AddressCollection& getUnicasts() const { + return unicasts_; + } + + /// @brief Returns the pointer to the buffer used for data reading. + /// + /// The returned pointer is only valid during the lifetime of the + /// object which returns it or until the buffer is resized. + /// This function is meant to be used with socket API to gather + /// data from the socket. + /// + /// @return Pointer to the first element of the read buffer or + /// NULL if the buffer is empty. + uint8_t* getReadBuffer() { + if (read_buffer_.empty()) { + return (0); + } + return (&read_buffer_[0]); + } + + /// @brief Returns the current size of the socket read buffer. + size_t getReadBufferSize() const { + return (read_buffer_.size()); + } + + /// @brief Reallocates the socket read buffer. + /// + /// @param new_size New size of the buffer. + void resizeReadBuffer(const size_t new_size) { + read_buffer_.resize(new_size); + } + + /// @brief Add an error to the list of messages. + /// + /// @param message the error message + void addError(std::string const& message); + + /// @brief Clears all errors. + void clearErrors(); + + /// @brief Get the consistent list of error messages. + /// + /// @return the list of messages + ErrorBuffer const& getErrors() const; + +protected: + /// Socket used to send data. + SocketCollection sockets_; + + /// Network interface name. + std::string name_; + + /// Interface index (a value that uniquely identifies an interface). + int ifindex_; + + /// List of assigned addresses. + AddressCollection addrs_; + + /// List of unicast addresses the server should listen on + AddressCollection unicasts_; + + /// Link-layer address. + uint8_t mac_[MAX_MAC_LEN]; + + /// Length of link-layer address (usually 6). + size_t mac_len_; + + /// Hardware type. + uint16_t hardware_type_; + +public: + /// @todo: Make those fields protected once we start supporting more + /// than just Linux + + /// 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). + /// Solaris based os have unsigned long flags field (64 bits). + /// It is usually 32 bits, though. + uint64_t flags_; + + /// Indicates that IPv4 sockets should (true) or should not (false) + /// be opened on this interface. + bool inactive4_; + + /// Indicates that IPv6 sockets should (true) or should not (false) + /// be opened on this interface. + bool inactive6_; + +private: + + /// @brief The buffer holding the data read from the socket. + /// + /// See @c Iface manager description for details. + std::vector<uint8_t> read_buffer_; + + /// @brief List of errors that occurred since the last attempt to open sockets + /// + /// This list needs to always have a consistent view of the errors. They should all belong to + /// the same session of socket opening i.e. the same call to openSockets[46]. This is currently + /// ensured by openSockets[46] and all the places where these errors are being used i.e. the + /// status-get handler, being sequential. + ErrorBuffer errors_; +}; + +/// @brief Type definition for the pointer to an @c Iface object. +typedef boost::shared_ptr<Iface> IfacePtr; + +/// @brief Collection of pointers to network interfaces. +class IfaceCollection { +public: + + /// @brief Multi index container for network interfaces. + /// + /// This container allows to search for a network interfaces using + /// three indexes: + /// - sequenced: used to access elements in the order they have + /// been added to the container. + /// - interface index: used to access an interface using its index. + /// - interface name: used to access an interface using its name. + /// Note that indexes and names are unique. + typedef boost::multi_index_container< + // Container comprises elements of IfacePtr type. + IfacePtr, + // Here we start enumerating various indexes. + boost::multi_index::indexed_by< + // Sequenced index allows accessing elements in the same way + // as elements in std::list. Sequenced is the index #0. + boost::multi_index::sequenced<>, + // Start definition of index #1. + boost::multi_index::hashed_unique< + // Use the interface index as the key. + boost::multi_index::const_mem_fun< + Iface, int, &Iface::getIndex + > + >, + // Start definition of index #2. + boost::multi_index::hashed_unique< + // Use the interface name as the key. + boost::multi_index::const_mem_fun< + Iface, std::string, &Iface::getName + > + > + > + > IfaceContainer; + + /// @brief Begin iterator. + /// + /// @return The container sequence begin iterator. + IfaceContainer::const_iterator begin() const { + return (ifaces_container_.begin()); + } + + /// @brief End iterator. + /// + /// @return The container sequence end iterator. + IfaceContainer::const_iterator end() const { + return (ifaces_container_.end()); + } + + /// @brief Empty predicate. + /// + /// @return If the container is empty true else false. + bool empty() const { + return (ifaces_container_.empty()); + } + + /// @brief Return the number of interfaces. + /// + /// @return The container size. + size_t size() const { + return (ifaces_container_.size()); + } + + /// @brief Clear the collection. + void clear() { + cache_.reset(); + ifaces_container_.clear(); + } + + /// @brief Adds an interface to the collection. + /// + /// The interface is added at the end of sequence. + /// + /// @param iface reference to Iface object. + void push_back(const IfacePtr& iface) { + ifaces_container_.push_back(iface); + } + + /// @brief Lookup by interface index. + /// + /// @param ifindex The index of the interface to find. + /// @return The interface with the index or null. + IfacePtr getIface(uint32_t ifindex); + + /// @brief Lookup by interface name. + /// + /// @param ifname The name of the interface to find. + /// @return The interface with the name or null. + IfacePtr getIface(const std::string& ifname); + +private: + /// @brief Lookup by interface index. + /// + /// @param ifindex The index of the interface to find. + /// @param need_lock True when the cache operation needs to hold the mutex. + /// @return The interface with the index or null. + IfacePtr getIfaceInternal(uint32_t ifindex, bool need_lock); + + /// @brief Lookup by interface name. + /// + /// The mutex must be held when called from a packet processing thread. + /// + /// @param ifname The name of the interface to find. + /// @param need_lock True when the cache operation needs to hold the mutex. + /// @return The interface with the name or null. + IfacePtr getIfaceInternal(const std::string& ifname, bool need_lock); + + /// @brief The mutex for protecting the cache from concurrent + /// access from packet processing threads. + std::mutex mutex_; + + /// @brief The last interface returned by a lookup method. + /// + /// A lookup method first tries the cache: if it matches the cache is + /// returned without an expensive lookup in the container. If it does + /// not match and a value is found in the container the cache is + /// updated with this value. + /// The cache should perform well when active interfaces are a small + /// subset of the whole interface set, or when consecutive packets + /// come from the same interface. + IfacePtr cache_; + + /// @brief The container. + IfaceContainer ifaces_container_; +}; + +/// @brief Type definition for the unordered set of IPv4 bound addresses. +/// +/// In order to make @c hasOpenSocket with an IPv4 address faster bound +/// addresses should be collected after calling @c CfgIface::openSockets. +typedef boost::multi_index_container< + /// Container comprises elements of asiolink::IOAddress type. + asiolink::IOAddress, + // Here we start enumerating the only index. + boost::multi_index::indexed_by< + // Start definition of index #0. + boost::multi_index::hashed_unique< + // Use the address in its network order integer form as the key. + boost::multi_index::const_mem_fun< + asiolink::IOAddress, uint32_t, &asiolink::IOAddress::toUint32 + > + > + > +> BoundAddresses; + +/// @brief Forward declaration to the @c IfaceMgr. +class IfaceMgr; + +/// @brief Type definition for the pointer to the @c IfaceMgr. +typedef boost::shared_ptr<IfaceMgr> IfaceMgrPtr; + +/// @brief This type describes the callback function invoked when error occurs +/// in the IfaceMgr. +/// +/// @param errmsg An error message. +typedef +std::function<void(const std::string& errmsg)> IfaceMgrErrorMsgCallback; + +/// @brief Handles network interfaces, transmission and reception. +/// +/// IfaceMgr is an interface manager class that detects available network +/// interfaces, configured addresses, link-local addresses, and provides +/// API for using sockets. +/// +class IfaceMgr : public boost::noncopyable { +public: + /// Defines callback used when data is received over external sockets. + /// @param fd socket descriptor of the ready socket + typedef std::function<void (int fd)> SocketCallback; + + /// Keeps callback information for external sockets. + struct SocketCallbackInfo { + /// Socket descriptor of the external socket. + int socket_; + + /// A callback that will be called when data arrives over socket_. + SocketCallback callback_; + }; + + /// Defines storage container for callbacks for external sockets + typedef std::list<SocketCallbackInfo> SocketCallbackInfoContainer; + + /// @brief Packet reception buffer size + /// + /// RFC 8415 states that server responses may be + /// fragmented if they are over MTU. There is no + /// text whether client's packets may be larger + /// than 1500. For now, we can assume that + /// we don't support packets larger than 1500. + static const uint32_t RCVBUFSIZE = 1500; + + /// IfaceMgr is a singleton class. This method returns reference + /// to its sole instance. + /// + /// @return the only existing instance of interface manager + static IfaceMgr& instance(); + + /// @brief Returns pointer to the sole instance of the interface manager. + /// + /// This method returns the pointer to the instance of the interface manager + /// which can be held in singleton objects that depend on it. This will + /// eliminate issues with the static deinitialization fiasco between this + /// object and dependent singleton objects. + /// + /// The @c IfaceMgr::instance method should be considered deprecated. + /// + /// @return Shared pointer to the @c IfaceMgr instance. + static const IfaceMgrPtr& instancePtr(); + + /// @brief Destructor. + /// + /// Closes open sockets. + virtual ~IfaceMgr(); + + /// @brief Sets or clears the test mode for @c IfaceMgr. + /// + /// Various unit test may set this flag to true, to indicate that the + /// @c IfaceMgr is in the test mode. There are places in the code that + /// modify the behavior depending if the @c IfaceMgr is in the test + /// mode or not. + /// + /// @param test_mode A flag which indicates that the @c IfaceMgr is in the + /// test mode (if true), or not (if false). + void setTestMode(const bool test_mode) { + test_mode_ = test_mode; + } + + /// @brief Checks if the @c IfaceMgr is in the test mode. + /// + /// @return true if the @c IfaceMgr is in the test mode, false otherwise. + bool isTestMode() const { + return (test_mode_); + } + + /// @brief Allows or disallows the loopback interface + /// + /// By default the loopback interface is not considered when opening + /// sockets. This flag provides a way to relax this constraint. + /// + void setAllowLoopBack(const bool allow_loopback) { + allow_loopback_ = allow_loopback; + } + + /// @brief Check if packet be sent directly to the client having no address. + /// + /// Checks if IfaceMgr can send DHCPv4 packet to the client + /// who hasn't got address assigned. If this is not supported + /// broadcast address should be used to send response to + /// the client. + /// + /// @return true if direct response is supported. + bool isDirectResponseSupported() const; + + /// @brief Returns interface specified interface index + /// + /// @param ifindex index of searched interface + /// + /// @return interface with requested index (or null if no such + /// interface is present) + /// + IfacePtr getIface(int ifindex); + + /// @brief Returns interface with specified interface name + /// + /// @param ifname name of searched interface + /// + /// @return interface with requested name (or null if no such + /// interface is present) + IfacePtr getIface(const std::string& ifname); + + /// @brief Returns interface with specified packet + /// + /// @note When the interface index is set (@c Pkt::indexSet + /// returns true) it is searched for, if it is not set + /// the name instead is searched for. + /// + /// @param pkt packet with interface index and name + /// + /// @return interface with packet interface index or name + /// (or null if no such interface is present) + IfacePtr getIface(const PktPtr& pkt); + + /// @brief Returns container with all interfaces. + /// + /// This reference is only valid as long as IfaceMgr is valid. However, + /// since IfaceMgr is a singleton and is expected to be destroyed after + /// main() function completes, you should not worry much about this. + /// + /// @return container with all interfaces. + const IfaceCollection& getIfaces() { return (ifaces_); } + + /// @brief Removes detected interfaces. + /// + /// This method removes all detected interfaces. This method should be + /// used by unit tests to supply a custom set of interfaces. For example: + /// a unit test may create a pool of fake interfaces and use the custom + /// @c PktFilter class to mimic socket operation on these interfaces. + void clearIfaces(); + + /// @brief Detects network interfaces. + /// + /// This method will eventually detect available interfaces. For now + /// it offers stub implementation. First interface name and link-local + /// IPv6 address is read from interfaces.txt file. + /// + /// @param update_only Only add interfaces that do not exist and update + /// existing interfaces. + void detectIfaces(bool update_only = false); + + /// @brief Clears unicast addresses on all interfaces. + void clearUnicasts(); + + /// @brief Clears the addresses all sockets are bound to. + void clearBoundAddresses(); + + /// @brief Collect the addresses all sockets are bound to. + void collectBoundAddresses(); + + /// @brief Return most suitable socket for transmitting specified IPv6 packet. + /// + /// This method takes Pkt6Ptr (see overloaded implementation that takes + /// Pkt4Ptr) and chooses appropriate socket to send it. This method + /// may throw if specified packet does not have outbound interface specified, + /// no such interface exists, or specified interface does not have any + /// appropriate sockets open. + /// + /// @param pkt a packet to be transmitted + /// + /// @return a socket descriptor + /// @throw SocketNotFound If no suitable socket found. + /// @throw IfaceNotFound If interface is not set for the packet. + uint16_t getSocket(const isc::dhcp::Pkt6Ptr& pkt); + + /// @brief Return most suitable socket for transmitting specified IPv4 packet. + /// + /// This method uses the local address assigned to the packet and tries + /// to match it with addresses to which sockets are bound for the particular + /// interface. If the match is not found, the method returns the first IPv4 + /// socket found for the particular interface. In case, there are no IPv4 + /// sockets assigned to the interface the exception is thrown. + /// + /// @param pkt A packet to be transmitted. It must hold a local address and + /// a valid pointer to the interface. + /// + /// @return A structure describing a socket. + /// @throw SocketNotFound if no suitable socket found. + SocketInfo getSocket(const isc::dhcp::Pkt4Ptr& pkt); + + /// Debugging method that prints out all available interfaces. + /// + /// @param out specifies stream to print list of interfaces to + void printIfaces(std::ostream& out = std::cout); + + /// @brief Sends an IPv6 packet. + /// + /// Sends an IPv6 packet. All parameters for actual transmission are specified in + /// Pkt6 structure itself. That includes destination address, src/dst port + /// and interface over which data will be sent. + /// + /// @param pkt packet to be sent + /// + /// @throw isc::BadValue if invalid interface specified in the packet. + /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet. + /// @return true if sending was successful + bool send(const Pkt6Ptr& pkt); + + /// @brief Sends an IPv4 packet. + /// + /// Sends an IPv4 packet. All parameters for actual transmission are specified + /// in Pkt4 structure itself. That includes destination address, src/dst + /// port and interface over which data will be sent. + /// + /// @param pkt a packet to be sent + /// + /// @throw isc::BadValue if invalid interface specified in the packet. + /// @throw isc::dhcp::SocketWriteError if sendmsg() failed to send packet. + /// @return true if sending was successful + bool send(const Pkt4Ptr& pkt); + + /// @brief Receive IPv4 packets or data from external sockets + /// + /// Wrapper around calls to either @c receive4Direct or @c + /// receive4Indirect. The former is called when packet queueing is + /// disabled, the latter when it is enabled. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @return Pkt4 object representing received packet (or null) + Pkt6Ptr receive6(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv4 packets or data from external sockets + /// + /// Wrapper around calls to either @c receive4Direct or @c + /// receive4Indirect. The former is called when packet queueing is + /// disabled, the latter when it is enabled. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// Opens UDP/IP socket and binds it to address, interface and port. + /// + /// Specific type of socket (UDP/IPv4 or UDP/IPv6) depends on passed addr + /// family. + /// + /// @param ifname name of the interface + /// @param addr address to be bound. + /// @param port UDP port. + /// @param receive_bcast configure IPv4 socket to receive broadcast + /// messages or IPv6 socket to join multicast group. + /// @param send_bcast configure IPv4 socket to send broadcast messages. + /// This parameter is ignored for IPv6 sockets. + /// + /// Method will throw if socket creation, socket binding or multicast + /// join fails. + /// + /// @return socket descriptor, if socket creation, binding and multicast + /// group join were all successful. + int openSocket(const std::string& ifname, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + const bool receive_bcast = false, + const bool send_bcast = false); + + /// @brief Opens UDP/IP socket and binds it to interface specified. + /// + /// This method differs from \ref openSocket in that it does not require + /// the specification of a local address to which socket will be bound. + /// Instead, the method searches through the addresses on the specified + /// interface and selects one that matches the address family. + /// + /// @note This method does not join the socket to the multicast group. + /// + /// @param ifname name of the interface + /// @param port UDP port + /// @param family address family (AF_INET or AF_INET6) + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket. + /// @throw isc::BadValue if there is no address on specified interface + /// that belongs to given family. + int openSocketFromIface(const std::string& ifname, + const uint16_t port, + const uint8_t family); + + /// @brief Opens UDP/IP socket and binds to address specified + /// + /// This methods differs from \ref openSocket in that it does not require + /// the specification of the interface to which the socket will be bound. + /// + /// @note This method does not join the socket to the multicast group. + /// + /// @param addr address to be bound + /// @param port UDP port + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket + /// @throw isc::BadValue if specified address is not available on + /// any interface + int openSocketFromAddress(const isc::asiolink::IOAddress& addr, + const uint16_t port); + + /// @brief Opens UDP/IP socket to be used to connect to remote address + /// + /// This method identifies the local address to be used to connect to the + /// remote address specified as argument. Once the local address is + /// identified, \ref openSocket is called to open a socket and bind it to + /// the interface, address and port. + /// + /// @note This method does not join the socket to a multicast group. + /// + /// @param remote_addr remote address to connect to + /// @param port UDP port + /// @return socket descriptor, if socket creation and binding was + /// successful. + /// @throw isc::Unexpected if failed to create and bind socket + int openSocketFromRemoteAddress(const isc::asiolink::IOAddress& remote_addr, + const uint16_t port); + + + /// @brief Opens IPv6 sockets on detected interfaces. + /// + /// This method opens sockets only on interfaces which have the + /// @c inactive6_ field set to false (are active). If the interface is active + /// but it is not running, it is down, or is a loopback interface when + /// loopback is not allowed, an error is reported. + /// + /// If sockets were successfully opened, it calls @ startDHCPReceiver to + /// start the receiver thread (if packet queueing is enabled). + /// + /// On the systems with multiple interfaces, it is often desired that the + /// failure to open a socket on a particular interface doesn't cause a + /// fatal error and sockets should be opened on remaining interfaces. + /// However, the warning about the failure for the particular socket should + /// be communicated to the caller. The libdhcp++ is a common library with + /// no logger associated with it. Most of the functions in this library + /// communicate errors via exceptions. In case of openSockets6 function + /// exception must not be thrown if the function is supposed to continue + /// opening sockets, despite an error. Therefore, if such a behavior is + /// desired, the error handler function can be passed as a parameter. + /// This error handler is called (if present) with an error string. + /// Typically, error handler will simply log an error using an application + /// logger, but it can do more sophisticated error handling too. + /// + /// @todo It is possible that additional parameters will have to be added + /// to the error handler, e.g. Iface if it was really supposed to do + /// some more sophisticated error handling. + /// + /// If the error handler is not installed (is null), the exception is thrown + /// for each failure (default behavior). + /// + /// @warning This function does not check if there has been any sockets + /// already open by the @c IfaceMgr. Therefore a caller should call + /// @c IfaceMgr::closeSockets() before calling this function. + /// If there are any sockets open, the function may either throw an + /// exception or invoke an error handler on attempt to bind the new socket + /// to the same address and port. + /// + /// @param port specifies port number (usually DHCP6_SERVER_PORT) + /// @param error_handler a pointer to an error handler function which is + /// called by the openSockets6 when it fails to open a socket. This + /// parameter can be null to indicate that the callback should not be used. + /// @param skip_opened skip the addresses that already have the opened port + /// + /// @throw SocketOpenFailure if tried and failed to open socket. + /// @return true if any sockets were open + bool openSockets6(const uint16_t port = DHCP6_SERVER_PORT, + IfaceMgrErrorMsgCallback error_handler = 0, + const bool skip_opened = false); + + /// @brief Opens IPv4 sockets on detected interfaces. + /// + /// This method opens sockets only on interfaces which have the + /// @c inactive4_ field set to false (are active). If the interface is active + /// but it is not running, it is down, or is a loopback interface when + /// loopback is not allowed, an error is reported. + /// + /// The type of the socket being open depends on the selected Packet Filter + /// represented by a class derived from @c isc::dhcp::PktFilter abstract + /// class. + /// + /// If sockets were successfully opened, it calls @ startDHCPReceiver to + /// start the receiver thread (if packet queueing is enabled). + /// + /// It is possible to specify whether sockets should be broadcast capable. + /// In most of the cases, the sockets should support broadcast traffic, e.g. + /// DHCPv4 server and relay need to listen to broadcast messages sent by + /// clients. If the socket has to be open on the particular interface, this + /// interface must have broadcast flag set. If this condition is not met, + /// the socket will be created in the unicast-only mode. If there are + /// multiple broadcast-capable interfaces present, they may be all open + /// in a broadcast mode only if the OS supports SO_BINDTODEVICE (bind socket + /// to a device) socket option. If this option is not supported, only the + /// first broadcast-capable socket will be opened in the broadcast mode. + /// The error will be reported for sockets being opened on other interfaces. + /// If the socket is bound to a device (interface), the broadcast traffic + /// sent to this interface will be received on this interface only. + /// This allows the DHCPv4 server or relay to detect the interface on which + /// the broadcast message has been received. This interface is later used + /// to send a response. + /// + /// On the systems with multiple interfaces, it is often desired that the + /// failure to open a socket on a particular interface doesn't cause a + /// fatal error and sockets should be opened on remaining interfaces. + /// However, the warning about the failure for the particular socket should + /// be communicated to the caller. The libdhcp++ is a common library with + /// no logger associated with it. Most of the functions in this library + /// communicate errors via exceptions. In case of openSockets4 function + /// exception must not be thrown if the function is supposed to continue + /// opening sockets, despite an error. Therefore, if such a behavior is + /// desired, the error handler function can be passed as a parameter. + /// This error handler is called (if present) with an error string. + /// Typically, error handler will simply log an error using an application + /// logger, but it can do more sophisticated error handling too. + /// + /// @todo It is possible that additional parameters will have to be added + /// to the error handler, e.g. Iface if it was really supposed to do + /// some more sophisticated error handling. + /// + /// If the error handler is not installed (is null), the exception is thrown + /// for each failure (default behavior). + /// + /// @warning This function does not check if there has been any sockets + /// already open by the @c IfaceMgr. Therefore a caller should call + /// @c IfaceMgr::closeSockets() before calling this function. + /// If there are any sockets open, the function may either throw an + /// exception or invoke an error handler on attempt to bind the new socket + /// to the same address and port. + /// + /// @param port specifies port number (usually DHCP4_SERVER_PORT) + /// @param use_bcast configure sockets to support broadcast messages. + /// @param error_handler a pointer to an error handler function which is + /// called by the openSockets4 when it fails to open a socket. This + /// parameter can be null to indicate that the callback should not be used. + /// @param skip_opened skip the addresses that already have the opened port + /// + /// @throw SocketOpenFailure if tried and failed to open socket and callback + /// function hasn't been specified. + /// @return true if any sockets were open + bool openSockets4(const uint16_t port = DHCP4_SERVER_PORT, + const bool use_bcast = true, + IfaceMgrErrorMsgCallback error_handler = 0, + const bool skip_opened = false); + + /// @brief Closes all open sockets. + /// + /// It calls @c stopDHCPReceiver to stop the receiver thread and then + /// it closes all open interface sockets. + /// + /// Is used in destructor, but also from Dhcpv4Srv and Dhcpv6Srv classes. + void closeSockets(); + + /// @brief Returns number of detected interfaces. + /// + /// @return number of detected interfaces + uint16_t countIfaces() { return ifaces_.size(); } + + /// @brief Adds external socket and a callback + /// + /// Specifies external socket and a callback that will be called + /// when data will be received over that socket. + /// + /// @param socketfd socket descriptor + /// @param callback callback function + void addExternalSocket(int socketfd, SocketCallback callback); + + /// @brief Deletes external socket + /// + /// @param socketfd socket descriptor + void deleteExternalSocket(int socketfd); + + /// @brief Scans registered socket set and removes any that are invalid. + /// + /// Walks the list of registered external sockets and tests each for + /// validity. If any are found to be invalid they are removed. This is + /// primarily a self-defense mechanism against hook libs or other users + /// of external sockets that may leave a closed socket registered by + /// mistake. + /// + /// @return A count of the sockets purged. + int purgeBadSockets(); + + /// @brief Deletes all external sockets. + void deleteAllExternalSockets(); + + /// @brief Set packet filter object to handle sending and receiving DHCPv4 + /// messages. + /// + /// Packet filter objects provide means for the @c IfaceMgr to open sockets + /// for IPv4 packets reception and sending. This function sets custom packet + /// filter (represented by a class derived from PktFilter) to be used by + /// @c IfaceMgr. Note that there must be no IPv4 sockets open when this + /// function is called. Call closeSockets(AF_INET) to close all hanging IPv4 + /// sockets opened by the current packet filter object. + /// + /// @param packet_filter a pointer to the new packet filter object to be + /// used by @c IfaceMgr. + /// + /// @throw InvalidPacketFilter if provided packet filter object is null. + /// @throw PacketFilterChangeDenied if there are open IPv4 sockets. + void setPacketFilter(const PktFilterPtr& packet_filter); + + /// @brief Set packet filter object to handle sending and receiving DHCPv6 + /// messages. + /// + /// Packet filter objects provide means for the @c IfaceMgr to open sockets + /// for IPv6 packets reception and sending. This function sets the new + /// instance of the packet filter which will be used by @c IfaceMgr to send + /// and receive DHCPv6 messages, until replaced by another packet filter. + /// + /// It is required that DHCPv6 messages are send and received using methods + /// of the same object that was used to open socket. Therefore, it is + /// required that all IPv6 sockets are closed prior to calling this + /// function. Call closeSockets(AF_INET6) to close all hanging IPv6 sockets + /// opened by the current packet filter object. + /// + /// @param packet_filter a pointer to the new packet filter object to be + /// used by @c IfaceMgr. + /// + /// @throw isc::dhcp::InvalidPacketFilter if specified object is null. + /// @throw isc::dhcp::PacketFilterChangeDenied if there are open IPv6 + /// sockets. + void setPacketFilter(const PktFilter6Ptr& packet_filter); + + /// @brief Set Packet Filter object to handle send/receive packets. + /// + /// This function sets Packet Filter object to be used by IfaceMgr, + /// appropriate for the current OS. Setting the argument to 'true' + /// indicates that function should set a packet filter class + /// which supports direct responses to clients having no address + /// assigned yet. Filters picked by this function will vary, depending + /// on the OS being used. There is no guarantee that there is an + /// implementation that supports this feature on a particular OS. + /// If there isn't, the PktFilterInet object will be set. If the + /// argument is set to 'false', PktFilterInet object instance will + /// be set as the Packet Filter regardless of the OS type. + /// + /// @param direct_response_desired specifies whether the Packet Filter + /// object being set should support direct traffic to the host + /// not having address assigned. + void setMatchingPacketFilter(const bool direct_response_desired = false); + + /// @brief Adds an interface to list of known interfaces. + /// + /// @param iface reference to Iface object. + /// @note This function must be public because it has to be callable + /// from unit tests. + /// @throw Unexpected when name or index already exists. + void addInterface(const IfacePtr& iface); + + /// @brief Checks if there is at least one socket of the specified family + /// open. + /// + /// @param family A socket family. + /// + /// @return true if there is at least one socket open, false otherwise. + bool hasOpenSocket(const uint16_t family) const; + + /// @brief Checks if there is a socket open and bound to an address. + /// + /// This function checks if one of the sockets opened by the IfaceMgr is + /// bound to the IP address specified as the method parameter. If the + /// socket is bound to the port (and address is unspecified), the + /// method will check if the address passed in the argument is configured + /// on an interface. + /// Note: On BSD and Solaris the socket is opened for "::" address instead + /// of the link-local address or the "ff02::1:2" address. If there are + /// multiple interfaces joining the multicast group, this function matches + /// the "::" address bound by any interface, not necessary the one with the + /// specified link-local address and returns true. + /// + /// @param addr Address of the socket being searched. + /// + /// @return true if there is a socket bound to the specified address. + bool hasOpenSocket(const isc::asiolink::IOAddress& addr) const; + + /// @brief Fetches the DHCPv4 packet queue manager + /// + /// @return pointer to the packet queue mgr + PacketQueueMgr4Ptr getPacketQueueMgr4() { + return (packet_queue_mgr4_); + } + + /// @brief Fetches the DHCPv4 receiver packet queue. + /// + /// Incoming packets are read by the receiver thread and + /// added to this queue. @c receive4() dequeues and + /// returns them. + /// @return pointer to the packet queue + PacketQueue4Ptr getPacketQueue4() { + return (packet_queue_mgr4_->getPacketQueue()); + } + + /// @brief Fetches the DHCPv6 packet queue manager + /// + /// @return pointer to the packet queue mgr + PacketQueueMgr6Ptr getPacketQueueMgr6() { + return (packet_queue_mgr6_); + } + + /// @brief Fetches the DHCPv6 receiver packet queue. + /// + /// Incoming packets are read by the receiver thread and + /// added to this queue. @c receive6() dequeues and + /// returns them. + /// @return pointer to the packet queue + PacketQueue6Ptr getPacketQueue6() { + return (packet_queue_mgr6_->getPacketQueue()); + } + + /// @brief Starts DHCP packet receiver. + /// + /// Starts the DHCP packet receiver thread for the given. + /// protocol, AF_NET or AF_INET6, if the packet queue + /// exists, otherwise it simply returns. + /// + /// @param family indicates which receiver to start, + /// (AF_INET or AF_INET6) + /// + /// @throw Unexpected if the thread already exists. + void startDHCPReceiver(const uint16_t family); + + /// @brief Stops the DHCP packet receiver. + /// + /// If the thread exists, it is stopped, deleted, and + /// the packet queue is flushed. + void stopDHCPReceiver(); + + /// @brief Returns true if there is a receiver exists and its + /// thread is currently running. + bool isDHCPReceiverRunning() const { + return (dhcp_receiver_ != 0 && dhcp_receiver_->isRunning()); + } + + /// @brief Configures DHCP packet queue + /// + /// If the given configuration enables packet queueing, then the + /// appropriate queue is created. Otherwise, the existing queue is + /// destroyed. If the receiver thread is running when this function + /// is invoked, it will throw. + /// + /// @param family indicates which receiver to start, + /// (AF_INET or AF_INET6) + /// @param queue_control configuration containing "dhcp-queue-control" + /// content + /// @return true if packet queueing has been enabled, false otherwise + /// @throw InvalidOperation if the receiver thread is currently running. + bool configureDHCPPacketQueue(const uint16_t family, + data::ConstElementPtr queue_control); + + /// @brief Convenience method for adding an descriptor to a set + /// + /// @param fd descriptor to add + /// @param[out] maxfd maximum fd value in the set. If the new fd is + /// larger than it's current value, it will be updated to new fd value + /// @param sockets pointer to the set of sockets + /// @throw BadValue if sockets is null + static void addFDtoSet(int fd, int& maxfd, fd_set* sockets); + + // don't use private, we need derived classes in tests +protected: + + /// @brief Protected constructor. + /// + /// Protected constructor. This is a singleton class. We don't want + /// anyone to create instances of IfaceMgr. Use instance() method instead. + IfaceMgr(); + + /// @brief Opens IPv4 socket. + /// + /// Please do not use this method directly. Use openSocket instead. + /// + /// This method may throw exception if socket creation fails. + /// + /// @param iface reference to interface structure. + /// @param addr an address the created socket should be bound to + /// @param port a port that created socket should be bound to + /// @param receive_bcast configure socket to receive broadcast messages + /// @param send_bcast configure socket to send broadcast messages. + /// + /// @return socket descriptor + int openSocket4(Iface& iface, const isc::asiolink::IOAddress& addr, + const uint16_t port, const bool receive_bcast = false, + const bool send_bcast = false); + + /// @brief Receive IPv4 packets directly or data from external sockets. + /// + /// Attempts to receive a single DHCPv4 message over any of the open + /// IPv4 sockets. If reception is successful and all information about + /// its sender is obtained, an Pkt4 object is created and returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv4 packets indirectly or data from external sockets. + /// + /// Attempts to receive a single DHCPv4 message from the packet queue. + /// The queue is populated by the receiver thread. If a packet is waiting + /// in the queue, a Pkt4 returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt4 object representing received packet (or null) + Pkt4Ptr receive4Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Opens IPv6 socket. + /// + /// Please do not use this method directly. Use openSocket instead. + /// + /// This method may throw exception if socket creation fails. + /// + /// @param iface reference to interface structure. + /// @param addr an address the created socket should be bound to + /// @param port a port that created socket should be bound to + /// @param join_multicast A boolean parameter which indicates whether + /// socket should join All_DHCP_Relay_Agents_and_servers multicast + /// group. + /// + /// @return socket descriptor + int openSocket6(Iface& iface, const isc::asiolink::IOAddress& addr, + uint16_t port, const bool join_multicast); + + /// @brief Receive IPv6 packets directly or data from external sockets. + /// + /// Attempts to receive a single DHCPv6 message over any of the open + /// IPv6 sockets. If reception is successful and all information about + /// its sender is obtained, an Pkt6 object is created and returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt6 object representing received packet (or null) + Pkt6Ptr receive6Direct(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + /// @brief Receive IPv6 packets indirectly or data from external sockets. + /// + /// Attempts to receive a single DHCPv6 message from the packet queue. + /// The queue is populated by the receiver thread. If a packet is waiting + /// in the queue, a Pkt6 returned. + /// + /// This method also checks if data arrived over registered external socket. + /// This data may be of a different protocol family than AF_INET. + /// + /// @param timeout_sec specifies integral part of the timeout (in seconds) + /// @param timeout_usec specifies fractional part of the timeout + /// (in microseconds) + /// + /// @throw isc::BadValue if timeout_usec is greater than one million + /// @throw isc::dhcp::SocketReadError if error occurred when receiving a + /// packet. + /// @throw isc::dhcp::SignalInterruptOnSelect when a call to select() is + /// interrupted by a signal. + /// + /// @return Pkt6 object representing received packet (or null) + Pkt6Ptr receive6Indirect(uint32_t timeout_sec, uint32_t timeout_usec = 0); + + + /// @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 + /// interfaces.txt file. + void stubDetectIfaces(); + + /// @brief List of available interfaces + IfaceCollection ifaces_; + + /// @brief Unordered set of IPv4 bound addresses. + BoundAddresses bound_address_; + + // TODO: Also keep this interface on Iface once interface detection + // is implemented. We may need it e.g. to close all sockets on + // specific interface + //int recvsock_; // TODO: should be fd_set eventually, but we have only + //int sendsock_; // 2 sockets for now. Will do for until next release + + // We can't use the same socket, as receiving socket + // is bound to multicast address. And we all know what happens + // to people who try to use multicast as source address. + +private: + /// @brief Identifies local network address to be used to + /// connect to remote address. + /// + /// This method identifies local network address that can be used + /// to connect to remote address specified. + /// It first creates socket and makes attempt to connect + /// to remote location via this socket. If connection + /// is established successfully, the local address to which + /// socket is bound is returned. + /// + /// @param remote_addr remote address to connect to + /// @param port port to be used + /// @return local address to be used to connect to remote address + /// @throw isc::Unexpected if unable to identify local address + isc::asiolink::IOAddress + getLocalAddress(const isc::asiolink::IOAddress& remote_addr, + const uint16_t port); + + + /// @brief Open an IPv6 socket with multicast support. + /// + /// This function opens a socket capable of receiving messages sent to + /// the All_DHCP_Relay_Agents_and_Servers (ff02::1:2) multicast address. + /// The socket is bound to the in6addr_any address and the IPV6_JOIN_GROUP + /// option is set to point to the ff02::1:2 multicast address. + /// + /// @note This function is intended to be called internally by the + /// @c IfaceMgr::openSockets6. It is not intended to be called from any + /// other function. + /// + /// @param iface Interface on which socket should be open. + /// @param addr Link-local address to bind the socket to. + /// @param port Port number to bind socket to. + /// @param error_handler Error handler function to be called when an + /// error occurs during opening a socket, or null if exception should + /// be thrown upon error. + bool openMulticastSocket(Iface& iface, + const isc::asiolink::IOAddress& addr, + const uint16_t port, + IfaceMgrErrorMsgCallback error_handler = 0); + + /// @brief DHCPv4 receiver method. + /// + /// Loops forever reading DHCPv4 packets from the interface sockets + /// and adds them to the packet queue. It monitors the "terminate" + /// watch socket, and exits if it is marked ready. This is method + /// is used as the worker function in the thread created by @c + /// startDHCP4Receiver(). It currently uses select() to monitor + /// socket readiness. If the select errors out (other than EINTR), + /// it marks the "error" watch socket as ready. + void receiveDHCP4Packets(); + + /// @brief Receives a single DHCPv4 packet from an interface socket + /// + /// Called by @c receiveDHPC4Packets when a socket fd is flagged as + /// ready. It uses the DHCPv4 packet filter to receive a single packet + /// from the given interface socket, adds it to the packet queue, and + /// marks the "receive" watch socket ready. If an error occurs during + /// the read, the "error" watch socket is marked ready. + /// + /// @param iface interface + /// @param socket_info structure holding socket information + void receiveDHCP4Packet(Iface& iface, const SocketInfo& socket_info); + + /// @brief DHCPv6 receiver method. + /// + /// Loops forever reading DHCPv6 packets from the interface sockets + /// and adds them to the packet queue. It monitors the "terminate" + /// watch socket, and exits if it is marked ready. This is method + /// is used as the worker function in the thread created by @c + /// startDHCP6Receiver(). It currently uses select() to monitor + /// socket readiness. If the select errors out (other than EINTR), + /// it marks the "error" watch socket as ready. + void receiveDHCP6Packets(); + + /// @brief Receives a single DHCPv6 packet from an interface socket + /// + /// Called by @c receiveDHPC6Packets when a socket fd is flagged as + /// ready. It uses the DHCPv6 packet filter to receive a single packet + /// from the given interface socket, adds it to the packet queue, and + /// marks the "receive" watch socket ready. If an error occurs during + /// the read, the "error" watch socket is marked ready. + /// + /// @param socket_info structure holding socket information + void receiveDHCP6Packet(const SocketInfo& socket_info); + + /// @brief Deletes external socket with the callbacks_mutex_ taken + /// + /// @param socketfd socket descriptor + void deleteExternalSocketInternal(int socketfd); + + /// Holds instance of a class derived from PktFilter, used by the + /// IfaceMgr to open sockets and send/receive packets through these + /// sockets. It is possible to supply custom object using + /// setPacketFilter method. Various Packet Filters differ mainly by using + /// different types of sockets, e.g. SOCK_DGRAM, SOCK_RAW and different + /// families, e.g. AF_INET, AF_PACKET etc. Another possible type of + /// Packet Filter is the one used for unit testing, which doesn't + /// open sockets but rather mimics their behavior (mock object). + PktFilterPtr packet_filter_; + + /// Holds instance of a class derived from PktFilter6, used by the + /// IfaceMgr to manage sockets used to send and receive DHCPv6 + /// messages. It is possible to supply a custom object using + /// setPacketFilter method. + PktFilter6Ptr packet_filter6_; + + /// @brief Contains list of callbacks for external sockets + SocketCallbackInfoContainer callbacks_; + + /// @brief Mutex to protect callbacks_ against concurrent access + std::mutex callbacks_mutex_; + + /// @brief Indicates if the IfaceMgr is in the test mode. + bool test_mode_; + + /// @brief Allows to use loopback + bool allow_loopback_; + + /// @brief Manager for DHCPv4 packet implementations and queues + PacketQueueMgr4Ptr packet_queue_mgr4_; + + /// @brief Manager for DHCPv6 packet implementations and queues + PacketQueueMgr6Ptr packet_queue_mgr6_; + + /// @brief DHCP packet receiver. + isc::util::WatchedThreadPtr dhcp_receiver_; +}; + +} // namespace isc::dhcp +} // namespace isc + +#endif // IFACE_MGR_H |