internet-apps: DHCPv6 Application model (GSoC 24)

This commit is contained in:
Kavya Bhat
2025-04-20 20:17:51 +00:00
committed by Tommaso Pecorella
parent e3094c99ba
commit 0485fa6dcf
22 changed files with 5359 additions and 6 deletions

View File

@@ -376,7 +376,9 @@ SOURCEFIGS = \
$(SRC)/energy/doc/figures/nimh.png \
$(SRC)/zigbee/doc/figures/zigbeeStackArch.dia \
$(SRC)/zigbee/doc/figures/manyToOne.dia \
$(SRC)/zigbee/doc/figures/mesh.dia
$(SRC)/zigbee/doc/figures/mesh.dia \
$(SRC)/internet-apps/doc/figures/dhcpv6-message-exchange.dia \
$(SRC)/internet-apps/doc/figures/stateful-dhcpv6-format.dia
# specify figures from which .png and .pdf figures need to be
# generated (all dia and eps figures)
@@ -502,7 +504,10 @@ IMAGES_EPS = \
$(FIGURES)/lena-radio-link-failure-two-enb.eps \
$(FIGURES)/zigbeeStackArch.eps \
$(FIGURES)/manyToOne.eps \
$(FIGURES)/mesh.eps
$(FIGURES)/mesh.eps \
$(FIGURES)/dhcpv6-message-exchange.eps \
$(FIGURES)/stateful-dhcpv6-format.eps
# rescale pdf figures as necessary
$(FIGURES)/testbed.pdf_width = 5in

View File

