Skip to content

Commit 0b0c79d

Browse files
committed
ospfv3: add packet serializer incl. checksumMode and packet format fixes
Add Ospfv3PacketSerializer (RFC 5340 Appendix A), modelled on the OSPFv2 serializer: the 16-octet common header, the Hello, Database Description, Link State Request, Link State Update and Link State Acknowledgement packets, the 20-octet LSA header, and the Router, Network, Inter-Area-Prefix, Link and Intra-Area-Prefix LSA bodies (the LSA types the model originates), plus the fixed-size address-prefix encodings. Byte counts match the lengths the OSPFv3 implementation assigns (Ospfv3Common.h). The address slots are written for whatever the L3Address fields hold (IPv6, IPv4-instance, or unset) so both address families serialize. Wire it into the version-dispatching ospf::OspfPacketSerializer (the v3 case was a TODO stub). Add tests/unit/OSPFv3_serializer.test: a byte-exact serialize->deserialize round-trip for every packet type and every supported LSA. Verified that the serialize path also runs cleanly (computed checksums/FCS) across all examples/ospfv3/* simulations; existing OSPFv3 fingerprints are unchanged. Fix: originate Router-LSAs without uninitialized gap entries Ospfv3Area::originateRouterLSA() indexed the Router-LSA's router entries by the interface index i (setRoutersArraySize(i+1); setRouters(i, ...)). When an interface contributes no entry (e.g. a host-facing or down interface), this left an uninitialized gap entry in the array; and when a point-to-point link had several full neighbors, each overwrote the same index i. The gap entries carried garbage (e.g. a non-deterministic neighborRouterID), which corrupted the SPF input and made the serialized Router-LSA bytes non-deterministic. Append each entry at the current array size instead, so the array holds exactly the real entries with no gaps and no overwrites. Fix: deterministic Router-LSA / retransmitted-LSU serialization Two packet-length bugs left OSPFv3 ~tND non-deterministic, so the ospfv3 examples could only carry tplx;~tNl until now: 1. Ospfv3Area::originateRouterLSA() never set the LSA's lsaLength field, unlike every other originate*LSA. The deserializer derives the router-entry count from lsaLength, so a Router-LSA serialized with lsaLength=0 was malformed and self-inconsistent. Set it from calculateLSASize(). 2. Ospfv3Neighbor::retransmitUpdatePacket() based the LSU length on OSPFV3_HEADER_LENGTH + OSPFV3_LSA_HEADER_LENGTH (16+20) instead of the OSPF header plus the 4-octet "# LSAs" field (16+4), as prepareUpdatePacket() does. This overcounted chunkLength by 16 octets, so the chunk serializer padded every retransmitted LSU with 16 uninitialized, run-to-run-varying bytes -- the actual source of the ~tND drift. Also set the packet-length header field, which this builder left at 0. Make OSPFv3 emit standards-compliant, dissectable wire packets: - Address prefixes: encode as the variable-length ((PrefixLength+31)/32) 32-bit words (RFC 5340 A.4.1) instead of a fixed 16-octet slot. The fixed encoding made the serialized length exceed the declared chunk length, so debug builds aborted on the chunk-length check for every prefix-bearing LSA (e.g. multi-area examples in the statistical tests). - LSA header LS Type: emit the full 16-bit value with the U bit and S1/S2 flooding-scope bits (RFC 5340 A.4.2.1): Router=0x2001, Network=0x2002, Inter-Area-Prefix=0x2003, AS-External=0x4005, Link=0x0008, etc. - OSPF packet checksum: compute the Internet checksum over the IPv6 upper-layer pseudo-header followed by the packet (RFC 5340 2.5) in Ospfv3Process::sendPacket, where the addresses are known. - LSA "LS checksum": add a Fletcher checksum utility (RFC 1008) and stamp each self-originated LSA at origination (RFC 2328 12.1.7); received LSAs already carry the originator's checksum. - Packet framing: set the OSPF header Packet Length on LS Request and LS Acknowledgement packets, set the LSA count on retransmitted LS Updates, and accumulate (instead of resetting per LSA) the packet length in prepareUpdatePacket so multi-LSA updates are sized correctly. Verified with tshark: a captured multi-area trace (2155 packets, all message and LSA types) dissects with zero malformed packets and all packet and LSA checksums valid. Gate packet and LSA checksum computation behind a checksumMode parameter Follow the OSPFv2 / UDP / ICMPv6 convention instead of computing the OSPFv3 packet and LSA checksums unconditionally: - Add a checksumMode parameter (@enum("declared", "computed"), default "declared") to the Ospfv3 module, threaded through Ospfv3Splitter to each Ospfv3Process. - Move the checksum logic into Ospfv3Checksum (setOspfChecksum / setLsaChecksum), mirroring ospfv2/Ospfv2Checksum. In "declared" mode the field carries a sentinel (no per-send serialization cost); in "computed" mode it holds the real value: the RFC 5340 2.5 packet checksum over the IPv6 pseudo-header, and the RFC 2328 12.1.7 Fletcher LS checksum. The packet checksum still has to be done in the module (Ospfv3Process::sendPacket) since it needs the addresses, as in UDP/TCP.
1 parent 0c88925 commit 0b0c79d

17 files changed

Lines changed: 1210 additions & 9 deletions

src/inet/common/checksum/Checksum.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,25 @@ uint16_t internetChecksum(const uint8_t *addr, size_t count, uint32_t sum)
184184
return ~sum16;
185185
}
186186

187+
uint16_t fletcherChecksum(const uint8_t *buf, size_t length, size_t checksumPos)
188+
{
189+
// Fletcher checksum over GF(255), per ISO 8473 / RFC 1008, as used for OSPF LS checksums.
190+
// The two check octets are chosen so that the running sums (c0, c1) come out to zero when
191+
// recomputed over the whole region including the checksum field.
192+
int32_t c0 = 0, c1 = 0;
193+
for (size_t i = 0; i < length; i++) {
194+
c0 = (c0 + buf[i]) % 255;
195+
c1 = (c1 + c0) % 255;
196+
}
197+
int32_t x = ((int32_t)(length - checksumPos - 1) * c0 - c1) % 255;
198+
if (x <= 0)
199+
x += 255;
200+
int32_t y = 510 - c0 - x;
201+
if (y > 255)
202+
y -= 255;
203+
return (uint16_t)(((x & 0xFF) << 8) | (y & 0xFF));
204+
}
205+
187206
inline uint32_t swapBytes(uint32_t crc)
188207
{
189208
return (crc >> 24) | ((crc >> 8) & 0x0000FF00) | ((crc << 8) & 0x00FF0000) | (crc << 24);

src/inet/common/checksum/Checksum.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ namespace inet {
2626
*/
2727
uint16_t internetChecksum(const uint8_t *addr, size_t count, uint32_t sum = 0);
2828

