From 7927bd340976cd170829a9b07412d1fc121f7ba8 Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Thu, 16 Jan 2025 08:30:32 +0000
Subject: [PATCH 1/6] ip6: always register to multicast addresses

As the loopback interface is not ethernet based, well known
multicast addresses were not registered: grout rejects the
neighbor solicitation and router solication messages.

Register to the well known multicast addresses even if there is no
ethernet addresses configured on the interface.

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 modules/ip6/control/address.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/modules/ip6/control/address.c b/modules/ip6/control/address.c
index 1fa8befe..0a9fb8ec 100644
--- a/modules/ip6/control/address.c
+++ b/modules/ip6/control/address.c
@@ -296,14 +296,10 @@ static void ip6_iface_event_handler(iface_event_t event, struct iface *iface) {
 			rte_ipv6_solnode_from_addr(&solicited_node, &link_local);
 			if (ip6_mcast_addr_add(iface, &solicited_node) < 0)
 				LOG(INFO, "%s: mcast_addr_add: %s", iface->name, strerror(errno));
-
-			for (i = 0; i < ARRAY_DIM(well_known_mcast_addrs); i++) {
-				if (ip6_mcast_addr_add(iface, &well_known_mcast_addrs[i]) < 0)
-					LOG(INFO,
-					    "%s: mcast_addr_add: %s",
-					    iface->name,
-					    strerror(errno));
-			}
+		}
+		for (i = 0; i < ARRAY_DIM(well_known_mcast_addrs); i++) {
+			if (ip6_mcast_addr_add(iface, &well_known_mcast_addrs[i]) < 0)
+				LOG(INFO, "%s: mcast_addr_add: %s", iface->name, strerror(errno));
 		}
 		break;
 	case IFACE_EVENT_PRE_REMOVE:

From aa7f895ae6f5e659c52ac69abc05deeccd10062d Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Sun, 15 Dec 2024 17:08:55 +0000
Subject: [PATCH 2/6] ip6: send icmp router advertisement

BGP unnumbered relies on RA message to discover
the local routers.

Broadcast the simplest router advertisement message
on ipv6 enabled interfaces.
Additional options (DNS, ... ) may be added in the future.

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 docs/graph.svg                      | 388 ++++++++++++++--------------
 modules/ip6/api/gr_ip6.h            |  11 +
 modules/ip6/control/meson.build     |   1 +
 modules/ip6/control/router_advert.c | 162 ++++++++++++
 4 files changed, 368 insertions(+), 194 deletions(-)
 create mode 100644 modules/ip6/control/router_advert.c

diff --git a/docs/graph.svg b/docs/graph.svg
index 31004b98..22003d18 100644
--- a/docs/graph.svg
+++ b/docs/graph.svg
@@ -1,577 +1,577 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<!-- Generated by graphviz version 12.1.0 (20240811.2233)
+<!-- Generated by graphviz version 2.44.0 (0)
  -->
-<!-- Title: gr&#45;0003 Pages: 1 -->
-<svg width="1208pt" height="696pt"
- viewBox="0.00 0.00 1207.77 696.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<!-- Title: gr&#45;0005 Pages: 1 -->
+<svg width="1175pt" height="696pt"
+ viewBox="0.00 0.00 1175.21 696.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 692)">
-<title>gr&#45;0003</title>
-<polygon fill="white" stroke="none" points="-4,4 -4,-692 1203.77,-692 1203.77,4 -4,4"/>
+<title>gr&#45;0005</title>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-692 1171.21,-692 1171.21,4 -4,4"/>
 <!-- control_input -->
 <g id="node1" class="node">
 <title>control_input</title>
-<ellipse fill="none" stroke="blue" stroke-width="2" cx="41.62" cy="-405" rx="41.62" ry="18"/>
-<text text-anchor="middle" x="41.62" y="-401.3" font-family="sans" font-size="11.00">control_input</text>
+<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-405" rx="40.32" ry="18"/>
+<text text-anchor="middle" x="40.16" y="-402.2" font-family="sans" font-size="11.00">control_input</text>
 </g>
 <!-- loopback_input -->
 <g id="node2" class="node">
 <title>loopback_input</title>
-<ellipse fill="none" stroke="black" cx="166.76" cy="-378" rx="47.52" ry="18"/>
-<text text-anchor="middle" x="166.76" y="-374.3" font-family="sans" font-size="11.00">loopback_input</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-378" rx="45.68" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-375.2" font-family="sans" font-size="11.00">loopback_input</text>
 </g>
 <!-- control_input&#45;&gt;loopback_input -->
 <g id="edge1" class="edge">
 <title>control_input&#45;&gt;loopback_input</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M79.24,-396.97C90.2,-394.57 102.43,-391.89 114.16,-389.31"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="113.23,-393.1 122.24,-387.54 111.73,-386.27 113.23,-393.1"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M76.44,-397.05C87.55,-394.54 100.01,-391.73 111.89,-389.05"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="112.7,-392.46 121.69,-386.84 111.17,-385.63 112.7,-392.46"/>
 </g>
 <!-- arp_output_request -->
 <g id="node3" class="node">
 <title>arp_output_request</title>
-<ellipse fill="none" stroke="black" cx="311.7" cy="-650" rx="61.42" ry="18"/>
-<text text-anchor="middle" x="311.7" y="-646.3" font-family="sans" font-size="11.00">arp_output_request</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-650" rx="59.31" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-647.2" font-family="sans" font-size="11.00">arp_output_request</text>
 </g>
 <!-- control_input&#45;&gt;arp_output_request -->
 <g id="edge2" class="edge">
 <title>control_input&#45;&gt;arp_output_request</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M57.86,-421.94C66.33,-432.09 76.48,-445.59 83.25,-459 108.51,-509.1 80.81,-539.13 119.25,-580 151.67,-614.48 202.47,-632.04 243.59,-640.94"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="241.18,-644.03 251.67,-642.58 242.57,-637.16 241.18,-644.03"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M55.82,-422.03C63.97,-432.21 73.76,-445.71 80.32,-459 105.15,-509.32 78.1,-538.92 116.32,-580 148.07,-614.12 198.14,-631.73 238.24,-640.75"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="237.69,-644.22 248.2,-642.87 239.15,-637.37 237.69,-644.22"/>
 </g>
 <!-- icmp_local_send -->
 <g id="node4" class="node">
 <title>icmp_local_send</title>
-<ellipse fill="none" stroke="black" cx="311.7" cy="-103" rx="51.31" ry="18"/>
-<text text-anchor="middle" x="311.7" y="-99.3" font-family="sans" font-size="11.00">icmp_local_send</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-103" rx="49.28" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-100.2" font-family="sans" font-size="11.00">icmp_local_send</text>
 </g>
 <!-- control_input&#45;&gt;icmp_local_send -->
 <g id="edge3" class="edge">
 <title>control_input&#45;&gt;icmp_local_send</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M56.58,-388C89.47,-348.11 174.76,-245.74 250.28,-164 261.49,-151.87 274.31,-138.82 285.25,-127.91"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="286.4,-131.7 291.04,-122.17 281.47,-126.73 286.4,-131.7"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M54.79,-388.04C86.89,-348.24 170.09,-246.03 243.5,-164 254.6,-151.6 267.32,-138.17 278.02,-127.08"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="280.62,-129.44 285.07,-119.82 275.59,-124.56 280.62,-129.44"/>
 </g>
 <!-- ndp_ns_output -->
 <g id="node5" class="node">
 <title>ndp_ns_output</title>
-<ellipse fill="none" stroke="black" cx="166.76" cy="-432" rx="46.26" ry="18"/>
-<text text-anchor="middle" x="166.76" y="-428.3" font-family="sans" font-size="11.00">ndp_ns_output</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-432" rx="44.51" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-429.2" font-family="sans" font-size="11.00">ndp_ns_output</text>
 </g>
 <!-- control_input&#45;&gt;ndp_ns_output -->
 <g id="edge4" class="edge">
 <title>control_input&#45;&gt;ndp_ns_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M79.24,-413.03C90.3,-415.45 102.67,-418.17 114.51,-420.76"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="112.16,-423.83 122.68,-422.55 113.66,-416.99 112.16,-423.83"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M76.44,-412.95C87.76,-415.51 100.5,-418.38 112.58,-421.1"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="112.01,-424.56 122.54,-423.35 113.55,-417.73 112.01,-424.56"/>
 </g>
 <!-- ip_output -->
 <g id="node6" class="node">
 <title>ip_output</title>
-<ellipse fill="none" stroke="black" cx="870.38" cy="-114" rx="30.25" ry="18"/>
-<text text-anchor="middle" x="870.38" y="-110.3" font-family="sans" font-size="11.00">ip_output</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-114" rx="29.12" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-111.2" font-family="sans" font-size="11.00">ip_output</text>
 </g>
 <!-- control_input&#45;&gt;ip_output -->
 <g id="edge5" class="edge">
 <title>control_input&#45;&gt;ip_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M45.42,-386.81C58.66,-308.01 122.45,0 310.7,0 310.7,0 310.7,0 582.35,0 682.93,0 790.22,-61.2 840.38,-93.92"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="837.13,-95.96 847.39,-98.58 841,-90.13 837.13,-95.96"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M43.7,-386.81C55.79,-308.01 115.02,0 301.66,0 301.66,0 301.66,0 566.15,0 665.31,0 770.58,-62.28 818.75,-94.78"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="816.84,-97.71 827.07,-100.48 820.8,-91.93 816.84,-97.71"/>
 </g>
 <!-- ip6_output -->
 <g id="node7" class="node">
 <title>ip6_output</title>
-<ellipse fill="none" stroke="black" cx="870.38" cy="-444" rx="34.04" ry="18"/>
-<text text-anchor="middle" x="870.38" y="-440.3" font-family="sans" font-size="11.00">ip6_output</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-444" rx="32.63" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-441.2" font-family="sans" font-size="11.00">ip6_output</text>
 </g>
 <!-- control_input&#45;&gt;ip6_output -->
 <g id="edge6" class="edge">
 <title>control_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M56.05,-422.32C64.09,-432.83 74.43,-446.56 83.25,-459 99.92,-482.53 94.65,-497.93 119.25,-513 229.85,-580.78 279.62,-539.43 409.11,-547 571.37,-556.48 627.18,-590.11 773.81,-520 794.22,-510.24 792.33,-498.37 809.81,-484 818.43,-476.91 828.31,-469.88 837.52,-463.74"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="838.14,-467.51 844.61,-459.12 834.32,-461.65 838.14,-467.51"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M54.04,-422.39C61.76,-432.91 71.73,-446.64 80.32,-459 96.79,-482.68 91.75,-497.89 116.32,-513 223.66,-579.03 272.02,-539.43 397.81,-547 555.41,-556.48 609.45,-587.82 752.03,-520 772.46,-510.28 770.66,-498.51 788.03,-484 796.74,-476.72 806.74,-469.44 815.94,-463.12"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="818.11,-465.88 824.45,-457.39 814.2,-460.08 818.11,-465.88"/>
 </g>
 <!-- ip_input -->
 <g id="node11" class="node">
 <title>ip_input</title>
-<ellipse fill="none" stroke="black" cx="311.7" cy="-191" rx="27" ry="18"/>
-<text text-anchor="middle" x="311.7" y="-187.3" font-family="sans" font-size="11.00">ip_input</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-191" rx="27" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-188.2" font-family="sans" font-size="11.00">ip_input</text>
 </g>
 <!-- loopback_input&#45;&gt;ip_input -->
 <g id="edge11" class="edge">
 <title>loopback_input&#45;&gt;ip_input</title>
-<path fill="none" stroke="black" d="M181.21,-360.41C206.71,-327.05 262.16,-254.5 291.54,-216.07"/>
-<polygon fill="black" stroke="black" points="294.2,-218.35 297.49,-208.28 288.64,-214.1 294.2,-218.35"/>
+<path fill="none" stroke="black" d="M175.72,-360.74C200.6,-327.2 255.53,-253.16 283.91,-214.91"/>
+<polygon fill="black" stroke="black" points="286.72,-217 289.87,-206.88 281.1,-212.83 286.72,-217"/>
 </g>
 <!-- ip6_input -->
 <g id="node12" class="node">
 <title>ip6_input</title>
-<ellipse fill="none" stroke="black" cx="311.7" cy="-482" rx="29.41" ry="18"/>
-<text text-anchor="middle" x="311.7" y="-478.3" font-family="sans" font-size="11.00">ip6_input</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-482" rx="28.44" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-479.2" font-family="sans" font-size="11.00">ip6_input</text>
 </g>
 <!-- loopback_input&#45;&gt;ip6_input -->
 <g id="edge12" class="edge">
 <title>loopback_input&#45;&gt;ip6_input</title>
-<path fill="none" stroke="black" d="M194.75,-392.76C201.26,-396.56 208.12,-400.78 214.28,-405 239.36,-422.15 266.23,-443.92 285.22,-459.94"/>
-<polygon fill="black" stroke="black" points="282.83,-462.5 292.72,-466.31 287.37,-457.17 282.83,-462.5"/>
+<path fill="none" stroke="black" d="M188.77,-392.82C195.01,-396.62 201.59,-400.82 207.5,-405 232.44,-422.62 259.25,-444.9 277.85,-460.96"/>
+<polygon fill="black" stroke="black" points="275.7,-463.72 285.54,-467.64 280.29,-458.44 275.7,-463.72"/>
 </g>
 <!-- eth_output -->
 <g id="node13" class="node">
 <title>eth_output</title>
-<ellipse fill="none" stroke="black" cx="1019.1" cy="-635" rx="34.89" ry="18"/>
-<text text-anchor="middle" x="1019.1" y="-631.3" font-family="sans" font-size="11.00">eth_output</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-635" rx="33.8" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-632.2" font-family="sans" font-size="11.00">eth_output</text>
 </g>
 <!-- arp_output_request&#45;&gt;eth_output -->
 <g id="edge21" class="edge">
 <title>arp_output_request&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M361.25,-661.09C414.65,-672.31 503.15,-688 580.35,-688 580.35,-688 580.35,-688 718.02,-688 813.16,-688 838.16,-686.98 930.95,-666 947.72,-662.21 965.7,-656.17 980.94,-650.43"/>
-<polygon fill="black" stroke="black" points="982.04,-653.76 990.1,-646.88 979.51,-647.23 982.04,-653.76"/>
+<path fill="none" stroke="black" d="M349.94,-660.89C401.8,-672.12 488.51,-688 564.15,-688 564.15,-688 564.15,-688 698.21,-688 790.75,-688 815.09,-686.82 905.25,-666 922.16,-662.1 940.31,-655.83 955.51,-649.95"/>
+<polygon fill="black" stroke="black" points="957.03,-653.11 965.04,-646.17 954.45,-646.61 957.03,-653.11"/>
 </g>
 <!-- icmp_output -->
 <g id="node21" class="node">
 <title>icmp_output</title>
-<ellipse fill="none" stroke="black" cx="717.02" cy="-160" rx="39.52" ry="18"/>
-<text text-anchor="middle" x="717.02" y="-156.3" font-family="sans" font-size="11.00">icmp_output</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-160" rx="37.98" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-157.2" font-family="sans" font-size="11.00">icmp_output</text>
 </g>
 <!-- icmp_local_send&#45;&gt;icmp_output -->
 <g id="edge24" class="edge">
 <title>icmp_local_send&#45;&gt;icmp_output</title>
-<path fill="none" stroke="black" d="M337.75,-87.22C356.57,-76.33 383.43,-62.81 409.11,-57 449.58,-47.84 462.02,-47.76 502.47,-57 571.46,-72.77 644.11,-113.81 684.38,-139.07"/>
-<polygon fill="black" stroke="black" points="682.31,-141.9 692.62,-144.32 686.06,-136 682.31,-141.9"/>
+<path fill="none" stroke="black" d="M327.68,-87.45C346.06,-76.53 372.47,-62.86 397.81,-57 436.82,-47.98 448.91,-47.89 487.9,-57 555.92,-72.9 627.2,-114.4 666.24,-139.6"/>
+<polygon fill="black" stroke="black" points="664.7,-142.78 674.98,-145.33 668.53,-136.92 664.7,-142.78"/>
 </g>
 <!-- ndp_ns_output&#45;&gt;ip6_output -->
 <g id="edge55" class="edge">
 <title>ndp_ns_output&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M213.26,-432.78C340.34,-434.95 698.04,-441.07 824.44,-443.23"/>
-<polygon fill="black" stroke="black" points="824.37,-446.73 834.42,-443.4 824.49,-439.73 824.37,-446.73"/>
+<path fill="none" stroke="black" d="M206.4,-432.76C329.97,-434.94 682.12,-441.13 803.73,-443.26"/>
+<polygon fill="black" stroke="black" points="803.7,-446.76 813.76,-443.44 803.83,-439.76 803.7,-446.76"/>
 </g>
 <!-- ip_output&#45;&gt;eth_output -->
 <g id="edge34" class="edge">
 <title>ip_output&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M882.49,-130.67C896.09,-151.89 918.81,-190.12 930.95,-226 962.43,-319.02 946.23,-348.01 966.95,-444 979.47,-501.98 998.4,-568.71 1009.37,-605.96"/>
-<polygon fill="black" stroke="black" points="1006.01,-606.93 1012.21,-615.52 1012.72,-604.94 1006.01,-606.93"/>
+<path fill="none" stroke="black" d="M858.37,-130.73C871.5,-152.01 893.45,-190.29 905.25,-226 936.09,-319.23 920.92,-347.93 941.25,-444 953.65,-502.56 972.17,-570.17 982.69,-607.19"/>
+<polygon fill="black" stroke="black" points="979.44,-608.57 985.55,-617.22 986.17,-606.64 979.44,-608.57"/>
 </g>
 <!-- loopback_output -->
 <g id="node15" class="node">
 <title>loopback_output</title>
-<ellipse fill="none" stroke="black" cx="1019.1" cy="-255" rx="52.15" ry="18"/>
-<text text-anchor="middle" x="1019.1" y="-251.3" font-family="sans" font-size="11.00">loopback_output</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-255" rx="50.45" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-252.2" font-family="sans" font-size="11.00">loopback_output</text>
 </g>
 <!-- ip_output&#45;&gt;loopback_output -->
 <g id="edge36" class="edge">
 <title>ip_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M887.36,-129.36C912.62,-153.63 961.99,-201.08 992.31,-230.21"/>
-<polygon fill="black" stroke="black" points="989.59,-232.46 999.23,-236.86 994.45,-227.41 989.59,-232.46"/>
+<path fill="none" stroke="black" d="M862.95,-129.09C887.57,-153.35 936.19,-201.26 965.84,-230.48"/>
+<polygon fill="black" stroke="black" points="963.73,-233.31 973.31,-237.83 968.64,-228.32 963.73,-233.31"/>
 </g>
 <!-- ip_hold -->
 <g id="node23" class="node">
 <title>ip_hold</title>
-<ellipse fill="none" stroke="black" cx="1019.1" cy="-160" rx="27" ry="18"/>
-<text text-anchor="middle" x="1019.1" y="-156.3" font-family="sans" font-size="11.00">ip_hold</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-160" rx="27" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-157.2" font-family="sans" font-size="11.00">ip_hold</text>
 </g>
 <!-- ip_output&#45;&gt;ip_hold -->
 <g id="edge35" class="edge">
 <title>ip_output&#45;&gt;ip_hold</title>