@@ -2,12 +2,18 @@ build_lib(
LIBNAME internet-apps
SOURCE_FILES
helper/dhcp-helper.cc
helper/dhcp6-helper.cc
helper/ping-helper.cc
helper/radvd-helper.cc
helper/v4traceroute-helper.cc
model/dhcp-client.cc
model/dhcp-header.cc
model/dhcp-server.cc
model/dhcp6-client.cc
model/dhcp6-duid.cc
model/dhcp6-header.cc
model/dhcp6-options.cc
model/dhcp6-server.cc
model/ping.cc
model/radvd-interface.cc
model/radvd-prefix.cc
@@ -15,12 +21,18 @@ build_lib(
model/v4traceroute.cc
HEADER_FILES
helper/dhcp-helper.h
helper/dhcp6-helper.h
helper/ping-helper.h
helper/radvd-helper.h
helper/v4traceroute-helper.h
model/dhcp-client.h
model/dhcp-header.h
model/dhcp-server.h
model/dhcp6-client.h
model/dhcp6-duid.h
model/dhcp6-header.h
model/dhcp6-options.h
model/dhcp6-server.h
model/ping.h
model/radvd-interface.h
model/radvd-prefix.h
@@ -29,6 +41,7 @@ build_lib(
LIBRARIES_TO_LINK ${libinternet}
TEST_SOURCES
test/dhcp-test.cc
test/dhcp6-test.cc
test/ipv6-radvd-test.cc
test/ping-test.cc
)

View File

@@ -329,3 +329,191 @@ V4TraceRoute
************
Documentation is missing for this application.
DHCPv6
******
The |ns3| implementation of Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
follows the specifications of :rfc:`8415`.
Model Description
=================
The aim of the DHCPv6 application is to provide a method for dynamically assigning IPv6 addresses to nodes.
This application functions similarly to the ``dhcpd`` daemon in Linux, although it supports fewer options.
The DHCPv6 client installed on a node sends Solicit messages to a server configured on the link,
which then responds with an IPv6 address offer.
The source code for DHCPv6 is located in ``src/internet-apps/model``.
There are two major operational models for DHCPv6: stateful and stateless.
Stateless DHCP is used when the client only needs to request configuration parameters from the server.
Typically, the DHCPv6 server is used to assign both addresses and other options like DNS information.
The server maintains information about the Identity Associations (IAs), which are collections of leases assigned to a client.
There are three types of IAs: IA_NA (non-temporary addresses), IA_TA (temporary addresses), and IA_PD (prefix delegation).
Temporary addresses were introduced in RFC 4941 to address privacy concerns. In DHCPv6, the IA_TA (Identity Association for Temporary Addresses)
is used for addresses that are only used for a short time, and their lifetimes are generally not extended. Non-temporary addresses, on the other hand,
are used for relatively longer durations and are usually renewed by the clients. Note that non-temporary addresses are not permanent leases - the term is
merely used to differentiate between the IA_TA and IA_NA types.
Currently, the application uses only IA_NA options to lease addresses to clients.
The state of the lease changes over time, which is why this is known as stateful DHCPv6.
The DHCPv6 client application is installed on each NetDevice (interface) of the client node.
Therefore, if there are two NetDevices installed on a single node, two client applications must be installed.
The DHCPv6 server application is installed on a single server node.
However, the user can specify multiple interfaces for the server to listen on.
Notes:
* Stateless DHCPv6 has not been implemented, as it is used to request DNS configuration information that is not currently used in |ns3|.
* All client and server nodes must be on the same link. The application does not support DHCPv6 relays and hence cannot work when the client and server are in entirely different subnets.
Stateful DHCPv6
###############
The server listens on port 547 for any incoming messages on the link.
Upon receiving a **Solicit** message from a client, the server sends an **Advertise** message with the available address pool.
The server is designed to advertise one available address from each address pool that is configured on it.
When the client sends a **Request** message, the server updates lease bindings with the new expiry time, and sends a **Reply** message.
The client attempts to accept all addresses present in the server's **Reply**.
However, before using the offered address, the client checks whether any other node on the link is using the same address (Duplicate Address Detection).
If the address is already in use, the client sends a **Decline** message to the server, which updates the lease bindings accordingly.
At the end of the preferred lifetime, the client sends a **Renew** message to the server.
The server should update the lease bindings with the new expiry time, and send a **Reply** with the corresponding status code.
In case the client does not receive a **Reply** from the server, it sends a **Rebind** message at the end of the valid lifetime.
The server is expected to update the lease bindings and send a **Reply** message back to the client.
The order of the message exchange between the client and the server is as follows:
.. figure:: figures/dhcpv6-message-exchange.*
The formats of the messages between a client and server are as follows:
.. figure:: figures/stateful-dhcpv6-format.*
Identifiers
###########
Each client and server has a unique identifier (DHCP Unique Identifier), which is used to identify the client and server in the message exchange.
There are four types of DUIDs that can be used in DHCPv6:
1. DUID-LLT (Link-layer Address plus Time): The DUID-LLT consists of a 32-bit timestamp and a variable-length link layer address. Not supported in this implementation.
2. Vendor-assigned unique ID based on Enterprise Number: This consists of the vendor's enterprise number and an identifier whose source is defined by the vendor. Not supported in this implementation.
3. **DUID-LL (Link-layer address)**: DUID-LL consists of a variable-length link-layer address as the identifier. Supported in this implementation.
4. UUID (Universally unique identifier): This should be an identifier that is persistent across system restarts and reconfigurations. Not supported in this implementation.
Usage
=====
The main way for |ns3| users to use the DHCPv6 application is through the helper
API and the publicly visible attributes of the client and server applications.
To use the DHCPv6 application, the user must install the client application on the node by passing a ``Ptr<Node>`` to the ``InstallDhcp6Client`` helper.
The server application is installed on the node of interest.
Users select the interfaces on which the server should listen for incoming messages and add them to a ``NetDeviceContainer``. This is then passed to the ``InstallDhcp6Server`` helper method.
Helpers
#######
The ``Dhcp6Helper`` supports the typical ``Install`` usage pattern in |ns3|. It
can be used to easily install DHCPv6 server and client applications on a set of
interfaces.
.. sourcecode:: cpp
NetDeviceContainer serverNetDevices;
serverNetDevices.Add(netdevice);
ApplicationContainer dhcpServerApp = dhcp6Helper.InstallDhcp6Server(serverNetDevices);
ApplicationContainer dhcpClientApp = dhcp6Helper.InstallDhcp6Client(clientNode);
Address pools can be configured on the DHCPv6 server using the following API:
.. sourcecode:: cpp
Ptr<Dhcp6Server> server = DynamicCast<Dhcp6Server>(dhcpServerApp.Get(0));
server->AddSubnet(Ipv6Address("2001:db8::"), Ipv6Prefix(64), Ipv6Address("2001:db8::1"), Ipv6Address("2001:db8::ff"));
In the line above, the ``AddSubnet()`` method has the following parameters:
1. ``Ipv6Address("2001:db8::")`` - The address pool that is managed by the server.
2. ``Ipv6Prefix(64)`` - The prefix of the address pool
3. ``Ipv6Address("2001:db8::1")`` - The minimum address that can be assigned to a client.
4. ``Ipv6Address("2001:db8::ff")`` - The maximum address that can be assigned to a client.
Essentially, parameters 1 and 2 define the subnet(s) managed by the server, while parameters
3 and 4 define the range of addresses that can be assigned.
If this method is not called, the server will not have any subnet configured and will not be able to assign addresses to clients.
While it does not throw an error, a user who does not configure any subnets on the server will not see any addresses leased to the client.
Attributes
##########
The following attributes can be configured on the client:
* ``Transactions``: A random variable used to set the transaction numbers.
* ``SolicitJitter``: The jitter in milliseconds that a node waits before sending a Solicit to the server.
* ``IaidValue``: The identifier of a new Identity Association that is created by a client.
The following values can be initially set on the client interface before stateful DHCPv6 begins. However, they are overridden with values received from the server during the message exchange:
* ``RenewTime``: The time after which client should renew its lease.
* ``RebindTime``: The time after which client should rebind its leased addresses.
* ``PreferredLifetime``: The preferred lifetime of the leased address.
* ``ValidLifetime``: Time after which client should release the address.
The following attributes can be configured on the server:
* ``RenewTime``: The time after which client should renew its lease.
* ``RebindTime``: The time after which client should rebind its leased addresses.
* ``PreferredLifetime``: The preferred lifetime of the leased address.
* ``ValidLifetime``: The time after which client should release the address.
Example
#######
The following example has been written in ``src/internet-apps/examples/``:
* ``dhcp6-example.cc``: Demonstrates the working of stateful DHCPv6 with a single server and 2 client nodes.
Test
====
The following example has been written in ``src/internet-apps/test/``:
* ``dhcp6-test.cc``: Tests the working of DHCPv6 with a single server and 2 client nodes that have 1 CSMA interface and 1 Wifi interface each.
Scope and Limitations
=====================
* Limited options have been included in the DHCPv6 implementation, namely:
* Server Identifier
* Client Identifier
* Identity Association for Non-temporary Addresses (IA_NA)
* Option Request (for Solicit MAX_RT)
* Status Code
* The implementation does not support the use of a DHCP Relay agent. Hence, all the server and client nodes should be on the same link.
* The application does not yet support prefix delegation. Each client currently receives only one IPv6 address.
* If a DUID-LLT (Link-Layer Address plus Time) is used as the client identifier, there is a possibility that the timestamps of two clients may be the same. This could lead to a conflict in the lease bindings on the server if the link-layer addresses are not unique.
* If a node (such as a Wifi device) leaves the network and rejoins at a later time, the Solicit messages are not automatically restarted.
* The client application does not retransmit lost messages. Its use in wireless scenarios might lead to inconsistencies.
Future Work
===========
* The Rapid Commit option may be implemented to allow a Solicit / Reply message exchange between the client and server.
* Addition of DHCPv6 relays to allow the client and server to be in different subnets.
* Implementation of stateless DHCPv6 to allow the client to request only configuration information from the server.
* Implement the ability to configure DHCPv6 through a configuration file, similar to how it is done in Linux systems.
* Allow users to manually set the DUIDs for the client(s) and server(s).
* Implement support for host reservations, allowing specific IPv6 addresses to be assigned to particular clients based on their DUIDs or other identifying information.
* Include the ``Preference Option`` in the Advertise message sent by the server. This option allows the client to identify and choose a single server based on the preference value, instead of obtaining leases from each server that responds to the Solicit message
References
==========
* :rfc:`8415` - Dynamic Host Configuration Protocol for IPv6 (DHCPv6)
* Infoblox Blog <https://blogs.infoblox.com/ipv6-coe/slaac-to-basics-part-2-of-2-configuring-slaac/ > to understand how SLAAC and DHCPv6 operate at the same time.

View File

@@ -9,6 +9,18 @@ build_lib_example(
${libapplications}
)
build_lib_example(
NAME dhcp6-example
SOURCE_FILES dhcp6-example.cc
LIBRARIES_TO_LINK
${libapplications}
${libcsma}
${libinternet}
${libinternet-apps}
${libpoint-to-point}
${libwifi}
)
build_lib_example(
NAME traceroute-example
SOURCE_FILES traceroute-example.cc

View File

@@ -0,0 +1,215 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
/*
* Network layout:
* The following devices have one CSMA interface each -
* S0 - DHCPv6 server
* N0, N1 - DHCPv6 clients
* R0 - router
* ┌-------------------------------------------------┐
* | DHCPv6 Clients |
* | |
* | Static address |
* | 2001:cafe::42:2 |
* | ┌──────┐ ┌──────┐ ┌──────┐ |
* | │ N0 │ │ N1 │ │ N2 │ |
* | └──────┘ └──────┘ └──────┘ |
* | │ │ │ |
* └-------│--------------│---------------│----------┘
* DHCPv6 Server │ │ │
* ┌──────┐ │ │ │ ┌──────┐
* │ S0 │────────┴──────────────┴───────────────┴──────│ R0 │Router
* └──────┘ └──────┘
* Notes:
* 1. The DHCPv6 server is not assigned any static address as it operates only
* in the link-local domain.
* 2. N2 has a statically assigned address to demonstrate the operation of the
* DHCPv6 Decline message.
* 3. The server is usually on the router in practice, but we demonstrate in
* this example a standalone server.
* 4. Linux uses fairly large values for address lifetimes (in thousands of
* seconds). In this example, we have set shorter lifetimes for the purpose
* of observing the Renew messages within a shorter simulation run.
* 5. The nodes use two interfaces each for the purpose of demonstrating
* DHCPv6 operation when multiple interfaces are present on the client or
* server nodes.
*
* This example demonstrates how to set up a simple DHCPv6 server and two DHCPv6
* clients. The clients begin to request an address lease using a Solicit
* message only after receiving a Router Advertisement containing the 'M' bit
* from the router, R0.
*
* The server responds with an Advertise message with all available address
* offers, and the client sends a Request message to the server for these
* addresses. The server then sends a Reply message to the client, which
* performs Duplicate Address Detection to check if any other node on the link
* already uses this address.
* If the address is in use by any other node, the client sends a Decline
* message to the server. If the address is not in use, the client begins using
* this address.
* At the end of the address lease lifetime, the client sends a Renew message
* to the server, which renews the lease and allows the client to continue using
* the same address.
*
* The user may enable packet traces in this example to observe the following
* message exchanges:
* 1. Solicit - Advertise - Request - Reply
* 2. Solicit - Advertise - Request - Reply - Decline
* 3. Renew - Reply
*
*/
#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-apps-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/ssid.h"
#include "ns3/wifi-helper.h"
#include "ns3/yans-wifi-helper.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE("Dhcp6Example");
void
SetInterfaceDown(Ptr<Node> node, uint32_t interface)
{
node->GetObject<Ipv6>()->SetDown(interface);
}
void
SetInterfaceUp(Ptr<Node> node, uint32_t interface)
{
node->GetObject<Ipv6>()->SetUp(interface);
}
int
main(int argc, char* argv[])
{
CommandLine cmd(__FILE__);
bool verbose = false;
bool enablePcap = false;
cmd.AddValue("verbose", "Turn on the logs", verbose);
cmd.AddValue("enablePcap", "Enable/Disable pcap file generation", enablePcap);
cmd.Parse(argc, argv);
GlobalValue::Bind("ChecksumEnabled", BooleanValue(true));
if (verbose)
{
LogComponentEnable("Dhcp6Server", LOG_LEVEL_INFO);
LogComponentEnable("Dhcp6Client", LOG_LEVEL_INFO);
}
Time stopTime = Seconds(25.0);
NS_LOG_INFO("Create nodes.");
NodeContainer nonRouterNodes;
nonRouterNodes.Create(4);
Ptr<Node> router = CreateObject<Node>();
NodeContainer all(nonRouterNodes, router);
NS_LOG_INFO("Create channels.");
CsmaHelper csma;
csma.SetChannelAttribute("DataRate", StringValue("5Mbps"));
csma.SetChannelAttribute("Delay", StringValue("2ms"));
NetDeviceContainer devices = csma.Install(all); // all nodes
InternetStackHelper internetv6;
internetv6.Install(all);
NS_LOG_INFO("Create networks and assign IPv6 Addresses.");
Ipv6AddressHelper ipv6;
ipv6.SetBase(Ipv6Address("2001:cafe::"), Ipv6Prefix(64));
NetDeviceContainer nonRouterDevices;
nonRouterDevices.Add(devices.Get(0)); // The server node, S0.
nonRouterDevices.Add(devices.Get(1)); // The first client node, N0.
nonRouterDevices.Add(devices.Get(2)); // The second client node, N1.
nonRouterDevices.Add(devices.Get(3)); // The third client node, N2.
Ipv6InterfaceContainer i = ipv6.AssignWithoutAddress(nonRouterDevices);
NS_LOG_INFO("Assign static IP address to the third node.");
Ptr<Ipv6> ipv6proto = nonRouterNodes.Get(3)->GetObject<Ipv6>();
int32_t ifIndex = ipv6proto->GetInterfaceForDevice(devices.Get(3));
Ipv6InterfaceAddress ipv6Addr =
Ipv6InterfaceAddress(Ipv6Address("2001:cafe::42:2"), Ipv6Prefix(128));
ipv6proto->AddAddress(ifIndex, ipv6Addr);
NS_LOG_INFO("Assign static IP address to the router node.");
NetDeviceContainer routerDevice;
routerDevice.Add(devices.Get(4)); // CSMA interface of the node R0.
Ipv6InterfaceContainer r1 = ipv6.Assign(routerDevice);
r1.SetForwarding(0, true);
NS_LOG_INFO("Create Radvd applications.");
RadvdHelper radvdHelper;
/* Set up unsolicited RAs */
radvdHelper.AddAnnouncedPrefix(r1.GetInterfaceIndex(0), Ipv6Address("2001:cafe::1"), 64);
radvdHelper.GetRadvdInterface(r1.GetInterfaceIndex(0))->SetManagedFlag(true);
NS_LOG_INFO("Create DHCP applications.");
Dhcp6Helper dhcp6Helper;
NS_LOG_INFO("Set timers to desired values.");
dhcp6Helper.SetServerAttribute("RenewTime", StringValue("10s"));
dhcp6Helper.SetServerAttribute("RebindTime", StringValue("16s"));
dhcp6Helper.SetServerAttribute("PreferredLifetime", StringValue("18s"));
dhcp6Helper.SetServerAttribute("ValidLifetime", StringValue("20s"));
// DHCPv6 clients
NodeContainer nodes = NodeContainer(nonRouterNodes.Get(1), nonRouterNodes.Get(2));
ApplicationContainer dhcpClients = dhcp6Helper.InstallDhcp6Client(nodes);
dhcpClients.Start(Seconds(1.0));
dhcpClients.Stop(stopTime);
// DHCP server
NetDeviceContainer serverNetDevices;
serverNetDevices.Add(nonRouterDevices.Get(0));
ApplicationContainer dhcpServerApp = dhcp6Helper.InstallDhcp6Server(serverNetDevices);
Ptr<Dhcp6Server> server = DynamicCast<Dhcp6Server>(dhcpServerApp.Get(0));
server->AddSubnet(Ipv6Address("2001:cafe::"),
Ipv6Prefix(64),
Ipv6Address("2001:cafe::42:1"),
Ipv6Address("2001:cafe::42:ffff"));
dhcpServerApp.Start(Seconds(0.0));
dhcpServerApp.Stop(stopTime);
ApplicationContainer radvdApps = radvdHelper.Install(router);
radvdApps.Start(Seconds(1.0));
radvdApps.Stop(stopTime);
Simulator::Stop(stopTime + Seconds(2.0));
// Schedule N0 interface to go down and come up to see the effects of
// link state change.
Simulator::Schedule(Seconds(4), &SetInterfaceDown, nonRouterNodes.Get(1), 1);
Simulator::Schedule(Seconds(5), &SetInterfaceUp, nonRouterNodes.Get(1), 1);
if (enablePcap)
{
csma.EnablePcapAll("dhcp6-csma");
}
NS_LOG_INFO("Run Simulation.");
Simulator::Run();
NS_LOG_INFO("Done.");
Simulator::Destroy();
return 0;
}

View File

@@ -0,0 +1,117 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-helper.h"
#include "ns3/dhcp6-client.h"
#include "ns3/dhcp6-server.h"
#include "ns3/ipv6.h"
#include "ns3/log.h"
#include "ns3/loopback-net-device.h"
#include "ns3/names.h"
#include "ns3/uinteger.h"
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Helper");
Dhcp6Helper::Dhcp6Helper()
{
m_clientFactory.SetTypeId(Dhcp6Client::GetTypeId());
m_serverFactory.SetTypeId(Dhcp6Server::GetTypeId());
}
void
Dhcp6Helper::SetClientAttribute(std::string name, const AttributeValue& value)
{
m_clientFactory.Set(name, value);
}
void
Dhcp6Helper::SetServerAttribute(std::string name, const AttributeValue& value)
{
m_serverFactory.Set(name, value);
}
ApplicationContainer
Dhcp6Helper::InstallDhcp6Client(NodeContainer clientNodes) const
{
ApplicationContainer installedApps;
for (auto iter = clientNodes.Begin(); iter != clientNodes.End(); iter++)
{
Ptr<Application> app = InstallDhcp6ClientInternal((*iter));
installedApps.Add(app);
}
return installedApps;
}
ApplicationContainer
Dhcp6Helper::InstallDhcp6Server(NetDeviceContainer netDevices)
{
ApplicationContainer installedApps;
for (auto itr = netDevices.Begin(); itr != netDevices.End(); itr++)
{
Ptr<NetDevice> netDevice = *itr;
Ptr<Node> node = netDevice->GetNode();
NS_ASSERT_MSG(node, "Dhcp6Helper: NetDevice is not associated with any node -> fail");
Ptr<Ipv6> ipv6 = node->GetObject<Ipv6>();
NS_ASSERT_MSG(ipv6,
"Dhcp6Helper: NetDevice is associated"
" with a node without IPv6 stack installed -> fail "
"(maybe need to use InternetStackHelper?)");
uint32_t nApplications = node->GetNApplications();
for (uint32_t i = 0; i < nApplications; i++)
{
Ptr<Dhcp6Server> server = DynamicCast<Dhcp6Server>(node->GetApplication(i));
if (!server)
{
Ptr<Dhcp6Server> app = m_serverFactory.Create<Dhcp6Server>();
node->AddApplication(app);
app->SetDhcp6ServerNetDevice(netDevices);
installedApps.Add(app);
}
}
if (nApplications == 0)
{
Ptr<Dhcp6Server> app = m_serverFactory.Create<Dhcp6Server>();
node->AddApplication(app);
app->SetDhcp6ServerNetDevice(netDevices);
installedApps.Add(app);
}
}
return installedApps;
}
Ptr<Application>
Dhcp6Helper::InstallDhcp6ClientInternal(Ptr<Node> clientNode) const
{
Ptr<Application> app;
for (uint32_t index = 0; index < clientNode->GetNApplications(); index++)
{
app = clientNode->GetApplication(index);
if (app->GetInstanceTypeId() == Dhcp6Client::GetTypeId())
{
return app;
}
}
app = m_clientFactory.Create<Dhcp6Client>();
clientNode->AddApplication(app);
return app;
}
} // namespace ns3

View File

@@ -0,0 +1,89 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_HELPER_H
#define DHCP6_HELPER_H
#include "ns3/application-container.h"
#include "ns3/dhcp6-server.h"
#include "ns3/ipv6-address.h"
#include "ns3/ipv6-interface-container.h"
#include "ns3/net-device-container.h"
#include "ns3/node-container.h"
#include "ns3/object-factory.h"
#include <stdint.h>
namespace ns3
{
/**
* @ingroup dhcp6
*
* @class Dhcp6Helper
* @brief The helper class used to configure and install DHCPv6 applications on nodes
*/
class Dhcp6Helper
{
public:
/**
* @brief Default constructor.
*/
Dhcp6Helper();
/**
* @brief Set DHCPv6 client attributes
* @param name Name of the attribute
* @param value Value to be set
*/
void SetClientAttribute(std::string name, const AttributeValue& value);
/**
* @brief Set DHCPv6 server attributes
* @param name Name of the attribute
* @param value Value to be set
*/
void SetServerAttribute(std::string name, const AttributeValue& value);
/**
* @brief Install DHCPv6 client on a set of nodes
*
* If there is already a DHCPv6 client on the node, the app is not installed,
* and the already existing one is returned.
*
* @param clientNodes Nodes on which the DHCPv6 client is installed
* @return The application container with DHCPv6 client installed
*/
ApplicationContainer InstallDhcp6Client(NodeContainer clientNodes) const;
/**
* @brief Install DHCPv6 server on a node / NetDevice. Also updates the
* interface -> server map.
* @param netDevices The NetDevices on which DHCPv6 server application has to be installed
* @return The application container with DHCPv6 server installed
*/
ApplicationContainer InstallDhcp6Server(NetDeviceContainer netDevices);
private:
/**
* @brief Helper method that iterates through the installed applications
* on a node to look for a Dhcp6Client application. It either installs a
* new Dhcp6Client or returns an existing one.
* @param clientNode The node on which the application is to be installed.
* @return Pointer to the Dhcp6Client application on the node.
*/
Ptr<Application> InstallDhcp6ClientInternal(Ptr<Node> clientNode) const;
ObjectFactory m_clientFactory; //!< DHCPv6 client factory.
ObjectFactory m_serverFactory; //!< DHCPv6 server factory.
};
} // namespace ns3
#endif /* DHCP6_HELPER_H */

View File

@@ -0,0 +1,943 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-client.h"
#include "dhcp6-duid.h"
#include "ns3/address-utils.h"
#include "ns3/icmpv6-l4-protocol.h"
#include "ns3/ipv6-interface.h"
#include "ns3/ipv6-l3-protocol.h"
#include "ns3/ipv6-packet-info-tag.h"
#include "ns3/ipv6.h"
#include "ns3/log.h"
#include "ns3/loopback-net-device.h"
#include "ns3/mac48-address.h"
#include "ns3/net-device-container.h"
#include "ns3/object.h"
#include "ns3/pointer.h"
#include "ns3/ptr.h"
#include "ns3/random-variable-stream.h"
#include "ns3/simulator.h"
#include "ns3/socket.h"
#include "ns3/string.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/traced-value.h"
#include "ns3/trickle-timer.h"
#include <algorithm>
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Client");
TypeId
Dhcp6Client::GetTypeId()
{
static TypeId tid =
TypeId("ns3::Dhcp6Client")
.SetParent<Application>()
.AddConstructor<Dhcp6Client>()
.SetGroupName("InternetApps")
.AddAttribute("Transactions",
"A value to be used as the transaction ID.",
StringValue("ns3::UniformRandomVariable[Min=0.0|Max=1000000.0]"),
MakePointerAccessor(&Dhcp6Client::m_transactionId),
MakePointerChecker<RandomVariableStream>())
.AddAttribute("SolicitJitter",
"The jitter in ms that a node waits before sending any solicitation. By "
"default, the model will wait for a duration in ms defined by a uniform "
"random-variable between 0 and SolicitJitter. This is equivalent to"
"SOL_MAX_DELAY (RFC 8415, Section 7.6).",
StringValue("ns3::UniformRandomVariable[Min=0.0|Max=1000.0]"),
MakePointerAccessor(&Dhcp6Client::m_solicitJitter),
MakePointerChecker<RandomVariableStream>())
.AddAttribute("IaidValue",
"The identifier for a new IA created by a client.",
StringValue("ns3::UniformRandomVariable[Min=0.0|Max=1000000.0]"),
MakePointerAccessor(&Dhcp6Client::m_iaidStream),
MakePointerChecker<RandomVariableStream>())
.AddAttribute("SolicitInterval",
"Time after which the client resends the Solicit."
"Equivalent to SOL_MAX_RT (RFC 8415, Section 7.6)",
TimeValue(Seconds(100)),
MakeTimeAccessor(&Dhcp6Client::m_solicitInterval),
MakeTimeChecker())
.AddTraceSource("NewLease",
"The client has obtained a lease",
MakeTraceSourceAccessor(&Dhcp6Client::m_newLease),
"ns3::Ipv6Address::TracedCallback");
return tid;
}
Dhcp6Client::Dhcp6Client()
{
NS_LOG_FUNCTION(this);
}
void
Dhcp6Client::DoDispose()
{
NS_LOG_FUNCTION(this);
for (auto& itr : m_interfaces)
{
itr.second->Cleanup();
itr.second = nullptr;
}
m_interfaces.clear();
m_iaidMap.clear();
Application::DoDispose();
}
int64_t
Dhcp6Client::AssignStreams(int64_t stream)
{
NS_LOG_FUNCTION(this << stream);
m_solicitJitter->SetStream(stream);
m_transactionId->SetStream(stream + 1);
m_iaidStream->SetStream(stream + 2);
return 3;
}
bool
Dhcp6Client::ValidateAdvertise(Dhcp6Header header, Ptr<NetDevice> iDev)
{
Ptr<Packet> packet = Create<Packet>();
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
uint32_t clientTransactId = m_interfaces[ifIndex]->m_transactId;
uint32_t receivedTransactId = header.GetTransactId();
if (clientTransactId != receivedTransactId)
{
return false;
}
Duid clientDuid = header.GetClientIdentifier().GetDuid();
NS_ASSERT_MSG(clientDuid == m_clientDuid, "Client DUID mismatch.");
m_interfaces[ifIndex]->m_serverDuid = header.GetServerIdentifier().GetDuid();
return true;
}
void
Dhcp6Client::SendRequest(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress server)
{
NS_LOG_FUNCTION(this << iDev << header << server);
Ptr<Packet> packet = Create<Packet>();
Dhcp6Header requestHeader;
requestHeader.ResetOptions();
requestHeader.SetMessageType(Dhcp6Header::MessageType::REQUEST);
// TODO: Use min, max for GetValue
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
const auto& dhcpInterface = m_interfaces[ifIndex];
dhcpInterface->m_transactId = static_cast<uint32_t>(m_transactionId->GetValue());
requestHeader.SetTransactId(dhcpInterface->m_transactId);
// Add Client Identifier Option.
requestHeader.AddClientIdentifier(m_clientDuid);
// Add Server Identifier Option, copied from the received header.
Duid serverDuid = header.GetServerIdentifier().GetDuid();
requestHeader.AddServerIdentifier(serverDuid);
// Add Elapsed Time Option.
uint32_t actualElapsedTime =
(Simulator::Now() - dhcpInterface->m_msgStartTime).GetMilliSeconds() / 10;
uint16_t elapsed = actualElapsedTime > 65535 ? 65535 : actualElapsedTime;
requestHeader.AddElapsedTime(elapsed);
// Add IA_NA option.
// Request all addresses from the Advertise message.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
for (const auto& iaOpt : ianaOptionsList)
{
// Iterate through the offered addresses.
// Current approach: Try to accept all offers.
for (const auto& iaAddrOpt : iaOpt.m_iaAddressOption)
{
requestHeader.AddIanaOption(iaOpt.GetIaid(), iaOpt.GetT1(), iaOpt.GetT2());
requestHeader.AddAddress(iaOpt.GetIaid(),
iaAddrOpt.GetIaAddress(),
iaAddrOpt.GetPreferredLifetime(),
iaAddrOpt.GetValidLifetime());
NS_LOG_DEBUG("Requesting " << iaAddrOpt.GetIaAddress());
}
}
// Add Option Request.
header.AddOptionRequest(Options::OptionType::OPTION_SOL_MAX_RT);
packet->AddHeader(requestHeader);
// TODO: Handle server unicast option.
// Send the request message.
dhcpInterface->m_state = State::WAIT_REPLY;
if (dhcpInterface->m_socket->SendTo(
packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(), Dhcp6Header::SERVER_PORT)) >= 0)
{
NS_LOG_INFO("DHCPv6 client: Request sent.");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Request.");
}
}
Dhcp6Client::InterfaceConfig::InterfaceConfig()
{
m_renewTime = Seconds(1000);
m_rebindTime = Seconds(2000);
m_prefLifetime = Seconds(3000);
m_validLifetime = Seconds(4000);
m_nAcceptedAddresses = 0;
}
Dhcp6Client::InterfaceConfig::~InterfaceConfig()
{
std::cout << "InterfaceConfig::~InterfaceConfig" << std::endl;
m_socket = nullptr;
m_client = nullptr;
m_iaids.clear();
m_solicitTimer.Stop();
m_declinedAddresses.clear();
m_renewEvent.Cancel();
m_rebindEvent.Cancel();
for (auto& itr : m_releaseEvent)
{
itr.Cancel();
}
}
void
Dhcp6Client::InterfaceConfig::AcceptedAddress(const Ipv6Address& offeredAddress)
{
NS_LOG_DEBUG("Accepting address " << offeredAddress);
// Check that the offered address is from DHCPv6.
bool found = false;
for (auto& addr : m_offeredAddresses)
{
if (addr == offeredAddress)
{
found = true;
break;
}
}
if (found)
{
m_nAcceptedAddresses += 1;
// Notify the new lease.
m_client->m_newLease(offeredAddress);
std::cerr << "* got a new lease " << offeredAddress << std::endl;
}
}
void
Dhcp6Client::InterfaceConfig::DeclinedAddress(const Ipv6Address& offeredAddress)
{
NS_LOG_DEBUG("Address to be declined " << offeredAddress);
// Check that the offered address is from DHCPv6.
bool found = false;
for (auto& addr : m_offeredAddresses)
{
if (addr == offeredAddress)
{
found = true;
break;
}
}
if (found)
{
m_declinedAddresses.emplace_back(offeredAddress);
if (m_declinedAddresses.size() + m_nAcceptedAddresses == m_offeredAddresses.size())
{
DeclineOffer();
}
}
}
void
Dhcp6Client::InterfaceConfig::DeclineOffer()
{
if (m_declinedAddresses.empty())
{
return;
}
// Cancel all scheduled Release, Renew, Rebind events.
m_renewEvent.Cancel();
m_rebindEvent.Cancel();
for (auto itr : m_releaseEvent)
{
itr.Cancel();
}
Dhcp6Header declineHeader;
Ptr<Packet> packet = Create<Packet>();
// Remove address associations.
for (const auto& offer : m_declinedAddresses)
{
uint32_t iaid = m_client->m_iaidMap[offer];
// IA_NA option, IA address option
declineHeader.AddIanaOption(iaid, m_renewTime.GetSeconds(), m_rebindTime.GetSeconds());
declineHeader.AddAddress(iaid,
offer,
m_prefLifetime.GetSeconds(),
m_validLifetime.GetSeconds());
NS_LOG_DEBUG("Declining address " << offer);
}
m_transactId = static_cast<uint32_t>(m_client->m_transactionId->GetValue());
declineHeader.SetTransactId(m_transactId);
declineHeader.SetMessageType(Dhcp6Header::MessageType::DECLINE);
// Add client identifier option
declineHeader.AddClientIdentifier(m_client->m_clientDuid);
// Add server identifier option
declineHeader.AddServerIdentifier(m_serverDuid);
m_msgStartTime = Simulator::Now();
declineHeader.AddElapsedTime(0);
packet->AddHeader(declineHeader);
if ((m_socket->SendTo(packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(),
Dhcp6Header::SERVER_PORT))) >= 0)
{
NS_LOG_INFO("DHCPv6 client: Decline sent");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Decline");
}
m_state = State::WAIT_REPLY_AFTER_DECLINE;
}
void
Dhcp6Client::InterfaceConfig::Cleanup()
{
m_solicitTimer.Stop();
for (auto& releaseEvent : m_releaseEvent)
{
releaseEvent.Cancel();
}
m_renewEvent.Cancel();
m_rebindEvent.Cancel();
m_socket->SetRecvCallback(MakeNullCallback<void, Ptr<Socket>>());
m_socket->Close();
Ptr<Ipv6> ipv6 = m_client->GetNode()->GetObject<Ipv6>();
// Remove the offered IPv6 addresses from the interface.
for (uint32_t i = 0; i < ipv6->GetNAddresses(m_interfaceIndex); i++)
{
for (const auto& addr : m_offeredAddresses)
{
if (ipv6->GetAddress(m_interfaceIndex, i) == addr)
{
ipv6->RemoveAddress(m_interfaceIndex, i);
break;
}
}
}
m_offeredAddresses.clear();
Ptr<Icmpv6L4Protocol> icmpv6 = DynamicCast<Icmpv6L4Protocol>(
ipv6->GetProtocol(Icmpv6L4Protocol::GetStaticProtocolNumber(), m_interfaceIndex));
icmpv6->TraceDisconnectWithoutContext("DadSuccess", m_acceptedAddressCb.value());
m_acceptedAddressCb = MakeNullCallback<void, const Ipv6Address&>();
icmpv6->TraceDisconnectWithoutContext("DadFailure", m_declinedAddressCb.value());
m_declinedAddressCb = MakeNullCallback<void, const Ipv6Address&>();
}
void
Dhcp6Client::CheckLeaseStatus(Ptr<NetDevice> iDev,
Dhcp6Header header,
Inet6SocketAddress server) const
{
// Read Status Code option.
Options::StatusCodeValues statusCode = header.GetStatusCodeOption().GetStatusCode();
NS_LOG_DEBUG("Received status " << (uint16_t)statusCode << " from DHCPv6 server");
if (statusCode == Options::StatusCodeValues::Success)
{
NS_LOG_INFO("DHCPv6 client: Server bindings updated successfully.");
}
else
{
NS_LOG_INFO("DHCPv6 client: Server bindings update failed.");
}
}
void
Dhcp6Client::ProcessReply(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress server)
{
NS_LOG_FUNCTION(this << iDev << header << server);
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
Ptr<InterfaceConfig> dhcpInterface = m_interfaces[ifIndex];
// Read IA_NA options.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
dhcpInterface->m_declinedAddresses.clear();
Time earliestRebind{Time::Max()};
Time earliestRenew{Time::Max()};
std::vector<uint32_t> iaidList;
for (const auto& iaOpt : ianaOptionsList)
{
// Iterate through the offered addresses.
// Current approach: Try to accept all offers.
for (const auto& iaAddrOpt : iaOpt.m_iaAddressOption)
{
Ipv6Address offeredAddress = iaAddrOpt.GetIaAddress();
// TODO: In Linux, all leased addresses seem to be /128. Double-check this.
Ipv6InterfaceAddress addr(offeredAddress, 128);
ipv6->AddAddress(ifIndex, addr);
ipv6->SetUp(ifIndex);
// Set the preferred and valid lifetimes.
dhcpInterface->m_prefLifetime = Seconds(iaAddrOpt.GetPreferredLifetime());
dhcpInterface->m_validLifetime = Seconds(iaAddrOpt.GetValidLifetime());
// Add the IPv6 address - IAID association.
m_iaidMap[offeredAddress] = iaOpt.GetIaid();
// TODO: Check whether Release event happens for each address.
dhcpInterface->m_releaseEvent.emplace_back(
Simulator::Schedule(dhcpInterface->m_validLifetime,
&Dhcp6Client::SendRelease,
this,
offeredAddress));
dhcpInterface->m_offeredAddresses.push_back(offeredAddress);
}
earliestRenew = std::min(earliestRenew, Seconds(iaOpt.GetT1()));
earliestRebind = std::min(earliestRebind, Seconds(iaOpt.GetT2()));
iaidList.emplace_back(iaOpt.GetIaid());
}
// The renew and rebind events are scheduled for the earliest time across
// all IA_NA options. RFC 8415, Section 18.2.4.
dhcpInterface->m_renewTime = earliestRenew;
dhcpInterface->m_renewEvent.Cancel();
dhcpInterface->m_renewEvent =
Simulator::Schedule(dhcpInterface->m_renewTime, &Dhcp6Client::SendRenew, this, ifIndex);
// Set the rebind timer and schedule the event.
dhcpInterface->m_rebindTime = earliestRebind;
dhcpInterface->m_rebindEvent.Cancel();
dhcpInterface->m_rebindEvent =
Simulator::Schedule(dhcpInterface->m_rebindTime, &Dhcp6Client::SendRebind, this, ifIndex);
int32_t interfaceId = ipv6->GetInterfaceForDevice(iDev);
Ptr<Icmpv6L4Protocol> icmpv6 = DynamicCast<Icmpv6L4Protocol>(
ipv6->GetProtocol(Icmpv6L4Protocol::GetStaticProtocolNumber(), interfaceId));
// If DAD fails, the offer is declined.
if (!dhcpInterface->m_acceptedAddressCb.has_value())
{
dhcpInterface->m_acceptedAddressCb =
MakeCallback(&Dhcp6Client::InterfaceConfig::AcceptedAddress, dhcpInterface);
icmpv6->TraceConnectWithoutContext("DadSuccess",
dhcpInterface->m_acceptedAddressCb.value());
}
if (!dhcpInterface->m_declinedAddressCb.has_value())
{
dhcpInterface->m_declinedAddressCb =
MakeCallback(&Dhcp6Client::InterfaceConfig::DeclinedAddress, dhcpInterface);
icmpv6->TraceConnectWithoutContext("DadFailure",
dhcpInterface->m_declinedAddressCb.value());
}
}
void
Dhcp6Client::SendRenew(uint32_t dhcpInterfaceIndex)
{
NS_LOG_FUNCTION(this);
Dhcp6Header header;
Ptr<Packet> packet = Create<Packet>();
m_interfaces[dhcpInterfaceIndex]->m_transactId =
static_cast<uint32_t>(m_transactionId->GetValue());
header.SetTransactId(m_interfaces[dhcpInterfaceIndex]->m_transactId);
header.SetMessageType(Dhcp6Header::MessageType::RENEW);
// Add client identifier option
header.AddClientIdentifier(m_clientDuid);
// Add server identifier option
header.AddServerIdentifier(m_interfaces[dhcpInterfaceIndex]->m_serverDuid);
m_interfaces[dhcpInterfaceIndex]->m_msgStartTime = Simulator::Now();
header.AddElapsedTime(0);
// Add IA_NA options.
for (const auto& iaidRenew : m_interfaces[dhcpInterfaceIndex]->m_iaids)
{
header.AddIanaOption(iaidRenew,
m_interfaces[dhcpInterfaceIndex]->m_renewTime.GetSeconds(),
m_interfaces[dhcpInterfaceIndex]->m_rebindTime.GetSeconds());
// Iterate through the IPv6Address - IAID map, and add all addresses
// that match the IAID to be renewed.
for (const auto& itr : m_iaidMap)
{
Ipv6Address address = itr.first;
uint32_t iaid = itr.second;
if (iaid == iaidRenew)
{
header.AddAddress(iaidRenew,
address,
m_interfaces[dhcpInterfaceIndex]->m_prefLifetime.GetSeconds(),
m_interfaces[dhcpInterfaceIndex]->m_validLifetime.GetSeconds());
}
}
NS_LOG_DEBUG("Renewing addresses in IAID " << iaidRenew);
}
// Add Option Request option.
header.AddOptionRequest(Options::OptionType::OPTION_SOL_MAX_RT);
packet->AddHeader(header);
if ((m_interfaces[dhcpInterfaceIndex]->m_socket->SendTo(
packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(), Dhcp6Header::SERVER_PORT))) >=
0)
{
NS_LOG_INFO("DHCPv6 client: Renew sent");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Renew");
}
m_interfaces[dhcpInterfaceIndex]->m_state = State::WAIT_REPLY;
}
void
Dhcp6Client::SendRebind(uint32_t dhcpInterfaceIndex)
{
NS_LOG_FUNCTION(this);
Dhcp6Header header;
Ptr<Packet> packet = Create<Packet>();
m_interfaces[dhcpInterfaceIndex]->m_transactId =
static_cast<uint32_t>(m_transactionId->GetValue());
header.SetTransactId(m_interfaces[dhcpInterfaceIndex]->m_transactId);
header.SetMessageType(Dhcp6Header::MessageType::REBIND);
// Add client identifier option
header.AddClientIdentifier(m_clientDuid);
m_interfaces[dhcpInterfaceIndex]->m_msgStartTime = Simulator::Now();
header.AddElapsedTime(0);
// Add IA_NA options.
for (const auto& iaid : m_interfaces[dhcpInterfaceIndex]->m_iaids)
{
header.AddIanaOption(iaid,
m_interfaces[dhcpInterfaceIndex]->m_renewTime.GetSeconds(),
m_interfaces[dhcpInterfaceIndex]->m_rebindTime.GetSeconds());
NS_LOG_DEBUG("Rebinding addresses in IAID " << iaid);
}
// Add Option Request option.
header.AddOptionRequest(Options::OptionType::OPTION_SOL_MAX_RT);
packet->AddHeader(header);
if ((m_interfaces[dhcpInterfaceIndex]->m_socket->SendTo(
packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(), Dhcp6Header::SERVER_PORT))) >=
0)
{
NS_LOG_INFO("DHCPv6 client: Rebind sent.");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Rebind");
}
m_interfaces[dhcpInterfaceIndex]->m_state = State::WAIT_REPLY;
}
void
Dhcp6Client::SendRelease(Ipv6Address address)
{
NS_LOG_FUNCTION(this);
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
Dhcp6Header header;
Ptr<Packet> packet = Create<Packet>();
for (const auto& itr : m_interfaces)
{
uint32_t ifIndex = itr.first;
Ptr<InterfaceConfig> dhcpInterface = itr.second;
dhcpInterface->m_transactId = static_cast<uint32_t>(m_transactionId->GetValue());
bool removed = ipv6->RemoveAddress(ifIndex, address);
if (!removed)
{
continue;
}
header.SetTransactId(dhcpInterface->m_transactId);
header.SetMessageType(Dhcp6Header::MessageType::RELEASE);
// Add client identifier option
header.AddClientIdentifier(m_clientDuid);
// Add server identifier option
header.AddServerIdentifier(dhcpInterface->m_serverDuid);
dhcpInterface->m_msgStartTime = Simulator::Now();
header.AddElapsedTime(0);
// IA_NA option, IA address option
uint32_t iaid = m_iaidMap[address];
header.AddIanaOption(iaid,
dhcpInterface->m_renewTime.GetSeconds(),
dhcpInterface->m_rebindTime.GetSeconds());
header.AddAddress(iaid,
address,
dhcpInterface->m_prefLifetime.GetSeconds(),
dhcpInterface->m_validLifetime.GetSeconds());
NS_LOG_DEBUG("Releasing address " << address);
packet->AddHeader(header);
if ((dhcpInterface->m_socket->SendTo(packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(),
Dhcp6Header::SERVER_PORT))) >= 0)
{
NS_LOG_INFO("DHCPv6 client: Release sent.");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Release");
}
dhcpInterface->m_state = State::WAIT_REPLY_AFTER_RELEASE;
}
}
void
Dhcp6Client::NetHandler(Ptr<Socket> socket)
{
NS_LOG_FUNCTION(this << socket);
Address from;
Ptr<Packet> packet = socket->RecvFrom(from);
Dhcp6Header header;
Inet6SocketAddress senderAddr = Inet6SocketAddress::ConvertFrom(from);
Ipv6PacketInfoTag interfaceInfo;
NS_ASSERT_MSG(packet->RemovePacketTag(interfaceInfo),
"No incoming interface on DHCPv6 message.");
uint32_t incomingIf = interfaceInfo.GetRecvIf();
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
Ptr<NetDevice> iDev = GetNode()->GetDevice(incomingIf);
uint32_t iIf = ipv6->GetInterfaceForDevice(iDev);
Ptr<InterfaceConfig> dhcpInterface = m_interfaces[iIf];
if (packet->RemoveHeader(header) == 0 || !dhcpInterface)
{
return;
}
if (dhcpInterface->m_state == State::WAIT_ADVERTISE &&
header.GetMessageType() == Dhcp6Header::MessageType::ADVERTISE)
{
NS_LOG_INFO("DHCPv6 client: Received Advertise.");
dhcpInterface->m_solicitTimer.Stop();
bool check = ValidateAdvertise(header, iDev);
if (check)
{
SendRequest(iDev, header, senderAddr);
}
}
if (dhcpInterface->m_state == State::WAIT_REPLY &&
header.GetMessageType() == Dhcp6Header::MessageType::REPLY)
{
NS_LOG_INFO("DHCPv6 client: Received Reply.");
dhcpInterface->m_renewEvent.Cancel();
dhcpInterface->m_rebindEvent.Cancel();
for (auto itr : dhcpInterface->m_releaseEvent)
{
itr.Cancel();
}
ProcessReply(iDev, header, senderAddr);
}
if ((dhcpInterface->m_state == State::WAIT_REPLY_AFTER_DECLINE ||
dhcpInterface->m_state == State::WAIT_REPLY_AFTER_RELEASE) &&
(header.GetMessageType() == Dhcp6Header::MessageType::REPLY))
{
NS_LOG_INFO("DHCPv6 client: Received Reply.");
CheckLeaseStatus(iDev, header, senderAddr);
}
}
void
Dhcp6Client::LinkStateHandler(bool isUp, int32_t ifIndex)
{
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
Ptr<InterfaceConfig> dhcpInterface = m_interfaces[ifIndex];
if (isUp)
{
NS_LOG_DEBUG("DHCPv6 client: Link up at " << Simulator::Now().As(Time::S));
StartApplication();
}
else
{
dhcpInterface->Cleanup();
m_interfaces[ifIndex] = nullptr;
NS_LOG_DEBUG("DHCPv6 client: Link down at " << Simulator::Now().As(Time::S));
}
}
Duid
Dhcp6Client::GetSelfDuid() const
{
return m_clientDuid;
}
void
Dhcp6Client::StartApplication()
{
Ptr<Node> node = GetNode();
NS_ASSERT_MSG(node, "Dhcp6Client::StartApplication: cannot get the node from the device.");
Ptr<Ipv6> ipv6 = node->GetObject<Ipv6>();
NS_ASSERT_MSG(ipv6, "Dhcp6Client::StartApplication: node does not have IPv6.");
NS_LOG_DEBUG("Starting DHCPv6 application on node " << node->GetId());
// Set DHCPv6 callback for each interface of the node.
uint32_t nInterfaces = ipv6->GetNInterfaces();
// We skip interface 0 because it's the Loopback.
for (uint32_t ifIndex = 1; ifIndex < nInterfaces; ifIndex++)
{
Ptr<NetDevice> device = ipv6->GetNetDevice(ifIndex);
Ptr<Icmpv6L4Protocol> icmpv6 = DynamicCast<Icmpv6L4Protocol>(
ipv6->GetProtocol(Icmpv6L4Protocol::GetStaticProtocolNumber(), ifIndex));
// If the RA message contains an M flag, the client starts sending Solicits.
icmpv6->SetDhcpv6Callback(MakeCallback(&Dhcp6Client::ReceiveMflag, this));
}
}
void
Dhcp6Client::ReceiveMflag(uint32_t recvInterface)
{
NS_LOG_FUNCTION(this);
Ptr<Node> node = GetNode();
Ptr<Ipv6L3Protocol> ipv6l3 = node->GetObject<Ipv6L3Protocol>();
if (!m_interfaces[recvInterface])
{
// Create config object if M flag is received for the first time.
Ptr<InterfaceConfig> dhcpInterface = Create<InterfaceConfig>();
dhcpInterface->m_client = this;
dhcpInterface->m_interfaceIndex = recvInterface;
dhcpInterface->m_socket = nullptr;
m_interfaces[recvInterface] = dhcpInterface;
// Add an IAID to the client interface.
// Note: There may be multiple IAIDs per interface. We use only one.
std::vector<uint32_t> existingIaNaIds;
while (true)
{
uint32_t iaid = m_iaidStream->GetInteger();
if (std::find(existingIaNaIds.begin(), existingIaNaIds.end(), iaid) ==
existingIaNaIds.end())
{
dhcpInterface->m_iaids.push_back(iaid);
existingIaNaIds.emplace_back(iaid);
break;
}
}
Ptr<Ipv6Interface> ipv6Interface =
node->GetObject<Ipv6L3Protocol>()->GetInterface(recvInterface);
ipv6Interface->TraceConnectWithoutContext(
"InterfaceStatus",
MakeCallback(&Dhcp6Client::LinkStateHandler, this));
}
for (const auto& itr : m_interfaces)
{
uint32_t interface = itr.first;
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
Ptr<NetDevice> device = ipv6->GetNetDevice(interface);
Ptr<InterfaceConfig> dhcpInterface = itr.second;
// Check that RA was received on this interface.
if (interface == recvInterface && m_interfaces[interface])
{
if (!m_interfaces[interface]->m_socket)
{
Ipv6Address linkLocal =
ipv6l3->GetInterface(interface)->GetLinkLocalAddress().GetAddress();
TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
Ptr<Socket> socket = Socket::CreateSocket(node, tid);
socket->Bind(Inet6SocketAddress(linkLocal, Dhcp6Header::CLIENT_PORT));
socket->BindToNetDevice(device);
socket->SetRecvPktInfo(true);
socket->SetRecvCallback(MakeCallback(&Dhcp6Client::NetHandler, this));
m_interfaces[interface]->m_socket = socket;
// Introduce a random delay before sending the Solicit message.
Simulator::Schedule(Time(MilliSeconds(m_solicitJitter->GetValue())),
&Dhcp6Client::Boot,
this,
device);
uint32_t minInterval = m_solicitInterval.GetSeconds() / 2;
dhcpInterface->m_solicitTimer = TrickleTimer(Seconds(minInterval), 4, 1);
dhcpInterface->m_solicitTimer.SetFunction(&Dhcp6Client::Boot, this);
dhcpInterface->m_solicitTimer.Enable();
break;
}
}
}
}
void
Dhcp6Client::Boot(Ptr<NetDevice> device)
{
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(device);
Ptr<InterfaceConfig> dhcpInterface = m_interfaces[ifIndex];
Ptr<Node> node = GetNode();
if (m_clientDuid.IsInvalid())
{
m_clientDuid.Initialize(node);
}
Dhcp6Header header;
Ptr<Packet> packet = Create<Packet>();
// Create a unique transaction ID.
dhcpInterface->m_transactId = static_cast<uint32_t>(m_transactionId->GetValue());
header.SetTransactId(dhcpInterface->m_transactId);
header.SetMessageType(Dhcp6Header::MessageType::SOLICIT);
// Store start time of the message exchange.
dhcpInterface->m_msgStartTime = Simulator::Now();
header.AddElapsedTime(0);
header.AddClientIdentifier(m_clientDuid);
header.AddOptionRequest(Options::OptionType::OPTION_SOL_MAX_RT);
// Add IA_NA option.
for (auto iaid : dhcpInterface->m_iaids)
{
header.AddIanaOption(iaid,
dhcpInterface->m_renewTime.GetSeconds(),
dhcpInterface->m_rebindTime.GetSeconds());
}
packet->AddHeader(header);
if ((dhcpInterface->m_socket->SendTo(packet,
0,
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(),
Dhcp6Header::SERVER_PORT))) >= 0)
{
NS_LOG_INFO("DHCPv6 client: Solicit sent");
}
else
{
NS_LOG_INFO("DHCPv6 client: Error while sending Solicit");
}
dhcpInterface->m_state = State::WAIT_ADVERTISE;
}
void
Dhcp6Client::StopApplication()
{
NS_LOG_FUNCTION(this);
for (auto& itr : m_interfaces)
{
// Close sockets.
if (!itr.second)
{
continue;
}
itr.second->Cleanup();
}
m_interfaces.clear();
m_iaidMap.clear();
}
} // namespace ns3