29+
/**
30+
* Computes the Fletcher checksum (ISO 8473 / RFC 1008) as used by IS-IS and the
31+
* OSPF LSA "LS Checksum" field. Returns the two 8-bit check octets packed big-endian
32+
* (first octet in the high byte). The two checksum octets, located at [checksumPos] and
33+
* [checksumPos+1] within the checksummed region, must be set to 0 in @p buf before calling;
34+
* the caller then stores the returned value into them. @p length is the size of the
35+
* checksummed region (which, for an OSPF LSA, excludes the leading LS Age field).
36+
*/
37+
uint16_t fletcherChecksum(const uint8_t *buf, size_t length, size_t checksumPos);
38+
2939
/**
3040
* Computes the FCS value used in 802.3 Ethernet and 802.11 Wifi as CRC32,
3141
* with the order of bytes reversed.

src/inet/routing/ospf_common/OspfPacketSerializer.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
#ifdef INET_WITH_OSPFv2
1313
#include "inet/routing/ospfv2/Ospfv2PacketSerializer.h"
1414
#endif // #ifdef INET_WITH_OSPFv2
15+
#ifdef INET_WITH_OSPFv3
16+
#include "inet/routing/ospfv3/Ospfv3PacketSerializer.h"
17+
#endif // #ifdef INET_WITH_OSPFv3
1518

1619
namespace inet {
1720
namespace ospf {
@@ -37,8 +40,8 @@ const Ptr<Chunk> OspfPacketSerializer::deserialize(MemoryInputStream& stream) co
3740
#endif // #ifdef INET_WITH_OSPFv2
3841
#ifdef INET_WITH_OSPFv3
3942
case 3:
40-
// TODO stream.seek(startPos);
41-
// TODO return ospfv3::Ospfv3PacketSerializer().deserialize(stream);
43+
stream.seek(startPos);
44+
return ospfv3::Ospfv3PacketSerializer().deserialize(stream);
4245
#endif // #ifdef INET_WITH_OSPFv3
4346
default: {
4447
auto ospfPacket = makeShared<OspfPacketBase>();

src/inet/routing/ospfv3/Ospfv3.ned

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ module Ospfv3 like IOspf
2121
string interfaceTableModule; // The path to the InterfaceTable module
2222
string routingTableModule;
2323
string routingTableModule6;
24+
string checksumMode @enum("declared", "computed") = default("declared"); // checksum mode for outgoing OSPFv3 packets and originated LSAs; set to "computed" for RFC-correct, Wireshark-verifiable checksums
2425
*.interfaceTableModule = default(absPath(this.interfaceTableModule));
2526
*.routingTableModule = default(absPath(this.routingTableModule));
2627
*.routingTableModule6 = default(absPath(this.routingTableModule6));
28+
*.checksumMode = default(this.checksumMode);
2729
@display("bgb=510,238;i=block/network2");
2830
gates:
2931
input ipIn @labels(Ipv6ControlInfo/up);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//
2+
// Copyright (C) 2026 OpenSim Ltd.
3+
//
4+
// SPDX-License-Identifier: LGPL-3.0-or-later
5+
//
6+
7+
8+
#include "inet/routing/ospfv3/Ospfv3Checksum.h"
9+
10+
#include "inet/common/MemoryOutputStream.h"
11+
#include "inet/common/checksum/Checksum.h"
12+
#include "inet/networklayer/common/IpProtocolId_m.h"
13+
#include "inet/routing/ospfv3/Ospfv3PacketSerializer.h"
14+
15+
namespace inet {
16+
namespace ospfv3 {
17+
18+
void setOspfChecksum(const Ptr<Ospfv3Packet>& ospfPacket, ChecksumMode checksumMode,
19+
const Ipv6Address& srcAddress, const Ipv6Address& destAddress)
20+
{
21+
ospfPacket->setChecksumMode(checksumMode);
22+
switch (checksumMode) {
23+
case CHECKSUM_DECLARED_CORRECT:
24+
ospfPacket->setChecksum(0xC00D);
25+
break;
26+
case CHECKSUM_DECLARED_INCORRECT:
27+
ospfPacket->setChecksum(0xBAAD);
28+
break;
29+
case CHECKSUM_COMPUTED: {
30+
ospfPacket->setChecksum(0);
31+
MemoryOutputStream ospfStream;
32+
Chunk::serialize(ospfStream, ospfPacket);
33+
const auto& ospfBytes = ospfStream.getData();
34+
// RFC 5340 2.5: the checksum is the standard Internet checksum over the IPv6
35+
// upper-layer pseudo-header followed by the OSPF packet (Checksum field = 0).
36+
MemoryOutputStream stream;
37+
stream.writeIpv6Address(srcAddress);
38+
stream.writeIpv6Address(destAddress);
39+
stream.writeUint32Be(ospfBytes.size());
40+
stream.writeUint16Be(0);
41+
stream.writeByte(0);
42+
stream.writeByte(IP_PROT_OSPF);
43+
stream.writeBytes(ospfBytes);
44+
ospfPacket->setChecksum(internetChecksum(stream.getData()));
45+
break;
46+
}
47+
default:
48+
throw cRuntimeError("Unknown CHECKSUM mode: %d", (int)checksumMode);
49+
}
50+
}
51+
52+
void setLsaChecksum(Ospfv3Lsa& lsa, ChecksumMode checksumMode)
53+
{
54+
auto& lsaHeader = lsa.getHeaderForUpdate();
55+
lsaHeader.setLsChecksumMode(checksumMode);
56+
switch (checksumMode) {
57+
case CHECKSUM_DECLARED_CORRECT:
58+
lsaHeader.setLsaChecksum(0xC00D);
59+
break;
60+
case CHECKSUM_DECLARED_INCORRECT:
61+
lsaHeader.setLsaChecksum(0xBAAD);
62+
break;
63+
case CHECKSUM_COMPUTED: {
64+
// RFC 2328 12.1.7: Fletcher checksum of the LSA contents excepting the LS Age field.
65+
lsaHeader.setLsaChecksum(0);
66+
MemoryOutputStream stream;
67+
Ospfv3PacketSerializer::serializeLsa(stream, lsa);
68+
const auto& bytes = stream.getData();
69+
// Checksummed region runs from the LS Type field (offset 2, skipping the 2-octet LS
70+
// Age) to the end; the two checksum octets sit at LSA offset 16, i.e. region offset 14.
71+
uint16_t checksum = fletcherChecksum(bytes.data() + 2, bytes.size() - 2, 14);
72+
lsaHeader.setLsaChecksum(checksum);
73+
break;
74+
}
75+
default:
76+
throw cRuntimeError("Unknown CHECKSUM mode: %d", (int)checksumMode);
77+
}
78+
}
79+
80+
} // namespace ospfv3
81+
} // namespace inet
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Copyright (C) 2026 OpenSim Ltd.
3+
//
4+
// SPDX-License-Identifier: LGPL-3.0-or-later
5+
//
6+
7+
8+
#ifndef __INET_OSPFV3CHECKSUM_H
9+
#define __INET_OSPFV3CHECKSUM_H
10+
11+
#include "inet/common/checksum/ChecksumMode_m.h"
12+
#include "inet/networklayer/contract/ipv6/Ipv6Address.h"
13+
#include "inet/routing/ospfv3/Ospfv3Packet_m.h"
14+
15+
namespace inet {
16+
namespace ospfv3 {
17+
18+
// Sets the OSPFv3 packet "Checksum" header field according to checksumMode. For CHECKSUM_COMPUTED
19+
// it is the Internet checksum over the IPv6 upper-layer pseudo-header (source/destination address,
20+
// upper-layer packet length, next header = OSPF) followed by the entire packet, per RFC 5340 2.5.
21+
// (Computing the real value requires the addresses, so unlike a plain chunk serializer this is
22+
// done by the module at send time; the serializer just writes the resulting value.)
23+
INET_API void setOspfChecksum(const Ptr<Ospfv3Packet>& ospfPacket, ChecksumMode checksumMode,
24+
const Ipv6Address& srcAddress, const Ipv6Address& destAddress);
25+
26+
// Sets the LSA "LS Checksum" header field according to checksumMode. For CHECKSUM_COMPUTED it is
27+
// the Fletcher checksum over the LSA contents excepting the LS Age field, per RFC 2328 12.1.7.
28+
INET_API void setLsaChecksum(Ospfv3Lsa& lsa, ChecksumMode checksumMode);
29+
30+
} // namespace ospfv3
31+
} // namespace inet
32+
33+
#endif

0 commit comments

Comments
 (0)