-<path fill="none" stroke="black" d="M897.61,-122.22C921.56,-129.73 957.12,-140.88 983.41,-149.12"/>
-<polygon fill="black" stroke="black" points="982.01,-152.35 992.6,-152 984.11,-145.67 982.01,-152.35"/>
+<path fill="none" stroke="black" d="M872.89,-122.12C896.41,-129.68 931.61,-141 957.42,-149.29"/>
+<polygon fill="black" stroke="black" points="956.56,-152.69 967.15,-152.42 958.7,-146.03 956.56,-152.69"/>
 </g>
 <!-- ipip_output -->
 <g id="node27" class="node">
 <title>ipip_output</title>
-<ellipse fill="none" stroke="black" cx="1019.1" cy="-106" rx="35.73" ry="18"/>
-<text text-anchor="middle" x="1019.1" y="-102.3" font-family="sans" font-size="11.00">ipip_output</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-106" rx="34.38" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-103.2" font-family="sans" font-size="11.00">ipip_output</text>
 </g>
 <!-- ip_output&#45;&gt;ipip_output -->
 <g id="edge37" class="edge">
 <title>ip_output&#45;&gt;ipip_output</title>
-<path fill="none" stroke="black" d="M898.63,-107.13C919.29,-104.57 948.14,-102.69 972.32,-102.19"/>
-<polygon fill="black" stroke="black" points="972.28,-105.69 982.24,-102.07 972.2,-98.69 972.28,-105.69"/>
+<path fill="none" stroke="black" d="M873.89,-107.17C894.55,-104.53 923.77,-102.6 947.83,-102.15"/>
+<polygon fill="black" stroke="black" points="948.15,-105.65 958.12,-102.05 948.09,-98.65 948.15,-105.65"/>
 </g>
 <!-- ip6_output&#45;&gt;eth_output -->
 <g id="edge49" class="edge">
 <title>ip6_output&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M895.31,-456.62C907.02,-463.61 920.75,-473.08 930.95,-484 965.52,-521 992.12,-574.12 1006.41,-606.49"/>
-<polygon fill="black" stroke="black" points="1003.18,-607.83 1010.35,-615.63 1009.61,-605.06 1003.18,-607.83"/>
+<path fill="none" stroke="black" d="M870.52,-456.56C881.91,-463.58 895.33,-473.11 905.25,-484 939.66,-521.72 965.97,-575.67 979.83,-607.83"/>
+<polygon fill="black" stroke="black" points="976.61,-609.22 983.72,-617.07 983.06,-606.5 976.61,-609.22"/>
 </g>
 <!-- ip6_output&#45;&gt;loopback_output -->
 <g id="edge51" class="edge">
 <title>ip6_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M884.4,-427.23C909.98,-394.27 966.97,-320.86 997.64,-281.36"/>
-<polygon fill="black" stroke="black" points="1000.18,-283.8 1003.55,-273.75 994.65,-279.5 1000.18,-283.8"/>
+<path fill="none" stroke="black" d="M860.09,-427.56C885.06,-394.58 941.41,-320.14 971.29,-280.68"/>
+<polygon fill="black" stroke="black" points="974.34,-282.44 977.59,-272.36 968.76,-278.22 974.34,-282.44"/>
 </g>
 <!-- ip6_hold -->
 <g id="node33" class="node">
 <title>ip6_hold</title>
-<ellipse fill="none" stroke="black" cx="1019.1" cy="-417" rx="27" ry="18"/>
-<text text-anchor="middle" x="1019.1" y="-413.3" font-family="sans" font-size="11.00">ip6_hold</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-417" rx="27" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-414.2" font-family="sans" font-size="11.00">ip6_hold</text>
 </g>
 <!-- ip6_output&#45;&gt;ip6_hold -->
 <g id="edge50" class="edge">
 <title>ip6_output&#45;&gt;ip6_hold</title>
-<path fill="none" stroke="black" d="M902.86,-438.21C925.87,-433.97 957.15,-428.22 981.31,-423.77"/>
-<polygon fill="black" stroke="black" points="981.85,-427.23 991.05,-421.98 980.58,-420.35 981.85,-427.23"/>
+<path fill="none" stroke="black" d="M877.64,-438.34C900.34,-434.06 931.58,-428.16 955.47,-423.65"/>
+<polygon fill="black" stroke="black" points="956.37,-427.05 965.55,-421.75 955.07,-420.17 956.37,-427.05"/>
 </g>
 <!-- control_output -->
 <g id="node8" class="node">
 <title>control_output</title>
-<ellipse fill="none" stroke="black" cx="1153.51" cy="-324" rx="46.26" ry="18"/>
-<text text-anchor="middle" x="1153.51" y="-320.3" font-family="sans" font-size="11.00">control_output</text>
+<ellipse fill="none" stroke="black" cx="1122.7" cy="-324" rx="44.51" ry="18"/>
+<text text-anchor="middle" x="1122.7" y="-321.2" font-family="sans" font-size="11.00">control_output</text>
 </g>
 <!-- eth_input -->
 <g id="node9" class="node">
 <title>eth_input</title>
-<ellipse fill="none" stroke="black" cx="166.76" cy="-486" rx="30.25" ry="18"/>
-<text text-anchor="middle" x="166.76" y="-482.3" font-family="sans" font-size="11.00">eth_input</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-486" rx="29.12" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-483.2" font-family="sans" font-size="11.00">eth_input</text>
 </g>
 <!-- arp_input -->
 <g id="node10" class="node">
 <title>arp_input</title>
-<ellipse fill="none" stroke="black" cx="581.35" cy="-604" rx="30.25" ry="18"/>
-<text text-anchor="middle" x="581.35" y="-600.3" font-family="sans" font-size="11.00">arp_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-604" rx="29.12" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-601.2" font-family="sans" font-size="11.00">arp_input</text>
 </g>
 <!-- eth_input&#45;&gt;arp_input -->
 <g id="edge7" class="edge">
 <title>eth_input&#45;&gt;arp_input</title>
-<path fill="none" stroke="black" d="M194.52,-493.65C265.81,-514.04 460.5,-569.72 542.82,-593.26"/>
-<polygon fill="black" stroke="black" points="541.63,-596.57 552.21,-595.95 543.55,-589.83 541.63,-596.57"/>
+<path fill="none" stroke="black" d="M188.62,-493.56C257.9,-513.93 448.59,-570.01 528.35,-593.47"/>
+<polygon fill="black" stroke="black" points="527.82,-596.96 538.4,-596.43 529.79,-590.25 527.82,-596.96"/>
 </g>
 <!-- eth_input&#45;&gt;ip_input -->
 <g id="edge8" class="edge">
 <title>eth_input&#45;&gt;ip_input</title>
-<path fill="none" stroke="black" d="M192.35,-475.87C200.23,-471.63 208.42,-466.03 214.28,-459 274.66,-386.6 298.58,-273.4 306.86,-220.32"/>
-<polygon fill="black" stroke="black" points="310.31,-220.96 308.3,-210.56 303.38,-219.94 310.31,-220.96"/>
+<path fill="none" stroke="black" d="M186.51,-475.71C194.06,-471.45 201.91,-465.88 207.5,-459 267.15,-385.56 290.27,-271.67 298.12,-219.21"/>
+<polygon fill="black" stroke="black" points="301.63,-219.38 299.57,-208.99 294.7,-218.4 301.63,-219.38"/>
 </g>
 <!-- eth_input&#45;&gt;ip6_input -->
 <g id="edge9" class="edge">
 <title>eth_input&#45;&gt;ip6_input</title>
-<path fill="none" stroke="black" d="M197.38,-485.17C218.65,-484.58 247.54,-483.77 270.84,-483.12"/>
-<polygon fill="black" stroke="black" points="270.69,-486.62 280.58,-482.84 270.49,-479.62 270.69,-486.62"/>
+<path fill="none" stroke="black" d="M191.32,-485.18C212.36,-484.57 241.23,-483.74 264.16,-483.08"/>
+<polygon fill="black" stroke="black" points="264.33,-486.58 274.23,-482.79 264.13,-479.58 264.33,-486.58"/>
 </g>
 <!-- arp_input_request -->
 <g id="node17" class="node">
 <title>arp_input_request</title>
-<ellipse fill="none" stroke="black" cx="717.02" cy="-642" rx="56.78" ry="18"/>
-<text text-anchor="middle" x="717.02" y="-638.3" font-family="sans" font-size="11.00">arp_input_request</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-642" rx="54.63" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-639.2" font-family="sans" font-size="11.00">arp_input_request</text>
 </g>
 <!-- arp_input&#45;&gt;arp_input_request -->
 <g id="edge15" class="edge">
 <title>arp_input&#45;&gt;arp_input_request</title>
-<path fill="none" stroke="black" d="M609.08,-611.6C624.49,-615.98 644.4,-621.64 662.86,-626.89"/>
-<polygon fill="black" stroke="black" points="661.78,-630.22 672.36,-629.59 663.7,-623.49 661.78,-630.22"/>
+<path fill="none" stroke="black" d="M591.85,-611.51C607.28,-616.02 627.46,-621.91 645.95,-627.31"/>
+<polygon fill="black" stroke="black" points="645.14,-630.72 655.72,-630.17 647.1,-624.01 645.14,-630.72"/>
 </g>
 <!-- arp_input_reply -->
 <g id="node18" class="node">
 <title>arp_input_reply</title>
-<ellipse fill="none" stroke="black" cx="870.38" cy="-511" rx="48.78" ry="18"/>
-<text text-anchor="middle" x="870.38" y="-507.3" font-family="sans" font-size="11.00">arp_input_reply</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-511" rx="47.43" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-508.2" font-family="sans" font-size="11.00">arp_input_reply</text>
 </g>
 <!-- arp_input&#45;&gt;arp_input_reply -->
 <g id="edge16" class="edge">
 <title>arp_input&#45;&gt;arp_input_reply</title>
-<path fill="none" stroke="black" d="M611.85,-602.73C650.3,-600.26 719.08,-593.04 773.81,-572 791.53,-565.19 793.8,-559.22 809.81,-549 818.06,-543.73 826.95,-538.06 835.29,-532.74"/>
-<polygon fill="black" stroke="black" points="836.92,-535.85 843.46,-527.52 833.15,-529.95 836.92,-535.85"/>
+<path fill="none" stroke="black" d="M594.5,-602.68C631.8,-600.16 698.84,-592.88 752.03,-572 753.76,-571.32 787.13,-549.49 813.54,-532.13"/>
+<polygon fill="black" stroke="black" points="815.74,-534.88 822.17,-526.46 811.89,-529.03 815.74,-534.88"/>
 </g>
 <!-- ip_input&#45;&gt;ip_output -->
 <g id="edge29" class="edge">
 <title>ip_input&#45;&gt;ip_output</title>
-<path fill="none" stroke="black" d="M337.23,-184.23C356.69,-178.9 384.61,-171.33 409.11,-165 466.45,-150.18 479.91,-141.85 538.47,-133 642.17,-117.33 765.93,-114.34 828.64,-113.92"/>
-<polygon fill="black" stroke="black" points="828.48,-117.42 838.46,-113.88 828.45,-110.42 828.48,-117.42"/>
+<path fill="none" stroke="black" d="M328,-184.13C346.99,-178.8 374.04,-171.3 397.81,-165 453.7,-150.19 466.77,-141.86 523.9,-133 625.1,-117.32 746.04,-114.35 806.77,-113.93"/>
+<polygon fill="black" stroke="black" points="807.06,-117.43 817.04,-113.88 807.03,-110.43 807.06,-117.43"/>
 </g>
 <!-- ip_forward -->
 <g id="node22" class="node">
 <title>ip_forward</title>
-<ellipse fill="none" stroke="black" cx="455.79" cy="-84" rx="33.62" ry="18"/>
-<text text-anchor="middle" x="455.79" y="-80.3" font-family="sans" font-size="11.00">ip_forward</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-84" rx="32.63" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-81.2" font-family="sans" font-size="11.00">ip_forward</text>
 </g>
 <!-- ip_input&#45;&gt;ip_forward -->
 <g id="edge28" class="edge">
 <title>ip_input&#45;&gt;ip_forward</title>
-<path fill="none" stroke="black" d="M329.92,-177.19C349.05,-161.94 380.97,-136.79 409.11,-116 414.18,-112.26 419.61,-108.36 424.91,-104.61"/>
-<polygon fill="black" stroke="black" points="426.75,-107.6 432.93,-98.99 422.73,-101.86 426.75,-107.6"/>
+<path fill="none" stroke="black" d="M320.49,-177.22C339.19,-162 370.37,-136.88 397.81,-116 402.95,-112.09 408.47,-108 413.82,-104.09"/>
+<polygon fill="black" stroke="black" points="415.95,-106.86 421.99,-98.15 411.84,-101.2 415.95,-106.86"/>
 </g>
 <!-- ip_input_local -->
 <g id="node24" class="node">
 <title>ip_input_local</title>
-<ellipse fill="none" stroke="black" cx="455.79" cy="-214" rx="42.89" ry="18"/>
-<text text-anchor="middle" x="455.79" y="-210.3" font-family="sans" font-size="11.00">ip_input_local</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-214" rx="41.49" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-211.2" font-family="sans" font-size="11.00">ip_input_local</text>
 </g>
 <!-- ip_input&#45;&gt;ip_input_local -->
 <g id="edge30" class="edge">
 <title>ip_input&#45;&gt;ip_input_local</title>
-<path fill="none" stroke="black" d="M338.43,-195.17C356.68,-198.12 381.78,-202.18 404,-205.78"/>
-<polygon fill="black" stroke="black" points="403.38,-209.22 413.81,-207.37 404.5,-202.31 403.38,-209.22"/>
+<path fill="none" stroke="black" d="M329,-195.22C347.17,-198.24 372.18,-202.4 394.05,-206.04"/>
+<polygon fill="black" stroke="black" points="393.72,-209.54 404.16,-207.73 394.87,-202.63 393.72,-209.54"/>
 </g>
 <!-- ip6_input&#45;&gt;ip6_output -->
 <g id="edge45" class="edge">
 <title>ip6_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M341.37,-481.44C413.93,-479.82 610.67,-474.16 773.81,-458 791.16,-456.28 810.19,-453.65 826.67,-451.13"/>
-<polygon fill="black" stroke="black" points="826.78,-454.65 836.12,-449.65 825.7,-447.74 826.78,-454.65"/>
+<path fill="none" stroke="black" d="M331.02,-481.44C401.16,-479.81 593.04,-474.11 752.03,-458 769.63,-456.22 788.98,-453.46 805.5,-450.86"/>
+<polygon fill="black" stroke="black" points="806.06,-454.31 815.38,-449.27 804.95,-447.4 806.06,-454.31"/>
 </g>
 <!-- ip6_forward -->
 <g id="node32" class="node">
 <title>ip6_forward</title>
-<ellipse fill="none" stroke="black" cx="455.79" cy="-520" rx="37.41" ry="18"/>
-<text text-anchor="middle" x="455.79" y="-516.3" font-family="sans" font-size="11.00">ip6_forward</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-520" rx="35.64" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-517.2" font-family="sans" font-size="11.00">ip6_forward</text>
 </g>
 <!-- ip6_input&#45;&gt;ip6_forward -->
 <g id="edge44" class="edge">
 <title>ip6_input&#45;&gt;ip6_forward</title>
-<path fill="none" stroke="black" d="M339.09,-489.06C359.46,-494.51 388.07,-502.16 411.74,-508.49"/>
-<polygon fill="black" stroke="black" points="410.53,-511.79 421.1,-510.99 412.34,-505.02 410.53,-511.79"/>
+<path fill="none" stroke="black" d="M329,-488.97C349.19,-494.52 377.83,-502.39 401.21,-508.82"/>
+<polygon fill="black" stroke="black" points="400.29,-512.2 410.86,-511.48 402.15,-505.45 400.29,-512.2"/>
 </g>
 <!-- ip6_input_local -->
 <g id="node34" class="node">
 <title>ip6_input_local</title>
-<ellipse fill="none" stroke="black" cx="455.79" cy="-344" rx="46.68" ry="18"/>
-<text text-anchor="middle" x="455.79" y="-340.3" font-family="sans" font-size="11.00">ip6_input_local</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-344" rx="45.09" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-341.2" font-family="sans" font-size="11.00">ip6_input_local</text>
 </g>
 <!-- ip6_input&#45;&gt;ip6_input_local -->
 <g id="edge46" class="edge">
 <title>ip6_input&#45;&gt;ip6_input_local</title>
-<path fill="none" stroke="black" d="M328.45,-466.7C352.91,-442.94 400.27,-396.95 429.55,-368.52"/>
-<polygon fill="black" stroke="black" points="431.83,-371.18 436.56,-361.7 426.95,-366.16 431.83,-371.18"/>
+<path fill="none" stroke="black" d="M318.71,-466.97C342.6,-443.11 389.4,-396.38 417.93,-367.89"/>
+<polygon fill="black" stroke="black" points="420.51,-370.27 425.11,-360.73 415.56,-365.31 420.51,-370.27"/>
 </g>
 <!-- port_tx -->
 <g id="node14" class="node">
 <title>port_tx</title>
-<ellipse fill="none" stroke="black" cx="1153.51" cy="-635" rx="27" ry="18"/>
-<text text-anchor="middle" x="1153.51" y="-631.3" font-family="sans" font-size="11.00">port_tx</text>
+<ellipse fill="none" stroke="black" cx="1122.7" cy="-635" rx="27" ry="18"/>
+<text text-anchor="middle" x="1122.7" y="-632.2" font-family="sans" font-size="11.00">port_tx</text>
 </g>
 <!-- eth_output&#45;&gt;port_tx -->
 <g id="edge10" class="edge">
 <title>eth_output&#45;&gt;port_tx</title>
-<path fill="none" stroke="black" d="M1054.2,-635C1072.73,-635 1095.69,-635 1114.75,-635"/>
-<polygon fill="black" stroke="black" points="1114.54,-638.5 1124.54,-635 1114.54,-631.5 1114.54,-638.5"/>
+<path fill="none" stroke="black" d="M1025.61,-635C1043.79,-635 1066.41,-635 1085.15,-635"/>
+<polygon fill="black" stroke="black" points="1085.38,-638.5 1095.38,-635 1085.38,-631.5 1085.38,-638.5"/>
 </g>
 <!-- loopback_output&#45;&gt;control_output -->
 <g id="edge13" class="edge">
 <title>loopback_output&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1049.16,-270.14C1068.44,-280.19 1093.93,-293.47 1114.77,-304.33"/>
-<polygon fill="black" stroke="black" points="1113.05,-307.38 1123.54,-308.9 1116.29,-301.18 1113.05,-307.38"/>
+<path fill="none" stroke="black" d="M1020.71,-269.97C1039.86,-280.21 1065.41,-293.88 1086.01,-304.91"/>
+<polygon fill="black" stroke="black" points="1084.63,-308.13 1095.09,-309.76 1087.93,-301.96 1084.63,-308.13"/>
 </g>
 <!-- port_rx -->
 <g id="node16" class="node">
 <title>port_rx</title>