View File

@@ -0,0 +1,279 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_CLIENT_H
#define DHCP6_CLIENT_H
#include "dhcp6-duid.h"
#include "dhcp6-header.h"
#include "ns3/application.h"
#include "ns3/inet6-socket-address.h"
#include "ns3/net-device-container.h"
#include "ns3/random-variable-stream.h"
#include "ns3/socket.h"
#include "ns3/traced-callback.h"
#include "ns3/trickle-timer.h"
#include <optional>
namespace ns3
{
/**
* @ingroup dhcp6
*
* @class Dhcp6Client
* @brief Implements the DHCPv6 client.
*/
class Dhcp6Client : public Application
{
public:
/**
* @brief Get the type ID.
* @return the object TypeId
*/
static TypeId GetTypeId();
Dhcp6Client();
/**
* @brief Get the DUID.
* @return The DUID which identifies the client.
*/
Duid GetSelfDuid() const;
/// State of the DHCPv6 client.
enum class State
{
WAIT_ADVERTISE, // Waiting for an advertise message
WAIT_REPLY, // Waiting for a reply message
RENEW, // Renewing the lease
WAIT_REPLY_AFTER_DECLINE, // Waiting for a reply after sending a decline message
WAIT_REPLY_AFTER_RELEASE, // Waiting for a reply after sending a release message
};
int64_t AssignStreams(int64_t stream) override;
// protected:
void DoDispose() override;
// private:
void StartApplication() override;
void StopApplication() override;
/**
* @ingroup dhcp6
*
* @class InterfaceConfig
* @brief DHCPv6 client config details about each interface on the node.
*/
class InterfaceConfig : public SimpleRefCount<InterfaceConfig>
{
public:
/**
* The default constructor.
*/
InterfaceConfig();
/**
* Destructor.
*/
~InterfaceConfig();
/// The Dhcp6Client on which the interface is present.
Ptr<Dhcp6Client> m_client;
/// The IPv6 interface index of this configuration.
uint32_t m_interfaceIndex;
/// The socket that has been opened for this interface.
Ptr<Socket> m_socket;
/// The DHCPv6 state of the client interface.
State m_state;
/// The server DUID.
Duid m_serverDuid;
/// The IAIDs associated with this DHCPv6 client interface.
std::vector<uint32_t> m_iaids;
TrickleTimer m_solicitTimer; //!< TrickleTimer to schedule Solicit messages.
Time m_msgStartTime; //!< Time when message exchange starts.
uint32_t m_transactId; //!< Transaction ID of the client-initiated message.
uint8_t m_nAcceptedAddresses; //!< Number of addresses accepted by client.
/// List of addresses offered to the client.
std::vector<Ipv6Address> m_offeredAddresses;
/// List of addresses to be declined by the client.
std::vector<Ipv6Address> m_declinedAddresses;
Time m_renewTime; //!< REN_MAX_RT, Time after which lease should be renewed.
Time m_rebindTime; //!< REB_MAX_RT, Time after which client should send a Rebind message.
Time m_prefLifetime; //!< Preferred lifetime of the address.
Time m_validLifetime; //!< Valid lifetime of the address.
EventId m_renewEvent; //!< Event ID for the Renew event.
EventId m_rebindEvent; //!< Event ID for the rebind event
/// Store all the Event IDs for the addresses being Released.
std::vector<EventId> m_releaseEvent;
/**
* @brief Accept the DHCPv6 offer.
* @param offeredAddress The IPv6 address that has been accepted.
*/
void AcceptedAddress(const Ipv6Address& offeredAddress);
/// Callback for the accepted addresses - needed for cleanup.
std::optional<Callback<void, const Ipv6Address&>> m_acceptedAddressCb{std::nullopt};
/**
* @brief Add a declined address to the list maintained by the client.
* @param offeredAddress The IPv6 address to be declined.
*/
void DeclinedAddress(const Ipv6Address& offeredAddress);
/// Callback for the declined addresses - needed for cleanup.
std::optional<Callback<void, const Ipv6Address&>> m_declinedAddressCb{std::nullopt};
/**
* @brief Send a Decline message to the DHCPv6 server
*/
void DeclineOffer();
/**
* @brief Cleanup the internal callbacks and timers.
*
* MUST be used before removing an interface to avoid circular references locks.
*/
void Cleanup();
};
/**
* @brief Verify the incoming advertise message.
* @param header The DHCPv6 header received.
* @param iDev The incoming NetDevice.
* @return True if the Advertise is valid.
*/
bool ValidateAdvertise(Dhcp6Header header, Ptr<NetDevice> iDev);
/**
* @brief Send a request to the DHCPv6 server.
* @param iDev The outgoing NetDevice
* @param header The DHCPv6 header
* @param server The address of the server
*/
void SendRequest(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress server);
/**
* @brief Send a request to the DHCPv6 server.
* @param iDev The outgoing NetDevice
* @param header The DHCPv6 header
* @param server The address of the server
*/
void ProcessReply(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress server);
/**
* @brief Check lease status after sending a Decline or Release message.
* @param iDev The incoming NetDevice
* @param header The DHCPv6 header
* @param server The address of the server
*/
void CheckLeaseStatus(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress server) const;
/**
* @brief Send a renew message to the DHCPv6 server.
* @param interfaceIndex The interface whose leases are to be renewed.
*/
void SendRenew(uint32_t interfaceIndex);
/**
* @brief Send a rebind message to the DHCPv6 server.
* @param interfaceIndex The interface whose leases are to be rebound.
*/
void SendRebind(uint32_t interfaceIndex);
/**
* @brief Send a Release message to the DHCPv6 server.
* @param address The address whose lease is to be released.
*/
void SendRelease(Ipv6Address address);
/**
* @brief Handles incoming packets from the network
* @param socket incoming Socket
*/
void NetHandler(Ptr<Socket> socket);
/**
* @brief Handle changes in the link state.
* @param isUp Indicates whether the interface is up.
* @param ifIndex The interface index.
*/
void LinkStateHandler(bool isUp, int32_t ifIndex);
/**
* @brief Callback for when an M flag is received.
*
* The M flag is carried by Router Advertisements (RA), and it is a signal
* that the DHCPv6 client must search for a DHCPv6 Server.
*
* @param recvInterface The interface on which the M flag was received.
*/
void ReceiveMflag(uint32_t recvInterface);
/**
* @brief Used to send the Solicit message and start the DHCPv6 client.
* @param device The client interface.
*/
void Boot(Ptr<NetDevice> device);
/**
* @brief Retrieve all existing IAIDs.
* @return A list of all IAIDs.
*/
std::vector<uint32_t> GetIaids();
/// Map each interface to its corresponding configuration details.
std::unordered_map<uint32_t, Ptr<InterfaceConfig>> m_interfaces;
Duid m_clientDuid; //!< The client DUID.
TracedCallback<const Ipv6Address&> m_newLease; //!< Trace the new lease.
/// Track the IPv6 Address - IAID association.
std::unordered_map<Ipv6Address, uint32_t, Ipv6AddressHash> m_iaidMap;
/// Random variable to set transaction ID
Ptr<RandomVariableStream> m_transactionId;
Time m_solicitInterval; //!< SOL_MAX_RT, time between solicitations.
/**
* Random jitter before sending the first Solicit. Equivalent to
* SOL_MAX_DELAY (RFC 8415, Section 7.6)
*/
Ptr<RandomVariableStream> m_solicitJitter;
/// Random variable used to create the IAID.
Ptr<RandomVariableStream> m_iaidStream;
};
/**
* @brief Stream output operator
* @param os output stream
* @param h the Dhcp6Client
* @return updated stream
*/
std::ostream& operator<<(std::ostream& os, const Dhcp6Client& h);
} // namespace ns3
#endif

