From 0485fa6dcf1af735c98ea0545984cd510e4c8f75 Mon Sep 17 00:00:00 2001 From: Kavya Bhat Date: Sun, 20 Apr 2025 20:17:51 +0000 Subject: [PATCH] internet-apps: DHCPv6 Application model (GSoC 24) --- doc/models/Makefile | 9 +- src/internet-apps/CMakeLists.txt | 13 + .../doc/figures/dhcpv6-message-exchange.dia | Bin 0 -> 1534 bytes .../doc/figures/stateful-dhcpv6-format.dia | Bin 0 -> 4024 bytes src/internet-apps/doc/internet-apps.rst | 188 ++++ src/internet-apps/examples/CMakeLists.txt | 12 + src/internet-apps/examples/dhcp6-example.cc | 215 ++++ src/internet-apps/helper/dhcp6-helper.cc | 117 +++ src/internet-apps/helper/dhcp6-helper.h | 89 ++ src/internet-apps/model/dhcp6-client.cc | 943 ++++++++++++++++++ src/internet-apps/model/dhcp6-client.h | 279 ++++++ src/internet-apps/model/dhcp6-duid.cc | 234 +++++ src/internet-apps/model/dhcp6-duid.h | 201 ++++ src/internet-apps/model/dhcp6-header.cc | 673 +++++++++++++ src/internet-apps/model/dhcp6-header.h | 287 ++++++ src/internet-apps/model/dhcp6-options.cc | 298 ++++++ src/internet-apps/model/dhcp6-options.h | 468 +++++++++ src/internet-apps/model/dhcp6-server.cc | 858 ++++++++++++++++ src/internet-apps/model/dhcp6-server.h | 266 +++++ src/internet-apps/model/radvd.cc | 14 +- src/internet-apps/test/dhcp6-test.cc | 200 ++++ src/internet/model/icmpv6-l4-protocol.cc | 1 + 22 files changed, 5359 insertions(+), 6 deletions(-) create mode 100644 src/internet-apps/doc/figures/dhcpv6-message-exchange.dia create mode 100644 src/internet-apps/doc/figures/stateful-dhcpv6-format.dia create mode 100644 src/internet-apps/examples/dhcp6-example.cc create mode 100644 src/internet-apps/helper/dhcp6-helper.cc create mode 100644 src/internet-apps/helper/dhcp6-helper.h create mode 100644 src/internet-apps/model/dhcp6-client.cc create mode 100644 src/internet-apps/model/dhcp6-client.h create mode 100644 src/internet-apps/model/dhcp6-duid.cc create mode 100644 src/internet-apps/model/dhcp6-duid.h create mode 100644 src/internet-apps/model/dhcp6-header.cc create mode 100644 src/internet-apps/model/dhcp6-header.h create mode 100644 src/internet-apps/model/dhcp6-options.cc create mode 100644 src/internet-apps/model/dhcp6-options.h create mode 100644 src/internet-apps/model/dhcp6-server.cc create mode 100644 src/internet-apps/model/dhcp6-server.h create mode 100644 src/internet-apps/test/dhcp6-test.cc diff --git a/doc/models/Makefile b/doc/models/Makefile index fc23b2109..0e43ef7de 100644 --- a/doc/models/Makefile +++ b/doc/models/Makefile @@ -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 diff --git a/src/internet-apps/CMakeLists.txt b/src/internet-apps/CMakeLists.txt index 3f6b9912a..af024ec84 100644 --- a/src/internet-apps/CMakeLists.txt +++ b/src/internet-apps/CMakeLists.txt @@ -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 ) diff --git a/src/internet-apps/doc/figures/dhcpv6-message-exchange.dia b/src/internet-apps/doc/figures/dhcpv6-message-exchange.dia new file mode 100644 index 0000000000000000000000000000000000000000..817543c1b9ca143c86d87e3f094cf0e774c626b1 GIT binary patch literal 1534 zcmVp>cGg`*+`rTx*-ENV$i+CJ_$delsiOCP{ zd3*v!lhN&krX4zPdCb+d>?)7rP|h~7(0slUH=`M!e_w>b#&<`lRc1F2ydcybxOX$U zyw4t^N#jZL<(mAh8HIm)iSopR-$F==LeTKg!Az9>>%IM8<*wr7cbb(mpqKMEA3P8 zINFPGkmAz48U3N>3!%7s;0?q;shE>ZOt*@FtjA*$F&oojk(M{C4r4YeLWC+_f8mJ? zMh651gE74(bZjFVHw6@)SWV|a=$FNEr-3CnNz*F__tfwRVnjc95Yhr`Ddb`q*MnhX z>$wMR2BvCiS}{sJUrH}QS##IT>%&4gI%;@hV)0norD6*x*wpPrG9~5g3Io+|UqAo( zU|rma5NfYxwmQOTb?P?o<0@2jUAqUJ=b_D6z>#kK*6Uk{VKc%5z9G0B)FHa zppAwNx`GP!S7%C8}?#@HeXp zfqASGRyC$Nl{DG6jplvCXxm2&2Lbyz<35{+VIxLK(njbt#>h}@q}L|WGOANLh>3L5 z!gOC^#?V3DVh8!`N#KW}gY?}&unxMR6rCP+l*Pp*g?#+3{&N{p%XSXbVRU4&p_L`_$soRC=){&{z4fI!A*G>2HX0C zZtHpK34d{H!fCA1_Ylv_P|qWDQzx_&i9>;rfdXk$v2k*}*oLK$9A;TbFVJCjHcW)E z!_Fads^YBf22eKyeh+o?M(f6EEud0zHF};4OiK|sG?=u#m@0pXZCZURr&4s)6MFy)|!t@7&1Y?B0|hQHF}1lBip>jHe$N1WHU0I zSMWCAM;G=zmXR2EsdrwgPRD_rUL`vjdI$8xEOo?$G3%M0SaRZK5vur!QkaObVfA}<;4nE}7vY3}|h6Lf^ zATLMraZyg*4FCQ9=W{*$;qBS?<0AXc{+m?UY+(PQT)KB}hPQRS`0nE3#DfEtMfsb&GI+H>umJbq?+HA z<6$_eIc_wc&a1(PZ2D&S)lKsnUNk4V*f`S@XJ2HK{JP4szxJOT+m|tioqUm3+b5na z=F7q!Qr|Bg9kS;P*9> zph#0n?d1?g8?3k@57*@7;;#D@H|d9|2Vb(IzMW0BYM`83bUAhoXU^26mU zN7>Y>*i*ymFp8U^uIJBu|C?;OY%_rH@#m-QrBCjpD#lMgxEn|8Im`Io{$Gre-ja{S zxW2vml<1a(v)?B=`-fs#Tu<{|-@Yj8Xs5rAbNX!!@Qhgfc}I;4XWI**vCc&<4uR0* zt{CUbXOwyKm_28@Z4SG5?z(Ru*YVPfi{)aP-9J5q?SuB5D=X{5+OTYyk0V){eWtR^ z7U%W+e9KNGlr+EoPd-X)|8M6X+l(lJl+nB_?QpCcdcK(3$8{M;WLs4XA8mQ4UaD2H zylTGbk1OAF&{&)1n+%%opRIkA+2MS6c(LBYjFDAG3 zeqjU|yzwH32`j8tR#%&_*?}99b78U-ZeAwDJ-}Bx;Dx6>SOWT^0##}M67-1d{RH)DYD z`qOiWDC?$7%2~equIA<4Y=;N?6l!;oZ=V}>vEtIesCWY^?({_? zEt8X~>j2x3ilNO%PLYJ9vRD)w&$moUs*w1@7)S-AdJUusoZ<{P6^T=ST1!99s<2toQ!PLkeZ`WL*e~8(8CwG?YyfV3XYifYE@c2v~p;0+m*e&mqI?=ZIak+ zk1)Vi38sWzIzu8#NYK(tr;_CvKJI+d)SBfh?{IDlSUE8q0ssgA(ggrQIKyHC03{(& zl^_6k@1BIUPE>h093w8IyyOg#kk?d%$m6#k|Ckn5Nx+IEPb2Ks z#@As*Z~`l`ZykgGO8cDw_RJ#}$2iH(?zlval}Kxxf*fm{m=g3-Xr#0x-XB+rv*R~U zznACM1^nym&%ClX0nAi#XJ@xwf|)u(GxdDdKy~(^Fj7t*6f%jODD0MpShIxvWS>FU zilw4M)~w}ZUxWd+NgHU?>Fm$0M4_9riG+Xg^LydeUsc(-xLYO&@*#ZyL~S{LsJ)3H zkU;2lrHhV`Kydd>BxfH9OIu_EKke3^Q0s`LW?%R5wLzamdOfX8Y04#;2C?kV0Hbe#sRg!FRF@p@tv76`3t3=kAR5 zX8bvy7Ng?J%d9Ib+>2_CDYHLKa=WreJ0WLvlBsoqBbX~jy+TmRcv^GS2vS@HbJh6q z2A@D~y{@da$U-?^;qC|lW7UJP+NqmhtWMKd#VDr{EQH~hB(A4<5G0s3Zhffo=87Y& zi8+C(z=dB`2{tOZjoPD1@+{#U7C>5u&NO17$*#0bT ziZ5^>PX7>g@bwl6b0#5K+FN}*i$zO@Xg{=MhH+M!a@jhQ?KsQkjF z4Nn1$KTWg6G9M4#7qc9w*asDTXzKbr0Tz42B9DMYU!F{mq^XzF7!w33+F;Sz5-C$- zvGefSVbS1qEJ_Jj1S}pM7Jq$r_0M;}!#>v3_!OCyy|LB}c$k=nA_fnozjIh64-<(O zM5qrFLv4jdiYAW~JCCp>68T21P6HAFiAOqrIrt<0?_Iuxbz%L)iq_B>s~r$3_J~Fq zq42_R6(GfR>R?fuMwDQo`U1NK*;H9vCJq^+46|Jc29|2QFZSyJkI(-3IO>> zKAM*|#pJHa2k%Csd;yT`C4kg~d3z@XM36mVvU731AYuMbAR=294B z!#YZRxnk0meh~)Pt~j5sulFD%Wh-vto;bm&(S~p(*`bR*p+XTV6rn;fdZ$0)BSt0( z1r+v%LLC8xvRSd59ECE-2_*C!6f!vnJCCjn2Nl{_abpxs+p>nUH4Z8b90U#m2al%X zE!9Xe2^j3HPM>rx)ECX#Oo6QNPKv=skVJwK6zYSqYOkAMtWMKdbuQEw z3>g;c<*A@at<_4BQqs6RbVyUFPl(Y_sSif#Ih_P6b&{p}ltyr=IQP|gCb5yCh?!8I zim8tPsq%F>!YH0J85&`PP^u5BN})6#sC#&Nlz9t8ULSLhcb1T{Sa7uH>IaTiUIRzp z^bNbjS1)kvGD0ZM2RObGIEG!L0FFJwQG_nv0_fNy9eD&gvQVz4aO%lJ777xTP@n1O zR$U32nvR`E2sQdZN2t*UBEE1{sqh9)!?qVa5>dn;qV_kA$mH>2Q>sducv1P19F3F5 zi=9XKrI+YK1>&nA;=ii=Ca>~xln=rjCeX2$c+oF5Wemc_{@^Ghz|l1T^_UVeb-+kT zBTAAcWYofMl4yaMnvfBO2}SyVNGQ?=Mjp;c?2Gi>PE!y<_D4w-fs(!nkqv<8p?iY0 zEle~z2vV3iLQ0p0rwoye?ng-o*?EZV_$W5;Xj0BOQlyBqFQEm+(r5vI1VFxI@$1>w z*~nfoH=;p3Z^FsJS=0UPrkc+NBfoU#8{lUz8CdVbySi-&0PPQ;oh$n}M$*j=hAp8| zc7n5pO9esFwMjL8GfzU2SNF#l;qi63VJ^a4lLp28P}~p2{YOyT&)pt(*05=W6^4v! ze<(B&+M-h5ZzeSfiASFkBtye+7fF)z{oW!B@c7!?*hgG+&*}mQ0fYd;=pb}<&6xuM zAndKp4Z{&YD3#A`rrwXZ5yTP4O1u4tDIwfTRtN9a{x>zcfqe)1YN@vy}80n26J_|>;AVkx}jjMlAEhN+6m_BG|g4#780Bxu0#A! z67STxk|eq0ms}v}_g-;x*@i=-8!%GO=_FXGlWcUuSd2ZQj5ifJ{dU4U2=dzrD=W~x z#fZ!F+e397AvC&yYDH*t1Jpe{L#n;4GqAaEk5h$=$D*TswELl>74$$y;9$yI_f0|~CWj-FfFJ`dOOD}Pw3`=X&M)4VtF)@P2C(?bs`z2ibe|!ka4l}Et&;>|#vT!=BOp?^P>vy`58a-w zEg%K+f;2PQ6(&*~Dib;5EHNHC5Ao=F+&~jJpd&PaJE^3q3g*s8C|K-~kew^{1woYh zrJO8n>An?2R1oA!_l5DL`x=SFmHROU*sg~S-B_!dwucQxO04y;!4hisp>`i?_m7}< z-*t48K3G&7P}m;|IgWrrzXZm(PTq-8=n6HQAdftu-`h6PremvrU2qTNT`CWe#<5p@q%sH&(lJ z6O7eq8Y>pTSQ+mV>(t#LK1(u)xfV7;A|iui2`y~ENVU*Ouu>=4!iMt*dqZ&(YV;Bt zDJzIK#emeOLK*cH5F~MtevA=93maHe3N37ax*f8k-quN|(eHDjP{g96v_V}jbhLsV z=%{=hgy@NmT}F8HEo^{}Ku2g{bG&rqeg(Zm81UF99%T$X5|1gGwuOxq8NRcAazJwMOOQQwU>;olVve4)3>uhANm>bcco)5yOXHB1* z!A&)v4Mu*N@*99?FX>m`^v2aq8v-1SUc2#IQRcfi+P$QoBiD?<9GnVeV9A9S%G6a#*byEw;bO=kp)P@2S20ZOaW0 zrJR@XjE{iP=jutt1s3|*UIKCST;LdbE(!hioM~jN)0SA*i4|lJ_A?E_)#~yf8Dr9e zrtco;Zfz$I#|pw;tHwuCLJ~s>xeV7^QLWP|p>&1fPLlRI$W_omDX8p02X~Kirw%If zaIB4VKnI`H!S37QawKR_jKo`Nal}<4$TflWnHKxz;1bc+>bGx*JB}2Hb-WhcG;dog e!ux4`` 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 server = DynamicCast(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 to understand how SLAAC and DHCPv6 operate at the same time. diff --git a/src/internet-apps/examples/CMakeLists.txt b/src/internet-apps/examples/CMakeLists.txt index 49139df8a..0b24baabf 100644 --- a/src/internet-apps/examples/CMakeLists.txt +++ b/src/internet-apps/examples/CMakeLists.txt @@ -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 diff --git a/src/internet-apps/examples/dhcp6-example.cc b/src/internet-apps/examples/dhcp6-example.cc new file mode 100644 index 000000000..5c1345c23 --- /dev/null +++ b/src/internet-apps/examples/dhcp6-example.cc @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +/* + * 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, uint32_t interface) +{ + node->GetObject()->SetDown(interface); +} + +void +SetInterfaceUp(Ptr node, uint32_t interface) +{ + node->GetObject()->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 router = CreateObject(); + 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 ipv6proto = nonRouterNodes.Get(3)->GetObject(); + 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 server = DynamicCast(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; +} diff --git a/src/internet-apps/helper/dhcp6-helper.cc b/src/internet-apps/helper/dhcp6-helper.cc new file mode 100644 index 000000000..b5042356e --- /dev/null +++ b/src/internet-apps/helper/dhcp6-helper.cc @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 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 = *itr; + Ptr node = netDevice->GetNode(); + NS_ASSERT_MSG(node, "Dhcp6Helper: NetDevice is not associated with any node -> fail"); + + Ptr ipv6 = node->GetObject(); + 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 server = DynamicCast(node->GetApplication(i)); + if (!server) + { + Ptr app = m_serverFactory.Create(); + node->AddApplication(app); + app->SetDhcp6ServerNetDevice(netDevices); + installedApps.Add(app); + } + } + + if (nApplications == 0) + { + Ptr app = m_serverFactory.Create(); + node->AddApplication(app); + app->SetDhcp6ServerNetDevice(netDevices); + installedApps.Add(app); + } + } + + return installedApps; +} + +Ptr +Dhcp6Helper::InstallDhcp6ClientInternal(Ptr clientNode) const +{ + Ptr 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(); + clientNode->AddApplication(app); + + return app; +} + +} // namespace ns3 diff --git a/src/internet-apps/helper/dhcp6-helper.h b/src/internet-apps/helper/dhcp6-helper.h new file mode 100644 index 000000000..54e29ac28 --- /dev/null +++ b/src/internet-apps/helper/dhcp6-helper.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +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 InstallDhcp6ClientInternal(Ptr clientNode) const; + + ObjectFactory m_clientFactory; //!< DHCPv6 client factory. + ObjectFactory m_serverFactory; //!< DHCPv6 server factory. +}; + +} // namespace ns3 + +#endif /* DHCP6_HELPER_H */ diff --git a/src/internet-apps/model/dhcp6-client.cc b/src/internet-apps/model/dhcp6-client.cc new file mode 100644 index 000000000..d0adca0e3 --- /dev/null +++ b/src/internet-apps/model/dhcp6-client.cc @@ -0,0 +1,943 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +namespace ns3 +{ + +NS_LOG_COMPONENT_DEFINE("Dhcp6Client"); + +TypeId +Dhcp6Client::GetTypeId() +{ + static TypeId tid = + TypeId("ns3::Dhcp6Client") + .SetParent() + .AddConstructor() + .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()) + .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()) + .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()) + .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 iDev) +{ + Ptr packet = Create(); + Ptr ipv6 = GetNode()->GetObject(); + 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 iDev, Dhcp6Header header, Inet6SocketAddress server) +{ + NS_LOG_FUNCTION(this << iDev << header << server); + + Ptr packet = Create(); + Dhcp6Header requestHeader; + requestHeader.ResetOptions(); + requestHeader.SetMessageType(Dhcp6Header::MessageType::REQUEST); + + // TODO: Use min, max for GetValue + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + const auto& dhcpInterface = m_interfaces[ifIndex]; + dhcpInterface->m_transactId = static_cast(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 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 = Create(); + + // 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(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>()); + m_socket->Close(); + + Ptr ipv6 = m_client->GetNode()->GetObject(); + + // 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 icmpv6 = DynamicCast( + ipv6->GetProtocol(Icmpv6L4Protocol::GetStaticProtocolNumber(), m_interfaceIndex)); + + icmpv6->TraceDisconnectWithoutContext("DadSuccess", m_acceptedAddressCb.value()); + m_acceptedAddressCb = MakeNullCallback(); + icmpv6->TraceDisconnectWithoutContext("DadFailure", m_declinedAddressCb.value()); + m_declinedAddressCb = MakeNullCallback(); +} + +void +Dhcp6Client::CheckLeaseStatus(Ptr 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 iDev, Dhcp6Header header, Inet6SocketAddress server) +{ + NS_LOG_FUNCTION(this << iDev << header << server); + + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + + Ptr dhcpInterface = m_interfaces[ifIndex]; + + // Read IA_NA options. + std::vector ianaOptionsList = header.GetIanaOptions(); + + dhcpInterface->m_declinedAddresses.clear(); + + Time earliestRebind{Time::Max()}; + Time earliestRenew{Time::Max()}; + std::vector 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 icmpv6 = DynamicCast( + 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 = Create(); + + m_interfaces[dhcpInterfaceIndex]->m_transactId = + static_cast(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 = Create(); + + m_interfaces[dhcpInterfaceIndex]->m_transactId = + static_cast(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 = GetNode()->GetObject(); + + Dhcp6Header header; + Ptr packet = Create(); + + for (const auto& itr : m_interfaces) + { + uint32_t ifIndex = itr.first; + Ptr dhcpInterface = itr.second; + + dhcpInterface->m_transactId = static_cast(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) +{ + NS_LOG_FUNCTION(this << socket); + + Address from; + Ptr 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 = GetNode()->GetObject(); + Ptr iDev = GetNode()->GetDevice(incomingIf); + uint32_t iIf = ipv6->GetInterfaceForDevice(iDev); + Ptr 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 = GetNode()->GetObject(); + Ptr 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 = GetNode(); + + NS_ASSERT_MSG(node, "Dhcp6Client::StartApplication: cannot get the node from the device."); + + Ptr ipv6 = node->GetObject(); + 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 device = ipv6->GetNetDevice(ifIndex); + Ptr icmpv6 = DynamicCast( + 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 = GetNode(); + Ptr ipv6l3 = node->GetObject(); + + if (!m_interfaces[recvInterface]) + { + // Create config object if M flag is received for the first time. + Ptr dhcpInterface = Create(); + 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 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 = + node->GetObject()->GetInterface(recvInterface); + ipv6Interface->TraceConnectWithoutContext( + "InterfaceStatus", + MakeCallback(&Dhcp6Client::LinkStateHandler, this)); + } + + for (const auto& itr : m_interfaces) + { + uint32_t interface = itr.first; + Ptr ipv6 = GetNode()->GetObject(); + Ptr device = ipv6->GetNetDevice(interface); + Ptr 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::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 device) +{ + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(device); + Ptr dhcpInterface = m_interfaces[ifIndex]; + + Ptr node = GetNode(); + if (m_clientDuid.IsInvalid()) + { + m_clientDuid.Initialize(node); + } + + Dhcp6Header header; + Ptr packet = Create(); + + // Create a unique transaction ID. + dhcpInterface->m_transactId = static_cast(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 diff --git a/src/internet-apps/model/dhcp6-client.h b/src/internet-apps/model/dhcp6-client.h new file mode 100644 index 000000000..43feb921c --- /dev/null +++ b/src/internet-apps/model/dhcp6-client.h @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +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 + { + public: + /** + * The default constructor. + */ + InterfaceConfig(); + + /** + * Destructor. + */ + ~InterfaceConfig(); + + /// The Dhcp6Client on which the interface is present. + Ptr m_client; + + /// The IPv6 interface index of this configuration. + uint32_t m_interfaceIndex; + + /// The socket that has been opened for this interface. + Ptr 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 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 m_offeredAddresses; + + /// List of addresses to be declined by the client. + std::vector 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 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> 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> 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 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 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 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 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); + + /** + * @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 device); + + /** + * @brief Retrieve all existing IAIDs. + * @return A list of all IAIDs. + */ + std::vector GetIaids(); + + /// Map each interface to its corresponding configuration details. + std::unordered_map> m_interfaces; + + Duid m_clientDuid; //!< The client DUID. + TracedCallback m_newLease; //!< Trace the new lease. + + /// Track the IPv6 Address - IAID association. + std::unordered_map m_iaidMap; + + /// Random variable to set transaction ID + Ptr 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 m_solicitJitter; + + /// Random variable used to create the IAID. + Ptr 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 diff --git a/src/internet-apps/model/dhcp6-duid.cc b/src/internet-apps/model/dhcp6-duid.cc new file mode 100644 index 000000000..a4e2aa641 --- /dev/null +++ b/src/internet-apps/model/dhcp6-duid.cc @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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(); +} + +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 +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 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) +{ + Ptr ipv6 = node->GetObject(); + uint32_t nInterfaces = ipv6->GetNInterfaces(); + + uint32_t maxAddressLength = 0; + Address duidAddress; + + for (uint32_t i = 0; i < nInterfaces; i++) + { + Ptr device = ipv6->GetNetDevice(i); + + // Discard the loopback device. + if (DynamicCast(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 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>(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(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 buffer = x.GetIdentifier(); + + std::string s(buffer.begin(), buffer.begin() + duidLen); + return std::hash{}(s); +} +} // namespace ns3 diff --git a/src/internet-apps/model/dhcp6-duid.h b/src/internet-apps/model/dhcp6-duid.h new file mode 100644 index 000000000..9367a6ce0 --- /dev/null +++ b/src/internet-apps/model/dhcp6-duid.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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); + + /** + * @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 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 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 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 diff --git a/src/internet-apps/model/dhcp6-header.cc b/src/internet-apps/model/dhcp6-header.cc new file mode 100644 index 000000000..5b4bb7380 --- /dev/null +++ b/src/internet-apps/model/dhcp6-header.cc @@ -0,0 +1,673 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +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
() + .SetGroupName("Internet-Apps") + .AddConstructor(); + 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 +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 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 +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 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 requestedOptions = m_optionRequest.GetRequestedOptions(); + for (const auto& itr : requestedOptions) + { + i.WriteHtonU16(static_cast(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(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(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(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 diff --git a/src/internet-apps/model/dhcp6-header.h b/src/internet-apps/model/dhcp6-header.h new file mode 100644 index 000000000..ad35fd5d0 --- /dev/null +++ b/src/internet-apps/model/dhcp6-header.h @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 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 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 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 m_ianaList; //!< Vector of IA_NA options. + std::vector 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 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 diff --git a/src/internet-apps/model/dhcp6-options.cc b/src/internet-apps/model/dhcp6-options.cc new file mode 100644 index 000000000..35b2e2eba --- /dev/null +++ b/src/internet-apps/model/dhcp6-options.cc @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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(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(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 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(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(); +} + +std::vector +RequestOptions::GetRequestedOptions() const +{ + return m_requestedOptions; +} + +void +RequestOptions::AddRequestedOption(OptionType requestedOption) +{ + m_requestedOptions.push_back(requestedOption); +} + +template +IntegerOptions::IntegerOptions() +{ + m_optionValue = 0; +} + +template +T +IntegerOptions::GetOptionValue() const +{ + return m_optionValue; +} + +template +void +IntegerOptions::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; +template class IntegerOptions; + +} // namespace ns3 diff --git a/src/internet-apps/model/dhcp6-options.h b/src/internet-apps/model/dhcp6-options.h new file mode 100644 index 000000000..581e0f54e --- /dev/null +++ b/src/internet-apps/model/dhcp6-options.h @@ -0,0 +1,468 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 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 GetRequestedOptions() const; + + /** + * @brief Set the option values. + * @param requestedOption option to be requested from the server. + */ + void AddRequestedOption(OptionType requestedOption); + + private: + std::vector m_requestedOptions; //!< List of requested options. +}; + +/** + * @ingroup dhcp6 + * + * @class IntegerOptions + * @brief Implements the Preference and Elapsed Time options. + */ +template +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 PreferenceOption; + +/** + * @ingroup dhcp6 + * Create the typedef ElapsedTimeOption with T as uint16_t + */ +typedef IntegerOptions ElapsedTimeOption; + +} // namespace ns3 + +#endif diff --git a/src/internet-apps/model/dhcp6-server.cc b/src/internet-apps/model/dhcp6-server.cc new file mode 100644 index 000000000..6412fbb03 --- /dev/null +++ b/src/internet-apps/model/dhcp6-server.cc @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +namespace ns3 +{ + +NS_LOG_COMPONENT_DEFINE("Dhcp6Server"); + +TypeId +Dhcp6Server::GetTypeId() +{ + static TypeId tid = + TypeId("ns3::Dhcp6Server") + .SetParent() + .AddConstructor() + .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>(), + MakeNullCallback>()); + itr.second->SetRecvCallback(MakeNullCallback>()); + itr.second->SetSendCallback(MakeNullCallback, uint32_t>()); + itr.second->Close(); + } + m_sendSockets.clear(); + m_iaBindings.clear(); + Application::DoDispose(); +} + +void +Dhcp6Server::ProcessSolicit(Ptr iDev, Dhcp6Header header, Inet6SocketAddress client) +{ + Duid clientDuid = header.GetClientIdentifier().GetDuid(); + std::map 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 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 iDev, Dhcp6Header header, Inet6SocketAddress client) +{ + NS_LOG_FUNCTION(this << iDev << header << client); + + // Options included according to RFC 8415 Section 18.3.9 + + Ptr packet = Create(); + 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 ianaOptionsList = header.GetIanaOptions(); + std::vector 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 headerOptions = header.GetOptionList(); + if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end()) + { + std::vector requestedOptions = + header.GetOptionRequest().GetRequestedOptions(); + advertiseHeader.HandleOptionRequest(requestedOptions); + } + + packet->AddHeader(advertiseHeader); + + // Find the socket corresponding to the NetDevice. + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + Ptr 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 iDev, Dhcp6Header header, Inet6SocketAddress client) +{ + NS_LOG_FUNCTION(this << iDev << header << client); + + // Options included according to RFC 8415 Section 18.3.10 + + Ptr packet = Create(); + 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 ianaOptionsList = header.GetIanaOptions(); + + for (auto& iaOpt : ianaOptionsList) + { + // Iterate through the offered addresses. + // Current approach: Try to accept all offers. + std::vector 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> updatedLifetimes; + for (auto it = range.first; it != range.second; it++) + { + Ipv6Address clientLease = it->second.first; + std::pair 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 headerOptions = header.GetOptionList(); + + // Check if the client has requested any options. + if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end()) + { + std::vector requestedOptions = + header.GetOptionRequest().GetRequestedOptions(); + replyHeader.HandleOptionRequest(requestedOptions); + } + + packet->AddHeader(replyHeader); + + // Find the socket corresponding to the NetDevice. + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + Ptr 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 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 = Create(); + 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 ianaOptionsList = header.GetIanaOptions(); + for (auto& iaOpt : ianaOptionsList) + { + std::vector 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 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 headerOptions = header.GetOptionList(); + if (headerOptions.find(Options::OptionType::OPTION_ORO) != headerOptions.end()) + { + std::vector requestedOptions = + header.GetOptionRequest().GetRequestedOptions(); + replyHeader.HandleOptionRequest(requestedOptions); + } + + packet->AddHeader(replyHeader); + + // Find the socket corresponding to the NetDevice. + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + Ptr 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 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 = Create(); + 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 ianaOptionsList = header.GetIanaOptions(); + for (const auto& iaOpt : ianaOptionsList) + { + std::vector 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 expiredLease = {duid, leaseAddr}; + subnet.m_expiredAddresses.insert({expiredTime, expiredLease}); + continue; + } + itr++; + } + } + } + } + } + + packet->AddHeader(replyHeader); + + // Find the socket corresponding to the NetDevice. + Ptr ipv6 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(iDev); + Ptr 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 = GetNode()->GetObject(); + int32_t ifIndex = ipv6->GetInterfaceForDevice(*itr); + Ptr sendSocket = m_sendSockets[ifIndex]; + m_sendSockets[ifIndex] = nullptr; + } +} + +void +Dhcp6Server::NetHandler(Ptr socket) +{ + NS_LOG_FUNCTION(this << socket); + + Dhcp6Header header; + Address from; + Ptr 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 iDev = GetNode()->GetDevice(incomingIf); + + if (packet->RemoveHeader(header) == 0) + { + return; + } + + // Initialize the DUID before responding to the client. + Ptr 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 = GetNode(); + Ptr ipv6 = node->GetObject(); + Ptr ipv6l3 = node->GetObject(); + + for (auto itr = m_sendSockets.begin(); itr != m_sendSockets.end(); itr++) + { + uint32_t ifIndex = itr->first; + Ptr device = GetNode()->GetDevice(ifIndex); + Ptr sendSocket = m_sendSockets[ifIndex]; + + if (ifIndex == recvInterface) + { + Ipv6Address linkLocal = + ipv6l3->GetInterface(ifIndex)->GetLinkLocalAddress().GetAddress(); + + TypeId tid = TypeId::LookupByName("ns3::UdpSocketFactory"); + + Ptr 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 = GetNode(); + Ptr ipv6 = node->GetObject(); + Ptr ipv6l3 = node->GetObject(); + + 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 iDev = GetNode()->GetDevice(ifIndex); + Ptr 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 icmpv6 = DynamicCast( + 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 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 diff --git a/src/internet-apps/model/dhcp6-server.h b/src/internet-apps/model/dhcp6-server.h new file mode 100644 index 000000000..289906dad --- /dev/null +++ b/src/internet-apps/model/dhcp6-server.h @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 + +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> ExpiredAddresses; + + /** + * @brief Leased Addresses + * Client DUID + Ipv6Address / Lease time + */ + typedef std::unordered_multimap, Duid::DuidHash> + LeasedAddresses; + + /** + * @brief Declined Addresses + * Ipv6Address + Client DUID + */ + typedef std::unordered_map 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); + + /** + * @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 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 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 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 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 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 m_recvSocket; //!< Socket bound to port 547. + Duid m_serverDuid; //!< Server DUID + std::vector 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> m_sendSockets; + + /// Store IA bindings. Map of DUID + IA Type / IAID + std::multimap> 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 diff --git a/src/internet-apps/model/radvd.cc b/src/internet-apps/model/radvd.cc index 422f666a8..0b41f7507 100644 --- a/src/internet-apps/model/radvd.cc +++ b/src/internet-apps/model/radvd.cc @@ -77,13 +77,19 @@ Radvd::DoDispose() { NS_LOG_FUNCTION(this); - m_recvSocket->Close(); - m_recvSocket = nullptr; + if (m_recvSocket) + { + m_recvSocket->Close(); + m_recvSocket = nullptr; + } for (auto it = m_sendSockets.begin(); it != m_sendSockets.end(); ++it) { - it->second->Close(); - it->second = nullptr; + if (it->second) + { + it->second->Close(); + it->second = nullptr; + } } Application::DoDispose(); diff --git a/src/internet-apps/test/dhcp6-test.cc b/src/internet-apps/test/dhcp6-test.cc new file mode 100644 index 000000000..029dbb429 --- /dev/null +++ b/src/internet-apps/test/dhcp6-test.cc @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2024 NITK Surathkal + * + * SPDX-License-Identifier: GPL-2.0-only + * + * Author: Kavya Bhat + * + */ + +#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 router = CreateObject(); + 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 server = DynamicCast(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_client1 = nonRouterNodes.Get(1)->GetObject(); + Ptr l3_client1 = ipv6_client1->GetObject(); + 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_client2 = nonRouterNodes.Get(2)->GetObject(); + Ptr l3_client2 = ipv6_client2->GetObject(); + 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 diff --git a/src/internet/model/icmpv6-l4-protocol.cc b/src/internet/model/icmpv6-l4-protocol.cc index f5bba8b89..475e7f1cc 100644 --- a/src/internet/model/icmpv6-l4-protocol.cc +++ b/src/internet/model/icmpv6-l4-protocol.cc @@ -168,6 +168,7 @@ Icmpv6L4Protocol::DoDispose() } m_cacheList.clear(); m_downTarget.Nullify(); + m_startDhcpv6.Nullify(); m_node = nullptr; IpL4Protocol::DoDispose();