-<ellipse fill="none" stroke="blue" stroke-width="2" cx="41.62" cy="-486" rx="27" ry="18"/>
-<text text-anchor="middle" x="41.62" y="-482.3" font-family="sans" font-size="11.00">port_rx</text>
+<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-486" rx="27" ry="18"/>
+<text text-anchor="middle" x="40.16" y="-483.2" font-family="sans" font-size="11.00">port_rx</text>
 </g>
 <!-- port_rx&#45;&gt;eth_input -->
 <g id="edge14" class="edge">
 <title>port_rx&#45;&gt;eth_input</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M69.04,-486C85.35,-486 106.61,-486 125.04,-486"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="123.34,-489.5 133.34,-486 123.34,-482.5 123.34,-489.5"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M67.44,-486C83.56,-486 104.47,-486 122.43,-486"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="122.6,-489.5 132.6,-486 122.6,-482.5 122.6,-489.5"/>
 </g>
 <!-- arp_input_request&#45;&gt;control_output -->
 <g id="edge18" class="edge">
 <title>arp_input_request&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M755.85,-628.49C836.67,-599.07 1022.79,-528.12 1071.26,-482 1109.3,-445.8 1132.68,-387.87 1144.07,-353.12"/>
-<polygon fill="black" stroke="black" points="1147.37,-354.32 1147.03,-343.73 1140.69,-352.22 1147.37,-354.32"/>
+<path fill="none" stroke="black" d="M734.18,-628.62C812.45,-598.98 994.98,-526.78 1042.2,-481 1079.97,-444.38 1102.9,-386 1113.86,-351.69"/>
+<polygon fill="black" stroke="black" points="1117.29,-352.46 1116.89,-341.87 1110.6,-350.4 1117.29,-352.46"/>
 </g>
 <!-- arp_output_reply -->
 <g id="node19" class="node">
 <title>arp_output_reply</title>
-<ellipse fill="none" stroke="black" cx="870.38" cy="-639" rx="53.41" ry="18"/>
-<text text-anchor="middle" x="870.38" y="-635.3" font-family="sans" font-size="11.00">arp_output_reply</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-639" rx="51.62" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-636.2" font-family="sans" font-size="11.00">arp_output_reply</text>
 </g>
 <!-- arp_input_request&#45;&gt;arp_output_reply -->
 <g id="edge19" class="edge">
 <title>arp_input_request&#45;&gt;arp_output_reply</title>
-<path fill="none" stroke="black" d="M773.95,-640.89C784.22,-640.69 795.01,-640.47 805.48,-640.27"/>
-<polygon fill="black" stroke="black" points="805.25,-643.77 815.18,-640.07 805.11,-636.77 805.25,-643.77"/>
+<path fill="none" stroke="black" d="M752.27,-640.9C762.86,-640.68 774.03,-640.46 784.82,-640.24"/>
+<polygon fill="black" stroke="black" points="785.09,-643.73 795.02,-640.03 784.95,-636.73 785.09,-643.73"/>
 </g>
 <!-- arp_input_reply&#45;&gt;control_output -->
 <g id="edge17" class="edge">
 <title>arp_input_reply&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M916.17,-504.51C959.45,-496.41 1025.06,-479.08 1071.26,-444 1103.13,-419.79 1126.77,-379.33 1140.1,-352.17"/>
-<polygon fill="black" stroke="black" points="1143.25,-353.68 1144.37,-343.14 1136.93,-350.68 1143.25,-353.68"/>
+<path fill="none" stroke="black" d="M890.92,-504.41C933.11,-496.19 997.28,-478.77 1042.2,-444 1074.06,-419.34 1097.32,-378.16 1110.18,-351.03"/>
+<polygon fill="black" stroke="black" points="1113.46,-352.28 1114.44,-341.74 1107.09,-349.37 1113.46,-352.28"/>
 </g>
 <!-- arp_output_reply&#45;&gt;eth_output -->
 <g id="edge20" class="edge">
 <title>arp_output_reply&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M923.94,-637.57C939.92,-637.13 957.35,-636.66 972.82,-636.23"/>
-<polygon fill="black" stroke="black" points="972.79,-639.74 982.69,-635.97 972.6,-632.74 972.79,-639.74"/>
+<path fill="none" stroke="black" d="M898.09,-637.59C914.17,-637.14 931.81,-636.65 947.35,-636.21"/>
+<polygon fill="black" stroke="black" points="947.82,-639.7 957.72,-635.92 947.63,-632.7 947.82,-639.7"/>
 </g>
 <!-- icmp_input -->
 <g id="node20" class="node">
 <title>icmp_input</title>
-<ellipse fill="none" stroke="black" cx="581.35" cy="-214" rx="34.89" ry="18"/>
-<text text-anchor="middle" x="581.35" y="-210.3" font-family="sans" font-size="11.00">icmp_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-214" rx="33.8" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-211.2" font-family="sans" font-size="11.00">icmp_input</text>
 </g>
 <!-- icmp_input&#45;&gt;control_output -->
 <g id="edge23" class="edge">
 <title>icmp_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M616.26,-211.45C713.4,-204.69 990.91,-189.34 1071.26,-228 1101.11,-242.37 1124.13,-273.44 1137.92,-296.36"/>
-<polygon fill="black" stroke="black" points="1134.82,-297.99 1142.84,-304.91 1140.89,-294.5 1134.82,-297.99"/>
+<path fill="none" stroke="black" d="M598.51,-211.55C692.54,-204.96 964,-189.81 1042.2,-228 1072.11,-242.61 1094.78,-274.31 1108.11,-297.3"/>
+<polygon fill="black" stroke="black" points="1105.16,-299.2 1113.08,-306.24 1111.28,-295.8 1105.16,-299.2"/>
 </g>
 <!-- icmp_input&#45;&gt;icmp_output -->
 <g id="edge22" class="edge">
 <title>icmp_input&#45;&gt;icmp_output</title>
-<path fill="none" stroke="black" d="M609.4,-203.07C628.59,-195.32 654.66,-184.79 676.21,-176.08"/>
-<polygon fill="black" stroke="black" points="677.33,-179.41 685.29,-172.42 674.71,-172.92 677.33,-179.41"/>
+<path fill="none" stroke="black" d="M592.16,-203.2C611.19,-195.3 637.29,-184.46 658.59,-175.62"/>
+<polygon fill="black" stroke="black" points="660.1,-178.78 668,-171.72 657.42,-172.32 660.1,-178.78"/>
 </g>
 <!-- icmp_output&#45;&gt;ip_output -->
 <g id="edge25" class="edge">
 <title>icmp_output&#45;&gt;ip_output</title>
-<path fill="none" stroke="black" d="M750.49,-150.13C774.45,-142.85 807.09,-132.93 832.11,-125.33"/>
-<polygon fill="black" stroke="black" points="833.01,-128.71 841.56,-122.46 830.97,-122.01 833.01,-128.71"/>
+<path fill="none" stroke="black" d="M729.48,-150.24C753.25,-142.83 785.96,-132.62 810.61,-124.93"/>
+<polygon fill="black" stroke="black" points="811.79,-128.23 820.3,-121.91 809.71,-121.55 811.79,-128.23"/>
 </g>
 <!-- ip_forward&#45;&gt;ip_output -->
 <g id="edge26" class="edge">
 <title>ip_forward&#45;&gt;ip_output</title>
-<path fill="none" stroke="black" d="M489.73,-84.63C547.41,-85.97 670.43,-89.84 773.81,-100 792.25,-101.81 812.58,-104.67 829.73,-107.33"/>
-<polygon fill="black" stroke="black" points="829.11,-110.78 839.53,-108.89 830.21,-103.86 829.11,-110.78"/>
+<path fill="none" stroke="black" d="M475.43,-84.63C531.32,-85.98 651.28,-89.88 752.03,-100 770.58,-101.86 791.07,-104.83 808.14,-107.55"/>
+<polygon fill="black" stroke="black" points="807.85,-111.04 818.28,-109.2 808.98,-104.14 807.85,-111.04"/>
 </g>
 <!-- ip_hold&#45;&gt;control_output -->
 <g id="edge27" class="edge">
 <title>ip_hold&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1041.59,-170.51C1051.39,-176 1062.69,-183.38 1071.26,-192 1101.85,-222.82 1126.16,-267.24 1139.96,-295.86"/>
-<polygon fill="black" stroke="black" points="1136.65,-297.07 1144.07,-304.63 1142.99,-294.09 1136.65,-297.07"/>
+<path fill="none" stroke="black" d="M1014.02,-170.88C1023.39,-176.36 1034.09,-183.62 1042.2,-192 1072.5,-223.29 1096.42,-268.17 1109.81,-296.69"/>
+<polygon fill="black" stroke="black" points="1106.76,-298.42 1114.11,-306.06 1113.12,-295.51 1106.76,-298.42"/>
 </g>
 <!-- ip_input_local&#45;&gt;icmp_input -->
 <g id="edge31" class="edge">
 <title>ip_input_local&#45;&gt;icmp_input</title>
-<path fill="none" stroke="black" d="M498.97,-214C510.5,-214 523.06,-214 534.77,-214"/>
-<polygon fill="black" stroke="black" points="534.63,-217.5 544.63,-214 534.63,-210.5 534.63,-217.5"/>
+<path fill="none" stroke="black" d="M484.25,-214C496.07,-214 509.05,-214 521.03,-214"/>
+<polygon fill="black" stroke="black" points="521.26,-217.5 531.26,-214 521.26,-210.5 521.26,-217.5"/>
 </g>
 <!-- ipip_input -->
 <g id="node25" class="node">
 <title>ipip_input</title>
-<ellipse fill="none" stroke="black" cx="581.35" cy="-160" rx="31.1" ry="18"/>
-<text text-anchor="middle" x="581.35" y="-156.3" font-family="sans" font-size="11.00">ipip_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-160" rx="29.7" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-157.2" font-family="sans" font-size="11.00">ipip_input</text>
 </g>
 <!-- ip_input_local&#45;&gt;ipip_input -->
 <g id="edge32" class="edge">
 <title>ip_input_local&#45;&gt;ipip_input</title>
-<path fill="none" stroke="black" d="M486.39,-201.07C504.17,-193.29 526.9,-183.36 545.54,-175.21"/>
-<polygon fill="black" stroke="black" points="546.83,-178.47 554.59,-171.26 544.03,-172.06 546.83,-178.47"/>
+<path fill="none" stroke="black" d="M472.37,-201.2C490.12,-193.24 513,-182.96 531.48,-174.66"/>
+<polygon fill="black" stroke="black" points="533,-177.82 540.69,-170.53 530.14,-171.43 533,-177.82"/>
 </g>
 <!-- l4_input_local -->
 <g id="node26" class="node">
 <title>l4_input_local</title>
-<ellipse fill="none" stroke="black" cx="581.35" cy="-268" rx="42.89" ry="18"/>
-<text text-anchor="middle" x="581.35" y="-264.3" font-family="sans" font-size="11.00">l4_input_local</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-268" rx="41.49" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-265.2" font-family="sans" font-size="11.00">l4_input_local</text>
 </g>
 <!-- ip_input_local&#45;&gt;l4_input_local -->
 <g id="edge33" class="edge">
 <title>ip_input_local&#45;&gt;l4_input_local</title>
-<path fill="none" stroke="black" d="M486.39,-226.93C502.47,-233.96 522.59,-242.76 540.08,-250.4"/>
-<polygon fill="black" stroke="black" points="538.62,-253.58 549.18,-254.38 541.42,-247.17 538.62,-253.58"/>
+<path fill="none" stroke="black" d="M472.37,-226.8C488.55,-234.06 508.99,-243.24 526.48,-251.09"/>
+<polygon fill="black" stroke="black" points="525.29,-254.39 535.85,-255.29 528.16,-248.01 525.29,-254.39"/>
 </g>
 <!-- ipip_input&#45;&gt;ip_input -->
 <g id="edge56" class="edge">
 <title>ipip_input&#45;&gt;ip_input</title>
-<path fill="none" stroke="black" d="M550.73,-163.43C501.59,-169.12 403.35,-180.5 349.73,-186.71"/>
-<polygon fill="black" stroke="black" points="349.4,-183.23 339.87,-187.85 350.2,-190.18 349.4,-183.23"/>
+<path fill="none" stroke="black" d="M535.57,-163.4C487.85,-169.08 392.05,-180.48 339.75,-186.71"/>
+<polygon fill="black" stroke="black" points="339.1,-183.26 329.59,-187.91 339.93,-190.21 339.1,-183.26"/>
 </g>
 <!-- l4_loopback_output -->
 <g id="node35" class="node">
 <title>l4_loopback_output</title>
-<ellipse fill="none" stroke="black" cx="870.38" cy="-253" rx="60.57" ry="18"/>
-<text text-anchor="middle" x="870.38" y="-249.3" font-family="sans" font-size="11.00">l4_loopback_output</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-253" rx="58.73" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-250.2" font-family="sans" font-size="11.00">l4_loopback_output</text>
 </g>
 <!-- l4_input_local&#45;&gt;l4_loopback_output -->
 <g id="edge58" class="edge">
 <title>l4_input_local&#45;&gt;l4_loopback_output</title>
-<path fill="none" stroke="black" d="M623.98,-265.2C635.71,-264.45 648.47,-263.66 660.24,-263 706.42,-260.41 758.36,-257.93 798.93,-256.08"/>
-<polygon fill="black" stroke="black" points="798.98,-259.58 808.81,-255.63 798.66,-252.59 798.98,-259.58"/>
+<path fill="none" stroke="black" d="M606.11,-265.26C617.8,-264.49 630.61,-263.68 642.4,-263 687.66,-260.4 738.62,-257.9 778.18,-256.04"/>
+<polygon fill="black" stroke="black" points="778.42,-259.54 788.25,-255.57 778.09,-252.54 778.42,-259.54"/>
 </g>
 <!-- ipip_output&#45;&gt;ip_output -->
 <g id="edge57" class="edge">
 <title>ipip_output&#45;&gt;ip_output</title>
-<path fill="none" stroke="black" d="M986.12,-113.43C964.27,-115.88 935.14,-117.61 911.7,-117.9"/>
-<polygon fill="black" stroke="black" points="911.88,-114.4 901.88,-117.92 911.89,-121.4 911.88,-114.4"/>
+<path fill="none" stroke="black" d="M960.4,-113.33C938.64,-115.88 909.18,-117.67 885.82,-117.92"/>
+<polygon fill="black" stroke="black" points="885.57,-114.42 875.57,-117.92 885.57,-121.42 885.57,-114.42"/>
 </g>
 <!-- icmp6_input -->
 <g id="node28" class="node">
 <title>icmp6_input</title>
-<ellipse fill="none" stroke="black" cx="581.35" cy="-344" rx="38.68" ry="18"/>
-<text text-anchor="middle" x="581.35" y="-340.3" font-family="sans" font-size="11.00">icmp6_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-344" rx="37.4" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-341.2" font-family="sans" font-size="11.00">icmp6_input</text>
 </g>
 <!-- icmp6_output -->
 <g id="node29" class="node">
 <title>icmp6_output</title>
-<ellipse fill="none" stroke="black" cx="717.02" cy="-398" rx="43.31" ry="18"/>
-<text text-anchor="middle" x="717.02" y="-394.3" font-family="sans" font-size="11.00">icmp6_output</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-398" rx="41.58" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-395.2" font-family="sans" font-size="11.00">icmp6_output</text>
 </g>
 <!-- icmp6_input&#45;&gt;icmp6_output -->
 <g id="edge38" class="edge">
 <title>icmp6_input&#45;&gt;icmp6_output</title>
-<path fill="none" stroke="black" d="M611.36,-355.72C629.84,-363.18 654.08,-372.98 674.58,-381.26"/>
-<polygon fill="black" stroke="black" points="673.23,-384.49 683.81,-384.99 675.85,-378 673.23,-384.49"/>
+<path fill="none" stroke="black" d="M594.05,-355.58C612.48,-363.24 636.89,-373.37 657.23,-381.82"/>
+<polygon fill="black" stroke="black" points="655.97,-385.08 666.55,-385.68 658.65,-378.62 655.97,-385.08"/>
 </g>
 <!-- ndp_ns_input -->
 <g id="node30" class="node">
 <title>ndp_ns_input</title>
-<ellipse fill="none" stroke="black" cx="717.02" cy="-344" rx="41.62" ry="18"/>
-<text text-anchor="middle" x="717.02" y="-340.3" font-family="sans" font-size="11.00">ndp_ns_input</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-344" rx="40.32" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-341.2" font-family="sans" font-size="11.00">ndp_ns_input</text>
 </g>
 <!-- icmp6_input&#45;&gt;ndp_ns_input -->
 <g id="edge39" class="edge">
 <title>icmp6_input&#45;&gt;ndp_ns_input</title>
-<path fill="none" stroke="black" d="M620.3,-344C633.82,-344 649.3,-344 663.8,-344"/>
-<polygon fill="black" stroke="black" points="663.66,-347.5 673.66,-344 663.66,-340.5 663.66,-347.5"/>
+<path fill="none" stroke="black" d="M602.73,-344C616.27,-344 631.86,-344 646.39,-344"/>
+<polygon fill="black" stroke="black" points="646.73,-347.5 656.73,-344 646.73,-340.5 646.73,-347.5"/>
 </g>
 <!-- ndp_na_input -->
 <g id="node31" class="node">
 <title>ndp_na_input</title>
-<ellipse fill="none" stroke="black" cx="717.02" cy="-290" rx="42.04" ry="18"/>
-<text text-anchor="middle" x="717.02" y="-286.3" font-family="sans" font-size="11.00">ndp_na_input</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-290" rx="40.91" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-287.2" font-family="sans" font-size="11.00">ndp_na_input</text>
 </g>
 <!-- icmp6_input&#45;&gt;ndp_na_input -->
 <g id="edge40" class="edge">
 <title>icmp6_input&#45;&gt;ndp_na_input</title>
-<path fill="none" stroke="black" d="M611.36,-332.28C629.97,-324.76 654.43,-314.88 675.01,-306.57"/>
-<polygon fill="black" stroke="black" points="676.32,-309.81 684.28,-302.82 673.7,-303.32 676.32,-309.81"/>
+<path fill="none" stroke="black" d="M594.05,-332.42C612.61,-324.71 637.23,-314.49 657.66,-306.01"/>
+<polygon fill="black" stroke="black" points="659.11,-309.19 667,-302.13 656.43,-302.73 659.11,-309.19"/>
 </g>
 <!-- icmp6_output&#45;&gt;ip6_output -->
 <g id="edge41" class="edge">
 <title>icmp6_output&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M752.75,-408.55C775.65,-415.51 805.72,-424.65 829.59,-431.91"/>
-<polygon fill="black" stroke="black" points="828.38,-435.2 838.96,-434.76 830.41,-428.5 828.38,-435.2"/>
+<path fill="none" stroke="black" d="M731.67,-408.44C754.41,-415.54 784.54,-424.94 808.1,-432.29"/>
+<polygon fill="black" stroke="black" points="807.17,-435.66 817.76,-435.3 809.26,-428.98 807.17,-435.66"/>
 </g>
 <!-- ndp_ns_input&#45;&gt;ip6_output -->
 <g id="edge53" class="edge">
 <title>ndp_ns_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M747.28,-356.8C756.01,-360.97 765.47,-365.87 773.81,-371 798.3,-386.07 824.1,-406.05 842.72,-421.3"/>
-<polygon fill="black" stroke="black" points="840.4,-423.93 850.33,-427.61 844.86,-418.54 840.4,-423.93"/>
+<path fill="none" stroke="black" d="M726.16,-356.72C734.66,-360.93 743.9,-365.86 752.03,-371 776.5,-386.47 802.31,-406.94 820.61,-422.27"/>
+<polygon fill="black" stroke="black" points="818.57,-425.13 828.46,-428.92 823.09,-419.78 818.57,-425.13"/>
 </g>
 <!-- ndp_ns_input&#45;&gt;control_output -->
 <g id="edge54" class="edge">
 <title>ndp_ns_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M758.9,-342.12C836.84,-338.53 1006.83,-330.71 1095.74,-326.61"/>