View File

@@ -0,0 +1,234 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-duid.h"
#include "ns3/address.h"
#include "ns3/assert.h"
#include "ns3/ipv6-l3-protocol.h"
#include "ns3/log.h"
#include "ns3/loopback-net-device.h"
#include "ns3/ptr.h"
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Duid");
Duid::Duid()
{
m_duidType = Duid::Type::LL;
m_hardwareType = 0;
m_time = Time();
m_identifier = std::vector<uint8_t>();
}
bool
Duid::operator==(const Duid& o) const
{
return (m_duidType == o.m_duidType && m_hardwareType == o.m_hardwareType &&
m_identifier == o.m_identifier);
}
bool
operator<(const Duid& a, const Duid& b)
{
if (a.m_duidType < b.m_duidType)
{
return true;
}
else if (a.m_duidType > b.m_duidType)
{
return false;
}
if (a.m_hardwareType < b.m_hardwareType)
{
return true;
}
else if (a.m_hardwareType > b.m_hardwareType)
{
return false;
}
NS_ASSERT(a.GetLength() == b.GetLength());
return a.m_identifier < b.m_identifier;
}
bool
Duid::IsInvalid() const
{
return m_identifier.empty();
}
uint8_t
Duid::GetLength() const
{
return m_identifier.size();
}
std::vector<uint8_t>
Duid::GetIdentifier() const
{
return m_identifier;
}
Duid::Type
Duid::GetDuidType() const
{
return m_duidType;
}
void
Duid::SetDuidType(Duid::Type duidType)
{
m_duidType = duidType;
}
uint16_t
Duid::GetHardwareType() const
{
return m_hardwareType;
}
void
Duid::SetHardwareType(uint16_t hardwareType)
{
NS_LOG_FUNCTION(this << hardwareType);
m_hardwareType = hardwareType;
}
void
Duid::SetDuid(std::vector<uint8_t> identifier)
{
NS_LOG_FUNCTION(this << identifier);
m_duidType = Type::LL;
uint8_t idLen = identifier.size();
NS_ASSERT_MSG(idLen == 6 || idLen == 8, "Duid: Invalid identifier length.");
switch (idLen)
{
case 6:
// Ethernet - 48 bit length
SetHardwareType(1);
break;
case 8:
// EUI-64 - 64 bit length
SetHardwareType(27);
break;
}
m_identifier.resize(idLen);
m_identifier = identifier;
}
void
Duid::Initialize(Ptr<Node> node)
{
Ptr<Ipv6L3Protocol> ipv6 = node->GetObject<Ipv6L3Protocol>();
uint32_t nInterfaces = ipv6->GetNInterfaces();
uint32_t maxAddressLength = 0;
Address duidAddress;
for (uint32_t i = 0; i < nInterfaces; i++)
{
Ptr<NetDevice> device = ipv6->GetNetDevice(i);
// Discard the loopback device.
if (DynamicCast<LoopbackNetDevice>(device))
{
continue;
}
// Check if the NetDevice is up.
if (device->IsLinkUp())
{
NS_LOG_DEBUG("Interface " << device->GetIfIndex() << " up on node " << node->GetId());
Address address = device->GetAddress();
if (address.GetLength() > maxAddressLength)
{
maxAddressLength = address.GetLength();
duidAddress = address;
}
}
}
NS_ASSERT_MSG(!duidAddress.IsInvalid(), "Duid: No suitable NetDevice found for DUID.");
NS_LOG_DEBUG("DUID of node " << node->GetId() << " is " << duidAddress);
// Consider the link-layer address of the first NetDevice in the list.
uint8_t buffer[16];
duidAddress.CopyTo(buffer);
std::vector<uint8_t> identifier(duidAddress.GetLength());
std::copy(buffer, buffer + duidAddress.GetLength(), identifier.begin());
SetDuid(identifier);
}
Time
Duid::GetTime() const
{
return m_time;
}
void
Duid::SetTime(Time time)
{
NS_LOG_FUNCTION(this << time);
m_time = time;
}
uint32_t
Duid::GetSerializedSize() const
{
return 4 + m_identifier.size();
}
void
Duid::Serialize(Buffer::Iterator start) const
{
Buffer::Iterator i = start;
i.WriteHtonU16(static_cast<std::underlying_type_t<Duid::Type>>(m_duidType));
i.WriteHtonU16(m_hardwareType);
for (const auto& byte : m_identifier)
{
i.WriteU8(byte);
}
}
uint32_t
Duid::Deserialize(Buffer::Iterator start, uint32_t len)
{
Buffer::Iterator i = start;
m_duidType = static_cast<Duid::Type>(i.ReadNtohU16());
m_hardwareType = i.ReadNtohU16();
m_identifier.resize(len);
for (uint32_t j = 0; j < len; j++)
{
m_identifier[j] = i.ReadU8();
}
return 4 + m_identifier.size();
}
size_t
Duid::DuidHash::operator()(const Duid& x) const noexcept
{
uint8_t duidLen = x.GetLength();
std::vector<uint8_t> buffer = x.GetIdentifier();
std::string s(buffer.begin(), buffer.begin() + duidLen);
return std::hash<std::string>{}(s);
}
} // namespace ns3

View File

@@ -0,0 +1,201 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_DUID_H
#define DHCP6_DUID_H
#include "ns3/buffer.h"
#include "ns3/node.h"
#include "ns3/nstime.h"
namespace ns3
{
/**
* @ingroup dhcp6
*
* @class Duid
* @brief Implements the unique identifier for DHCPv6.
*/
class Duid
{
public:
/**
* @brief Default constructor.
*/
Duid();
/// DUID type.
enum class Type
{
LLT = 1, // Link-layer address plus time
EN, // Vendor-assigned unique ID based on Enterprise Number
LL, // Link-layer address
UUID, // Universally Unique Identifier (UUID) [RFC6355]
};
/**
* @ingroup dhcp6
*
* @class DuidHash
* @brief Class providing a hash for DUIDs
*/
class DuidHash
{
public:
/**
* @brief Returns the hash of a DUID.
* @param x the DUID
* @return the hash
*
* This method uses std::hash rather than class Hash
* as speed is more important than cryptographic robustness.
*/
size_t operator()(const Duid& x) const noexcept;
};
/**
* @brief Initialize the DUID for a client or server.
* @param node The node for which the DUID is to be generated.
*/
void Initialize(Ptr<Node> node);
/**
* @brief Check if the DUID is invalid.
* @return true if the DUID is invalid.
*/
bool IsInvalid() const;
/**
* @brief Get the DUID type
* @return the DUID type.
*/
Type GetDuidType() const;
/**
* @brief Set the DUID type
* @param duidType the DUID type.
*/
void SetDuidType(Type duidType);
/**
* @brief Get the hardware type.
* @return the hardware type
*/
uint16_t GetHardwareType() const;
/**
* @brief Set the hardware type.
* @param hardwareType the hardware type.
*/
void SetHardwareType(uint16_t hardwareType);
/**
* @brief Set the identifier as the DUID.
* @param identifier the identifier of the node.
*/
void SetDuid(std::vector<uint8_t> identifier);
/**
* @brief Get the time at which the DUID is generated.
* @return the timestamp.
*/
Time GetTime() const;
/**
* @brief Get the length of the DUID.
* @return the DUID length.
*/
uint8_t GetLength() const;
/**
* @brief Set the time at which DUID is generated.
* @param time the timestamp.
*/
void SetTime(Time time);
/**
* @brief Get the DUID serialized size.
* @return The DUID serialized sized in bytes.
*/
uint32_t GetSerializedSize() const;
/**
* @brief Serialize the DUID.
* @param start The buffer iterator.
*/
void Serialize(Buffer::Iterator start) const;
/**
* @brief Deserialize the DUID.
* @param start The buffer iterator.
* @param len The number of bytes to be read.
* @return The number of bytes read.
*/
uint32_t Deserialize(Buffer::Iterator start, uint32_t len);
/**
* @brief Comparison operator
* @param duid header to compare
* @return true if the headers are equal
*/
bool operator==(const Duid& duid) const;
/**
* @brief Less than operator.
*
* @param a the first operand
* @param b the first operand
* @returns true if the operand a is less than operand b
*/
friend bool operator<(const Duid& a, const Duid& b);
// TypeId GetInstanceTypeId() const override;
// void Print(std::ostream& os) const override;
// uint32_t GetSerializedSize() const override;
// void Serialize(Buffer::Iterator start) const override;
// uint32_t Deserialize(Buffer::Iterator start) override;
private:
/**
* @brief Return the identifier of the node.
* @return the identifier.
*/
std::vector<uint8_t> GetIdentifier() const;
/**
* Type of the DUID.
* We currently use only DUID-LL, based on the link-layer address.
*/
Type m_duidType;
uint16_t m_hardwareType; //!< Valid hardware type assigned by IANA.
Time m_time; //!< Time at which the DUID is generated. Used in DUID-LLT.
std::vector<uint8_t> m_identifier; //!< Identifier of the node in bytes.
};
/**
* @brief Stream output operator
* @param os output stream
* @param duid The reference to the DUID object.
* @return updated stream
*/
std::ostream& operator<<(std::ostream& os, const Duid& duid);
/**
* Stream extraction operator
* @param is input stream
* @param duid The reference to the DUID object.
* @return std::istream
*/
std::istream& operator>>(std::istream& is, Duid& duid);
} // namespace ns3
#endif

View File