-<polygon fill="black" stroke="black" points="1095.87,-330.11 1105.7,-326.15 1095.55,-323.12 1095.87,-330.11"/>
+<path fill="none" stroke="black" d="M737.29,-342.15C813.44,-338.56 981.77,-330.61 1068.2,-326.53"/>
+<polygon fill="black" stroke="black" points="1068.5,-330.02 1078.32,-326.05 1068.17,-323.02 1068.5,-330.02"/>
 </g>
 <!-- ndp_na_input&#45;&gt;control_output -->
 <g id="edge52" class="edge">
 <title>ndp_na_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M758.9,-293.2C837.04,-299.31 1007.71,-312.67 1096.43,-319.61"/>
-<polygon fill="black" stroke="black" points="1096.12,-323.1 1106.36,-320.39 1096.67,-316.12 1096.12,-323.1"/>
+<path fill="none" stroke="black" d="M737.29,-293.14C813.64,-299.27 982.64,-312.84 1068.87,-319.76"/>
+<polygon fill="black" stroke="black" points="1068.72,-323.26 1078.96,-320.57 1069.28,-316.28 1068.72,-323.26"/>
 </g>
 <!-- ip6_forward&#45;&gt;ip6_output -->
 <g id="edge42" class="edge">
 <title>ip6_forward&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M493.33,-522.13C553.27,-524.56 675.61,-524.98 773.81,-496 797.35,-489.05 821.61,-475.76 839.8,-464.36"/>
-<polygon fill="black" stroke="black" points="841.47,-467.45 847.97,-459.09 837.67,-461.57 841.47,-467.45"/>
+<path fill="none" stroke="black" d="M478.48,-522.03C536.44,-524.4 656.19,-524.85 752.03,-496 775.37,-488.97 799.37,-475.5 817.22,-464.03"/>
+<polygon fill="black" stroke="black" points="819.54,-466.69 825.96,-458.26 815.68,-460.85 819.54,-466.69"/>
 </g>
 <!-- ip6_hold&#45;&gt;control_output -->
 <g id="edge43" class="edge">
 <title>ip6_hold&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1038.52,-404.07C1059.69,-389.2 1094.82,-364.52 1120.5,-346.49"/>
-<polygon fill="black" stroke="black" points="1122.41,-349.42 1128.59,-340.8 1118.39,-343.69 1122.41,-349.42"/>
+<path fill="none" stroke="black" d="M1010.66,-404.07C1031.59,-388.98 1066.52,-363.79 1091.62,-345.69"/>
+<polygon fill="black" stroke="black" points="1093.99,-348.3 1100.05,-339.61 1089.89,-342.62 1093.99,-348.3"/>
 </g>
 <!-- ip6_input_local&#45;&gt;l4_input_local -->
 <g id="edge48" class="edge">
 <title>ip6_input_local&#45;&gt;l4_input_local</title>
-<path fill="none" stroke="black" d="M481.8,-328.62C500.46,-317.14 526.17,-301.33 546.65,-288.73"/>
-<polygon fill="black" stroke="black" points="548.25,-291.85 554.94,-283.63 544.59,-285.89 548.25,-291.85"/>
+<path fill="none" stroke="black" d="M467.91,-328.8C486.43,-317.11 512.18,-300.83 532.39,-288.07"/>
+<polygon fill="black" stroke="black" points="534.37,-290.95 540.96,-282.65 530.63,-285.04 534.37,-290.95"/>
 </g>
 <!-- ip6_input_local&#45;&gt;icmp6_input -->
 <g id="edge47" class="edge">
 <title>ip6_input_local&#45;&gt;icmp6_input</title>
-<path fill="none" stroke="black" d="M502.8,-344C511.97,-344 521.63,-344 530.91,-344"/>
-<polygon fill="black" stroke="black" points="530.87,-347.5 540.87,-344 530.87,-340.5 530.87,-347.5"/>
+<path fill="none" stroke="black" d="M487.97,-344C497.52,-344 507.66,-344 517.31,-344"/>
+<polygon fill="black" stroke="black" points="517.55,-347.5 527.55,-344 517.55,-340.5 517.55,-347.5"/>
 </g>
 <!-- l4_loopback_output&#45;&gt;loopback_output -->
 <g id="edge59" class="edge">
 <title>l4_loopback_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M931.06,-253.81C939.15,-253.92 947.47,-254.04 955.6,-254.15"/>
-<polygon fill="black" stroke="black" points="955.38,-257.65 965.43,-254.28 955.48,-250.65 955.38,-257.65"/>
+<path fill="none" stroke="black" d="M905.43,-253.81C913.81,-253.92 922.45,-254.05 930.87,-254.16"/>
+<polygon fill="black" stroke="black" points="930.96,-257.66 941,-254.3 931.05,-250.67 930.96,-257.66"/>
 </g>
 </g>
 </svg>
diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h
index 8abf3cbe..01137176 100644
--- a/modules/ip6/api/gr_ip6.h
+++ b/modules/ip6/api/gr_ip6.h
@@ -132,4 +132,15 @@ struct gr_ip6_addr_list_resp {
 	struct gr_ip6_ifaddr addrs[/* n_addrs */];
 };
 
+#define GR_IP6_IFACE_RA_SET REQUEST_TYPE(GR_IP6_MODULE, 0x0030)
+struct gr_ip6_ra_set_req {
+	uint16_t iface_id;
+};
+// struct gr_ip6_ra_set_resp { };
+
+#define GR_IP6_IFACE_RA_CLEAR REQUEST_TYPE(GR_IP6_MODULE, 0x0031)
+struct gr_ip6_ra_clear_req {
+	uint16_t iface_id;
+};
+// struct gr_ip6_ra_clear_resp { };
 #endif
diff --git a/modules/ip6/control/meson.build b/modules/ip6/control/meson.build
index 0fbd8ec2..248f09f9 100644
--- a/modules/ip6/control/meson.build
+++ b/modules/ip6/control/meson.build
@@ -5,5 +5,6 @@ src += files(
   'address.c',
   'nexthop.c',
   'route.c',
+  'router_advert.c',
 )
 inc += include_directories('.')
diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c
new file mode 100644
index 00000000..7636da62
--- /dev/null
+++ b/modules/ip6/control/router_advert.c
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) 2025 Christophe Fontaine
+
+#include <gr_api.h>
+#include <gr_control_input.h>
+#include <gr_iface.h>
+#include <gr_ip6.h>
+#include <gr_ip6_control.h>
+#include <gr_ip6_datapath.h>
+#include <gr_log.h>
+#include <gr_mempool.h>
+#include <gr_module.h>
+#include <gr_vec.h>
+
+#include <event2/event.h>
+#include <rte_errno.h>
+#include <rte_ether.h>
+#include <rte_ip6.h>
+#include <rte_mbuf.h>
+#include <rte_mempool.h>
+
+struct ra_ctx {
+	struct rte_mempool *mp;
+	struct event *timer;
+};
+
+static struct ra_ctx ra_ctx;
+static control_input_t ra_output;
+
+static struct api_out iface_ra_set(const void *request, void ** /*response*/) {
+	const struct gr_ip6_ra_set_req *req = request;
+	if (iface_from_id(req->iface_id) == NULL)
+		return api_out(errno, 0);
+
+	return api_out(0, 0);
+}
+
+static struct api_out iface_ra_clear(const void *request, void ** /*response*/) {
+	const struct gr_ip6_ra_clear_req *req = request;
+	if (iface_from_id(req->iface_id) == NULL)
+		return api_out(errno, 0);
+
+	return api_out(0, 0);
+}
+
+static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) {
+	struct rte_ipv6_addr dst = RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL;
+	struct rte_ipv6_addr src = *srcv6;
+	struct icmp6_opt_lladdr *lladdr;
+	struct icmp6_router_advert *ra;
+	struct rte_ether_addr mac;
+	struct rte_ipv6_hdr *ip;
+	struct icmp6_opt *opt;
+	uint16_t payload_len;
+	struct icmp6 *icmp6;
+	uint16_t iface_id;
+	uint16_t vrf_id;
+
+	vrf_id = mbuf_data(m)->iface->vrf_id;
+	iface_id = mbuf_data(m)->iface->id;
+	ip6_output_mbuf_data(m)->nh = ip6_nexthop_lookup(vrf_id, iface_id, &dst);
+	ip = (struct rte_ipv6_hdr *)rte_pktmbuf_append(m, sizeof(*ip));
+	icmp6 = (struct icmp6 *)rte_pktmbuf_append(m, sizeof(*icmp6));
+	icmp6->type = ICMP6_TYPE_ROUTER_ADVERT;
+	icmp6->code = 0;
+	ra = (struct icmp6_router_advert *)rte_pktmbuf_append(m, sizeof(*ra));
+	ra->cur_hoplim = IP6_DEFAULT_HOP_LIMIT; // Default TTL for this network
+	ra->managed_addr = 0; // DHCPv6 is available
+	ra->other_config = 0; // DNS available, ...
+	ra->lifetime = RTE_BE16(0); // Not a default router
+	ra->reachable_time = RTE_BE16(0);
+	ra->retrans_timer = RTE_BE16(0);
+
+	payload_len = sizeof(*icmp6) + sizeof(*ra);
+
+	if (iface_get_eth_addr(mbuf_data(m)->iface->id, &mac) == 0) {
+		opt = (struct icmp6_opt *)rte_pktmbuf_append(m, sizeof(*opt));
+		opt->type = ICMP6_OPT_SRC_LLADDR;
+		opt->len = ICMP6_OPT_LEN(sizeof(*opt) + sizeof(*lladdr));
+		lladdr = (struct icmp6_opt_lladdr *)rte_pktmbuf_append(m, sizeof(*lladdr));
+		lladdr->mac = mac;
+		payload_len += sizeof(*opt) + sizeof(*lladdr);
+	}
+
+	ip6_set_fields(ip, payload_len, IPPROTO_ICMPV6, &src, &dst);
+	icmp6->cksum = 0;
+	icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6);
+}
+
+static void send_ra_cb(evutil_socket_t, short /*what*/, void * /*priv*/) {
+	struct iface *iface = NULL;
+	struct rte_mbuf *m;
+
+	while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) {
+		struct hoplist *hl = ip6_addr_get_all(iface->id);
+		if (hl == NULL)
+			continue;
+		for (unsigned i = 0; i < gr_vec_len(hl); i++) {
+			struct nexthop *nh = hl->nh[i];
+			struct rte_ipv6_addr ip = nh->ipv6;
+			if (nh->family != AF_INET6)
+				continue;
+			if (!rte_ipv6_addr_is_linklocal(&ip))
+				continue;
+
+			if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) {
+				LOG(ERR, "rte_pktmbuf_alloc");
+				return;
+			}
+			mbuf_data(m)->iface = iface;
+			build_ra_packet(m, &ip);
+			post_to_stack(ra_output, m);
+		}
+	}
+}
+
+static void ra_init(struct event_base *ev_base) {
+	ra_output = gr_control_input_register_handler("ip6_output", true);
+	ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512);
+	if (ra_ctx.mp == NULL) {
+		ABORT("gr_pktmbuf_pool_get ENOMEM");
+	}
+
+	ra_ctx.timer = event_new(ev_base, -1, EV_PERSIST, send_ra_cb, NULL);
+	if (ra_ctx.timer == NULL) {
+		ABORT("event_new() failed");
+	}
+
+	if (event_add(ra_ctx.timer, &(struct timeval) {.tv_sec = 600}) < 0) {
+		ABORT("event_add() failed");
+	}
+}
+
+static void ra_fini(struct event_base * /*ev_base*/) {
+	gr_pktmbuf_pool_release(ra_ctx.mp, 512);
+	event_free(ra_ctx.timer);
+}
+
+static struct gr_module ra_module = {
+	.name = "ipv6 router advertisement",
+	.init = ra_init,
+	.fini = ra_fini,
+	.fini_prio = 20000,
+};
+
+static struct gr_api_handler ra_set_handler = {
+	.name = "set interface ra",
+	.request_type = GR_IP6_IFACE_RA_SET,
+	.callback = iface_ra_set,
+};
+
+static struct gr_api_handler ra_clear_handler = {
+	.name = "clear interface ra",
+	.request_type = GR_IP6_IFACE_RA_CLEAR,
+	.callback = iface_ra_clear,
+};
+
+RTE_INIT(router_advertisement_init) {
+	gr_register_module(&ra_module);
+	gr_register_api_handler(&ra_set_handler);
+	gr_register_api_handler(&ra_clear_handler);
+}

From f2f2e8162261e30abe5c278b177dc4fd746896c0 Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Fri, 20 Dec 2024 10:53:57 +0000
Subject: [PATCH 3/6] ip6: fix iface output for multicast destination

The returned nexthop for a multicast route lookup doesn't have
a defined iface.
Yet, this nexthop is valid, and we can rely on the iface defined
by the parent node to select the output iface.

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 modules/ip6/datapath/ip6_output.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/modules/ip6/datapath/ip6_output.c b/modules/ip6/datapath/ip6_output.c
index 9362c50a..3ab0f404 100644
--- a/modules/ip6/datapath/ip6_output.c
+++ b/modules/ip6/datapath/ip6_output.c
@@ -57,7 +57,12 @@ ip6_output_process(struct rte_graph *graph, struct rte_node *node, void **objs,
 			edge = DEST_UNREACH;
 			goto next;
 		}
-		iface = iface_from_id(nh->iface_id);
+
+		// For multicast destination, nh->iface will be NULL
+		if (rte_ipv6_addr_is_mcast(&ip->dst_addr))
+			iface = mbuf_data(mbuf)->iface;
+		else
+			iface = iface_from_id(nh->iface_id);
 		if (iface == NULL) {
 			edge = ERROR;
 			goto next;

From 73cf73452632923c5a0a6b26a21beca1c78e7ddb Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Fri, 17 Jan 2025 11:51:26 +0000
Subject: [PATCH 4/6] ip6: tests - use traceroute

Use traceroute instead of traceroute6, to use the same tool
as ip4_forward_test.sh

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 smoke/ip6_forward_test.sh   | 4 ++--
 smoke/ip6_same_peer_test.sh | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/smoke/ip6_forward_test.sh b/smoke/ip6_forward_test.sh
index 43fad0bf..02ce3886 100755
--- a/smoke/ip6_forward_test.sh
+++ b/smoke/ip6_forward_test.sh
@@ -32,5 +32,5 @@ ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:2::2
 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:1::2
 ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:1::1
 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:2::1
-ip netns exec $p1 traceroute6 -N1 fd00:ba4:2::2
-ip netns exec $p2 traceroute6 -N1 fd00:ba4:1::2
+ip netns exec $p1 traceroute -N1 fd00:ba4:2::2
+ip netns exec $p2 traceroute -N1 fd00:ba4:1::2
diff --git a/smoke/ip6_same_peer_test.sh b/smoke/ip6_same_peer_test.sh
index 4192d569..92c5341f 100755
--- a/smoke/ip6_same_peer_test.sh
+++ b/smoke/ip6_same_peer_test.sh
@@ -35,5 +35,5 @@ ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:2::2
 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:1::2
 ip netns exec $p1 ping6 -i0.01 -c3 fd00:ba4:1::1
 ip netns exec $p2 ping6 -i0.01 -c3 fd00:ba4:2::1
-ip netns exec $p1 traceroute6 -N1 fd00:ba4:2::2
-ip netns exec $p2 traceroute6 -N1 fd00:ba4:1::2
+ip netns exec $p1 traceroute -N1 fd00:ba4:2::2
+ip netns exec $p2 traceroute -N1 fd00:ba4:1::2

From 711a2f3a3d347c00fb7fae37b0331626846dfa07 Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Thu, 19 Dec 2024 13:33:18 +0000
Subject: [PATCH 5/6] ip6: answer to router sollicit message

Upon reception of a router sollicit message, trigger the existing
timer to send immediately a router advertismement packet.

Add relevant test: as the same mac address is defined for 2 interfaces
in the 2 namespaces, the same link local ip address will be generated
and used by linux.

Update github workflow and doc to reflect the addition of 'ndisc6'
as a test tool.

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 .github/workflows/check.yml          |   3 +-
 README.md                            |  19 +-
 docs/graph.svg                       | 352 ++++++++++++++-------------
 modules/ip6/control/gr_ip6_control.h |   1 +
 modules/ip6/control/router_advert.c  |   5 +
 modules/ip6/datapath/icmp6_input.c   |   5 +
 modules/ip6/datapath/meson.build     |   1 +
 modules/ip6/datapath/ndp_rs_input.c  |  92 +++++++
 smoke/ip6_rs_ra_test.sh              |  25 ++
 9 files changed, 331 insertions(+), 172 deletions(-)
 create mode 100644 modules/ip6/datapath/ndp_rs_input.c
 create mode 100755 smoke/ip6_rs_ra_test.sh

diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 11de4fe1..29e1f1dc 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -66,7 +66,8 @@ jobs:
           sudo NEEDRESTART_MODE=l apt-get install -qy --no-install-recommends \
             git socat tcpdump traceroute graphviz \
             iproute2 iputils-ping libasan8 libedit2 \
-            libevent-2.1-7t64 libsmartcols1 libnuma1
+            libevent-2.1-7t64 libsmartcols1 libnuma1 \
+            ndisc6
       - uses: actions/checkout@v4
         with:
           persist-credentials: false
diff --git a/README.md b/README.md
index 8a30130d..0d17dc77 100644
--- a/README.md
+++ b/README.md
@@ -237,8 +237,7 @@ image.
 ```sh
 dnf install gcc git make meson ninja-build pkgconf scdoc python3-pyelftools \
         libcmocka-devel libedit-devel libevent-devel numactl-devel \
-        libsmartcols-devel libarchive-devel rdma-core-devel \
-        clang-tools-extra jq curl traceroute graphviz
+        libsmartcols-devel libarchive-devel rdma-core-devel
 ```
 
 or
@@ -246,8 +245,20 @@ or
 ```sh
 apt install git gcc make meson ninja-build pkgconf scdoc python3-pyelftools \
         libcmocka-dev libedit-dev libevent-dev libnuma-dev \
-        libsmartcols-dev libarchive-dev libibverbs-dev \
-        clang-format jq curl traceroute graphviz
+        libsmartcols-dev libarchive-dev libibverbs-dev
+```
+
+### Install optional dependencies
+In order to run "make smoke-test" and "make update-graph", you'll need additional packages:
+
+```sh
+dnf install clang-tools-extra jq curl traceroute graphviz ndisc6
+```
+
+or
+
+```sh
+apt install clang-format jq curl traceroute graphviz ndisc6
 ```
 
 ### Build
diff --git a/docs/graph.svg b/docs/graph.svg
index 22003d18..13465fa5 100644
--- a/docs/graph.svg
+++ b/docs/graph.svg
@@ -4,64 +4,64 @@
 <!-- Generated by graphviz version 2.44.0 (0)
  -->
 <!-- Title: gr&#45;0005 Pages: 1 -->
-<svg width="1175pt" height="696pt"
- viewBox="0.00 0.00 1175.21 696.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 692)">
+<svg width="1175pt" height="734pt"
+ viewBox="0.00 0.00 1175.21 734.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 730)">
 <title>gr&#45;0005</title>
-<polygon fill="white" stroke="transparent" points="-4,4 -4,-692 1171.21,-692 1171.21,4 -4,4"/>
+<polygon fill="white" stroke="transparent" points="-4,4 -4,-730 1171.21,-730 1171.21,4 -4,4"/>
 <!-- control_input -->
 <g id="node1" class="node">
 <title>control_input</title>
-<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-405" rx="40.32" ry="18"/>
-<text text-anchor="middle" x="40.16" y="-402.2" font-family="sans" font-size="11.00">control_input</text>
+<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-439" rx="40.32" ry="18"/>
+<text text-anchor="middle" x="40.16" y="-436.2" font-family="sans" font-size="11.00">control_input</text>
 </g>
 <!-- loopback_input -->
 <g id="node2" class="node">
 <title>loopback_input</title>
-<ellipse fill="none" stroke="black" cx="161.91" cy="-378" rx="45.68" ry="18"/>
-<text text-anchor="middle" x="161.91" y="-375.2" font-family="sans" font-size="11.00">loopback_input</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-420" rx="45.68" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-417.2" font-family="sans" font-size="11.00">loopback_input</text>
 </g>
 <!-- control_input&#45;&gt;loopback_input -->
 <g id="edge1" class="edge">
 <title>control_input&#45;&gt;loopback_input</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M76.44,-397.05C87.55,-394.54 100.01,-391.73 111.89,-389.05"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="112.7,-392.46 121.69,-386.84 111.17,-385.63 112.7,-392.46"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M78.39,-433.09C88.08,-431.56 98.68,-429.87 108.97,-428.24"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="109.78,-431.66 119.11,-426.63 108.68,-424.74 109.78,-431.66"/>
 </g>
 <!-- arp_output_request -->
 <g id="node3" class="node">
 <title>arp_output_request</title>
-<ellipse fill="none" stroke="black" cx="302.66" cy="-650" rx="59.31" ry="18"/>
-<text text-anchor="middle" x="302.66" y="-647.2" font-family="sans" font-size="11.00">arp_output_request</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-684" rx="59.31" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-681.2" font-family="sans" font-size="11.00">arp_output_request</text>
 </g>
 <!-- control_input&#45;&gt;arp_output_request -->
 <g id="edge2" class="edge">
 <title>control_input&#45;&gt;arp_output_request</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M55.82,-422.03C63.97,-432.21 73.76,-445.71 80.32,-459 105.15,-509.32 78.1,-538.92 116.32,-580 148.07,-614.12 198.14,-631.73 238.24,-640.75"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="237.69,-644.22 248.2,-642.87 239.15,-637.37 237.69,-644.22"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M57.91,-455.2C65.66,-463.49 74.44,-474.11 80.32,-485 107.38,-535.12 78.2,-565.68 116.32,-608 148.47,-643.69 199.87,-662.99 240.4,-673.2"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="239.64,-676.62 250.18,-675.54 241.27,-669.81 239.64,-676.62"/>
 </g>
 <!-- icmp_local_send -->
 <g id="node4" class="node">
 <title>icmp_local_send</title>
-<ellipse fill="none" stroke="black" cx="302.66" cy="-103" rx="49.28" ry="18"/>
-<text text-anchor="middle" x="302.66" y="-100.2" font-family="sans" font-size="11.00">icmp_local_send</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-80" rx="49.28" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-77.2" font-family="sans" font-size="11.00">icmp_local_send</text>
 </g>
 <!-- control_input&#45;&gt;icmp_local_send -->
 <g id="edge3" class="edge">
 <title>control_input&#45;&gt;icmp_local_send</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M54.79,-388.04C86.89,-348.24 170.09,-246.03 243.5,-164 254.6,-151.6 267.32,-138.17 278.02,-127.08"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="280.62,-129.44 285.07,-119.82 275.59,-124.56 280.62,-129.44"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M53.73,-421.68C95.93,-363.53 232.75,-174.96 282.96,-105.77"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="285.87,-107.71 288.91,-97.56 280.21,-103.6 285.87,-107.71"/>
 </g>
 <!-- ndp_ns_output -->
 <g id="node5" class="node">
 <title>ndp_ns_output</title>
-<ellipse fill="none" stroke="black" cx="161.91" cy="-432" rx="44.51" ry="18"/>
-<text text-anchor="middle" x="161.91" y="-429.2" font-family="sans" font-size="11.00">ndp_ns_output</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-581" rx="44.51" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-578.2" font-family="sans" font-size="11.00">ndp_ns_output</text>
 </g>
 <!-- control_input&#45;&gt;ndp_ns_output -->
 <g id="edge4" class="edge">
 <title>control_input&#45;&gt;ndp_ns_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M76.44,-412.95C87.76,-415.51 100.5,-418.38 112.58,-421.1"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="112.01,-424.56 122.54,-423.35 113.55,-417.73 112.01,-424.56"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M56.15,-455.65C63.66,-464.22 72.74,-474.96 80.32,-485 97.7,-508.02 97.7,-516.97 116.32,-539 121.73,-545.4 128.03,-551.83 134.16,-557.68"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="132.04,-560.49 141.76,-564.72 136.8,-555.35 132.04,-560.49"/>
 </g>
 <!-- ip_output -->
 <g id="node6" class="node">
@@ -72,20 +72,20 @@
 <!-- control_input&#45;&gt;ip_output -->
 <g id="edge5" class="edge">
 <title>control_input&#45;&gt;ip_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M43.7,-386.81C55.79,-308.01 115.02,0 301.66,0 301.66,0 301.66,0 566.15,0 665.31,0 770.58,-62.28 818.75,-94.78"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M42.95,-420.97C52.25,-338.36 102.48,0 301.66,0 301.66,0 301.66,0 566.15,0 665.31,0 770.58,-62.28 818.75,-94.78"/>
 <polygon fill="blue" stroke="blue" stroke-width="2" points="816.84,-97.71 827.07,-100.48 820.8,-91.93 816.84,-97.71"/>
 </g>
 <!-- ip6_output -->
 <g id="node7" class="node">
 <title>ip6_output</title>
-<ellipse fill="none" stroke="black" cx="846.64" cy="-444" rx="32.63" ry="18"/>
-<text text-anchor="middle" x="846.64" y="-441.2" font-family="sans" font-size="11.00">ip6_output</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-482" rx="32.63" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-479.2" font-family="sans" font-size="11.00">ip6_output</text>
 </g>
 <!-- control_input&#45;&gt;ip6_output -->
 <g id="edge6" class="edge">
 <title>control_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M54.04,-422.39C61.76,-432.91 71.73,-446.64 80.32,-459 96.79,-482.68 91.75,-497.89 116.32,-513 223.66,-579.03 272.02,-539.43 397.81,-547 555.41,-556.48 609.45,-587.82 752.03,-520 772.46,-510.28 770.66,-498.51 788.03,-484 796.74,-476.72 806.74,-469.44 815.94,-463.12"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="818.11,-465.88 824.45,-457.39 814.2,-460.08 818.11,-465.88"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M79.36,-443.5C91.19,-444.78 104.29,-446.07 116.32,-447 377.42,-467.14 692.06,-477.57 803.8,-480.84"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="803.87,-484.35 813.97,-481.14 804.07,-477.35 803.87,-484.35"/>
 </g>
 <!-- ip_input -->
 <g id="node11" class="node">
@@ -96,32 +96,32 @@
 <!-- loopback_input&#45;&gt;ip_input -->
 <g id="edge11" class="edge">
 <title>loopback_input&#45;&gt;ip_input</title>
-<path fill="none" stroke="black" d="M175.72,-360.74C200.6,-327.2 255.53,-253.16 283.91,-214.91"/>
-<polygon fill="black" stroke="black" points="286.72,-217 289.87,-206.88 281.1,-212.83 286.72,-217"/>
+<path fill="none" stroke="black" d="M173.57,-402.41C197.96,-362.15 257.92,-263.19 286.29,-216.35"/>
+<polygon fill="black" stroke="black" points="289.34,-218.08 291.53,-207.72 283.35,-214.45 289.34,-218.08"/>
 </g>
 <!-- ip6_input -->
 <g id="node12" class="node">
 <title>ip6_input</title>
-<ellipse fill="none" stroke="black" cx="302.66" cy="-482" rx="28.44" ry="18"/>
-<text text-anchor="middle" x="302.66" y="-479.2" font-family="sans" font-size="11.00">ip6_input</text>
+<ellipse fill="none" stroke="black" cx="302.66" cy="-512" rx="28.44" ry="18"/>
+<text text-anchor="middle" x="302.66" y="-509.2" font-family="sans" font-size="11.00">ip6_input</text>
 </g>
 <!-- loopback_input&#45;&gt;ip6_input -->
 <g id="edge12" class="edge">
 <title>loopback_input&#45;&gt;ip6_input</title>
-<path fill="none" stroke="black" d="M188.77,-392.82C195.01,-396.62 201.59,-400.82 207.5,-405 232.44,-422.62 259.25,-444.9 277.85,-460.96"/>
-<polygon fill="black" stroke="black" points="275.7,-463.72 285.54,-467.64 280.29,-458.44 275.7,-463.72"/>
+<path fill="none" stroke="black" d="M186.13,-435.4C210.29,-451.41 248.1,-476.49 273.83,-493.55"/>
+<polygon fill="black" stroke="black" points="272.12,-496.61 282.39,-499.22 275.99,-490.78 272.12,-496.61"/>
 </g>
 <!-- eth_output -->
 <g id="node13" class="node">
 <title>eth_output</title>
-<ellipse fill="none" stroke="black" cx="991.73" cy="-635" rx="33.8" ry="18"/>
-<text text-anchor="middle" x="991.73" y="-632.2" font-family="sans" font-size="11.00">eth_output</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-673" rx="33.8" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-670.2" font-family="sans" font-size="11.00">eth_output</text>
 </g>
 <!-- arp_output_request&#45;&gt;eth_output -->
 <g id="edge21" class="edge">
 <title>arp_output_request&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M349.94,-660.89C401.8,-672.12 488.51,-688 564.15,-688 564.15,-688 564.15,-688 698.21,-688 790.75,-688 815.09,-686.82 905.25,-666 922.16,-662.1 940.31,-655.83 955.51,-649.95"/>
-<polygon fill="black" stroke="black" points="957.03,-653.11 965.04,-646.17 954.45,-646.61 957.03,-653.11"/>
+<path fill="none" stroke="black" d="M348.02,-695.59C399.58,-708.02 487.42,-726 564.15,-726 564.15,-726 564.15,-726 698.21,-726 790.75,-726 815.09,-724.82 905.25,-704 922.16,-700.1 940.31,-693.83 955.51,-687.95"/>
+<polygon fill="black" stroke="black" points="957.03,-691.11 965.04,-684.17 954.45,-684.61 957.03,-691.11"/>
 </g>
 <!-- icmp_output -->
 <g id="node21" class="node">
@@ -132,32 +132,32 @@
 <!-- icmp_local_send&#45;&gt;icmp_output -->
 <g id="edge24" class="edge">
 <title>icmp_local_send&#45;&gt;icmp_output</title>
-<path fill="none" stroke="black" d="M327.68,-87.45C346.06,-76.53 372.47,-62.86 397.81,-57 436.82,-47.98 448.91,-47.89 487.9,-57 555.92,-72.9 627.2,-114.4 666.24,-139.6"/>
+<path fill="none" stroke="black" d="M339.72,-68.07C377.15,-57.41 437.21,-45.15 487.9,-57 555.92,-72.9 627.2,-114.4 666.24,-139.6"/>
 <polygon fill="black" stroke="black" points="664.7,-142.78 674.98,-145.33 668.53,-136.92 664.7,-142.78"/>
 </g>
 <!-- ndp_ns_output&#45;&gt;ip6_output -->
-<g id="edge55" class="edge">
+<g id="edge56" class="edge">
 <title>ndp_ns_output&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M206.4,-432.76C329.97,-434.94 682.12,-441.13 803.73,-443.26"/>
-<polygon fill="black" stroke="black" points="803.7,-446.76 813.76,-443.44 803.83,-439.76 803.7,-446.76"/>
+<path fill="none" stroke="black" d="M205.88,-584.75C260.63,-589.19 358.2,-596 441.86,-596 441.86,-596 441.86,-596 566.15,-596 664.57,-596 769.01,-534.64 817.66,-501.96"/>
+<polygon fill="black" stroke="black" points="819.8,-504.74 826.09,-496.21 815.85,-498.95 819.8,-504.74"/>
 </g>
 <!-- ip_output&#45;&gt;eth_output -->
 <g id="edge34" class="edge">
 <title>ip_output&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M858.37,-130.73C871.5,-152.01 893.45,-190.29 905.25,-226 936.09,-319.23 920.92,-347.93 941.25,-444 953.65,-502.56 972.17,-570.17 982.69,-607.19"/>
-<polygon fill="black" stroke="black" points="979.44,-608.57 985.55,-617.22 986.17,-606.64 979.44,-608.57"/>
+<path fill="none" stroke="black" d="M858.52,-130.52C871.82,-151.54 893.94,-189.41 905.25,-225 940.74,-336.59 918.24,-371.19 941.25,-486 952.73,-543.24 971.43,-608.97 982.26,-645.27"/>
+<polygon fill="black" stroke="black" points="978.99,-646.55 985.22,-655.12 985.69,-644.53 978.99,-646.55"/>
 </g>
 <!-- loopback_output -->
 <g id="node15" class="node">
 <title>loopback_output</title>
-<ellipse fill="none" stroke="black" cx="991.73" cy="-255" rx="50.45" ry="18"/>
-<text text-anchor="middle" x="991.73" y="-252.2" font-family="sans" font-size="11.00">loopback_output</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-252" rx="50.45" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-249.2" font-family="sans" font-size="11.00">loopback_output</text>
 </g>
 <!-- ip_output&#45;&gt;loopback_output -->
 <g id="edge36" class="edge">
 <title>ip_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M862.95,-129.09C887.57,-153.35 936.19,-201.26 965.84,-230.48"/>
-<polygon fill="black" stroke="black" points="963.73,-233.31 973.31,-237.83 968.64,-228.32 963.73,-233.31"/>
+<path fill="none" stroke="black" d="M863.23,-129.03C887.83,-152.76 935.9,-199.12 965.47,-227.64"/>
+<polygon fill="black" stroke="black" points="963.3,-230.41 972.93,-234.83 968.16,-225.37 963.3,-230.41"/>
 </g>
 <!-- ip_hold -->
 <g id="node23" class="node">
@@ -184,88 +184,88 @@
 <polygon fill="black" stroke="black" points="948.15,-105.65 958.12,-102.05 948.09,-98.65 948.15,-105.65"/>
 </g>
 <!-- ip6_output&#45;&gt;eth_output -->
-<g id="edge49" class="edge">
+<g id="edge50" class="edge">
 <title>ip6_output&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M870.52,-456.56C881.91,-463.58 895.33,-473.11 905.25,-484 939.66,-521.72 965.97,-575.67 979.83,-607.83"/>
-<polygon fill="black" stroke="black" points="976.61,-609.22 983.72,-617.07 983.06,-606.5 976.61,-609.22"/>
+<path fill="none" stroke="black" d="M870.52,-494.56C881.91,-501.58 895.33,-511.11 905.25,-522 939.66,-559.72 965.97,-613.67 979.83,-645.83"/>
+<polygon fill="black" stroke="black" points="976.61,-647.22 983.72,-655.07 983.06,-644.5 976.61,-647.22"/>
 </g>
 <!-- ip6_output&#45;&gt;loopback_output -->
-<g id="edge51" class="edge">
+<g id="edge52" class="edge">
 <title>ip6_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M860.09,-427.56C885.06,-394.58 941.41,-320.14 971.29,-280.68"/>
-<polygon fill="black" stroke="black" points="974.34,-282.44 977.59,-272.36 968.76,-278.22 974.34,-282.44"/>
+<path fill="none" stroke="black" d="M858.16,-465.09C882.9,-425.32 944.89,-325.67 974.5,-278.09"/>
+<polygon fill="black" stroke="black" points="977.49,-279.9 979.8,-269.56 971.55,-276.21 977.49,-279.9"/>
 </g>
 <!-- ip6_hold -->
-<g id="node33" class="node">
+<g id="node34" class="node">
 <title>ip6_hold</title>
-<ellipse fill="none" stroke="black" cx="991.73" cy="-417" rx="27" ry="18"/>
-<text text-anchor="middle" x="991.73" y="-414.2" font-family="sans" font-size="11.00">ip6_hold</text>
+<ellipse fill="none" stroke="black" cx="991.73" cy="-459" rx="27" ry="18"/>
+<text text-anchor="middle" x="991.73" y="-456.2" font-family="sans" font-size="11.00">ip6_hold</text>
 </g>
 <!-- ip6_output&#45;&gt;ip6_hold -->
-<g id="edge50" class="edge">
+<g id="edge51" class="edge">
 <title>ip6_output&#45;&gt;ip6_hold</title>
-<path fill="none" stroke="black" d="M877.64,-438.34C900.34,-434.06 931.58,-428.16 955.47,-423.65"/>
-<polygon fill="black" stroke="black" points="956.37,-427.05 965.55,-421.75 955.07,-420.17 956.37,-427.05"/>
+<path fill="none" stroke="black" d="M877.99,-477.12C900.57,-473.49 931.46,-468.53 955.19,-464.71"/>
+<polygon fill="black" stroke="black" points="955.89,-468.14 965.21,-463.1 954.78,-461.23 955.89,-468.14"/>
 </g>
 <!-- control_output -->
 <g id="node8" class="node">
 <title>control_output</title>
-<ellipse fill="none" stroke="black" cx="1122.7" cy="-324" rx="44.51" ry="18"/>
-<text text-anchor="middle" x="1122.7" y="-321.2" font-family="sans" font-size="11.00">control_output</text>
+<ellipse fill="none" stroke="black" cx="1122.7" cy="-344" rx="44.51" ry="18"/>
+<text text-anchor="middle" x="1122.7" y="-341.2" font-family="sans" font-size="11.00">control_output</text>
 </g>
 <!-- eth_input -->
 <g id="node9" class="node">
 <title>eth_input</title>
-<ellipse fill="none" stroke="black" cx="161.91" cy="-486" rx="29.12" ry="18"/>
-<text text-anchor="middle" x="161.91" y="-483.2" font-family="sans" font-size="11.00">eth_input</text>
+<ellipse fill="none" stroke="black" cx="161.91" cy="-512" rx="29.12" ry="18"/>
+<text text-anchor="middle" x="161.91" y="-509.2" font-family="sans" font-size="11.00">eth_input</text>
 </g>
 <!-- arp_input -->
 <g id="node10" class="node">
 <title>arp_input</title>
-<ellipse fill="none" stroke="black" cx="565.15" cy="-604" rx="29.12" ry="18"/>
-<text text-anchor="middle" x="565.15" y="-601.2" font-family="sans" font-size="11.00">arp_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-642" rx="29.12" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-639.2" font-family="sans" font-size="11.00">arp_input</text>
 </g>
 <!-- eth_input&#45;&gt;arp_input -->
 <g id="edge7" class="edge">
 <title>eth_input&#45;&gt;arp_input</title>
-<path fill="none" stroke="black" d="M188.62,-493.56C257.9,-513.93 448.59,-570.01 528.35,-593.47"/>
-<polygon fill="black" stroke="black" points="527.82,-596.96 538.4,-596.43 529.79,-590.25 527.82,-596.96"/>
+<path fill="none" stroke="black" d="M187.93,-520.47C203.83,-525.83 224.85,-532.88 243.5,-539 347.22,-573.03 470.2,-612.21 529.18,-630.92"/>
+<polygon fill="black" stroke="black" points="528.17,-634.28 538.76,-633.96 530.29,-627.6 528.17,-634.28"/>
 </g>
 <!-- eth_input&#45;&gt;ip_input -->
 <g id="edge8" class="edge">
 <title>eth_input&#45;&gt;ip_input</title>
-<path fill="none" stroke="black" d="M186.51,-475.71C194.06,-471.45 201.91,-465.88 207.5,-459 267.15,-385.56 290.27,-271.67 298.12,-219.21"/>
-<polygon fill="black" stroke="black" points="301.63,-219.38 299.57,-208.99 294.7,-218.4 301.63,-219.38"/>
+<path fill="none" stroke="black" d="M175.65,-495.88C185.3,-483.14 198.39,-464.63 207.5,-447 248.5,-367.63 280.21,-266.21 294.05,-218.35"/>
+<polygon fill="black" stroke="black" points="297.42,-219.28 296.81,-208.71 290.69,-217.36 297.42,-219.28"/>
 </g>
 <!-- eth_input&#45;&gt;ip6_input -->
 <g id="edge9" class="edge">
 <title>eth_input&#45;&gt;ip6_input</title>
-<path fill="none" stroke="black" d="M191.32,-485.18C212.36,-484.57 241.23,-483.74 264.16,-483.08"/>
-<polygon fill="black" stroke="black" points="264.33,-486.58 274.23,-482.79 264.13,-479.58 264.33,-486.58"/>
+<path fill="none" stroke="black" d="M191.32,-512C212.36,-512 241.23,-512 264.16,-512"/>
+<polygon fill="black" stroke="black" points="264.23,-515.5 274.23,-512 264.23,-508.5 264.23,-515.5"/>
 </g>
 <!-- arp_input_request -->
 <g id="node17" class="node">
 <title>arp_input_request</title>
-<ellipse fill="none" stroke="black" cx="697.21" cy="-642" rx="54.63" ry="18"/>
-<text text-anchor="middle" x="697.21" y="-639.2" font-family="sans" font-size="11.00">arp_input_request</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-680" rx="54.63" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-677.2" font-family="sans" font-size="11.00">arp_input_request</text>
 </g>
 <!-- arp_input&#45;&gt;arp_input_request -->
 <g id="edge15" class="edge">
 <title>arp_input&#45;&gt;arp_input_request</title>
-<path fill="none" stroke="black" d="M591.85,-611.51C607.28,-616.02 627.46,-621.91 645.95,-627.31"/>
-<polygon fill="black" stroke="black" points="645.14,-630.72 655.72,-630.17 647.1,-624.01 645.14,-630.72"/>
+<path fill="none" stroke="black" d="M591.85,-649.51C607.28,-654.02 627.46,-659.91 645.95,-665.31"/>
+<polygon fill="black" stroke="black" points="645.14,-668.72 655.72,-668.17 647.1,-662.01 645.14,-668.72"/>
 </g>
 <!-- arp_input_reply -->
 <g id="node18" class="node">
 <title>arp_input_reply</title>
-<ellipse fill="none" stroke="black" cx="846.64" cy="-511" rx="47.43" ry="18"/>
-<text text-anchor="middle" x="846.64" y="-508.2" font-family="sans" font-size="11.00">arp_input_reply</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-549" rx="47.43" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-546.2" font-family="sans" font-size="11.00">arp_input_reply</text>
 </g>
 <!-- arp_input&#45;&gt;arp_input_reply -->
 <g id="edge16" class="edge">
 <title>arp_input&#45;&gt;arp_input_reply</title>
-<path fill="none" stroke="black" d="M594.5,-602.68C631.8,-600.16 698.84,-592.88 752.03,-572 753.76,-571.32 787.13,-549.49 813.54,-532.13"/>
-<polygon fill="black" stroke="black" points="815.74,-534.88 822.17,-526.46 811.89,-529.03 815.74,-534.88"/>
+<path fill="none" stroke="black" d="M594.5,-640.68C631.8,-638.16 698.84,-630.88 752.03,-610 753.76,-609.32 787.13,-587.49 813.54,-570.13"/>
+<polygon fill="black" stroke="black" points="815.74,-572.88 822.17,-564.46 811.89,-567.03 815.74,-572.88"/>
 </g>
 <!-- ip_input&#45;&gt;ip_output -->
 <g id="edge29" class="edge">
@@ -298,94 +298,94 @@
 <polygon fill="black" stroke="black" points="393.72,-209.54 404.16,-207.73 394.87,-202.63 393.72,-209.54"/>
 </g>
 <!-- ip6_input&#45;&gt;ip6_output -->
-<g id="edge45" class="edge">
+<g id="edge46" class="edge">
 <title>ip6_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M331.02,-481.44C401.16,-479.81 593.04,-474.11 752.03,-458 769.63,-456.22 788.98,-453.46 805.5,-450.86"/>
-<polygon fill="black" stroke="black" points="806.06,-454.31 815.38,-449.27 804.95,-447.4 806.06,-454.31"/>
+<path fill="none" stroke="black" d="M327.64,-520.89C346.43,-527.36 373.39,-535.57 397.81,-539 496.69,-552.9 734.27,-517.82 752.03,-514 771.68,-509.77 792.93,-502.72 810.21,-496.31"/>
+<polygon fill="black" stroke="black" points="811.77,-499.46 819.88,-492.64 809.29,-492.92 811.77,-499.46"/>
 </g>
 <!-- ip6_forward -->
-<g id="node32" class="node">
+<g id="node33" class="node">
 <title>ip6_forward</title>
-<ellipse fill="none" stroke="black" cx="442.86" cy="-520" rx="35.64" ry="18"/>
-<text text-anchor="middle" x="442.86" y="-517.2" font-family="sans" font-size="11.00">ip6_forward</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-512" rx="35.64" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-509.2" font-family="sans" font-size="11.00">ip6_forward</text>
 </g>
 <!-- ip6_input&#45;&gt;ip6_forward -->
-<g id="edge44" class="edge">
+<g id="edge45" class="edge">
 <title>ip6_input&#45;&gt;ip6_forward</title>
-<path fill="none" stroke="black" d="M329,-488.97C349.19,-494.52 377.83,-502.39 401.21,-508.82"/>
-<polygon fill="black" stroke="black" points="400.29,-512.2 410.86,-511.48 402.15,-505.45 400.29,-512.2"/>
+<path fill="none" stroke="black" d="M330.96,-512C349.77,-512 375.2,-512 396.97,-512"/>
+<polygon fill="black" stroke="black" points="396.99,-515.5 406.99,-512 396.99,-508.5 396.99,-515.5"/>
 </g>
 <!-- ip6_input_local -->
-<g id="node34" class="node">
+<g id="node35" class="node">
 <title>ip6_input_local</title>
-<ellipse fill="none" stroke="black" cx="442.86" cy="-344" rx="45.09" ry="18"/>
-<text text-anchor="middle" x="442.86" y="-341.2" font-family="sans" font-size="11.00">ip6_input_local</text>
+<ellipse fill="none" stroke="black" cx="442.86" cy="-359" rx="45.09" ry="18"/>
+<text text-anchor="middle" x="442.86" y="-356.2" font-family="sans" font-size="11.00">ip6_input_local</text>
 </g>
 <!-- ip6_input&#45;&gt;ip6_input_local -->
-<g id="edge46" class="edge">
+<g id="edge47" class="edge">
 <title>ip6_input&#45;&gt;ip6_input_local</title>
-<path fill="none" stroke="black" d="M318.71,-466.97C342.6,-443.11 389.4,-396.38 417.93,-367.89"/>
-<polygon fill="black" stroke="black" points="420.51,-370.27 425.11,-360.73 415.56,-365.31 420.51,-370.27"/>
+<path fill="none" stroke="black" d="M317.67,-496.49C341.71,-469.87 390.99,-415.32 419.72,-383.51"/>
+<polygon fill="black" stroke="black" points="422.44,-385.71 426.55,-375.95 417.25,-381.02 422.44,-385.71"/>
 </g>
 <!-- port_tx -->
 <g id="node14" class="node">
 <title>port_tx</title>
-<ellipse fill="none" stroke="black" cx="1122.7" cy="-635" rx="27" ry="18"/>
-<text text-anchor="middle" x="1122.7" y="-632.2" font-family="sans" font-size="11.00">port_tx</text>
+<ellipse fill="none" stroke="black" cx="1122.7" cy="-673" rx="27" ry="18"/>
+<text text-anchor="middle" x="1122.7" y="-670.2" font-family="sans" font-size="11.00">port_tx</text>
 </g>
 <!-- eth_output&#45;&gt;port_tx -->
 <g id="edge10" class="edge">
 <title>eth_output&#45;&gt;port_tx</title>
-<path fill="none" stroke="black" d="M1025.61,-635C1043.79,-635 1066.41,-635 1085.15,-635"/>
-<polygon fill="black" stroke="black" points="1085.38,-638.5 1095.38,-635 1085.38,-631.5 1085.38,-638.5"/>
+<path fill="none" stroke="black" d="M1025.61,-673C1043.79,-673 1066.41,-673 1085.15,-673"/>
+<polygon fill="black" stroke="black" points="1085.38,-676.5 1095.38,-673 1085.38,-669.5 1085.38,-676.5"/>
 </g>
 <!-- loopback_output&#45;&gt;control_output -->
 <g id="edge13" class="edge">
 <title>loopback_output&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1020.71,-269.97C1039.86,-280.21 1065.41,-293.88 1086.01,-304.91"/>
-<polygon fill="black" stroke="black" points="1084.63,-308.13 1095.09,-309.76 1087.93,-301.96 1084.63,-308.13"/>
+<path fill="none" stroke="black" d="M1017.62,-267.6C1025.56,-272.71 1034.32,-278.49 1042.2,-284 1059.54,-296.12 1078.46,-310.36 1093.5,-321.93"/>
+<polygon fill="black" stroke="black" points="1091.36,-324.7 1101.41,-328.05 1095.64,-319.16 1091.36,-324.7"/>
 </g>
 <!-- port_rx -->
 <g id="node16" class="node">
 <title>port_rx</title>
-<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-486" rx="27" ry="18"/>
-<text text-anchor="middle" x="40.16" y="-483.2" font-family="sans" font-size="11.00">port_rx</text>
+<ellipse fill="none" stroke="blue" stroke-width="2" cx="40.16" cy="-512" rx="27" ry="18"/>
+<text text-anchor="middle" x="40.16" y="-509.2" font-family="sans" font-size="11.00">port_rx</text>
 </g>
 <!-- port_rx&#45;&gt;eth_input -->
 <g id="edge14" class="edge">
 <title>port_rx&#45;&gt;eth_input</title>
-<path fill="none" stroke="blue" stroke-width="2" d="M67.44,-486C83.56,-486 104.47,-486 122.43,-486"/>
-<polygon fill="blue" stroke="blue" stroke-width="2" points="122.6,-489.5 132.6,-486 122.6,-482.5 122.6,-489.5"/>
+<path fill="none" stroke="blue" stroke-width="2" d="M67.44,-512C83.56,-512 104.47,-512 122.43,-512"/>
+<polygon fill="blue" stroke="blue" stroke-width="2" points="122.6,-515.5 132.6,-512 122.6,-508.5 122.6,-515.5"/>
 </g>
 <!-- arp_input_request&#45;&gt;control_output -->
 <g id="edge18" class="edge">
 <title>arp_input_request&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M734.18,-628.62C812.45,-598.98 994.98,-526.78 1042.2,-481 1079.97,-444.38 1102.9,-386 1113.86,-351.69"/>
-<polygon fill="black" stroke="black" points="1117.29,-352.46 1116.89,-341.87 1110.6,-350.4 1117.29,-352.46"/>
+<path fill="none" stroke="black" d="M735.24,-666.91C814.34,-638.37 996.31,-569.36 1042.2,-523 1084.62,-480.15 1106.42,-410.77 1115.81,-372.18"/>
+<polygon fill="black" stroke="black" points="1119.29,-372.69 1118.14,-362.16 1112.47,-371.11 1119.29,-372.69"/>
 </g>
 <!-- arp_output_reply -->
 <g id="node19" class="node">
 <title>arp_output_reply</title>
-<ellipse fill="none" stroke="black" cx="846.64" cy="-639" rx="51.62" ry="18"/>
-<text text-anchor="middle" x="846.64" y="-636.2" font-family="sans" font-size="11.00">arp_output_reply</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-677" rx="51.62" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-674.2" font-family="sans" font-size="11.00">arp_output_reply</text>
 </g>
 <!-- arp_input_request&#45;&gt;arp_output_reply -->
 <g id="edge19" class="edge">
 <title>arp_input_request&#45;&gt;arp_output_reply</title>
-<path fill="none" stroke="black" d="M752.27,-640.9C762.86,-640.68 774.03,-640.46 784.82,-640.24"/>
-<polygon fill="black" stroke="black" points="785.09,-643.73 795.02,-640.03 784.95,-636.73 785.09,-643.73"/>
+<path fill="none" stroke="black" d="M752.27,-678.9C762.86,-678.68 774.03,-678.46 784.82,-678.24"/>
+<polygon fill="black" stroke="black" points="785.09,-681.73 795.02,-678.03 784.95,-674.73 785.09,-681.73"/>
 </g>
 <!-- arp_input_reply&#45;&gt;control_output -->
 <g id="edge17" class="edge">
 <title>arp_input_reply&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M890.92,-504.41C933.11,-496.19 997.28,-478.77 1042.2,-444 1074.06,-419.34 1097.32,-378.16 1110.18,-351.03"/>
-<polygon fill="black" stroke="black" points="1113.46,-352.28 1114.44,-341.74 1107.09,-349.37 1113.46,-352.28"/>
+<path fill="none" stroke="black" d="M892.28,-544.01C934.88,-537.23 998.82,-521.57 1042.2,-486 1079.07,-455.77 1101.87,-403.7 1113.14,-371.71"/>
+<polygon fill="black" stroke="black" points="1116.51,-372.68 1116.39,-362.09 1109.88,-370.44 1116.51,-372.68"/>
 </g>
 <!-- arp_output_reply&#45;&gt;eth_output -->
 <g id="edge20" class="edge">
 <title>arp_output_reply&#45;&gt;eth_output</title>
-<path fill="none" stroke="black" d="M898.09,-637.59C914.17,-637.14 931.81,-636.65 947.35,-636.21"/>
-<polygon fill="black" stroke="black" points="947.82,-639.7 957.72,-635.92 947.63,-632.7 947.82,-639.7"/>
+<path fill="none" stroke="black" d="M898.09,-675.59C914.17,-675.14 931.81,-674.65 947.35,-674.21"/>
+<polygon fill="black" stroke="black" points="947.82,-677.7 957.72,-673.92 947.63,-670.7 947.82,-677.7"/>
 </g>
 <!-- icmp_input -->
 <g id="node20" class="node">
@@ -396,8 +396,8 @@
 <!-- icmp_input&#45;&gt;control_output -->
 <g id="edge23" class="edge">
 <title>icmp_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M598.51,-211.55C692.54,-204.96 964,-189.81 1042.2,-228 1072.11,-242.61 1094.78,-274.31 1108.11,-297.3"/>
-<polygon fill="black" stroke="black" points="1105.16,-299.2 1113.08,-306.24 1111.28,-295.8 1105.16,-299.2"/>
+<path fill="none" stroke="black" d="M598.36,-210.98C692.58,-202.71 965.85,-183.05 1042.2,-225 1077.85,-244.59 1100.41,-287.78 1112.05,-316.39"/>
+<polygon fill="black" stroke="black" points="1108.84,-317.77 1115.72,-325.83 1115.36,-315.24 1108.84,-317.77"/>
 </g>
 <!-- icmp_input&#45;&gt;icmp_output -->
 <g id="edge22" class="edge">
@@ -420,8 +420,8 @@
 <!-- ip_hold&#45;&gt;control_output -->
 <g id="edge27" class="edge">
 <title>ip_hold&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1014.02,-170.88C1023.39,-176.36 1034.09,-183.62 1042.2,-192 1072.5,-223.29 1096.42,-268.17 1109.81,-296.69"/>
-<polygon fill="black" stroke="black" points="1106.76,-298.42 1114.11,-306.06 1113.12,-295.51 1106.76,-298.42"/>
+<path fill="none" stroke="black" d="M1014.25,-170.66C1023.67,-176.09 1034.34,-183.38 1042.2,-192 1076.06,-229.13 1099.82,-283.35 1112.09,-316"/>
+<polygon fill="black" stroke="black" points="1108.95,-317.6 1115.67,-325.79 1115.52,-315.2 1108.95,-317.6"/>
 </g>
 <!-- ip_input_local&#45;&gt;icmp_input -->
 <g id="edge31" class="edge">
@@ -454,25 +454,25 @@
 <polygon fill="black" stroke="black" points="525.29,-254.39 535.85,-255.29 528.16,-248.01 525.29,-254.39"/>
 </g>
 <!-- ipip_input&#45;&gt;ip_input -->
-<g id="edge56" class="edge">
+<g id="edge58" class="edge">
 <title>ipip_input&#45;&gt;ip_input</title>
 <path fill="none" stroke="black" d="M535.57,-163.4C487.85,-169.08 392.05,-180.48 339.75,-186.71"/>
 <polygon fill="black" stroke="black" points="339.1,-183.26 329.59,-187.91 339.93,-190.21 339.1,-183.26"/>
 </g>
 <!-- l4_loopback_output -->
-<g id="node35" class="node">
+<g id="node36" class="node">
 <title>l4_loopback_output</title>
-<ellipse fill="none" stroke="black" cx="846.64" cy="-253" rx="58.73" ry="18"/>
-<text text-anchor="middle" x="846.64" y="-250.2" font-family="sans" font-size="11.00">l4_loopback_output</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-252" rx="58.73" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-249.2" font-family="sans" font-size="11.00">l4_loopback_output</text>
 </g>
 <!-- l4_input_local&#45;&gt;l4_loopback_output -->
-<g id="edge58" class="edge">
+<g id="edge60" class="edge">
 <title>l4_input_local&#45;&gt;l4_loopback_output</title>
-<path fill="none" stroke="black" d="M606.11,-265.26C617.8,-264.49 630.61,-263.68 642.4,-263 687.66,-260.4 738.62,-257.9 778.18,-256.04"/>
-<polygon fill="black" stroke="black" points="778.42,-259.54 788.25,-255.57 778.09,-252.54 778.42,-259.54"/>
+<path fill="none" stroke="black" d="M606.11,-265.29C617.81,-264.52 630.61,-263.71 642.4,-263 687.83,-260.28 738.99,-257.51 778.61,-255.44"/>
+<polygon fill="black" stroke="black" points="778.88,-258.93 788.69,-254.91 778.52,-251.94 778.88,-258.93"/>
 </g>
 <!-- ipip_output&#45;&gt;ip_output -->
-<g id="edge57" class="edge">
+<g id="edge59" class="edge">
 <title>ipip_output&#45;&gt;ip_output</title>
 <path fill="none" stroke="black" d="M960.4,-113.33C938.64,-115.88 909.18,-117.67 885.82,-117.92"/>
 <polygon fill="black" stroke="black" points="885.57,-114.42 875.57,-117.92 885.57,-121.42 885.57,-114.42"/>
@@ -480,98 +480,116 @@
 <!-- icmp6_input -->
 <g id="node28" class="node">
 <title>icmp6_input</title>
-<ellipse fill="none" stroke="black" cx="565.15" cy="-344" rx="37.4" ry="18"/>
-<text text-anchor="middle" x="565.15" y="-341.2" font-family="sans" font-size="11.00">icmp6_input</text>
+<ellipse fill="none" stroke="black" cx="565.15" cy="-359" rx="37.4" ry="18"/>
+<text text-anchor="middle" x="565.15" y="-356.2" font-family="sans" font-size="11.00">icmp6_input</text>
 </g>
 <!-- icmp6_output -->
 <g id="node29" class="node">
 <title>icmp6_output</title>
-<ellipse fill="none" stroke="black" cx="697.21" cy="-398" rx="41.58" ry="18"/>
-<text text-anchor="middle" x="697.21" y="-395.2" font-family="sans" font-size="11.00">icmp6_output</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-436" rx="41.58" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-433.2" font-family="sans" font-size="11.00">icmp6_output</text>
 </g>
 <!-- icmp6_input&#45;&gt;icmp6_output -->
 <g id="edge38" class="edge">
 <title>icmp6_input&#45;&gt;icmp6_output</title>
-<path fill="none" stroke="black" d="M594.05,-355.58C612.48,-363.24 636.89,-373.37 657.23,-381.82"/>
-<polygon fill="black" stroke="black" points="655.97,-385.08 666.55,-385.68 658.65,-378.62 655.97,-385.08"/>
+<path fill="none" stroke="black" d="M587.1,-373.68C602.19,-384.02 623.2,-397.94 642.4,-409 647.87,-412.15 653.75,-415.32 659.54,-418.32"/>
+<polygon fill="black" stroke="black" points="658.01,-421.46 668.51,-422.87 661.18,-415.22 658.01,-421.46"/>
 </g>
 <!-- ndp_ns_input -->
 <g id="node30" class="node">
 <title>ndp_ns_input</title>
-<ellipse fill="none" stroke="black" cx="697.21" cy="-344" rx="40.32" ry="18"/>
-<text text-anchor="middle" x="697.21" y="-341.2" font-family="sans" font-size="11.00">ndp_ns_input</text>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-382" rx="40.32" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-379.2" font-family="sans" font-size="11.00">ndp_ns_input</text>
 </g>
 <!-- icmp6_input&#45;&gt;ndp_ns_input -->
 <g id="edge39" class="edge">
 <title>icmp6_input&#45;&gt;ndp_ns_input</title>
-<path fill="none" stroke="black" d="M602.73,-344C616.27,-344 631.86,-344 646.39,-344"/>
-<polygon fill="black" stroke="black" points="646.73,-347.5 656.73,-344 646.73,-340.5 646.73,-347.5"/>
+<path fill="none" stroke="black" d="M600.66,-365.1C615.6,-367.75 633.32,-370.88 649.42,-373.73"/>
+<polygon fill="black" stroke="black" points="649.26,-377.25 659.72,-375.55 650.48,-370.36 649.26,-377.25"/>
 </g>
 <!-- ndp_na_input -->
 <g id="node31" class="node">
 <title>ndp_na_input</title>
-<ellipse fill="none" stroke="black" cx="697.21" cy="-290" rx="40.91" ry="18"/>
-<text text-anchor="middle" x="697.21" y="-287.2" font-family="sans" font-size="11.00">ndp_na_input</text>
+<ellipse fill="none" stroke="black" cx="846.64" cy="-344" rx="40.91" ry="18"/>
+<text text-anchor="middle" x="846.64" y="-341.2" font-family="sans" font-size="11.00">ndp_na_input</text>
 </g>
 <!-- icmp6_input&#45;&gt;ndp_na_input -->
 <g id="edge40" class="edge">
 <title>icmp6_input&#45;&gt;ndp_na_input</title>
-<path fill="none" stroke="black" d="M594.05,-332.42C612.61,-324.71 637.23,-314.49 657.66,-306.01"/>
-<polygon fill="black" stroke="black" points="659.11,-309.19 667,-302.13 656.43,-302.73 659.11,-309.19"/>
+<path fill="none" stroke="black" d="M602.4,-357.05C651.66,-354.41 739.95,-349.67 795.53,-346.69"/>
+<polygon fill="black" stroke="black" points="796.05,-350.17 805.84,-346.14 795.67,-343.18 796.05,-350.17"/>
 </g>
-<!-- icmp6_output&#45;&gt;ip6_output -->
+<!-- ndp_rs_input -->
+<g id="node32" class="node">
+<title>ndp_rs_input</title>
+<ellipse fill="none" stroke="black" cx="697.21" cy="-290" rx="39.15" ry="18"/>
+<text text-anchor="middle" x="697.21" y="-287.2" font-family="sans" font-size="11.00">ndp_rs_input</text>
+</g>
+<!-- icmp6_input&#45;&gt;ndp_rs_input -->
 <g id="edge41" class="edge">
+<title>icmp6_input&#45;&gt;ndp_rs_input</title>
+<path fill="none" stroke="black" d="M590.92,-345.86C610.91,-335.25 639.27,-320.21 661.53,-308.4"/>
+<polygon fill="black" stroke="black" points="663.45,-311.34 670.65,-303.56 660.17,-305.16 663.45,-311.34"/>
+</g>
+<!-- icmp6_output&#45;&gt;ip6_output -->
+<g id="edge42" class="edge">
 <title>icmp6_output&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M731.67,-408.44C754.41,-415.54 784.54,-424.94 808.1,-432.29"/>
-<polygon fill="black" stroke="black" points="807.17,-435.66 817.76,-435.3 809.26,-428.98 807.17,-435.66"/>
+<path fill="none" stroke="black" d="M731.67,-446.44C754.41,-453.54 784.54,-462.94 808.1,-470.29"/>
+<polygon fill="black" stroke="black" points="807.17,-473.66 817.76,-473.3 809.26,-466.98 807.17,-473.66"/>
 </g>
 <!-- ndp_ns_input&#45;&gt;ip6_output -->
-<g id="edge53" class="edge">
+<g id="edge54" class="edge">
 <title>ndp_ns_input&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M726.16,-356.72C734.66,-360.93 743.9,-365.86 752.03,-371 776.5,-386.47 802.31,-406.94 820.61,-422.27"/>
-<polygon fill="black" stroke="black" points="818.57,-425.13 828.46,-428.92 823.09,-419.78 818.57,-425.13"/>
+<path fill="none" stroke="black" d="M726.16,-394.72C734.66,-398.93 743.9,-403.86 752.03,-409 776.5,-424.47 802.31,-444.94 820.61,-460.27"/>
+<polygon fill="black" stroke="black" points="818.57,-463.13 828.46,-466.92 823.09,-457.78 818.57,-463.13"/>
 </g>
 <!-- ndp_ns_input&#45;&gt;control_output -->
-<g id="edge54" class="edge">
+<g id="edge55" class="edge">
 <title>ndp_ns_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M737.29,-342.15C813.44,-338.56 981.77,-330.61 1068.2,-326.53"/>
-<polygon fill="black" stroke="black" points="1068.5,-330.02 1078.32,-326.05 1068.17,-323.02 1068.5,-330.02"/>
+<path fill="none" stroke="black" d="M737.33,-380.92C801.33,-378.79 932.3,-372.87 1042.2,-358 1051.89,-356.69 1062.2,-355.05 1072.08,-353.37"/>
+<polygon fill="black" stroke="black" points="1072.92,-356.77 1082.17,-351.6 1071.72,-349.88 1072.92,-356.77"/>
 </g>
 <!-- ndp_na_input&#45;&gt;control_output -->
-<g id="edge52" class="edge">
+<g id="edge53" class="edge">
 <title>ndp_na_input&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M737.29,-293.14C813.64,-299.27 982.64,-312.84 1068.87,-319.76"/>
-<polygon fill="black" stroke="black" points="1068.72,-323.26 1078.96,-320.57 1069.28,-316.28 1068.72,-323.26"/>
+<path fill="none" stroke="black" d="M887.49,-344C935.09,-344 1014.92,-344 1067.95,-344"/>
+<polygon fill="black" stroke="black" points="1068.18,-347.5 1078.18,-344 1068.18,-340.5 1068.18,-347.5"/>
+</g>
+<!-- ndp_rs_input&#45;&gt;control_output -->
+<g id="edge57" class="edge">
+<title>ndp_rs_input&#45;&gt;control_output</title>
+<path fill="none" stroke="black" d="M735.05,-294.7C810.57,-304.33 983.08,-326.32 1069.85,-337.39"/>
+<polygon fill="black" stroke="black" points="1069.63,-340.89 1079.99,-338.68 1070.52,-333.94 1069.63,-340.89"/>
 </g>
 <!-- ip6_forward&#45;&gt;ip6_output -->
-<g id="edge42" class="edge">
+<g id="edge43" class="edge">
 <title>ip6_forward&#45;&gt;ip6_output</title>
-<path fill="none" stroke="black" d="M478.48,-522.03C536.44,-524.4 656.19,-524.85 752.03,-496 775.37,-488.97 799.37,-475.5 817.22,-464.03"/>
-<polygon fill="black" stroke="black" points="819.54,-466.69 825.96,-458.26 815.68,-460.85 819.54,-466.69"/>
+<path fill="none" stroke="black" d="M479.07,-511.28C536.39,-509.85 653.41,-505.91 752.03,-496 769.63,-494.23 788.98,-491.47 805.5,-488.87"/>
+<polygon fill="black" stroke="black" points="806.06,-492.33 815.38,-487.28 804.95,-485.42 806.06,-492.33"/>
 </g>
 <!-- ip6_hold&#45;&gt;control_output -->
-<g id="edge43" class="edge">
+<g id="edge44" class="edge">
 <title>ip6_hold&#45;&gt;control_output</title>
-<path fill="none" stroke="black" d="M1010.66,-404.07C1031.59,-388.98 1066.52,-363.79 1091.62,-345.69"/>
-<polygon fill="black" stroke="black" points="1093.99,-348.3 1100.05,-339.61 1089.89,-342.62 1093.99,-348.3"/>
+<path fill="none" stroke="black" d="M1008.54,-444.9C1030.24,-425.55 1069.53,-390.52 1095.62,-367.26"/>
+<polygon fill="black" stroke="black" points="1098.12,-369.71 1103.26,-360.45 1093.47,-364.49 1098.12,-369.71"/>
 </g>
 <!-- ip6_input_local&#45;&gt;l4_input_local -->
-<g id="edge48" class="edge">
+<g id="edge49" class="edge">
 <title>ip6_input_local&#45;&gt;l4_input_local</title>
-<path fill="none" stroke="black" d="M467.91,-328.8C486.43,-317.11 512.18,-300.83 532.39,-288.07"/>
-<polygon fill="black" stroke="black" points="534.37,-290.95 540.96,-282.65 530.63,-285.04 534.37,-290.95"/>
+<path fill="none" stroke="black" d="M464.81,-343.15C484.3,-328.4 513.52,-306.3 535.23,-289.88"/>
+<polygon fill="black" stroke="black" points="537.57,-292.49 543.44,-283.67 533.35,-286.91 537.57,-292.49"/>
 </g>
 <!-- ip6_input_local&#45;&gt;icmp6_input -->
-<g id="edge47" class="edge">
+<g id="edge48" class="edge">
 <title>ip6_input_local&#45;&gt;icmp6_input</title>
-<path fill="none" stroke="black" d="M487.97,-344C497.52,-344 507.66,-344 517.31,-344"/>
-<polygon fill="black" stroke="black" points="517.55,-347.5 527.55,-344 517.55,-340.5 517.55,-347.5"/>
+<path fill="none" stroke="black" d="M487.97,-359C497.52,-359 507.66,-359 517.31,-359"/>
+<polygon fill="black" stroke="black" points="517.55,-362.5 527.55,-359 517.55,-355.5 517.55,-362.5"/>
 </g>
 <!-- l4_loopback_output&#45;&gt;loopback_output -->
-<g id="edge59" class="edge">
+<g id="edge61" class="edge">
 <title>l4_loopback_output&#45;&gt;loopback_output</title>
-<path fill="none" stroke="black" d="M905.43,-253.81C913.81,-253.92 922.45,-254.05 930.87,-254.16"/>
-<polygon fill="black" stroke="black" points="930.96,-257.66 941,-254.3 931.05,-250.67 930.96,-257.66"/>
+<path fill="none" stroke="black" d="M905.43,-252C913.81,-252 922.45,-252 930.87,-252"/>
+<polygon fill="black" stroke="black" points="931,-255.5 941,-252 931,-248.5 931,-255.5"/>
 </g>
 </g>
 </svg>
diff --git a/modules/ip6/control/gr_ip6_control.h b/modules/ip6/control/gr_ip6_control.h
index 55ac7f69..02086956 100644
--- a/modules/ip6/control/gr_ip6_control.h
+++ b/modules/ip6/control/gr_ip6_control.h
@@ -26,6 +26,7 @@ struct nexthop *ip6_nexthop_new(uint16_t vrf_id, uint16_t iface_id, const struct
 
 void ip6_nexthop_unreachable_cb(struct rte_mbuf *m);
 void ndp_probe_input_cb(struct rte_mbuf *m);
+void ndp_router_sollicit_input_cb(struct rte_mbuf *m);
 
 int ip6_route_insert(uint16_t vrf_id, uint16_t iface_id, const struct rte_ipv6_addr *, uint8_t prefixlen, struct nexthop *);
 int ip6_route_delete(
diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c
index 7636da62..db7f0885 100644
--- a/modules/ip6/control/router_advert.c
+++ b/modules/ip6/control/router_advert.c
@@ -43,6 +43,11 @@ static struct api_out iface_ra_clear(const void *request, void ** /*response*/)
 	return api_out(0, 0);
 }
 
+void ndp_router_sollicit_input_cb(struct rte_mbuf *m) {
+	rte_pktmbuf_free(m);
+	event_active(ra_ctx.timer, 0, 0);
+}
+
 static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) {
 	struct rte_ipv6_addr dst = RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL;
 	struct rte_ipv6_addr src = *srcv6;
diff --git a/modules/ip6/datapath/icmp6_input.c b/modules/ip6/datapath/icmp6_input.c
index 493ae99c..c650d9b8 100644
--- a/modules/ip6/datapath/icmp6_input.c
+++ b/modules/ip6/datapath/icmp6_input.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: BSD-3-Clause
 // Copyright (c) 2024 Robin Jarry
 
+#include <gr_control_output.h>
 #include <gr_datapath.h>
 #include <gr_graph.h>
 #include <gr_icmp6.h>
@@ -19,6 +20,7 @@ enum {
 	ICMP6_OUTPUT = 0,
 	NEIGH_SOLICIT,
 	NEIGH_ADVERT,
+	ROUTER_SOLICIT,
 	BAD_CHECKSUM,
 	INVALID,
 	UNSUPPORTED,
@@ -64,6 +66,8 @@ icmp6_input_process(struct rte_graph *graph, struct rte_node *node, void **objs,
 			next = NEIGH_ADVERT;
 			break;
 		case ICMP6_TYPE_ROUTER_SOLICIT:
+			next = ROUTER_SOLICIT;
+			break;
 		case ICMP6_TYPE_ROUTER_ADVERT:
 		default:
 			next = UNSUPPORTED;
@@ -89,6 +93,7 @@ static struct rte_node_register icmp6_input_node = {
 		[ICMP6_OUTPUT] = "icmp6_output",
 		[NEIGH_SOLICIT] = "ndp_ns_input",
 		[NEIGH_ADVERT] = "ndp_na_input",
+		[ROUTER_SOLICIT] = "ndp_rs_input",
 		[BAD_CHECKSUM] = "icmp6_input_bad_checksum",
 		[INVALID] = "icmp6_input_invalid",
 		[UNSUPPORTED] = "icmp6_input_unsupported",
diff --git a/modules/ip6/datapath/meson.build b/modules/ip6/datapath/meson.build
index a8f14ca3..729a613f 100644
--- a/modules/ip6/datapath/meson.build
+++ b/modules/ip6/datapath/meson.build
@@ -13,5 +13,6 @@ src += files(
   'ndp_na_input.c',
   'ndp_ns_input.c',
   'ndp_ns_output.c',
+  'ndp_rs_input.c',
 )
 inc += include_directories('.')
diff --git a/modules/ip6/datapath/ndp_rs_input.c b/modules/ip6/datapath/ndp_rs_input.c
new file mode 100644
index 00000000..1724757c
--- /dev/null
+++ b/modules/ip6/datapath/ndp_rs_input.c
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) 2025 Christophe Fontaine
+
+#include <gr_control_output.h>
+#include <gr_graph.h>
+#include <gr_icmp6.h>
+#include <gr_ip6_control.h>
+#include <gr_ip6_datapath.h>
+#include <gr_log.h>
+#include <gr_mbuf.h>
+#include <gr_trace.h>
+
+#include <rte_byteorder.h>
+#include <rte_ether.h>
+#include <rte_graph_worker.h>
+#include <rte_ip6.h>
+
+enum {
+	CONTROL,
+	INVAL,
+	EDGE_COUNT,
+};
+
+static uint16_t ndp_rs_input_process(
+	struct rte_graph *graph,
+	struct rte_node *node,
+	void **objs,
+	uint16_t nb_objs
+) {
+	struct control_output_mbuf_data *co;
+	struct ip6_local_mbuf_data *d;
+	struct rte_mbuf *mbuf;
+	struct icmp6 *icmp6;
+	rte_edge_t next;
+
+#define ASSERT_NDP(condition)                                                                      \
+	do {                                                                                       \
+		if (!(condition)) {                                                                \
+			next = INVAL;                                                              \
+			goto next;                                                                 \
+		}                                                                                  \
+	} while (0)
+
+	for (uint16_t i = 0; i < nb_objs; i++) {
+		mbuf = objs[i];
+
+		d = ip6_local_mbuf_data(mbuf);
+		icmp6 = rte_pktmbuf_mtod(mbuf, struct icmp6 *);
+
+		// Validation of Router Solicitations
+		// https://www.rfc-editor.org/rfc/rfc4861#section-6.1.1
+		//
+		// - The IP Hop Limit field has a value of 255, i.e., the packet
+		//   could not possibly have been forwarded by a router.
+		ASSERT_NDP(d->hop_limit == 255);
+		// - ICMP Checksum is valid. (already checked in icmp6_input)
+		//
+		// - ICMP Code is 0.
+		ASSERT_NDP(icmp6->code == 0);
+		// - ICMP length (derived from the IP length) is 8 or more octets.
+		ASSERT_NDP(d->len >= 8);
+
+		next = CONTROL;
+		co = control_output_mbuf_data(mbuf);
+		co->callback = ndp_router_sollicit_input_cb;
+next:
+		if (gr_mbuf_is_traced(mbuf))
+			gr_mbuf_trace_add(mbuf, node, 0);
+		rte_node_enqueue_x1(graph, node, next, mbuf);
+	}
+
+	return nb_objs;
+}
+
+static struct rte_node_register node = {
+	.name = "ndp_rs_input",
+	.process = ndp_rs_input_process,
+	.nb_edges = EDGE_COUNT,
+	.next_nodes = {
+		[CONTROL] = "control_output",
+		[INVAL] = "ndp_rs_input_inval",
+	},
+};
+
+static struct gr_node_info info = {
+	.node = &node,
+	.trace_format = (gr_trace_format_cb_t)trace_icmp6_format,
+};
+
+GR_NODE_REGISTER(info);
+
+GR_DROP_REGISTER(ndp_rs_input_inval);
diff --git a/smoke/ip6_rs_ra_test.sh b/smoke/ip6_rs_ra_test.sh
new file mode 100755
index 00000000..05551c12
--- /dev/null
+++ b/smoke/ip6_rs_ra_test.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright (c) 2025 Christophe Fontaine
+
+. $(dirname $0)/_init.sh
+
+p1=${run_id}1
+
+grcli add interface port $p1 devargs net_tap0,iface=$p1 mac d2:f0:0c:ba:a4:11
+grcli add ip6 address fd00:ba4:1::1/64 iface $p1
+
+for n in 1; do
+	p=$run_id$n
+	ip netns add $p
+	echo ip netns del $p >> $tmp/cleanup
+	ip link set $p netns $p
+	ip -n $p link set $p address d2:ad:ca:ca:a4:1$n
+	ip -n $p link set $p up
+	ip -n $p addr add fd00:ba4:$n::2/64 dev $p
+	ip -n $p addr show
+done
+
+sleep 3  # wait for DAD
+
+ip netns exec $p1 rdisc6 $p1

From ebcf795e39243ee5848fdec5b58cf979ba6a0f50 Mon Sep 17 00:00:00 2001
From: Christophe Fontaine <cfontain@redhat.com>
Date: Fri, 20 Dec 2024 07:38:35 +0000
Subject: [PATCH 6/6] ip6: router advertisement configuration

Add configuration knobs for RA messages.
Instead of a hardcoded periodic message, allow a per-interface
configuration.

Signed-off-by: Christophe Fontaine <cfontain@redhat.com>
---
 modules/ip6/api/gr_ip6.h            |  23 ++++
 modules/ip6/cli/ip.h                |   3 +
 modules/ip6/cli/meson.build         |   1 +
 modules/ip6/cli/router_advert.c     | 134 +++++++++++++++++++++++
 modules/ip6/control/router_advert.c | 160 ++++++++++++++++++++++------
 5 files changed, 286 insertions(+), 35 deletions(-)
 create mode 100644 modules/ip6/cli/router_advert.c

diff --git a/modules/ip6/api/gr_ip6.h b/modules/ip6/api/gr_ip6.h
index 01137176..c34b1d1b 100644
--- a/modules/ip6/api/gr_ip6.h
+++ b/modules/ip6/api/gr_ip6.h
@@ -132,9 +132,15 @@ struct gr_ip6_addr_list_resp {
 	struct gr_ip6_ifaddr addrs[/* n_addrs */];
 };
 
+// router advertisement ////////////////////////////////////////////////////////
 #define GR_IP6_IFACE_RA_SET REQUEST_TYPE(GR_IP6_MODULE, 0x0030)
 struct gr_ip6_ra_set_req {
 	uint16_t iface_id;
+	uint16_t set_interval : 1;
+	uint16_t set_lifetime : 1;
+
+	uint16_t interval;
+	uint16_t lifetime;
 };
 // struct gr_ip6_ra_set_resp { };
 
@@ -143,4 +149,21 @@ struct gr_ip6_ra_clear_req {
 	uint16_t iface_id;
 };
 // struct gr_ip6_ra_clear_resp { };
+
+#define GR_IP6_IFACE_RA_SHOW REQUEST_TYPE(GR_IP6_MODULE, 0x0032)
+struct gr_ip6_ra_show_req {
+	uint16_t iface_id;
+};
+
+struct gr_ip6_ra_conf {
+	bool enabled;
+	uint16_t iface_id;
+	uint16_t interval;
+	uint16_t lifetime;
+};
+
+struct gr_ip6_ra_show_resp {
+	uint16_t n_ras;
+	struct gr_ip6_ra_conf ras[];
+};
 #endif
diff --git a/modules/ip6/cli/ip.h b/modules/ip6/cli/ip.h
index e99433ec..7cc04474 100644
--- a/modules/ip6/cli/ip.h
+++ b/modules/ip6/cli/ip.h
@@ -9,5 +9,8 @@
 #define IP6_ADD_CTX(root) CLI_CONTEXT(root, CTX_ADD, CTX_ARG("ip6", "Create IPv6 stack elements."))
 #define IP6_DEL_CTX(root) CLI_CONTEXT(root, CTX_DEL, CTX_ARG("ip6", "Delete IPv6 stack elements."))
 #define IP6_SHOW_CTX(root) CLI_CONTEXT(root, CTX_SHOW, CTX_ARG("ip6", "Show IPv6 stack details."))
+#define IP6_SET_CTX(root) CLI_CONTEXT(root, CTX_SET, CTX_ARG("ip6", "Set IPv6 stack elements."))
+#define IP6_CLEAR_CTX(root)                                                                        \
+	CLI_CONTEXT(root, CTX_CLEAR, CTX_ARG("ip6", "Clear IPv6 stack elements."))
 
 #endif
diff --git a/modules/ip6/cli/meson.build b/modules/ip6/cli/meson.build
index 69c41354..806d7726 100644
--- a/modules/ip6/cli/meson.build
+++ b/modules/ip6/cli/meson.build
@@ -5,4 +5,5 @@ cli_src += files(
   'address.c',
   'nexthop.c',
   'route.c',
+  'router_advert.c',
 )
diff --git a/modules/ip6/cli/router_advert.c b/modules/ip6/cli/router_advert.c
new file mode 100644
index 00000000..447877de
--- /dev/null
+++ b/modules/ip6/cli/router_advert.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: BSD-3-Clause
+// Copyright (c) 2025 Christophe Fontaine
+
+#include "ip.h"
+
+#include <gr_api.h>
+#include <gr_cli.h>
+#include <gr_cli_iface.h>
+#include <gr_ip6.h>
+#include <gr_net_types.h>
+#include <gr_table.h>
+
+#include <ecoli.h>
+#include <libsmartcols.h>
+
+#include <errno.h>
+
+static cmd_status_t ra_show(const struct gr_api_client *c, const struct ec_pnode *p) {
+	struct libscols_table *table = scols_new_table();
+	struct gr_ip6_ra_show_resp *resp;
+	struct gr_ip6_ra_show_req req;
+	struct gr_iface iface;
+	void *resp_ptr = NULL;
+
+	if (!iface_from_name(c, arg_str(p, "IFACE"), &iface))
+		req.iface_id = iface.id;
+	else
+		req.iface_id = 0;
+
+	if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SHOW, sizeof(req), &req, &resp_ptr) < 0)
+		return CMD_ERROR;
+	resp = resp_ptr;
+
+	scols_table_new_column(table, "IFACE", 0, 0);
+	scols_table_new_column(table, "RA", 0, 0);
+	scols_table_new_column(table, "interval", 0, 0);
+	scols_table_new_column(table, "lifetime", 0, 0);
+	scols_table_set_column_separator(table, "  ");
+
+	for (uint16_t i = 0; i < resp->n_ras; i++) {
+		struct libscols_line *line = scols_table_new_line(table, NULL);
+		if (iface_from_id(c, resp->ras[i].iface_id, &iface) == 0)
+			scols_line_sprintf(line, 0, "%s", iface.name);
+		else
+			scols_line_sprintf(line, 0, "%u", resp->ras[i].iface_id);
+		scols_line_sprintf(line, 1, "%u", resp->ras[i].enabled);
+		scols_line_sprintf(line, 2, "%u", resp->ras[i].interval);
+		scols_line_sprintf(line, 3, "%u", resp->ras[i].lifetime);
+	}
+
+	scols_print_table(table);
+	scols_unref_table(table);
+	free(resp_ptr);
+	return CMD_SUCCESS;
+}
+
+static cmd_status_t ra_set(const struct gr_api_client *c, const struct ec_pnode *p) {
+	struct gr_ip6_ra_set_req req = {0};
+	struct gr_iface iface;
+
+	if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0)
+		return CMD_ERROR;
+
+	req.iface_id = iface.id;
+	if (!arg_u16(p, "IT", &req.interval))
+		req.set_interval = 1;
+
+	if (!arg_u16(p, "LT", &req.lifetime))
+		req.set_lifetime = 1;
+
+	if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_SET, sizeof(req), &req, NULL) < 0)
+		return CMD_ERROR;
+	return CMD_SUCCESS;
+}
+
+static cmd_status_t ra_clear(const struct gr_api_client *c, const struct ec_pnode *p) {
+	struct gr_ip6_ra_clear_req req;
+	struct gr_iface iface;
+
+	if (iface_from_name(c, arg_str(p, "IFACE"), &iface) < 0)
+		return CMD_ERROR;
+
+	req.iface_id = iface.id;
+	if (gr_api_client_send_recv(c, GR_IP6_IFACE_RA_CLEAR, sizeof(req), &req, NULL) < 0)
+		return CMD_ERROR;
+	return CMD_SUCCESS;
+}
+
+static int ctx_init(struct ec_node *root) {
+	int ret;
+
+	ret = CLI_COMMAND(
+		IP6_SHOW_CTX(root),
+		"router-advert [IFACE]",
+		ra_show,
+		"Show router advertisement configuration",
+		with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL))
+	);
+	if (ret < 0)
+		return ret;
+
+	ret = CLI_COMMAND(
+		IP6_SET_CTX(root),
+		"router-advert IFACE [interval IT] [lifetime LT]",
+		ra_set,
+		"Set router advertisement parameters",
+		with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL)),
+		with_help("Interval", ec_node_uint("IT", 0, UINT16_MAX - 1, 10)),
+		with_help("Life time", ec_node_uint("LT", 0, UINT16_MAX - 1, 10))
+	);
+	if (ret < 0)
+		return ret;
+
+	ret = CLI_COMMAND(
+		IP6_CLEAR_CTX(root),
+		"router-advert IFACE",
+		ra_clear,
+		"Disable router advertisement and reset parameters",
+		with_help("Interface name.", ec_node_dyn("IFACE", complete_iface_names, NULL))
+	);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static struct gr_cli_context ctx = {
+	.name = "ipv6 router-advert",
+	.init = ctx_init,
+};
+
+static void __attribute__((constructor, used)) init(void) {
+	register_context(&ctx);
+}
diff --git a/modules/ip6/control/router_advert.c b/modules/ip6/control/router_advert.c
index db7f0885..de30b658 100644
--- a/modules/ip6/control/router_advert.c
+++ b/modules/ip6/control/router_advert.c
@@ -5,7 +5,6 @@
 #include <gr_control_input.h>
 #include <gr_iface.h>
 #include <gr_ip6.h>