@@ -0,0 +1,673 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-header.h"
#include "dhcp6-duid.h"
#include "ns3/address-utils.h"
#include "ns3/assert.h"
#include "ns3/log.h"
#include "ns3/simulator.h"
#include <bitset>
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Header");
Dhcp6Header::Dhcp6Header()
: m_len(4),
m_msgType(MessageType::INIT),
m_transactId(0)
{
m_solMaxRt = 7200;
}
Dhcp6Header::MessageType
Dhcp6Header::GetMessageType() const
{
return m_msgType;
}
void
Dhcp6Header::SetMessageType(MessageType msgType)
{
NS_LOG_FUNCTION(this << (uint8_t)msgType);
m_msgType = msgType;
}
uint32_t
Dhcp6Header::GetTransactId() const
{
return m_transactId;
}
void
Dhcp6Header::SetTransactId(uint32_t transactId)
{
NS_LOG_FUNCTION(this << transactId);
m_transactId = transactId;
}
void
Dhcp6Header::AddMessageLength(uint32_t len)
{
m_len += len;
}
void
Dhcp6Header::ResetOptions()
{
m_len = 4;
m_options.clear();
}
TypeId
Dhcp6Header::GetTypeId()
{
static TypeId tid = TypeId("ns3::Dhcp6Header")
.SetParent<Header>()
.SetGroupName("Internet-Apps")
.AddConstructor<Dhcp6Header>();
return tid;
}
TypeId
Dhcp6Header::GetInstanceTypeId() const
{
return GetTypeId();
}
IdentifierOption
Dhcp6Header::GetClientIdentifier()
{
return m_clientIdentifier;
}
IdentifierOption
Dhcp6Header::GetServerIdentifier()
{
return m_serverIdentifier;
}
StatusCodeOption
Dhcp6Header::GetStatusCodeOption()
{
return m_statusCode;
}
std::vector<IaOptions>
Dhcp6Header::GetIanaOptions()
{
return m_ianaList;
}
void
Dhcp6Header::AddElapsedTime(uint16_t timestamp)
{
// Set the code, length, value.
m_elapsedTime.SetOptionCode(Options::OptionType::OPTION_ELAPSED_TIME);
m_elapsedTime.SetOptionLength(2);
m_elapsedTime.SetOptionValue(timestamp);
// Increase the total length by 6 bytes.
AddMessageLength(6);
// Set the option flag to true.
m_options[Options::OptionType::OPTION_ELAPSED_TIME] = true;
}
void
Dhcp6Header::AddClientIdentifier(Duid duid)
{
AddIdentifierOption(m_clientIdentifier, Options::OptionType::OPTION_CLIENTID, duid);
}
void
Dhcp6Header::AddServerIdentifier(Duid duid)
{
AddIdentifierOption(m_serverIdentifier, Options::OptionType::OPTION_SERVERID, duid);
}
void
Dhcp6Header::AddIdentifierOption(IdentifierOption& identifier,
Options::OptionType optionType,
Duid duid)
{
// DUID type (2 bytes) + hw type (2 bytes) + Link-layer Address (variable)
uint16_t duidLength = 2 + 2 + duid.GetLength();
// Set the option code, length, hardware type, link layer address.
identifier.SetOptionCode(optionType);
identifier.SetOptionLength(duidLength);
identifier.SetDuid(duid);
// Increase the total length by (4 + duidLength) bytes.
AddMessageLength(4 + duidLength);
// Set the option flag to true.
m_options[optionType] = true;
}
RequestOptions
Dhcp6Header::GetOptionRequest()
{
return m_optionRequest;
}
void
Dhcp6Header::AddOptionRequest(Options::OptionType optionType)
{
// Check if this is the first option request.
if (m_optionRequest.GetOptionLength() == 0)
{
AddMessageLength(4);
}
// Set the option code, length, and add the requested option.
m_optionRequest.SetOptionCode(Options::OptionType::OPTION_ORO);
m_optionRequest.SetOptionLength(m_optionRequest.GetOptionLength() + 2);
m_optionRequest.AddRequestedOption(optionType);
// Increase the total length by 2 bytes.
AddMessageLength(2);
// Set the option flag to true.
m_options[Options::OptionType::OPTION_ORO] = true;
}
void
Dhcp6Header::HandleOptionRequest(std::vector<Options::OptionType> requestedOptions)
{
// Currently, only Options::OptionType::OPTION_SOL_MAX_RT is supported.
for (auto itr : requestedOptions)
{
switch (itr)
{
case Options::OptionType::OPTION_SOL_MAX_RT:
AddSolMaxRt();
break;
default:
NS_LOG_WARN("Requested Option not supported.");
}
}
}
void
Dhcp6Header::AddSolMaxRt()
{
// Increase the total message length.
// 4 bytes - for option code + option length.
// 4 bytes - for the option value.
AddMessageLength(4 + 4);
m_options[Options::OptionType::OPTION_SOL_MAX_RT] = true;
}
void
Dhcp6Header::AddIanaOption(uint32_t iaid, uint32_t t1, uint32_t t2)
{
AddIaOption(Options::OptionType::OPTION_IA_NA, iaid, t1, t2);
}
void
Dhcp6Header::AddIataOption(uint32_t iaid)
{
AddIaOption(Options::OptionType::OPTION_IA_TA, iaid);
}
void
Dhcp6Header::AddIaOption(Options::OptionType optionType, uint32_t iaid, uint32_t t1, uint32_t t2)
{
// Create a new identity association.
IaOptions newIa;
newIa.SetOptionCode(optionType);
// Minimum option length of an IA is 12 bytes.
uint16_t optionLength = 12;
newIa.SetOptionLength(optionLength);
newIa.SetIaid(iaid);
newIa.SetT1(t1);
newIa.SetT2(t2);
// Check if the IA is to be added to the list of IANA or IATA options.
// If the IAID is already present, it is not added.
switch (optionType)
{
case Options::OptionType::OPTION_IA_NA: {
bool iaidPresent = false;
for (const auto& itr : m_ianaList)
{
if (itr.GetIaid() == newIa.GetIaid())
{
iaidPresent = true;
break;
}
}
if (!iaidPresent)
{
m_ianaList.push_back(newIa);
AddMessageLength(4 + optionLength);
}
break;
}
case Options::OptionType::OPTION_IA_TA: {
bool iaidPresent = false;
for (const auto& itr : m_iataList)
{
if (itr.GetIaid() == newIa.GetIaid())
{
iaidPresent = true;
break;
}
}
if (!iaidPresent)
{
m_iataList.push_back(newIa);
AddMessageLength(4 + optionLength);
}
break;
}
default:
break;
}
// Set the option flag to true.
m_options[optionType] = true;
}
void
Dhcp6Header::AddAddress(uint32_t iaid,
Ipv6Address address,
uint32_t prefLifetime,
uint32_t validLifetime)
{
bool isIana = false;
bool isIata = false;
// Check if IAID corresponds to an IANA option.
auto itr = m_ianaList.begin();
while (itr != m_ianaList.end())
{
if (iaid == (*itr).GetIaid())
{
isIana = true;
break;
}
itr++;
}
// Else, check if IAID corresponds to an IATA option.
if (!isIana)
{
itr = m_iataList.begin();
while (itr != m_iataList.end())
{
if (iaid == (*itr).GetIaid())
{
isIata = true;
break;
}
itr++;
}
}
if (!isIana && !isIata)
{
NS_LOG_ERROR("Given IAID does not exist, cannot add address.");
}
IaAddressOption adrOpt;
adrOpt.SetOptionCode(Options::OptionType::OPTION_IAADDR);
// Set length of IA Address option without including additional option list.
adrOpt.SetOptionLength(24);
adrOpt.SetIaAddress(address);
adrOpt.SetPreferredLifetime(prefLifetime);
adrOpt.SetValidLifetime(validLifetime);
(*itr).m_iaAddressOption.push_back(adrOpt);
// Add the address option length to the overall IANA or IATA length.
(*itr).SetOptionLength((*itr).GetOptionLength() + 28);
// Increase the total message length.
AddMessageLength(4 + 24);
}
std::map<Options::OptionType, bool>
Dhcp6Header::GetOptionList()
{
return m_options;
}
void
Dhcp6Header::AddStatusCode(Options::StatusCodeValues status, std::string statusMsg)
{
m_statusCode.SetOptionCode(Options::OptionType::OPTION_STATUS_CODE);
m_statusCode.SetStatusCode(status);
m_statusCode.SetStatusMessage(statusMsg);
m_statusCode.SetOptionLength(2 + m_statusCode.GetStatusMessage().length());
// Increase the total message length.
AddMessageLength(4 + m_statusCode.GetOptionLength());
// Set the option flag to true.
m_options[Options::OptionType::OPTION_STATUS_CODE] = true;
}
uint32_t
Dhcp6Header::GetSerializedSize() const
{
return m_len;
}
void
Dhcp6Header::Print(std::ostream& os) const
{
os << "(type=" << +(uint8_t)m_msgType << ")";
}
void
Dhcp6Header::Serialize(Buffer::Iterator start) const
{
Buffer::Iterator i = start;
uint32_t mTTid = (uint32_t)m_msgType << 24 | m_transactId;
i.WriteHtonU32(mTTid);
if (m_options.find(Options::OptionType::OPTION_CLIENTID) != m_options.end())
{
i.WriteHtonU16((uint16_t)m_clientIdentifier.GetOptionCode());
i.WriteHtonU16(m_clientIdentifier.GetOptionLength());
Duid duid = m_clientIdentifier.GetDuid();
uint32_t size = duid.GetSerializedSize();
duid.Serialize(i);
i.Next(size);
}
if (m_options.find(Options::OptionType::OPTION_SERVERID) != m_options.end())
{
i.WriteHtonU16((uint16_t)m_serverIdentifier.GetOptionCode());
i.WriteHtonU16(m_serverIdentifier.GetOptionLength());
Duid duid = m_serverIdentifier.GetDuid();
uint32_t size = duid.GetSerializedSize();
duid.Serialize(i);
i.Next(size);
}
if (m_options.find(Options::OptionType::OPTION_IA_NA) != m_options.end())
{
for (const auto& itr : m_ianaList)
{
i.WriteHtonU16((uint16_t)itr.GetOptionCode());
i.WriteHtonU16(itr.GetOptionLength());
i.WriteHtonU32(itr.GetIaid());
i.WriteHtonU32(itr.GetT1());
i.WriteHtonU32(itr.GetT2());
std::vector<IaAddressOption> iaAddresses = itr.m_iaAddressOption;
for (const auto& iaItr : iaAddresses)
{
i.WriteHtonU16((uint16_t)iaItr.GetOptionCode());
i.WriteHtonU16(iaItr.GetOptionLength());
Address addr = iaItr.GetIaAddress();
uint8_t addrBuf[16];
addr.CopyTo(addrBuf);
i.Write(addrBuf, 16);
i.WriteHtonU32(iaItr.GetPreferredLifetime());
i.WriteHtonU32(iaItr.GetValidLifetime());
}
}
}
if (m_options.find(Options::OptionType::OPTION_ELAPSED_TIME) != m_options.end())
{
i.WriteHtonU16((uint16_t)m_elapsedTime.GetOptionCode());
i.WriteHtonU16(m_elapsedTime.GetOptionLength());
i.WriteHtonU16(m_elapsedTime.GetOptionValue());
}
if (m_options.find(Options::OptionType::OPTION_ORO) != m_options.end())
{
i.WriteHtonU16((uint16_t)m_optionRequest.GetOptionCode());
i.WriteHtonU16(m_optionRequest.GetOptionLength());
std::vector<Options::OptionType> requestedOptions = m_optionRequest.GetRequestedOptions();
for (const auto& itr : requestedOptions)
{
i.WriteHtonU16(static_cast<uint16_t>(itr));
}
}
if (m_options.find(Options::OptionType::OPTION_SOL_MAX_RT) != m_options.end())
{
i.WriteHtonU16((uint16_t)Options::OptionType::OPTION_SOL_MAX_RT);
i.WriteHtonU16(4);
i.WriteHtonU32(m_solMaxRt);
}
if (m_options.find(Options::OptionType::OPTION_STATUS_CODE) != m_options.end())
{
i.WriteHtonU16((uint16_t)Options::OptionType::OPTION_STATUS_CODE);
i.WriteHtonU16(m_statusCode.GetOptionLength());
i.WriteHtonU16((uint16_t)m_statusCode.GetStatusCode());
// Considering a maximum message length of 128 bytes (arbitrary).
uint8_t strBuf[128];
m_statusCode.GetStatusMessage().copy((char*)strBuf,
m_statusCode.GetStatusMessage().length());
strBuf[m_statusCode.GetOptionLength() - 2] = '\0';
i.Write(strBuf, m_statusCode.GetStatusMessage().length());
}
}
uint32_t
Dhcp6Header::Deserialize(Buffer::Iterator start)
{
Buffer::Iterator i = start;
uint32_t cLen = i.GetSize();
uint32_t mTTid = i.ReadNtohU32();
m_msgType = (MessageType)(mTTid >> 24);
m_transactId = mTTid & 0x00FFFFFF;
uint32_t len = 4;
uint16_t option;
bool loop = true;
do
{
if (len + 2 <= cLen)
{
option = i.ReadNtohU16();
len += 2;
}
else
{
m_len = len;
return m_len;
}
auto opt = static_cast<Options::OptionType>(option);
switch (opt)
{
case Options::OptionType::OPTION_CLIENTID:
NS_LOG_INFO("Client Identifier Option");
if (len + 2 <= cLen)
{
m_clientIdentifier.SetOptionCode(opt);
m_clientIdentifier.SetOptionLength(i.ReadNtohU16());
len += 2;
if (len + m_clientIdentifier.GetOptionLength() <= cLen)
{
uint32_t duidLength = m_clientIdentifier.GetOptionLength();
// Read DUID.
Duid duid;
uint32_t read = duid.Deserialize(i, duidLength - 4);
i.Next(read);
m_clientIdentifier.SetDuid(duid);
len += m_clientIdentifier.GetOptionLength();
}
}
break;
case Options::OptionType::OPTION_SERVERID:
NS_LOG_INFO("Server ID Option");
if (len + 2 <= cLen)
{
m_serverIdentifier.SetOptionCode(opt);
m_serverIdentifier.SetOptionLength(i.ReadNtohU16());
len += 2;
}
if (len + m_clientIdentifier.GetOptionLength() <= cLen)
{
uint32_t duidLength = m_serverIdentifier.GetOptionLength();
// Read DUID.
Duid duid;
uint32_t read = duid.Deserialize(i, duidLength - 4);
i.Next(read);
m_serverIdentifier.SetDuid(duid);
len += m_serverIdentifier.GetOptionLength();
}
break;
case Options::OptionType::OPTION_IA_NA: {
NS_LOG_INFO("IANA Option");
IaOptions iana;
uint32_t iaAddrOptLen = 0;
if (len + 2 <= cLen)
{
iana.SetOptionCode(opt);
iana.SetOptionLength(i.ReadNtohU16());
iaAddrOptLen = iana.GetOptionLength();
len += 2;
}
if (len + 12 <= cLen)
{
iana.SetIaid(i.ReadNtohU32());
iana.SetT1(i.ReadNtohU32());
iana.SetT2(i.ReadNtohU32());
len += 12;
iaAddrOptLen -= 12;
}
uint32_t readLen = 0;
while (readLen < iaAddrOptLen)
{
IaAddressOption iaAddrOpt;
iaAddrOpt.SetOptionCode(static_cast<Options::OptionType>(i.ReadNtohU16()));
iaAddrOpt.SetOptionLength(i.ReadNtohU16());
uint8_t addrBuf[16];
i.Read(addrBuf, 16);
iaAddrOpt.SetIaAddress(Ipv6Address(addrBuf));
iaAddrOpt.SetPreferredLifetime(i.ReadNtohU32());
iaAddrOpt.SetValidLifetime(i.ReadNtohU32());
iana.m_iaAddressOption.push_back(iaAddrOpt);
len += 4 + iaAddrOpt.GetOptionLength();
readLen += 4 + iaAddrOpt.GetOptionLength();
}
m_ianaList.push_back(iana);
m_options[Options::OptionType::OPTION_IA_NA] = true;
break;
}
case Options::OptionType::OPTION_ELAPSED_TIME:
NS_LOG_INFO("Elapsed Time Option");
if (len + 4 <= cLen)
{
m_elapsedTime.SetOptionCode(opt);
m_elapsedTime.SetOptionLength(i.ReadNtohU16());
m_elapsedTime.SetOptionValue(i.ReadNtohU16());
m_options[Options::OptionType::OPTION_ELAPSED_TIME] = true;
len += 4;
}
else
{
NS_LOG_WARN("Malformed Packet");
return 0;
}
break;
case Options::OptionType::OPTION_ORO:
NS_LOG_INFO("Option Request Option");
if (len + 2 <= cLen)
{
m_optionRequest.SetOptionCode(opt);
m_optionRequest.SetOptionLength(i.ReadNtohU16());
len += 2;
}
while (len + 2 <= cLen)
{
m_optionRequest.AddRequestedOption(
static_cast<Options::OptionType>(i.ReadNtohU16()));
len += 2;
}
m_options[Options::OptionType::OPTION_ORO] = true;
break;
case Options::OptionType::OPTION_SOL_MAX_RT:
NS_LOG_INFO("Solicit Max RT Option");
if (len + 6 <= cLen)
{
i.ReadNtohU16();
m_solMaxRt = i.ReadNtohU32();
len += 6;
}
m_options[Options::OptionType::OPTION_SOL_MAX_RT] = true;
break;
case Options::OptionType::OPTION_STATUS_CODE:
NS_LOG_INFO("Status Code Option");
if (len + 2 <= cLen)
{
m_statusCode.SetOptionCode(opt);
m_statusCode.SetOptionLength(i.ReadNtohU16());
len += 2;
}
if (len + 2 <= cLen)
{
m_statusCode.SetStatusCode((Options::StatusCodeValues)i.ReadNtohU16());
len += 2;
}
if (len + (m_statusCode.GetOptionLength() - 2) <= cLen)
{
uint8_t msgLength = m_statusCode.GetOptionLength() - 2;
uint8_t strBuf[128];
i.Read(strBuf, msgLength);
strBuf[msgLength] = '\0';
std::string statusMsg((char*)strBuf);
m_statusCode.SetStatusMessage(statusMsg);
len += msgLength;
}
m_options[Options::OptionType::OPTION_STATUS_CODE] = true;
break;
default:
NS_LOG_WARN("Unidentified Option " << option);
NS_LOG_WARN("Malformed Packet");
return 0;
}
} while (loop);
m_len = len;
return m_len;
}
} // namespace ns3

View File

@@ -0,0 +1,287 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_HEADER_H
#define DHCP6_HEADER_H
#include "dhcp6-duid.h"
#include "dhcp6-options.h"
#include "ns3/address.h"
#include "ns3/buffer.h"
#include "ns3/header.h"
#include "ns3/ipv6-address.h"
#include "ns3/ptr.h"
#include "ns3/random-variable-stream.h"
namespace ns3
{
/**
* @ingroup internet-apps
* @defgroup dhcp6 DHCPv6 Client and Server
*/
/**
* @ingroup dhcp6
*
* @class Dhcp6Header
* @brief Implements the DHCPv6 header.
*/
class Dhcp6Header : public Header
{
public:
/**
* @brief Get the type ID.
* @return the object TypeId
*/
static TypeId GetTypeId();
/**
* @brief Default constructor.
*/
Dhcp6Header();
/**
* Enum to identify the message type.
* RELAY_FORW, RELAY_REPL message types are not currently implemented.
* These symbols and values are defined in [RFC 8415,
* section 7.3](https://datatracker.ietf.org/doc/html/rfc8415#section-7.3)
*/
enum class MessageType
{
INIT = 0, // Added for initialization
SOLICIT = 1,
ADVERTISE = 2,
REQUEST = 3,
CONFIRM = 4,
RENEW = 5,
REBIND = 6,
REPLY = 7,
RELEASE = 8,
DECLINE = 9,
RECONFIGURE = 10,
INFORMATION_REQUEST = 11,
RELAY_FORW = 12,
RELAY_REPL = 13
};
/**
* @brief Get the type of message.
* @return integer corresponding to the message type.
*/
MessageType GetMessageType() const;
/**
* @brief Set the message type.
* @param msgType integer corresponding to the message type.
*/
void SetMessageType(MessageType msgType);
/**
* @brief Get the transaction ID.
* @return the 32-bit transaction ID
*/
uint32_t GetTransactId() const;
/**
* @brief Set the transaction ID.
* @param transactId A 32-bit transaction ID.
*/
void SetTransactId(uint32_t transactId);
/**
* @brief Reset all options.
*/
void ResetOptions();
/**
* @brief Get the client identifier.
* @return the client identifier option.
*/
IdentifierOption GetClientIdentifier();
/**
* @brief Get the server identifier.
* @return the server identifier option.
*/
IdentifierOption GetServerIdentifier();
/**
* @brief Get the list of IA_NA options.
* @return the list of IA_NA options.
*/
std::vector<IaOptions> GetIanaOptions();
/**
* @brief Get the status code of the operation.
* @return the status code option.
*/
StatusCodeOption GetStatusCodeOption();
/**
* @brief Set the elapsed time option.
* @param timestamp the time at which the client began the exchange.
*/
void AddElapsedTime(uint16_t timestamp);
/**
* @brief Add the client identifier option.
* @param duid The DUID which identifies the client.
*/
void AddClientIdentifier(Duid duid);
/**
* @brief Add the server identifier option.
* @param duid The DUID which identifies the server.
*/
void AddServerIdentifier(Duid duid);
/**
* @brief Request additional options.
* @param optionType the option to be requested.
*/
void AddOptionRequest(Options::OptionType optionType);
/**
* @brief Add the status code option.
* @param statusCode the status code of the operation.
* @param statusMsg the status message.
*/
void AddStatusCode(Options::StatusCodeValues statusCode, std::string statusMsg);
/**
* @brief Add IANA option.
* @param iaid
* @param t1
* @param t2
*/
void AddIanaOption(uint32_t iaid, uint32_t t1, uint32_t t2);
/**
* @brief Add IATA option.
* @param iaid
*/
void AddIataOption(uint32_t iaid);
/**
* @brief Add IA address option to the IANA or IATA.
* @param iaid the unique identifier of the identity association.
* @param address The IPv6 address to be offered.
* @param prefLifetime the preferred lifetime in seconds.
* @param validLifetime the valid lifetime in seconds.
*/
void AddAddress(uint32_t iaid,
Ipv6Address address,
uint32_t prefLifetime,
uint32_t validLifetime);
/**
* @brief Get the option request option.
* @return the option request option.
*/
RequestOptions GetOptionRequest();
/**
* @brief Handle all options requested by client.
* @param requestedOptions the options requested by the client.
*/
void HandleOptionRequest(std::vector<Options::OptionType> requestedOptions);
/**
* @brief Add the SOL_MAX_RT option.
*/
void AddSolMaxRt();
/**
* @brief Get list of all options set in the header.
* @return the list of options.
*/
std::map<Options::OptionType, bool> GetOptionList();
/**
* @brief The port number of the DHCPv6 client.
*/
static const uint16_t CLIENT_PORT = 546;
/**
* @brief The port number of the DHCPv6 server.
*/
static const uint16_t SERVER_PORT = 547;
private:
TypeId GetInstanceTypeId() const override;
void Print(std::ostream& os) const override;
uint32_t GetSerializedSize() const override;
void Serialize(Buffer::Iterator start) const override;
uint32_t Deserialize(Buffer::Iterator start) override;
/**
* @brief Update the message length.
* @param len The length to be added to the total.
*/
void AddMessageLength(uint32_t len);
/**
* @brief Add an identifier option to the header.
* @param identifier the client or server identifier option object.
* @param optionType identify whether to add a client or server identifier.
* @param duid The unique identifier for the client or server.
*/
void AddIdentifierOption(IdentifierOption& identifier,
Options::OptionType optionType,
Duid duid);
/**
* @brief Add IANA or IATA option to the header.
* @param optionType identify whether to add an IANA or IATA.
* @param iaid
* @param t1
* @param t2
*/
void AddIaOption(Options::OptionType optionType,
uint32_t iaid,
uint32_t t1 = 0,
uint32_t t2 = 0);
uint32_t m_len; //!< The length of the message.
MessageType m_msgType; //!< The message type.
IdentifierOption m_clientIdentifier; //!< The client identifier option.
IdentifierOption m_serverIdentifier; //!< The server identifier option.
std::vector<IaOptions> m_ianaList; //!< Vector of IA_NA options.
std::vector<IaOptions> m_iataList; //!< Vector of IA_TA options.
uint32_t m_solMaxRt; //!< Default value for SOL_MAX_RT option.
/**
* The transaction ID calculated by the client or the server.
* This is a 24-bit integer.
*/
uint32_t m_transactId : 24;
/**
* Options present in the header, indexed by option code.
* TODO: Use std::set instead.
*/
std::map<Options::OptionType, bool> m_options;
/// (optional) The status code of the operation just performed.
StatusCodeOption m_statusCode;
/// List of additional options requested.
RequestOptions m_optionRequest;
/// The preference value for the server.
PreferenceOption m_preference;
/// The amount of time since the client began the transaction.
ElapsedTimeOption m_elapsedTime;
};
} // namespace ns3
#endif