-#include <gr_ip6_control.h>
 #include <gr_ip6_datapath.h>
 #include <gr_log.h>
 #include <gr_mempool.h>
@@ -19,33 +18,111 @@
 #include <rte_mbuf.h>
 #include <rte_mempool.h>
 
+#define RA_DEFAULT_INTERVAL 600
+#define RA_DEFAULT_LIFETIME 1800
+
 struct ra_ctx {
 	struct rte_mempool *mp;
-	struct event *timer;
 };
 
 static struct ra_ctx ra_ctx;
 static control_input_t ra_output;
+static struct event_base *ev_base;
+
+struct ra_iface_conf {
+	struct event *timer;
+	bool enabled;
+	uint16_t interval;
+	uint16_t lifetime;
+};
+
+static struct ra_iface_conf ra_conf[MAX_IFACES];
 
 static struct api_out iface_ra_set(const void *request, void ** /*response*/) {
 	const struct gr_ip6_ra_set_req *req = request;
+
 	if (iface_from_id(req->iface_id) == NULL)
 		return api_out(errno, 0);
 
+	if (req->set_interval)
+		ra_conf[req->iface_id].interval = req->interval;
+	if (req->set_lifetime)
+		ra_conf[req->iface_id].lifetime = req->lifetime;
+
+	event_add(
+		ra_conf[req->iface_id].timer,
+		&(struct timeval) {.tv_sec = ra_conf[req->iface_id].interval}
+	);
+	event_active(ra_conf[req->iface_id].timer, 0, 0);
 	return api_out(0, 0);
 }
 
 static struct api_out iface_ra_clear(const void *request, void ** /*response*/) {
 	const struct gr_ip6_ra_clear_req *req = request;
+
 	if (iface_from_id(req->iface_id) == NULL)
 		return api_out(errno, 0);
 
+	event_del(ra_conf[req->iface_id].timer);
+	ra_conf[req->iface_id].interval = RA_DEFAULT_INTERVAL;
+	ra_conf[req->iface_id].lifetime = RA_DEFAULT_LIFETIME;
+
 	return api_out(0, 0);
 }
 
+static struct api_out iface_ra_show(const void *request, void **response) {
+	const struct gr_ip6_ra_show_req *req = request;
+	struct gr_ip6_ra_show_resp *resp;
+	uint16_t iface_id, n_ras;
+	struct hoplist *addrs;
+	bool show_all = false;
+	size_t len;
+
+	if (req->iface_id == 0)
+		show_all = true;
+	else if (iface_from_id(req->iface_id) == NULL)
+		return api_out(errno, 0);
+
+	n_ras = 0;
+	for (iface_id = 0; iface_id < MAX_IFACES; iface_id++) {
+		addrs = ip6_addr_get_all(iface_id);
+		if (addrs == NULL || gr_vec_len(addrs) == 0)
+			continue;
+		if (show_all == false && iface_id != req->iface_id)
+			continue;
+		n_ras++;
+	}
+
+	len = sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf);
+	resp = calloc(1, sizeof(*resp) + n_ras * sizeof(struct gr_ip6_ra_conf));
+	if (!resp)
+		return api_out(ENOMEM, 0);
+	resp->n_ras = n_ras;
+	n_ras = 0;
+	for (uint16_t iface_id = 0; iface_id < MAX_IFACES; iface_id++) {
+		addrs = ip6_addr_get_all(iface_id);
+		if (addrs == NULL || gr_vec_len(addrs) == 0)
+			continue;
+		if (show_all == false && iface_id != req->iface_id)
+			continue;
+
+		resp->ras[n_ras].iface_id = iface_id;
+		resp->ras[n_ras].enabled = event_pending(
+			ra_conf[iface_id].timer, EV_TIMEOUT | EV_READ | EV_WRITE | EV_SIGNAL, 0
+		);
+		resp->ras[n_ras].interval = ra_conf[iface_id].interval;
+		resp->ras[n_ras].lifetime = ra_conf[iface_id].lifetime;
+		n_ras++;
+	}
+
+	*response = resp;
+	return api_out(0, len);
+}
+
 void ndp_router_sollicit_input_cb(struct rte_mbuf *m) {
+	uint16_t iface_id = mbuf_data(m)->iface->id;
 	rte_pktmbuf_free(m);
-	event_active(ra_ctx.timer, 0, 0);
+	event_active(ra_conf[iface_id].timer, 0, 0);
 }
 
 static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) {
@@ -72,7 +149,7 @@ static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) {
 	ra->cur_hoplim = IP6_DEFAULT_HOP_LIMIT; // Default TTL for this network
 	ra->managed_addr = 0; // DHCPv6 is available
 	ra->other_config = 0; // DNS available, ...
-	ra->lifetime = RTE_BE16(0); // Not a default router
+	ra->lifetime = rte_cpu_to_be_16(ra_conf[iface_id].lifetime);
 	ra->reachable_time = RTE_BE16(0);
 	ra->retrans_timer = RTE_BE16(0);
 
@@ -92,53 +169,42 @@ static void build_ra_packet(struct rte_mbuf *m, struct rte_ipv6_addr *srcv6) {
 	icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6);
 }
 