View File

@@ -0,0 +1,298 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-options.h"
#include "dhcp6-duid.h"
#include "ns3/address-utils.h"
#include "ns3/assert.h"
#include "ns3/log.h"
#include "ns3/loopback-net-device.h"
#include "ns3/ptr.h"
#include "ns3/simulator.h"
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Options");
Options::Options()
{
m_optionCode = OptionType::OPTION_INIT;
m_optionLength = 0;
}
Options::Options(OptionType code, uint16_t length)
{
NS_LOG_FUNCTION(this << static_cast<uint16_t>(code) << length);
m_optionCode = code;
m_optionLength = length;
}
Options::OptionType
Options::GetOptionCode() const
{
return m_optionCode;
}
void
Options::SetOptionCode(OptionType code)
{
NS_LOG_FUNCTION(this << static_cast<uint16_t>(code));
m_optionCode = code;
}
uint16_t
Options::GetOptionLength() const
{
return m_optionLength;
}
void
Options::SetOptionLength(uint16_t length)
{
NS_LOG_FUNCTION(this << length);
m_optionLength = length;
}
IdentifierOption::IdentifierOption()
{
}
IdentifierOption::IdentifierOption(uint16_t hardwareType, Address linkLayerAddress, Time time)
{
NS_LOG_FUNCTION(this << hardwareType << linkLayerAddress);
if (time.IsZero())
{
m_duid.SetDuidType(Duid::Type::LL);
}
else
{
m_duid.SetDuidType(Duid::Type::LLT);
}
m_duid.SetHardwareType(hardwareType);
uint8_t buffer[16];
linkLayerAddress.CopyTo(buffer);
std::vector<uint8_t> identifier;
std::copy(buffer, buffer + linkLayerAddress.GetLength(), identifier.begin());
m_duid.SetDuid(identifier);
}
void
IdentifierOption::SetDuid(Duid duid)
{
NS_LOG_FUNCTION(this);
m_duid = duid;
}
Duid
IdentifierOption::GetDuid() const
{
return m_duid;
}
StatusCodeOption::StatusCodeOption()
{
m_statusCode = StatusCodeValues::Success;
m_statusMessage = "";
}
Options::StatusCodeValues
StatusCodeOption::GetStatusCode() const
{
return m_statusCode;
}
void
StatusCodeOption::SetStatusCode(StatusCodeValues statusCode)
{
NS_LOG_FUNCTION(this << static_cast<uint16_t>(statusCode));
m_statusCode = statusCode;
}
std::string
StatusCodeOption::GetStatusMessage() const
{
return m_statusMessage;
}
void
StatusCodeOption::SetStatusMessage(std::string statusMessage)
{
NS_LOG_FUNCTION(this);
m_statusMessage = statusMessage;
}
IaAddressOption::IaAddressOption()
{
m_iaAddress = Ipv6Address("::");
m_preferredLifetime = 0;
m_validLifetime = 0;
}
IaAddressOption::IaAddressOption(Ipv6Address iaAddress,
uint32_t preferredLifetime,
uint32_t validLifetime)
{
m_iaAddress = iaAddress;
m_preferredLifetime = preferredLifetime;
m_validLifetime = validLifetime;
}
Ipv6Address
IaAddressOption::GetIaAddress() const
{
return m_iaAddress;
}
void
IaAddressOption::SetIaAddress(Ipv6Address iaAddress)
{
NS_LOG_FUNCTION(this << iaAddress);
m_iaAddress = iaAddress;
}
uint32_t
IaAddressOption::GetPreferredLifetime() const
{
return m_preferredLifetime;
}
void
IaAddressOption::SetPreferredLifetime(uint32_t preferredLifetime)
{
NS_LOG_FUNCTION(this << preferredLifetime);
m_preferredLifetime = preferredLifetime;
}
uint32_t
IaAddressOption::GetValidLifetime() const
{
return m_validLifetime;
}
void
IaAddressOption::SetValidLifetime(uint32_t validLifetime)
{
NS_LOG_FUNCTION(this << validLifetime);
m_validLifetime = validLifetime;
}
IaOptions::IaOptions()
{
m_iaid = 0;
m_t1 = 0;
m_t2 = 0;
}
uint32_t
IaOptions::GetIaid() const
{
return m_iaid;
}
void
IaOptions::SetIaid(uint32_t iaid)
{
NS_LOG_FUNCTION(this << iaid);
m_iaid = iaid;
}
uint32_t
IaOptions::GetT1() const
{
return m_t1;
}
void
IaOptions::SetT1(uint32_t t1)
{
NS_LOG_FUNCTION(this << t1);
m_t1 = t1;
}
uint32_t
IaOptions::GetT2() const
{
return m_t2;
}
void
IaOptions::SetT2(uint32_t t2)
{
NS_LOG_FUNCTION(this << t2);
m_t2 = t2;
}
RequestOptions::RequestOptions()
{
m_requestedOptions = std::vector<OptionType>();
}
std::vector<Options::OptionType>
RequestOptions::GetRequestedOptions() const
{
return m_requestedOptions;
}
void
RequestOptions::AddRequestedOption(OptionType requestedOption)
{
m_requestedOptions.push_back(requestedOption);
}
template <typename T>
IntegerOptions<T>::IntegerOptions()
{
m_optionValue = 0;
}
template <typename T>
T
IntegerOptions<T>::GetOptionValue() const
{
return m_optionValue;
}
template <typename T>
void
IntegerOptions<T>::SetOptionValue(T optionValue)
{
NS_LOG_FUNCTION(this << optionValue);
m_optionValue = optionValue;
}
ServerUnicastOption::ServerUnicastOption()
{
m_serverAddress = Ipv6Address("::");
}
Ipv6Address
ServerUnicastOption::GetServerAddress()
{
return m_serverAddress;
}
void
ServerUnicastOption::SetServerAddress(Ipv6Address serverAddress)
{
NS_LOG_FUNCTION(this << serverAddress);
m_serverAddress = serverAddress;
}
// Public template function declarations.
template class IntegerOptions<uint16_t>;
template class IntegerOptions<uint8_t>;
} // namespace ns3

View File

@@ -0,0 +1,468 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_OPTIONS_H
#define DHCP6_OPTIONS_H
#include "dhcp6-duid.h"
#include "ns3/address.h"
#include "ns3/buffer.h"
#include "ns3/header.h"
#include "ns3/ipv6-address.h"
#include "ns3/node.h"
#include "ns3/nstime.h"
#include "ns3/ptr.h"
#include "ns3/random-variable-stream.h"
namespace ns3
{
/**
* @ingroup dhcp6
*
* @class Options
* @brief Implements the functionality of DHCPv6 options
*/
class Options
{
public:
/**
* Enum to identify the status code of the operation.
* These symbols and values are defined in [RFC 8415,
* section 21.13](https://datatracker.ietf.org/doc/html/rfc8415#section-21.13)
*/
enum class StatusCodeValues
{
Success = 0,
UnspecFail = 1,
NoAddrsAvail = 2,
NoBinding = 3,
NotOnLink = 4,
UseMulticast = 5,
NoPrefixAvail = 6,
};
/**
* Enum to identify the option type.
* These symbols and values are defined in [RFC 8415, section 21]
* (https://datatracker.ietf.org/doc/html/rfc8415#section-21)
*/
enum class OptionType
{
OPTION_INIT = 0, // Added for initialization
OPTION_CLIENTID = 1,
OPTION_SERVERID = 2,
OPTION_IA_NA = 3,
OPTION_IA_TA = 4,
OPTION_IAADDR = 5,
OPTION_ORO = 6,
OPTION_PREFERENCE = 7,
OPTION_ELAPSED_TIME = 8,
OPTION_RELAY_MSG = 9,
OPTION_AUTH = 11,
OPTION_UNICAST = 12,
OPTION_STATUS_CODE = 13,
OPTION_RAPID_COMMIT = 14,
OPTION_USER_CLASS = 15,
OPTION_VENDOR_CLASS = 16,
OPTION_VENDOR_OPTS = 17,
OPTION_INTERFACE_ID = 18,
OPTION_RECONF_MSG = 19,
OPTION_RECONF_ACCEPT = 20,
OPTION_IA_PD = 25,
OPTION_IAPREFIX = 26,
OPTION_INFORMATION_REFRESH_TIME = 32,
OPTION_SOL_MAX_RT = 82,
OPTION_INF_MAX_RT = 83,
};
/**
* @brief Default constructor.
*/
Options();
/**
* @brief Constructor.
* @param code The option code.
* @param length The option length.
*/
Options(OptionType code, uint16_t length);
/**
* @brief Get the option code.
* @return option code
*/
OptionType GetOptionCode() const;
/**
* @brief Set the option code.
* @param code The option code to be added.
*/
void SetOptionCode(OptionType code);
/**
* @brief Get the option length.
* @return option length
*/
uint16_t GetOptionLength() const;
/**
* @brief Set the option length.
* @param length The option length to be parsed.
*/
void SetOptionLength(uint16_t length);
private:
OptionType m_optionCode; //!< Option code
uint16_t m_optionLength; //!< Option length
};
/**
* @ingroup dhcp6
*
* @class IdentifierOption
* @brief Implements the client and server identifier options.
*/
class IdentifierOption : public Options
{
public:
/**
* Default constructor.
*/
IdentifierOption();
/**
* @brief Constructor.
* @param hardwareType The hardware type.
* @param linkLayerAddress The link-layer address.
* @param time The time at which the DUID is generated.
*/
IdentifierOption(uint16_t hardwareType, Address linkLayerAddress, Time time = Time());
/**
* @brief Set the DUID.
* @param duid The DUID.
*/
void SetDuid(Duid duid);
/**
* @brief Get the DUID object.
* @return the DUID.
*/
Duid GetDuid() const;
private:
Duid m_duid; //!< Unique identifier of the node.
};
/**
* @ingroup dhcp6
*
* @class StatusCodeOption
* @brief Implements the Status Code option.
*/
class StatusCodeOption : public Options
{
public:
/**
* @brief Default constructor.
*/
StatusCodeOption();
/**
* @brief Get the status code of the operation.
* @return the status code.
*/
StatusCodeValues GetStatusCode() const;
/**
* @brief Set the status code of the operation.
* @param statusCode the status code of the performed operation.
*/
void SetStatusCode(StatusCodeValues statusCode);
/**
* @brief Get the status message of the operation.
* @return the status message
*/
std::string GetStatusMessage() const;
/**
* @brief Set the status message of the operation.
* @param statusMessage the status message of the operation.
*/
void SetStatusMessage(std::string statusMessage);
private:
/**
* The status code of an operation involving the IA_NA, IA_TA or
* IA address.
*/
StatusCodeValues m_statusCode;
/**
* The status message of the operation. This is to be UTF-8 encoded
* as per RFC 3629.
*/
std::string m_statusMessage;
};
/**
* @ingroup dhcp6
*
* @class IaAddressOption
* @brief Implements the IA Address options.
*/
class IaAddressOption : public Options
{
public:
/**
* @brief Default constructor.
*/
IaAddressOption();
/**
* @brief Constructor.
* @param iaAddress The IA Address.
* @param preferredLifetime The preferred lifetime of the address.
* @param validLifetime The valid lifetime of the address.
*/
IaAddressOption(Ipv6Address iaAddress, uint32_t preferredLifetime, uint32_t validLifetime);
/**
* @brief Get the IA Address.
* @return the IPv6 address of the Identity Association
*/
Ipv6Address GetIaAddress() const;
/**
* @brief Set the IA Address.
* @param iaAddress the IPv6 address of this Identity Association.
*/
void SetIaAddress(Ipv6Address iaAddress);
/**
* @brief Get the preferred lifetime.
* @return the preferred lifetime
*/
uint32_t GetPreferredLifetime() const;
/**
* @brief Set the preferred lifetime.
* @param preferredLifetime the preferred lifetime for this address.
*/
void SetPreferredLifetime(uint32_t preferredLifetime);
/**
* @brief Get the valid lifetime.
* @return the lifetime for which the address is valid.
*/
uint32_t GetValidLifetime() const;
/**
* @brief Set the valid lifetime.
* @param validLifetime the lifetime for which the address is valid.
*/
void SetValidLifetime(uint32_t validLifetime);
private:
Ipv6Address m_iaAddress; //!< the IPv6 address offered to the client.
uint32_t m_preferredLifetime; //!< The preferred lifetime of the address, in seconds.
uint32_t m_validLifetime; //!< The valid lifetime of the address, in seconds.
/// (optional) The status code of any operation involving this address
StatusCodeOption m_statusCodeOption;
};
/**
* @ingroup dhcp6
*
* @class IaOptions
* @brief Implements the IANA and IATA options.
*/
class IaOptions : public Options
{
public:
/**
* @brief Default constructor.
*/
IaOptions();
/**
* @brief Get the unique identifier for the given IANA or IATA.
* @return the ID of the IANA or IATA
*/
uint32_t GetIaid() const;
/**
* @brief Set the unique identifier for the given IANA or IATA.
* @param iaid the unique ID for the IANA or IATA.
*/
void SetIaid(uint32_t iaid);
/**
* @brief Get the time interval in seconds after which the client contacts
* the server which provided the address to extend the lifetime.
* @return the time interval T1
*/
uint32_t GetT1() const;
/**
* @brief Set the time interval in seconds after which the client contacts
* the server which provided the address to extend the lifetime.
* @param t1 the time interval in seconds.
*/
void SetT1(uint32_t t1);
/**
* @brief Get the time interval in seconds after which the client contacts
* any available server to extend the address lifetime.
* @return the time interval T2
*/
uint32_t GetT2() const;
/**
* @brief Set the time interval in seconds after which the client contacts
* any available server to extend the address lifetime.
* @param t2 time interval in seconds.
*/
void SetT2(uint32_t t2);
/// The list of IA Address options associated with the IANA.
std::vector<IaAddressOption> m_iaAddressOption;
private:
/// The unique identifier for the given IA_NA or IA_TA.
uint32_t m_iaid;
/**
* The time interval in seconds after which the client contacts the
* server which provided the address to extend the lifetime.
*/
uint32_t m_t1;
/**
* The time interval in seconds after which the client contacts any
* available server to extend the address lifetime.
*/
uint32_t m_t2;
/// (optional) The status code of any operation involving the IANA.
StatusCodeOption m_statusCodeOption;
};
/**
* @ingroup dhcp6
*
* @class RequestOptions
* @brief Implements the Option Request option.
*/
class RequestOptions : public Options
{
public:
/**
* @brief Constructor.
*/
RequestOptions();
/**
* @brief Get the option values
* @return requested option list.
*/
std::vector<OptionType> GetRequestedOptions() const;
/**
* @brief Set the option values.
* @param requestedOption option to be requested from the server.
*/
void AddRequestedOption(OptionType requestedOption);
private:
std::vector<OptionType> m_requestedOptions; //!< List of requested options.
};
/**
* @ingroup dhcp6
*
* @class IntegerOptions
* @brief Implements the Preference and Elapsed Time options.
*/
template <typename T>
class IntegerOptions : public Options
{
public:
/**
* @brief Constructor.
*/
IntegerOptions();
/**
* @brief Get the option value
* @return elapsed time or preference value.
*/
T GetOptionValue() const;
/**
* @brief Set the option value.
* @param optionValue elapsed time or preference value.
*/
void SetOptionValue(T optionValue);
private:
/// Indicates the elapsed time or preference value.
T m_optionValue;
};
/**
* @ingroup dhcp6
*
* @class ServerUnicastOption
* @brief Implements the Server Unicast option.
*/
class ServerUnicastOption : public Options
{
public:
ServerUnicastOption();
/**
* @brief Get the server address.
* @return The 128 bit server address.
*/
Ipv6Address GetServerAddress();
/**
* @brief Set the server address.
* @param serverAddress the 128-bit server address.
*/
void SetServerAddress(Ipv6Address serverAddress);
private:
/**
* The 128-bit server address to which the client should send
* unicast messages.
*/
Ipv6Address m_serverAddress;
};
/**
* @ingroup dhcp6
* Create the typedef PreferenceOption with T as uint8_t
*/
typedef IntegerOptions<uint8_t> PreferenceOption;
/**
* @ingroup dhcp6
* Create the typedef ElapsedTimeOption with T as uint16_t
*/
typedef IntegerOptions<uint16_t> ElapsedTimeOption;
} // namespace ns3
#endif

View File

@@ -0,0 +1,858 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "dhcp6-server.h"
#include "dhcp6-duid.h"
#include "ns3/address-utils.h"
#include "ns3/icmpv6-l4-protocol.h"
#include "ns3/ipv6-interface.h"
#include "ns3/ipv6-l3-protocol.h"
#include "ns3/ipv6-packet-info-tag.h"
#include "ns3/ipv6.h"
#include "ns3/log.h"
#include "ns3/loopback-net-device.h"
#include "ns3/simulator.h"
#include "ns3/socket.h"
#include <algorithm>
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Dhcp6Server");
TypeId
Dhcp6Server::GetTypeId()
{
static TypeId tid =
TypeId("ns3::Dhcp6Server")
.SetParent<Application>()
.AddConstructor<Dhcp6Server>()
.SetGroupName("InternetApps")
.AddAttribute("RenewTime",
"Time after which client should renew. 1000 seconds by default in Linux. "
"This is equivalent to REN_MAX_RT (RFC 8415, Section 7.6).",
TimeValue(Seconds(1000)),
MakeTimeAccessor(&Dhcp6Server::m_renew),
MakeTimeChecker())
.AddAttribute("RebindTime",
"Time after which client should rebind. "
"2000 seconds by default in Linux. "
"This is equivalent to REB_MAX_RT (RFC 8415, Section 7.6).",
TimeValue(Seconds(2000)),
MakeTimeAccessor(&Dhcp6Server::m_rebind),
MakeTimeChecker())
.AddAttribute("PreferredLifetime",
"The preferred lifetime of the leased address. "
"3000 seconds by default in Linux.",
TimeValue(Seconds(3000)),
MakeTimeAccessor(&Dhcp6Server::m_prefLifetime),
MakeTimeChecker())
.AddAttribute("ValidLifetime",
"Time after which client should release the address. "
"4000 seconds by default in Linux.",
TimeValue(Seconds(4000)),
MakeTimeAccessor(&Dhcp6Server::m_validLifetime),
MakeTimeChecker());
return tid;
}
Dhcp6Server::Dhcp6Server()
{
NS_LOG_FUNCTION(this);
}
void
Dhcp6Server::DoDispose()
{
NS_LOG_FUNCTION(this);
if (m_recvSocket)
{
m_recvSocket->Close();
m_recvSocket = nullptr;
}
for (auto& itr : m_subnets)
{
itr.m_leasedAddresses.clear();
itr.m_expiredAddresses.clear();
itr.m_declinedAddresses.clear();
}
m_subnets.clear();
m_leaseCleanupEvent.Cancel();
for (auto& itr : m_sendSockets)
{
// Close sockets.
if (!itr.second)
{
continue;
}
itr.second->SetCloseCallbacks(MakeNullCallback<void, Ptr<Socket>>(),
MakeNullCallback<void, Ptr<Socket>>());
itr.second->SetRecvCallback(MakeNullCallback<void, Ptr<Socket>>());
itr.second->SetSendCallback(MakeNullCallback<void, Ptr<Socket>, uint32_t>());
itr.second->Close();
}
m_sendSockets.clear();
m_iaBindings.clear();
Application::DoDispose();
}
void
Dhcp6Server::ProcessSolicit(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client)
{
Duid clientDuid = header.GetClientIdentifier().GetDuid();
std::map<Options::OptionType, bool> headerOptions = header.GetOptionList();
// Add each IA in the header to the IA bindings.
if (headerOptions.find(Options::OptionType::OPTION_IA_NA) != headerOptions.end())
{
std::vector<IaOptions> iaOpt = header.GetIanaOptions();
for (const auto& itr : iaOpt)
{
uint32_t iaid = itr.GetIaid();
m_iaBindings.insert(
{clientDuid, std::make_pair(Options::OptionType::OPTION_IA_NA, iaid)});
NS_LOG_DEBUG("DHCPv6 server: Client registering IAID " << iaid);
}
}
}
void
Dhcp6Server::SendAdvertise(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client)
{
NS_LOG_FUNCTION(this << iDev << header << client);
// Options included according to RFC 8415 Section 18.3.9
Ptr<Packet> packet = Create<Packet>();
Dhcp6Header advertiseHeader;
advertiseHeader.ResetOptions();
advertiseHeader.SetMessageType(Dhcp6Header::MessageType::ADVERTISE);
advertiseHeader.SetTransactId(header.GetTransactId());
// Add Client Identifier Option, copied from the received header.
Duid clientDuid = header.GetClientIdentifier().GetDuid();
advertiseHeader.AddClientIdentifier(clientDuid);
// Add Server Identifier Option.
advertiseHeader.AddServerIdentifier(m_serverDuid);
// Find all requested IAIDs for this client.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
std::vector<uint32_t> requestedIa(ianaOptionsList.size());
for (std::size_t i = 0; i < ianaOptionsList.size(); i++)
{
requestedIa[i] = ianaOptionsList[i].GetIaid();
}
// Add IA_NA option.
// Available address pools and IA information is sent in this option.
for (auto& subnet : m_subnets)
{
Ipv6Address pool = subnet.GetAddressPool();
Ipv6Prefix prefix = subnet.GetPrefix();
Ipv6Address minAddress = subnet.GetMinAddress();
Ipv6Address maxAddress = subnet.GetMaxAddress();
/*
* Find the next available address. Checks the expired address map.
* If there are no expired addresses, it advertises a new address.
*/
uint8_t offeredAddrBuf[16];
bool foundAddress = false;
if (!subnet.m_expiredAddresses.empty())
{
Ipv6Address nextAddress;
for (auto itr = subnet.m_expiredAddresses.begin();
itr != subnet.m_expiredAddresses.end();)
{
if (itr->second.first == clientDuid)
{
nextAddress = itr->second.second;
nextAddress.GetBytes(offeredAddrBuf);
itr = subnet.m_expiredAddresses.erase(itr);
foundAddress = true;
break;
}
itr++;
}
/*
Prevent Expired Addresses from building up.
We set a maximum limit of 30 expired addresses, after which the
oldest expired address is removed and offered to a client.
*/
if (!foundAddress && subnet.m_expiredAddresses.size() > 30)
{
auto firstExpiredAddress = subnet.m_expiredAddresses.begin();
nextAddress = firstExpiredAddress->second.second;
nextAddress.GetBytes(offeredAddrBuf);
subnet.m_expiredAddresses.erase(firstExpiredAddress);
foundAddress = true;
}
}
if (!foundAddress)
{
// Allocate a new address.
uint8_t minAddrBuf[16];
minAddress.GetBytes(minAddrBuf);
// Get the latest leased address.
uint8_t lastLeasedAddrBuf[16];
if (!subnet.m_leasedAddresses.empty())
{
// Obtain the highest address that has been offered.
subnet.m_maxOfferedAddress.GetBytes(lastLeasedAddrBuf);
memcpy(offeredAddrBuf, lastLeasedAddrBuf, 16);
// Increment the address by adding 1. Bitwise addition is used.
bool addedOne = false;
for (uint8_t i = 15; !addedOne && i >= 0; i--)
{
for (int j = 0; j < 8; j++)
{
uint8_t bit = (offeredAddrBuf[i] & (1 << j)) >> j;
if (bit == 0)
{
offeredAddrBuf[i] = offeredAddrBuf[i] | (1 << j);
addedOne = true;
break;
}
offeredAddrBuf[i] = offeredAddrBuf[i] & ~(1 << j);
}
}
}
else
{
memcpy(offeredAddrBuf, minAddrBuf, 16);
}
Ipv6Address offer(offeredAddrBuf);
subnet.m_maxOfferedAddress = offer;
/*
Optimistic assumption that the address will be leased to this client.
This is to prevent multiple clients from receiving the same address.
*/
subnet.m_leasedAddresses.insert(
{clientDuid, std::make_pair(offer, Seconds(m_prefLifetime.GetSeconds()))});
}
Ipv6Address offeredAddr(offeredAddrBuf);
NS_LOG_INFO("Offered address: " << offeredAddr);
NS_LOG_DEBUG("Offered address: " << offeredAddr);
for (const auto& iaid : requestedIa)
{
// Add the IA_NA option and IA Address option.
advertiseHeader.AddIanaOption(iaid, m_renew.GetSeconds(), m_rebind.GetSeconds());
advertiseHeader.AddAddress(iaid,
offeredAddr,
m_prefLifetime.GetSeconds(),
m_validLifetime.GetSeconds());
}
}
std::map<Options::OptionType, bool> headerOptions = header.GetOptionList();
if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end())
{
std::vector<Options::OptionType> requestedOptions =
header.GetOptionRequest().GetRequestedOptions();
advertiseHeader.HandleOptionRequest(requestedOptions);
}
packet->AddHeader(advertiseHeader);
// Find the socket corresponding to the NetDevice.
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
// Send the advertise message.
if (sendSocket->SendTo(packet, 0, client) >= 0)
{
NS_LOG_INFO("DHCPv6 Advertise sent.");
}
else
{
NS_LOG_INFO("Error while sending DHCPv6 Advertise.");
}
}
void
Dhcp6Server::SendReply(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client)
{
NS_LOG_FUNCTION(this << iDev << header << client);
// Options included according to RFC 8415 Section 18.3.10
Ptr<Packet> packet = Create<Packet>();
Dhcp6Header replyHeader;
replyHeader.ResetOptions();
replyHeader.SetMessageType(Dhcp6Header::MessageType::REPLY);
replyHeader.SetTransactId(header.GetTransactId());
// Add Client Identifier Option, copied from the received header.
Duid clientDuid = header.GetClientIdentifier().GetDuid();
replyHeader.AddClientIdentifier(clientDuid);
// Add Server Identifier Option.
replyHeader.AddServerIdentifier(m_serverDuid);
// Add IA_NA option.
// Retrieve requested IA Option from client header.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
for (auto& iaOpt : ianaOptionsList)
{
// Iterate through the offered addresses.
// Current approach: Try to accept all offers.
std::vector<IaAddressOption> iaAddrOptList = iaOpt.m_iaAddressOption;
for (auto& addrItr : iaAddrOptList)
{
Ipv6Address requestedAddr = addrItr.GetIaAddress();
for (auto& subnet : m_subnets)
{
Ipv6Address pool = subnet.GetAddressPool();
Ipv6Prefix prefix = subnet.GetPrefix();
Ipv6Address minAddress = subnet.GetMinAddress();
Ipv6Address maxAddress = subnet.GetMaxAddress();
// Check if the requested address has been declined earlier.
// In this case, it cannot be leased.
if (subnet.m_declinedAddresses.find(requestedAddr) !=
subnet.m_declinedAddresses.end())
{
NS_LOG_INFO("Requested address" << requestedAddr << "is declined.");
return;
}
// Check whether this subnet matches the requested address.
if (prefix.IsMatch(requestedAddr, pool))
{
uint8_t minBuf[16];
uint8_t maxBuf[16];
uint8_t requestedBuf[16];
minAddress.GetBytes(minBuf);
maxAddress.GetBytes(maxBuf);
requestedAddr.GetBytes(requestedBuf);
if (memcmp(requestedBuf, minBuf, 16) < 0 ||
memcmp(requestedBuf, maxBuf, 16) > 0)
{
NS_LOG_INFO("Requested address is not in the range of the subnet.");
return;
}
// Add the IA_NA option and IA Address option.
replyHeader.AddIanaOption(iaOpt.GetIaid(), iaOpt.GetT1(), iaOpt.GetT2());
replyHeader.AddAddress(iaOpt.GetIaid(),
requestedAddr,
m_prefLifetime.GetSeconds(),
m_validLifetime.GetSeconds());
NS_LOG_DEBUG("Adding address " << requestedAddr << " to lease");
// Update the lease time of the newly leased addresses.
// Find all the leases for this client.
auto range = subnet.m_leasedAddresses.equal_range(clientDuid);
// Create a new multimap to store the updated lifetimes.
std::multimap<Duid, std::pair<Ipv6Address, Time>> updatedLifetimes;
for (auto it = range.first; it != range.second; it++)
{
Ipv6Address clientLease = it->second.first;
std::pair<Ipv6Address, Time> clientLeaseTime = {
clientLease,
Seconds(m_prefLifetime.GetSeconds())};
// Add the DUID + Ipv6Address / LeaseTime to the map.
updatedLifetimes.insert({clientDuid, clientLeaseTime});
}
// Remove all the old leases for this client.
// This is done to prevent multiple entries for the same lease.
subnet.m_leasedAddresses.erase(range.first->first);
// Add the updated leases to the subnet.
for (auto& itr : updatedLifetimes)
{
subnet.m_leasedAddresses.insert({itr.first, itr.second});
}
break;
}
}
}
}
std::map<Options::OptionType, bool> headerOptions = header.GetOptionList();
// Check if the client has requested any options.
if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end())
{
std::vector<Options::OptionType> requestedOptions =
header.GetOptionRequest().GetRequestedOptions();
replyHeader.HandleOptionRequest(requestedOptions);
}
packet->AddHeader(replyHeader);
// Find the socket corresponding to the NetDevice.
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
// Send the Reply message.
if (sendSocket->SendTo(packet, 0, client) >= 0)
{
NS_LOG_INFO("DHCPv6 Reply sent.");
}
else
{
NS_LOG_INFO("Error while sending DHCPv6 Reply.");
}
}
void
Dhcp6Server::RenewRebindLeases(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client)
{
NS_LOG_FUNCTION(this << iDev << header << client);
// Options included according to RFC 8415 Section 18.3.4, 18.3.5
Ptr<Packet> packet = Create<Packet>();
Dhcp6Header replyHeader;
replyHeader.ResetOptions();
replyHeader.SetMessageType(Dhcp6Header::MessageType::REPLY);
replyHeader.SetTransactId(header.GetTransactId());
// Add Client Identifier Option, copied from the received header.
Duid clientDuid = header.GetClientIdentifier().GetDuid();
replyHeader.AddClientIdentifier(clientDuid);
// Add Server Identifier Option.
replyHeader.AddServerIdentifier(m_serverDuid);
// Add IA_NA option.
// Retrieve IA_NAs from client header.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
for (auto& iaOpt : ianaOptionsList)
{
std::vector<IaAddressOption> iaAddrOptList = iaOpt.m_iaAddressOption;
// Add the IA_NA option.
replyHeader.AddIanaOption(iaOpt.GetIaid(), iaOpt.GetT1(), iaOpt.GetT2());
for (const auto& addrItr : iaAddrOptList)
{
// Find the lease address which is to be renewed or rebound.
Ipv6Address clientLease = addrItr.GetIaAddress();
// Update the lifetime for the address.
// Iterate through the subnet list to find the subnet that the
// address belongs to.
for (auto& subnet : m_subnets)
{
Ipv6Prefix prefix = subnet.GetPrefix();
Ipv6Address pool = subnet.GetAddressPool();
// Check if the prefix of the lease matches that of the pool.
if (prefix.IsMatch(clientLease, pool))
{
// Find all the leases for this client.
auto range = subnet.m_leasedAddresses.equal_range(clientDuid);
for (auto itr = range.first; itr != range.second; itr++)
{
// Check if the IPv6 address matches the client lease.
if (itr->second.first == clientLease)
{
NS_LOG_DEBUG("Renewing address: " << itr->second.first);
std::pair<Ipv6Address, Time> clientLeaseTime = {
clientLease,
Seconds(m_prefLifetime.GetSeconds())};
// Remove the old lease information.
subnet.m_leasedAddresses.erase(itr);
// Add the new lease information (with updated time)
subnet.m_leasedAddresses.insert({clientDuid, clientLeaseTime});
// Add the IA Address option.
replyHeader.AddAddress(iaOpt.GetIaid(),
clientLease,
m_prefLifetime.GetSeconds(),
m_validLifetime.GetSeconds());
break;
}
}
}
}
}
}
std::map<Options::OptionType, bool> headerOptions = header.GetOptionList();
if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end())
{
std::vector<Options::OptionType> requestedOptions =
header.GetOptionRequest().GetRequestedOptions();
replyHeader.HandleOptionRequest(requestedOptions);
}
packet->AddHeader(replyHeader);
// Find the socket corresponding to the NetDevice.
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
// Send the Reply message.
if (sendSocket->SendTo(packet, 0, client) >= 0)
{
NS_LOG_INFO("DHCPv6 Reply sent.");
}
else
{
NS_LOG_INFO("Error while sending DHCPv6 Reply.");
}
}
void
Dhcp6Server::UpdateBindings(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client)
{
// Invoked in case a Decline or Release message is received.
NS_LOG_FUNCTION(this << iDev << header << client);
// Options included in accordance with RFC 8415, Section 18.3.7, 18.3.8
Ptr<Packet> packet = Create<Packet>();
Dhcp6Header replyHeader;
replyHeader.ResetOptions();
replyHeader.SetMessageType(Dhcp6Header::MessageType::REPLY);
replyHeader.SetTransactId(header.GetTransactId());
// Add Client Identifier Option, copied from the received header.
Duid clientDuid = header.GetClientIdentifier().GetDuid();
replyHeader.AddClientIdentifier(clientDuid);
// Add Server Identifier Option.
replyHeader.AddServerIdentifier(m_serverDuid);
// Add Status code option.
replyHeader.AddStatusCode(Options::StatusCodeValues::Success, "Address declined.");
// Add the declined or expired address to the subnet information.
std::vector<IaOptions> ianaOptionsList = header.GetIanaOptions();
for (const auto& iaOpt : ianaOptionsList)
{
std::vector<IaAddressOption> iaAddrOptList = iaOpt.m_iaAddressOption;
for (const auto& addrItr : iaAddrOptList)
{
Ipv6Address address = addrItr.GetIaAddress();
if (header.GetMessageType() == Dhcp6Header::MessageType::DECLINE)
{
// Find the subnet that this address belongs to.
for (auto& subnet : m_subnets)
{
// Find the client that the address currently belongs to.
for (auto itr = subnet.m_leasedAddresses.begin();
itr != subnet.m_leasedAddresses.end();)
{
Ipv6Address leaseAddr = itr->second.first;
if (leaseAddr == address)
{
itr = subnet.m_leasedAddresses.erase(itr);
subnet.m_declinedAddresses[address] = clientDuid;
continue;
}
itr++;
}
}
}
else if (header.GetMessageType() == Dhcp6Header::MessageType::RELEASE)
{
// Find the subnet that this address belongs to.
for (auto& subnet : m_subnets)
{
// Find the client that the address currently belongs to.
for (auto itr = subnet.m_leasedAddresses.begin();
itr != subnet.m_leasedAddresses.end();)
{
Duid duid = itr->first;
Ipv6Address leaseAddr = itr->second.first;
Time expiredTime = itr->second.second;
if (leaseAddr == address)
{
itr = subnet.m_leasedAddresses.erase(itr);
std::pair<Duid, Ipv6Address> expiredLease = {duid, leaseAddr};
subnet.m_expiredAddresses.insert({expiredTime, expiredLease});
continue;
}
itr++;
}
}
}
}
}
packet->AddHeader(replyHeader);
// Find the socket corresponding to the NetDevice.
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
// Send the Reply message.
if (sendSocket->SendTo(packet, 0, client) >= 0)
{
NS_LOG_INFO("DHCPv6 Reply sent.");
}
else
{
NS_LOG_INFO("Error while sending DHCPv6 Reply.");
}
}
void
Dhcp6Server::SetDhcp6ServerNetDevice(NetDeviceContainer netDevices)
{
for (auto itr = netDevices.Begin(); itr != netDevices.End(); itr++)
{
Ptr<Ipv6> ipv6 = GetNode()->GetObject<Ipv6>();
int32_t ifIndex = ipv6->GetInterfaceForDevice(*itr);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
m_sendSockets[ifIndex] = nullptr;
}
}
void
Dhcp6Server::NetHandler(Ptr<Socket> socket)
{
NS_LOG_FUNCTION(this << socket);
Dhcp6Header header;
Address from;
Ptr<Packet> packet = m_recvSocket->RecvFrom(from);
Inet6SocketAddress senderAddr = Inet6SocketAddress::ConvertFrom(from);
Ipv6PacketInfoTag interfaceInfo;
NS_ASSERT_MSG(packet->RemovePacketTag(interfaceInfo),
"No incoming interface on DHCPv6 message.");
uint32_t incomingIf = interfaceInfo.GetRecvIf();
Ptr<NetDevice> iDev = GetNode()->GetDevice(incomingIf);
if (packet->RemoveHeader(header) == 0)
{
return;
}
// Initialize the DUID before responding to the client.
Ptr<Node> node = GetNode();
m_serverDuid.Initialize(node);
if (header.GetMessageType() == Dhcp6Header::MessageType::SOLICIT)
{
ProcessSolicit(iDev, header, senderAddr);
SendAdvertise(iDev, header, senderAddr);
}
if (header.GetMessageType() == Dhcp6Header::MessageType::REQUEST)
{
SendReply(iDev, header, senderAddr);
}
if ((header.GetMessageType() == Dhcp6Header::MessageType::RENEW) ||
(header.GetMessageType() == Dhcp6Header::MessageType::REBIND))
{
RenewRebindLeases(iDev, header, senderAddr);
}
if ((header.GetMessageType() == Dhcp6Header::MessageType::RELEASE) ||
(header.GetMessageType() == Dhcp6Header::MessageType::DECLINE))
{
UpdateBindings(iDev, header, senderAddr);
}
}
void
Dhcp6Server::AddSubnet(Ipv6Address addressPool,
Ipv6Prefix prefix,
Ipv6Address minAddress,
Ipv6Address maxAddress)
{
NS_LOG_FUNCTION(this << addressPool << prefix << minAddress << maxAddress);
NS_LOG_DEBUG("DHCPv6 server: Adding subnet " << addressPool << " to lease information.");
LeaseInfo newSubnet(addressPool, prefix, minAddress, maxAddress);
m_subnets.emplace_back(newSubnet);
}
void
Dhcp6Server::ReceiveMflag(uint32_t recvInterface)
{
Ptr<Node> node = GetNode();
Ptr<Ipv6> ipv6 = node->GetObject<Ipv6>();
Ptr<Ipv6L3Protocol> ipv6l3 = node->GetObject<Ipv6L3Protocol>();
for (auto itr = m_sendSockets.begin(); itr != m_sendSockets.end(); itr++)
{
uint32_t ifIndex = itr->first;
Ptr<NetDevice> device = GetNode()->GetDevice(ifIndex);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
if (ifIndex == recvInterface)
{
Ipv6Address linkLocal =
ipv6l3->GetInterface(ifIndex)->GetLinkLocalAddress().GetAddress();
TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
Ptr<Socket> socket = Socket::CreateSocket(node, tid);
socket->Bind(Inet6SocketAddress(linkLocal, Dhcp6Header::SERVER_PORT));
socket->BindToNetDevice(device);
socket->SetRecvPktInfo(true);
socket->SetRecvCallback(MakeCallback(&Dhcp6Server::NetHandler, this));
m_sendSockets[ifIndex] = socket;
}
}
}
void
Dhcp6Server::StartApplication()
{
NS_LOG_INFO("Starting DHCPv6 server.");
if (m_recvSocket)
{
NS_LOG_INFO("DHCPv6 daemon is not meant to be started repeatedly.");
return;
}
Ptr<Node> node = GetNode();
Ptr<Ipv6> ipv6 = node->GetObject<Ipv6>();
Ptr<Ipv6L3Protocol> ipv6l3 = node->GetObject<Ipv6L3Protocol>();
TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory");
m_recvSocket = Socket::CreateSocket(node, tid);
Inet6SocketAddress local =
Inet6SocketAddress(Ipv6Address::GetAllNodesMulticast(), Dhcp6Header::SERVER_PORT);
m_recvSocket->Bind(local);
m_recvSocket->SetRecvPktInfo(true);
m_recvSocket->SetRecvCallback(MakeCallback(&Dhcp6Server::NetHandler, this));
for (auto itr = m_sendSockets.begin(); itr != m_sendSockets.end(); itr++)
{
int32_t ifIndex = itr->first;
Ptr<NetDevice> iDev = GetNode()->GetDevice(ifIndex);
Ptr<Socket> sendSocket = m_sendSockets[ifIndex];
NS_LOG_DEBUG("DHCPv6 server: Node " << node->GetId() << " listening on interface "
<< ifIndex);
NS_ASSERT_MSG(ifIndex >= 0,
"Dhcp6Server::StartApplication: device is not connected to IPv6.");
Ptr<Icmpv6L4Protocol> icmpv6 = DynamicCast<Icmpv6L4Protocol>(
ipv6->GetProtocol(Icmpv6L4Protocol::GetStaticProtocolNumber(), ifIndex));
icmpv6->SetDhcpv6Callback(MakeCallback(&Dhcp6Server::ReceiveMflag, this));
}
m_leaseCleanupEvent = Simulator::Schedule(m_leaseCleanup, &Dhcp6Server::CleanLeases, this);
}
void
Dhcp6Server::StopApplication()
{
NS_LOG_FUNCTION(this);
}
void
Dhcp6Server::CleanLeases()
{
NS_LOG_DEBUG("Cleaning up leases.");
for (auto& subnet : m_subnets)
{
for (auto itr = subnet.m_leasedAddresses.begin(); itr != subnet.m_leasedAddresses.end();)
{
Duid duid = itr->first;
Ipv6Address address = itr->second.first;
Time leaseTime = itr->second.second;
if (Simulator::Now() >= leaseTime)
{
NS_LOG_DEBUG("DHCPv6 server: Removing expired lease for " << address);
std::pair<Duid, Ipv6Address> expiredLease = {duid, address};
subnet.m_expiredAddresses.insert({leaseTime, expiredLease});
itr = subnet.m_leasedAddresses.erase(itr);
continue;
}
itr++;
}
}
m_leaseCleanupEvent = Simulator::Schedule(m_leaseCleanup, &Dhcp6Server::CleanLeases, this);
}
LeaseInfo::LeaseInfo(Ipv6Address addressPool,
Ipv6Prefix prefix,
Ipv6Address minAddress,
Ipv6Address maxAddress)
{
m_addressPool = addressPool;
m_prefix = prefix;
m_minAddress = minAddress;
m_maxAddress = maxAddress;
m_numAddresses = 0;
}
Ipv6Address
LeaseInfo::GetAddressPool() const
{
return m_addressPool;
}
Ipv6Prefix
LeaseInfo::GetPrefix() const
{
return m_prefix;
}
Ipv6Address
LeaseInfo::GetMinAddress() const
{
return m_minAddress;
}
Ipv6Address
LeaseInfo::GetMaxAddress() const
{
return m_maxAddress;
}
uint32_t
LeaseInfo::GetNumAddresses() const
{
return m_numAddresses;
}
} // namespace ns3