-static void send_ra_cb(evutil_socket_t, short /*what*/, void * /*priv*/) {
-	struct iface *iface = NULL;
+static void send_ra_cb(evutil_socket_t, short /*what*/, void *priv) {
+	struct iface *iface = priv;
+	struct hoplist *hl;
+	struct nexthop *nh;
 	struct rte_mbuf *m;
 
-	while ((iface = iface_next(GR_IFACE_TYPE_UNDEF, iface)) != NULL) {
-		struct hoplist *hl = ip6_addr_get_all(iface->id);
-		if (hl == NULL)
+	if ((hl = ip6_addr_get_all(iface->id)) == NULL)
+		return;
+
+	gr_vec_foreach (nh, hl->nh) {
+		struct rte_ipv6_addr ip = nh->ipv6;
+		if (nh->family != AF_INET6)
+			continue;
+		if (!rte_ipv6_addr_is_linklocal(&ip))
 			continue;
-		for (unsigned i = 0; i < gr_vec_len(hl); i++) {
-			struct nexthop *nh = hl->nh[i];
-			struct rte_ipv6_addr ip = nh->ipv6;
-			if (nh->family != AF_INET6)
-				continue;
-			if (!rte_ipv6_addr_is_linklocal(&ip))
-				continue;
-
-			if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) {
-				LOG(ERR, "rte_pktmbuf_alloc");
-				return;
-			}
-			mbuf_data(m)->iface = iface;
-			build_ra_packet(m, &ip);
-			post_to_stack(ra_output, m);
+		if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) {
+			LOG(ERR, "rte_pktmbuf_alloc");
+			return;
 		}
+		mbuf_data(m)->iface = iface;
+		build_ra_packet(m, &ip);
+		post_to_stack(ra_output, m);
 	}
 }
 
-static void ra_init(struct event_base *ev_base) {
+static void ra_init(struct event_base *base) {
+	ev_base = base;
 	ra_output = gr_control_input_register_handler("ip6_output", true);
 	ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512);
 	if (ra_ctx.mp == NULL) {
 		ABORT("gr_pktmbuf_pool_get ENOMEM");
 	}
-
-	ra_ctx.timer = event_new(ev_base, -1, EV_PERSIST, send_ra_cb, NULL);
-	if (ra_ctx.timer == NULL) {
-		ABORT("event_new() failed");
-	}
-
-	if (event_add(ra_ctx.timer, &(struct timeval) {.tv_sec = 600}) < 0) {
-		ABORT("event_add() failed");
-	}
 }
 
 static void ra_fini(struct event_base * /*ev_base*/) {
 	gr_pktmbuf_pool_release(ra_ctx.mp, 512);
-	event_free(ra_ctx.timer);
 }
 
 static struct gr_module ra_module = {
@@ -160,8 +226,32 @@ static struct gr_api_handler ra_clear_handler = {
 	.callback = iface_ra_clear,
 };
 
+static struct gr_api_handler ra_show_handler = {
+	.name = "show interface ra",
+	.request_type = GR_IP6_IFACE_RA_SHOW,
+	.callback = iface_ra_show,
+};
+
+static void iface_event_handler(iface_event_t event, struct iface *iface) {
+	if (event == IFACE_EVENT_POST_ADD) {
+		ra_conf[iface->id].interval = RA_DEFAULT_INTERVAL;
+		ra_conf[iface->id].lifetime = RA_DEFAULT_LIFETIME;
+
+		ra_conf[iface->id].timer = event_new(ev_base, -1, EV_PERSIST, send_ra_cb, iface);
+	} else if (event == IFACE_EVENT_PRE_REMOVE) {
+		event_free(ra_conf[iface->id].timer);
+		ra_conf[iface->id].timer = NULL;
+	}
+}
+
+static struct iface_event_handler iface_event_address_handler = {
+	.callback = iface_event_handler,
+};
+
 RTE_INIT(router_advertisement_init) {
 	gr_register_module(&ra_module);
 	gr_register_api_handler(&ra_set_handler);
 	gr_register_api_handler(&ra_clear_handler);
+	gr_register_api_handler(&ra_show_handler);
+	iface_event_register_handler(&iface_event_address_handler);
 }