View File

@@ -0,0 +1,266 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#ifndef DHCP6_SERVER_H
#define DHCP6_SERVER_H
#include "dhcp6-duid.h"
#include "dhcp6-header.h"
#include "ns3/application.h"
#include "ns3/ipv6-address.h"
#include "ns3/net-device-container.h"
#include "ns3/pair.h"
#include "ns3/ptr.h"
#include <map>
namespace ns3
{
class Inet6SocketAddress;
class Socket;
class Packet;
/**
* @ingroup dhcp6
*
* @class LeaseInfo
* @brief Includes information about available subnets and corresponding leases.
*/
class LeaseInfo
{
public:
/**
* Constructor.
* @param addressPool Address pool
* @param prefix Prefix of the address pool
* @param minAddress Minimum address in the pool
* @param maxAddress Maximum address in the pool
*/
LeaseInfo(Ipv6Address addressPool,
Ipv6Prefix prefix,
Ipv6Address minAddress,
Ipv6Address maxAddress);
friend class Dhcp6Server;
private:
/**
* @brief Get the address pool.
* @return The address pool
*/
Ipv6Address GetAddressPool() const;
/**
* @brief Get the prefix of the address pool.
* @return The prefix of the address pool
*/
Ipv6Prefix GetPrefix() const;
/**
* @brief Get the minimum address in the pool.
* @return The minimum address in the pool
*/
Ipv6Address GetMinAddress() const;
/**
* @brief Get the maximum address in the pool.
* @return The maximum address in the pool
*/
Ipv6Address GetMaxAddress() const;
/**
* @brief Get the number of addresses leased.
* @return The number of addresses leased
*/
uint32_t GetNumAddresses() const;
/**
* @brief Expired Addresses (Section 6.2 of RFC 8415)
* Expired time / Ipv6Address
*/
typedef std::multimap<Time, std::pair<Duid, Ipv6Address>> ExpiredAddresses;
/**
* @brief Leased Addresses
* Client DUID + Ipv6Address / Lease time
*/
typedef std::unordered_multimap<Duid, std::pair<Ipv6Address, Time>, Duid::DuidHash>
LeasedAddresses;
/**
* @brief Declined Addresses
* Ipv6Address + Client DUID
*/
typedef std::unordered_map<Ipv6Address, Duid, Ipv6AddressHash> DeclinedAddresses;
LeasedAddresses m_leasedAddresses; //!< Leased addresses
ExpiredAddresses m_expiredAddresses; //!< Expired addresses
DeclinedAddresses m_declinedAddresses; //!< Declined addresses
Ipv6Address m_maxOfferedAddress; //!< Maximum address offered so far.
Ipv6Address m_addressPool; //!< Address pool
Ipv6Prefix m_prefix; //!< Prefix of the address pool
Ipv6Address m_minAddress; //!< Minimum address in the pool
Ipv6Address m_maxAddress; //!< Maximum address in the pool
uint32_t m_numAddresses; //!< Number of addresses leased.
};
/**
* @ingroup dhcp6
*
* @class Dhcp6Server
* @brief Implements the DHCPv6 server.
*/
class Dhcp6Server : public Application
{
public:
/**
* @brief Get the type ID.
* @return the object TypeId
*/
static TypeId GetTypeId();
/**
* @brief Default constructor.
*/
Dhcp6Server();
/**
* @brief Set the list of net devices that the DHCPv6 server will use.
* @param netDevices The net devices that the server will listen on.
*/
void SetDhcp6ServerNetDevice(NetDeviceContainer netDevices);
/**
* @brief Add a managed address pool.
* @param pool The address pool to be managed by the server.
* @param prefix The prefix of the address pool.
* @param minAddress The minimum address in the pool.
* @param maxAddress The maximum address in the pool.
*/
void AddSubnet(Ipv6Address pool,
Ipv6Prefix prefix,
Ipv6Address minAddress,
Ipv6Address maxAddress);
protected:
void DoDispose() override;
private:
void StartApplication() override;
void StopApplication() override;
/**
* @brief Handles incoming packets from the network
* @param socket Socket bound to port 547 of the DHCP server
*/
void NetHandler(Ptr<Socket> socket);
/**
* @brief Sends DHCPv6 Advertise after receiving DHCPv6 Solicit.
* @param iDev incoming NetDevice
* @param header DHCPv6 header of the received message
* @param client Address of the DHCPv6 client
*/
void ProcessSolicit(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client);
/**
* @brief Sends DHCPv6 Advertise after receiving DHCPv6 Solicit.
* @param iDev incoming NetDevice
* @param header DHCPv6 header of the received message
* @param client Address of the DHCPv6 client
*/
void SendAdvertise(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client);
/**
* @brief Sends Reply after receiving Request
* @param iDev incoming NetDevice
* @param header DHCPv6 header of the received message
* @param client Address of the DHCP client
*/
void SendReply(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client);
/**
* @brief Sends Reply after receiving Request
* @param iDev incoming NetDevice
* @param header DHCPv6 header of the received message
* @param client Address of the DHCP client
*/
void RenewRebindLeases(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client);
/**
* @brief Sends Reply after receiving Request
* @param iDev incoming NetDevice
* @param header DHCPv6 header of the received message
* @param client Address of the DHCP client
*/
void UpdateBindings(Ptr<NetDevice> iDev, Dhcp6Header header, Inet6SocketAddress client);
/**
* @brief Modifies the remaining lease time of addresses
*/
void TimerHandler();
/**
* @brief Callback for when an M flag is received.
* @param recvInterface The interface on which the M flag was received.
*/
void ReceiveMflag(uint32_t recvInterface);
/**
* @brief Clean up stale lease info.
*/
void CleanLeases();
Ptr<Socket> m_recvSocket; //!< Socket bound to port 547.
Duid m_serverDuid; //!< Server DUID
std::vector<LeaseInfo> m_subnets; //!< List of managed subnets.
Time m_leaseCleanup = Seconds(10.0); //!< Lease cleanup time
EventId m_leaseCleanupEvent; //!< Event ID for lease cleanup
/// Map of NetDevice - Corresponding socket used to send packets.
std::unordered_map<uint32_t, Ptr<Socket>> m_sendSockets;
/// Store IA bindings. Map of DUID + IA Type / IAID
std::multimap<Duid, std::pair<Options::OptionType, uint32_t>> m_iaBindings;
/**
* @brief Default preferred lifetime for an address.
* According to ISC's Kea guide, the default preferred lifetime is 3000
* seconds.
*/
Time m_prefLifetime;
/**
* @brief Default valid lifetime.
* According to ISC's Kea guide, the default valid lifetime is 4000 seconds.
*/
Time m_validLifetime;
/**
* @brief The default renew timer.
* This defines the T1 timer. According to ISC's Kea guide, the default
* renew timer is 1000 seconds.
* Maximum value is REN_MAX_RT (RFC 8415, Section 7.6).
*/
Time m_renew;
/**
* @brief The default rebind timer.
* This defines the T2 timer. According to ISC's Kea guide, the default
* rebind timer is 2000 seconds.
* Maximum value is REB_MAX_RT (RFC 8415, Section 7.6).
*/
Time m_rebind;
};
} // namespace ns3
#endif

View File

@@ -77,14 +77,20 @@ Radvd::DoDispose()
{
NS_LOG_FUNCTION(this);
if (m_recvSocket)
{
m_recvSocket->Close();
m_recvSocket = nullptr;
}
for (auto it = m_sendSockets.begin(); it != m_sendSockets.end(); ++it)
{
if (it->second)
{
it->second->Close();
it->second = nullptr;
}
}
Application::DoDispose();
}

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2024 NITK Surathkal
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Kavya Bhat <kavyabhat@gmail.com>
*
*/
#include "ns3/applications-module.h"
#include "ns3/core-module.h"
#include "ns3/csma-module.h"
#include "ns3/data-rate.h"
#include "ns3/dhcp6-header.h"
#include "ns3/dhcp6-helper.h"
#include "ns3/header-serialization-test.h"
#include "ns3/internet-apps-module.h"
#include "ns3/internet-module.h"
#include "ns3/internet-stack-helper.h"
#include "ns3/ipv6-address-helper.h"
#include "ns3/log.h"
#include "ns3/mobility-module.h"
#include "ns3/network-module.h"
#include "ns3/ping-helper.h"
#include "ns3/point-to-point-module.h"
#include "ns3/simple-net-device-helper.h"
#include "ns3/simple-net-device.h"
#include "ns3/simulator.h"
#include "ns3/ssid.h"
#include "ns3/test.h"
#include "ns3/trace-helper.h"
#include "ns3/wifi-helper.h"
#include "ns3/yans-wifi-helper.h"
using namespace ns3;
/**
* @ingroup dhcp6
* @defgroup dhcp6-test DHCPv6 module tests
*/
/**
* @ingroup dhcp6-test
* @ingroup tests
*
* @brief DHCPv6 header tests
*/
class Dhcp6TestCase : public TestCase
{
public:
Dhcp6TestCase();
~Dhcp6TestCase() override;
/**
* Triggered by an address lease on a client.
* @param context The test name.
* @param newAddress The leased address.
*/
void LeaseObtained(std::string context, const Ipv6Address& newAddress);
private:
void DoRun() override;
Ipv6Address m_leasedAddress[2]; //!< Address given to the nodes
};
Dhcp6TestCase::Dhcp6TestCase()
: TestCase("Dhcp6 test case ")
{
}
Dhcp6TestCase::~Dhcp6TestCase()
{
}
void
Dhcp6TestCase::LeaseObtained(std::string context, const Ipv6Address& newAddress)
{
uint8_t numericalContext = std::stoi(context, nullptr, 10);
if (numericalContext >= 0 && numericalContext < std::size(m_leasedAddress))
{
m_leasedAddress[numericalContext] = newAddress;
}
}
void
Dhcp6TestCase::DoRun()
{
NodeContainer nonRouterNodes;
nonRouterNodes.Create(3);
Ptr<Node> router = CreateObject<Node>();
NodeContainer all(nonRouterNodes, router);
SimpleNetDeviceHelper simpleNetDevice;
simpleNetDevice.SetChannelAttribute("Delay", TimeValue(MilliSeconds(2)));
simpleNetDevice.SetDeviceAttribute("DataRate", DataRateValue(DataRate("5Mbps")));
NetDeviceContainer devices = simpleNetDevice.Install(all); // all nodes
InternetStackHelper internetv6;
internetv6.Install(all);
Ipv6AddressHelper ipv6;
ipv6.SetBase(Ipv6Address("2001:cafe::"), Ipv6Prefix(64));
NetDeviceContainer nonRouterDevices;
nonRouterDevices.Add(devices.Get(0)); // The server node, S0.
nonRouterDevices.Add(devices.Get(1)); // The first client node, N0.
nonRouterDevices.Add(devices.Get(2)); // The second client node, N1.
Ipv6InterfaceContainer i = ipv6.AssignWithoutAddress(nonRouterDevices);
NetDeviceContainer routerDevice;
routerDevice.Add(devices.Get(3)); // CSMA interface of the node R0.
Ipv6InterfaceContainer r1 = ipv6.Assign(routerDevice);
r1.SetForwarding(0, true);
RadvdHelper radvdHelper;
/* Set up unsolicited RAs */
radvdHelper.AddAnnouncedPrefix(r1.GetInterfaceIndex(0), Ipv6Address("2001:cafe::1"), 64);
radvdHelper.GetRadvdInterface(r1.GetInterfaceIndex(0))->SetManagedFlag(true);
Dhcp6Helper dhcp6Helper;
dhcp6Helper.SetServerAttribute("RenewTime", StringValue("10s"));
dhcp6Helper.SetServerAttribute("RebindTime", StringValue("16s"));
dhcp6Helper.SetServerAttribute("PreferredLifetime", StringValue("18s"));
dhcp6Helper.SetServerAttribute("ValidLifetime", StringValue("20s"));
// DHCPv6 clients
NodeContainer nodes = NodeContainer(nonRouterNodes.Get(1), nonRouterNodes.Get(2));
ApplicationContainer dhcpClients = dhcp6Helper.InstallDhcp6Client(nodes);
dhcpClients.Start(Seconds(1.0));
dhcpClients.Stop(Seconds(20.0));
// DHCPv6 server
NetDeviceContainer serverNetDevices;
serverNetDevices.Add(nonRouterDevices.Get(0));
ApplicationContainer dhcpServerApp = dhcp6Helper.InstallDhcp6Server(serverNetDevices);
Ptr<Dhcp6Server> server = DynamicCast<Dhcp6Server>(dhcpServerApp.Get(0));
server->AddSubnet(Ipv6Address("2001:cafe::"),
Ipv6Prefix(64),
Ipv6Address("2001:cafe::42:1"),
Ipv6Address("2001:cafe::42:ffff"));
dhcpServerApp.Start(Seconds(0.0));
dhcpServerApp.Stop(Seconds(20.0));
ApplicationContainer radvdApps = radvdHelper.Install(router);
radvdApps.Start(Seconds(1.0));
radvdApps.Stop(Seconds(20.0));
dhcpClients.Get(0)->TraceConnect("NewLease",
"0",
MakeCallback(&Dhcp6TestCase::LeaseObtained, this));
dhcpClients.Get(1)->TraceConnect("NewLease",
"1",
MakeCallback(&Dhcp6TestCase::LeaseObtained, this));
Simulator::Stop(Seconds(21.0));
Simulator::Run();
// Validate that the client nodes have three addresses on each interface -
// link-local, autoconfigured, and leased from DHCPv6 server.
Ptr<Ipv6> ipv6_client1 = nonRouterNodes.Get(1)->GetObject<Ipv6>();
Ptr<Ipv6L3Protocol> l3_client1 = ipv6_client1->GetObject<Ipv6L3Protocol>();
int32_t ifIndex_client1 = ipv6_client1->GetInterfaceForDevice(nonRouterDevices.Get(1));
NS_TEST_ASSERT_MSG_EQ(l3_client1->GetNAddresses(ifIndex_client1),
3,
"Incorrect number of addresses.");
Ptr<Ipv6> ipv6_client2 = nonRouterNodes.Get(2)->GetObject<Ipv6>();
Ptr<Ipv6L3Protocol> l3_client2 = ipv6_client2->GetObject<Ipv6L3Protocol>();
int32_t ifIndex_client2 = ipv6_client2->GetInterfaceForDevice(nonRouterDevices.Get(2));
NS_TEST_ASSERT_MSG_EQ(l3_client2->GetNAddresses(ifIndex_client2),
3,
"Incorrect number of addresses.");
Simulator::Destroy();
}
/**
* @ingroup dhcp6-test
* @ingroup tests
*
* @brief DHCPv6 TestSuite
*/
class Dhcp6TestSuite : public TestSuite
{
public:
Dhcp6TestSuite();
};
Dhcp6TestSuite::Dhcp6TestSuite()
: TestSuite("dhcp6", Type::UNIT)
{
AddTestCase(new Dhcp6TestCase, TestCase::Duration::QUICK);
}
static Dhcp6TestSuite dhcp6TestSuite; //!< Static variable for test initialization

View File

@@ -168,6 +168,7 @@ Icmpv6L4Protocol::DoDispose()
}
m_cacheList.clear();
m_downTarget.Nullify();
m_startDhcpv6.Nullify();
m_node = nullptr;
IpL4Protocol::DoDispose();