From 76a52a4024476cd2a0058f92d036ccafd765b827 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sun, 12 May 2024 21:34:53 +0200 Subject: [PATCH 01/36] [pylibs] Add CCM-commissioning unit test; extend Commissioning unit test to include multi-hop commissioning. --- pylibs/otns/cli/OTNS.py | 22 +++++- pylibs/unittests/test_ccm_commissioning.py | 79 ++++++++++++++++++++++ pylibs/unittests/test_commissioning.py | 53 +++++++++++++-- 3 files changed, 148 insertions(+), 6 deletions(-) create mode 100755 pylibs/unittests/test_ccm_commissioning.py diff --git a/pylibs/otns/cli/OTNS.py b/pylibs/otns/cli/OTNS.py index 783bad51..b7e1676d 100644 --- a/pylibs/otns/cli/OTNS.py +++ b/pylibs/otns/cli/OTNS.py @@ -1033,18 +1033,38 @@ def joiner_start(self, nodeid: int, pwd: str) -> None: """ self.node_cmd(nodeid, f"joiner start {pwd}") + def ccm_joiner_start(self, nodeid: int) -> None: + """ + Start CCM joiner. + + :param nodeid: joiner node ID + """ + self.node_cmd(nodeid, f"joiner ccm") + def commissioner_joiner_add(self, nodeid: int, usr: str, pwd: str, timeout=None) -> None: """ Add joiner to commissioner. :param nodeid: commissioner node ID - :param usr: commissioning user + :param usr: joiner EUI-64 or discerner (id) or '*' for any joiners :param pwd: commissioning password :param timeout: commissioning session timeout """ timeout_s = f" {timeout}" if timeout is not None else "" self.node_cmd(nodeid, f"commissioner joiner add {usr} {pwd}{timeout_s}") + def commissioner_ccm_joiner_add(self, nodeid: int, usr: str, timeout=None) -> None: + """ + Add CCM joiner to commissioner. + + :param nodeid: commissioner node ID + :param usr: joiner EUI-64 or discerner (id) or '*' for any joiners + :param pwd: commissioning password + :param timeout: commissioning session timeout + """ + timeout_s = f" {timeout}" if timeout is not None else "" + self.node_cmd(nodeid, f"commissioner joiner add {usr} CCMCCM{timeout_s}") + def config_visualization(self, broadcast_message: bool = None, unicast_message: bool = None, ack_message: bool = None, router_table: bool = None, child_table: bool = None) \ -> Dict[str, bool]: diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py new file mode 100755 index 00000000..37a02b36 --- /dev/null +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024, The OTNS Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import tracemalloc +import unittest + +from OTNSTestCase import OTNSTestCase +from otns.cli import OTNS + +tracemalloc.start() + + +class CommissioningTests(OTNSTestCase): + + def setUp(self) -> None: + self.ns = OTNS(otns_args=['-ot-script', 'none', '-log', 'debug']) + self.ns.speed = float('inf') + + def setFirstNodeDataset(self, n1) -> None: + self.ns.node_cmd(n1, "dataset init new") + self.ns.node_cmd(n1, "dataset networkkey 00112233445566778899aabbccddeeff") # allow easy Wireshark dissecting + self.ns.node_cmd(n1, "dataset commit active") + + def testCommissioningOneHop(self): + ns = self.ns + ns.web() + ns.radiomodel = 'MIDisc' + + n1 = ns.add("br", radio_range = 110) + n2 = ns.add("router", radio_range = 110) + + self.setFirstNodeDataset(n1) + ns.ifconfig_up(n1) + ns.thread_start(n1) + self.go(35) + self.assertTrue(ns.get_state(n1) == "leader") + ns.commissioner_start(n1) + ns.commissioner_ccm_joiner_add(n1, "*") + + ns.ifconfig_up(n2) + ns.ccm_joiner_start(n2) + self.go(20) + ns.thread_start(n2) + self.go(100) + c = ns.counters() + print('counters', c) + joins = ns.joins() + print('joins', joins) + + ns.interactive_cli() + self.assertFormPartitions(1) + self.assertTrue(joins and joins[0][1] > 0) # assert join success + +if __name__ == '__main__': + unittest.main() diff --git a/pylibs/unittests/test_commissioning.py b/pylibs/unittests/test_commissioning.py index c364905c..c2fa7262 100755 --- a/pylibs/unittests/test_commissioning.py +++ b/pylibs/unittests/test_commissioning.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2020-2022, The OTNS Authors. +# Copyright (c) 2020-2024, The OTNS Authors. # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -40,6 +40,11 @@ def setUp(self) -> None: self.ns = OTNS(otns_args=['-ot-script', 'none', '-log', 'debug', '-pcap', 'wpan-tap']) self.ns.speed = float('inf') + def setFirstNodeDataset(self, n1) -> None: + self.ns.node_cmd(n1, "dataset init new") + self.ns.node_cmd(n1, "dataset networkkey 00112233445566778899aabbccddeeff") # allow easy Wireshark dissecting + self.ns.node_cmd(n1, "dataset commit active") + def testRawNoSetup(self): ns = self.ns ns.add("router") @@ -76,15 +81,13 @@ def testRawSetup(self): self.go(300) self.assertFormPartitions(1) - def testCommissioning(self): + def testCommissioningOneHop(self): ns = self.ns n1 = ns.add("router") n2 = ns.add("router") - ns.node_cmd(n1, "dataset init new") - ns.node_cmd(n1, "dataset") - ns.node_cmd(n1, "dataset commit active") + self.setFirstNodeDataset(n1) ns.ifconfig_up(n1) ns.thread_start(n1) self.go(35) @@ -104,6 +107,46 @@ def testCommissioning(self): self.assertFormPartitions(1) self.assertTrue(joins and joins[0][1] > 0) # assert join success + def testCommissioningThreeHop(self): + ns = self.ns + ns.radiomodel = 'MIDisc' + + n1 = ns.add("router", radio_range = 110) + n2 = ns.add("router", radio_range = 110) + n3 = ns.add("router", radio_range = 110) + n4 = ns.add("router", radio_range = 110) + + self.setFirstNodeDataset(n1) + ns.ifconfig_up(n1) + ns.thread_start(n1) + self.go(35) + self.assertTrue(ns.get_state(n1) == "leader") + ns.commissioner_start(n1) + ns.commissioner_joiner_add(n1, "*", "TEST123") + + ns.ifconfig_up(n2) + ns.joiner_start(n2, "TEST123") + self.go(20) + ns.thread_start(n2) + + ns.ifconfig_up(n3) + ns.joiner_start(n3, "TEST123") + self.go(20) + ns.thread_start(n3) + self.go(20) + + ns.ifconfig_up(n4) + ns.joiner_start(n4, "TEST123") + self.go(100) + ns.thread_start(n4) + self.go(100) + c = ns.counters() + print('counters', c) + joins = ns.joins() + print('joins', joins) + self.assertFormPartitions(1) + self.assertTrue(joins and joins[0][1] > 0) # assert join success + if __name__ == '__main__': unittest.main() From 2cd644471defe1c44e422131755f045ee6b08deb Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Mon, 13 May 2024 12:57:20 +0200 Subject: [PATCH 02/36] [pylibs] test extended to include normal and CCM joiner in one test. --- pylibs/unittests/OTNSTestCase.py | 6 +++++ pylibs/unittests/test_ccm_commissioning.py | 28 ++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/pylibs/unittests/OTNSTestCase.py b/pylibs/unittests/OTNSTestCase.py index 6e87d9b5..f64c3411 100644 --- a/pylibs/unittests/OTNSTestCase.py +++ b/pylibs/unittests/OTNSTestCase.py @@ -62,6 +62,12 @@ def assertFormPartitions(self, count: int): self.assertEqual(count, len(pars), f"Partitions count mismatch: expected {count}, but is {len(pars)}") self.assertTrue(0 not in pars, pars) + def assertFormPartitionsIgnoreOrphans(self, count: int): + pars = self.ns.partitions() + parsNoOrphans = [] + parsNoOrphans[:] = (value for value in pars if value != 0) + self.assertEqual(count, len(parsNoOrphans), f"Partitions count mismatch: expected {count}, but is {len(parsNoOrphans)}") + def assertNodeState(self, nodeid: int, state: str): cur_state = self.ns.get_state(nodeid) self.assertEqual(state, cur_state, f"Node {nodeid} state mismatch: expected {state}, but is {cur_state}") diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index 37a02b36..b1dfcb21 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -43,15 +43,18 @@ def setUp(self) -> None: def setFirstNodeDataset(self, n1) -> None: self.ns.node_cmd(n1, "dataset init new") self.ns.node_cmd(n1, "dataset networkkey 00112233445566778899aabbccddeeff") # allow easy Wireshark dissecting + self.ns.node_cmd(n1, "dataset securitypolicy 672 orcCR 3") # enable CCM-commissioning flag in secpolicy self.ns.node_cmd(n1, "dataset commit active") def testCommissioningOneHop(self): ns = self.ns ns.web() - ns.radiomodel = 'MIDisc' + ns.coaps_enable() + ns.radiomodel = 'MIDisc' # enforce strict line topologies for testing - n1 = ns.add("br", radio_range = 110) - n2 = ns.add("router", radio_range = 110) + n1 = ns.add("br", x = 100, y = 100, radio_range = 120) + n2 = ns.add("router", x = 100, y = 200, radio_range = 120) + n3 = ns.add("router", x = 200, y = 100, radio_range = 120) self.setFirstNodeDataset(n1) ns.ifconfig_up(n1) @@ -59,10 +62,11 @@ def testCommissioningOneHop(self): self.go(35) self.assertTrue(ns.get_state(n1) == "leader") ns.commissioner_start(n1) - ns.commissioner_ccm_joiner_add(n1, "*") + # n2 joins as regular joiner + ns.commissioner_joiner_add(n1, "*", "TEST123") ns.ifconfig_up(n2) - ns.ccm_joiner_start(n2) + ns.joiner_start(n2, "TEST123") self.go(20) ns.thread_start(n2) self.go(100) @@ -70,10 +74,24 @@ def testCommissioningOneHop(self): print('counters', c) joins = ns.joins() print('joins', joins) + self.assertFormPartitionsIgnoreOrphans(1) + self.assertTrue(joins and joins[0][1] > 0) # assert join success + # n3 joins as CCM joiner + ns.commissioner_ccm_joiner_add(n1, "*") + ns.ifconfig_up(n3) + ns.ccm_joiner_start(n3) + self.go(20) + ns.thread_start(n3) + self.go(100) + c = ns.counters() + print('counters', c) + joins = ns.joins() + print('joins', joins) ns.interactive_cli() self.assertFormPartitions(1) self.assertTrue(joins and joins[0][1] > 0) # assert join success + if __name__ == '__main__': unittest.main() From 42ac323559ba0bd134bd866d2397f417e0f0e22f Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 14 May 2024 11:07:37 +0200 Subject: [PATCH 03/36] Rebased to 'main' branch. Added event type for UdpToAil forwarding - sending the Udp pkt to simulator. --- dispatcher/dispatcher.go | 2 ++ event/event.go | 17 +++++++++++++++++ event/event_test.go | 10 ++++++++++ ot-rfsim/script/build_br | 7 +++++++ ot-rfsim/src/event-sim.c | 11 ++++++++++- ot-rfsim/src/event-sim.h | 12 +++++++++++- ot-rfsim/src/platform-rfsim.c | 17 ++++++++++++++++- ot-rfsim/src/platform-rfsim.h | 18 ++++++++++++++++++ ot-rfsim/src/system.c | 18 ++++++++++++++---- 9 files changed, 105 insertions(+), 7 deletions(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index d97f7bf0..431dc1cc 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -428,6 +428,8 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { logger.Debugf("%s socket disconnected.", node) d.setSleeping(node.Id) d.alarmMgr.SetTimestamp(node.Id, Ever) + case EventTypeUdpToAil: + logger.Warnf("FIXME got UdpToAil: %d", evt.UdpAilData.DestPort) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) diff --git a/event/event.go b/event/event.go index 8f1a58c3..7c00c24f 100644 --- a/event/event.go +++ b/event/event.go @@ -61,6 +61,7 @@ const ( EventTypeRadioRfSimParamSet EventType = 17 EventTypeRadioRfSimParamRsp EventType = 18 EventTypeLogWrite EventType = 19 + EventTypeUdpToAil EventType = 20 ) const ( @@ -87,6 +88,7 @@ type Event struct { RadioStateData RadioStateEventData NodeInfoData NodeInfoEventData RfSimParamData RfSimParamEventData + UdpAilData UdpAilEventData } // All ...EventData formats below only used by OT nodes supporting advanced @@ -121,6 +123,11 @@ type RfSimParamEventData struct { Value int32 } +const udpAilEventDataHeaderLen = 2 // from OT-RFSIM platform +type UdpAilEventData struct { + DestPort uint16 +} + /* RadioMessagePsduOffset is the offset of mPsdu data in a received OpenThread RadioMessage, from OT-RFSIM platform, radio.h. @@ -207,6 +214,8 @@ func (e *Event) Deserialize(data []byte) int { payloadOffset += nodeInfoEventDataHeaderLen case EventTypeRadioRfSimParamRsp: e.RfSimParamData = deserializeRfSimParamData(e.Data) + case EventTypeUdpToAil: + e.UdpAilData = deserializeUdpAilData(e.Data) default: break } @@ -263,6 +272,14 @@ func deserializeRfSimParamData(data []byte) RfSimParamEventData { return s } +func deserializeUdpAilData(data []byte) UdpAilEventData { + logger.AssertTrue(len(data) >= udpAilEventDataHeaderLen) + s := UdpAilEventData{ + DestPort: binary.LittleEndian.Uint16(data[0:2]), + } + return s +} + // Copy creates a (struct) copy of the Event. func (e *Event) Copy() Event { newEv := *e diff --git a/event/event_test.go b/event/event_test.go index 10e49400..8a3495a1 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -233,6 +233,16 @@ func TestDeserializeRfSimRspEvent(t *testing.T) { assert.Equal(t, int32(1234), ev.RfSimParamData.Value) } +func TestDeserializeUdpToAilEvent(t *testing.T) { + data, _ := hex.DecodeString("000000000000000013040000000000000002003316") + var ev Event + ev.Deserialize(data) + assert.True(t, 0 == ev.Delay) + assert.Equal(t, EventTypeUdpToAil, ev.Type) + assert.Equal(t, uint64(4), ev.MsgId) + assert.Equal(t, uint16(5683), ev.UdpAilData.DestPort) +} + func TestEventCopy(t *testing.T) { ev := &Event{ Type: EventTypeRadioRxDone, diff --git a/ot-rfsim/script/build_br b/ot-rfsim/script/build_br index 95eff881..b12b2e77 100755 --- a/ot-rfsim/script/build_br +++ b/ot-rfsim/script/build_br @@ -43,6 +43,7 @@ OTBR_OPTIONS=( "-DOT_NAT64_BORDER_ROUTING=ON" "-DOT_BORDER_AGENT=ON" "-DOT_MLR=ON" + "-DOT_UDP_FORWARD=ON" "-DOT_COAP_BLOCK=ON" "-DOT_DNSSD_SERVER=ON" "-DOT_NETDATA_PUBLISHER=ON" @@ -60,7 +61,13 @@ main() local options=() options+=("${OTBR_OPTIONS[@]}" "$@") +<<<<<<< HEAD ./script/build "${options[@]}" +======= + #git submodule update + #rm -rf build + ./script/build "${options[@]}" ot-cli-ftd +>>>>>>> 3862892 (Added event type for UdpToAil forwarding - sending the Udp pkt to simulator.) cp ./build/bin/ot-cli-ftd ./ot-versions/ot-cli-ftd_br } diff --git a/ot-rfsim/src/event-sim.c b/ot-rfsim/src/event-sim.c index 4bc54054..488ac4be 100644 --- a/ot-rfsim/src/event-sim.c +++ b/ot-rfsim/src/event-sim.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022-2023, The OpenThread Authors. + * Copyright (c) 2022-2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -171,6 +171,15 @@ void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value) { otSimSendEvent(&event); } +void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData) { + struct Event event; + event.mEvent = OT_SIM_EVENT_UDP_TO_AIL; + event.mDelay = 0; + memcpy(event.mData, aEventData, sizeof(struct UdpAilEventData)); + event.mDataLength = sizeof(struct UdpAilEventData); + otSimSendEvent(&event); +} + void otSimSendEvent(struct Event *aEvent) { aEvent->mMsgId = gLastMsgId; diff --git a/ot-rfsim/src/event-sim.h b/ot-rfsim/src/event-sim.h index c8cbcd93..c7ae5440 100644 --- a/ot-rfsim/src/event-sim.h +++ b/ot-rfsim/src/event-sim.h @@ -1,5 +1,5 @@ /* -* Copyright (c) 2020-2023, The OpenThread Authors. +* Copyright (c) 2020-2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,6 +65,7 @@ enum OT_SIM_EVENT_RFSIM_PARAM_SET = 17, OT_SIM_EVENT_RFSIM_PARAM_RSP = 18, OT_SIM_EVENT_LOG_WRITE = 19, + OT_SIM_EVENT_UDP_TO_AIL = 20, }; #define OT_EVENT_DATA_MAX_SIZE 1024 @@ -116,6 +117,12 @@ struct RfSimParamEventData int32_t mValue; } OT_TOOL_PACKED_END; +OT_TOOL_PACKED_BEGIN +struct UdpAilEventData +{ + uint16_t mDestPort; +} OT_TOOL_PACKED_END; + /** * Send a generic simulation event to the simulator. Event fields are * updated to the values that were used for sending the event. @@ -206,4 +213,7 @@ void otSimSendNodeInfoEvent(uint32_t nodeId); // TODO void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value); +// TODO +void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData); + #endif // PLATFORM_RFSIM_EVENT_SIM_H diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 2de12609..9c7479e2 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -29,7 +29,8 @@ /** * @file * @brief - * This file includes the platform-specific initializers and processing functions. + * This file includes the platform-specific initializers and processing functions + * to let the simulated OT node communicate with the simulator. */ #include "platform-rfsim.h" @@ -41,6 +42,7 @@ #include #include + #include "common/debug.hpp" #include "event-sim.h" @@ -158,3 +160,16 @@ void otPlatOtnsStatus(const char *aStatus) } otSimSendOtnsStatusPushEvent(aStatus, statusLength); } + +#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE +void platformUdpForwarder(otMessage *aMessage, + uint16_t aPeerPort, + otIp6Address *aPeerAddr, + uint16_t aSockPort, + void *aContext) +{ + struct UdpAilEventData evData; + evData.mDestPort = aPeerPort; + otSimSendUdpAilEvent(&evData); +} +#endif diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index 8f1bdd51..0f3aa738 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -58,6 +58,8 @@ #include #include +#include +#include #include "event-sim.h" @@ -221,6 +223,22 @@ bool platformRadioIsTransmitPending(void); */ void platformRadioReportStateToSimulator(bool force); +/** + * Callback that gets called when OT stack has a UDP message that needs to go to + * the host interface. + * + * @param aMessage + * @param aPeerPort + * @param aPeerAddr + * @param aSockPort + * @param aContext + */ +void platformUdpForwarder(otMessage *aMessage, + uint16_t aPeerPort, + otIp6Address *aPeerAddr, + uint16_t aSockPort, + void *aContext); + /** * checks if the radio is busy performing some task such as transmission, * actively receiving a frame, returning an ACK, or doing a CCA. Idle listening diff --git a/ot-rfsim/src/system.c b/ot-rfsim/src/system.c index b6cb43c3..52bc8b71 100644 --- a/ot-rfsim/src/system.c +++ b/ot-rfsim/src/system.c @@ -29,7 +29,8 @@ /** * @file * @brief - * This file includes the platform-specific OT system initializers and processing. + * This file includes the platform-specific OT system initializers and processing + * for the OT-RFSIM simulation platform. */ #include "platform-rfsim.h" @@ -40,6 +41,7 @@ #include #include +#include extern void platformReceiveEvent(otInstance *aInstance); @@ -52,8 +54,8 @@ static void handleSignal(int aSignal); volatile bool gTerminate = false; uint32_t gNodeId = 0; int gSockFd = 0; -uint16_t sPortBase = 9000; -uint16_t sPortOffset; + +static uint16_t sIsInstanceInitDone = false; void otSysInit(int argc, char *argv[]) { char *endptr; @@ -95,7 +97,7 @@ void otSysInit(int argc, char *argv[]) { platformLoggingInit(argv[0]); socket_init(argv[2]); - platformAlarmInit(1); + platformAlarmInit(); platformRadioInit(); platformRandomInit(randomSeed); @@ -122,6 +124,14 @@ void otSysProcessDrivers(otInstance *aInstance) { platformExit(EXIT_SUCCESS); } + // on the first call, perform any init that requires the aInstance. + if (!sIsInstanceInitDone) { +#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + otUdpForwardSetForwarder(aInstance, platformUdpForwarder, aInstance); +#endif + sIsInstanceInitDone = true; + } + FD_ZERO(&read_fds); FD_ZERO(&write_fds); FD_ZERO(&error_fds); From fe476243b78c7aff907d5c15a754d7764a3e3db7 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 14 May 2024 18:30:51 +0200 Subject: [PATCH 04/36] WIP - sending of UDP and IPv6 datagrams to simulator by BR. --- dispatcher/dispatcher.go | 4 +- event/event.go | 14 +++- event/event_test.go | 5 +- ot-rfsim/src/event-sim.c | 11 +++- ot-rfsim/src/event-sim.h | 4 +- ot-rfsim/src/platform-rfsim.c | 74 +++++++++++++++++++++- ot-rfsim/src/platform-rfsim.h | 19 +++++- ot-rfsim/src/system.c | 3 +- pylibs/unittests/test_ccm_commissioning.py | 5 ++ 9 files changed, 127 insertions(+), 12 deletions(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 431dc1cc..a617b569 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -40,6 +40,7 @@ import ( "sync" "time" + "encoding/hex" "github.com/openthread/ot-ns/dissectpkt" "github.com/openthread/ot-ns/dissectpkt/wpan" "github.com/openthread/ot-ns/energy" @@ -429,7 +430,8 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.setSleeping(node.Id) d.alarmMgr.SetTimestamp(node.Id, Ever) case EventTypeUdpToAil: - logger.Warnf("FIXME got UdpToAil: %d", evt.UdpAilData.DestPort) + logger.Warnf("FIXME got UdpToAil: %+v", evt.UdpAilData) + logger.Warnf(" msg = %s", hex.EncodeToString(evt.Data)) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) diff --git a/event/event.go b/event/event.go index 7c00c24f..76790fb9 100644 --- a/event/event.go +++ b/event/event.go @@ -123,9 +123,11 @@ type RfSimParamEventData struct { Value int32 } -const udpAilEventDataHeaderLen = 2 // from OT-RFSIM platform +const udpAilEventDataHeaderLen = 20 // from OT-RFSIM platform type UdpAilEventData struct { - DestPort uint16 + SrcPort uint16 + DestPort uint16 + DestIp6Address [16]byte } /* @@ -214,8 +216,10 @@ func (e *Event) Deserialize(data []byte) int { payloadOffset += nodeInfoEventDataHeaderLen case EventTypeRadioRfSimParamRsp: e.RfSimParamData = deserializeRfSimParamData(e.Data) + payloadOffset += rfSimParamEventDataHeaderLen case EventTypeUdpToAil: e.UdpAilData = deserializeUdpAilData(e.Data) + payloadOffset += udpAilEventDataHeaderLen default: break } @@ -274,8 +278,12 @@ func deserializeRfSimParamData(data []byte) RfSimParamEventData { func deserializeUdpAilData(data []byte) UdpAilEventData { logger.AssertTrue(len(data) >= udpAilEventDataHeaderLen) + ip6Addr := [16]byte{} + copy(ip6Addr[:], data[4:20]) s := UdpAilEventData{ - DestPort: binary.LittleEndian.Uint16(data[0:2]), + SrcPort: binary.LittleEndian.Uint16(data[0:2]), + DestPort: binary.LittleEndian.Uint16(data[2:4]), + DestIp6Address: ip6Addr, } return s } diff --git a/event/event_test.go b/event/event_test.go index 8a3495a1..77f510c8 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -234,13 +234,16 @@ func TestDeserializeRfSimRspEvent(t *testing.T) { } func TestDeserializeUdpToAilEvent(t *testing.T) { - data, _ := hex.DecodeString("000000000000000013040000000000000002003316") + data, _ := hex.DecodeString("00000000000000001304000000000000001900efbe3316fe8000000000000000000000000012340102030405") + testIp6Addr, _ := hex.DecodeString("fe800000000000000000000000001234") var ev Event ev.Deserialize(data) assert.True(t, 0 == ev.Delay) assert.Equal(t, EventTypeUdpToAil, ev.Type) assert.Equal(t, uint64(4), ev.MsgId) + assert.Equal(t, uint16(48879), ev.UdpAilData.SrcPort) assert.Equal(t, uint16(5683), ev.UdpAilData.DestPort) + assert.Equal(t, testIp6Addr, ev.UdpAilData.DestIp6Address[:]) } func TestEventCopy(t *testing.T) { diff --git a/ot-rfsim/src/event-sim.c b/ot-rfsim/src/event-sim.c index 488ac4be..0094e39b 100644 --- a/ot-rfsim/src/event-sim.c +++ b/ot-rfsim/src/event-sim.c @@ -171,12 +171,17 @@ void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value) { otSimSendEvent(&event); } -void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData) { +void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen) { + const size_t evDataSz = sizeof(struct UdpAilEventData); + OT_ASSERT(aMsgLen <= OT_EVENT_DATA_MAX_SIZE - evDataSz); + struct Event event; event.mEvent = OT_SIM_EVENT_UDP_TO_AIL; event.mDelay = 0; - memcpy(event.mData, aEventData, sizeof(struct UdpAilEventData)); - event.mDataLength = sizeof(struct UdpAilEventData); + memcpy(event.mData, aEventData, evDataSz); + memcpy(event.mData + evDataSz, aMsgBytes, aMsgLen); + event.mDataLength = evDataSz + aMsgLen; + otSimSendEvent(&event); } diff --git a/ot-rfsim/src/event-sim.h b/ot-rfsim/src/event-sim.h index c7ae5440..fbdf3fc5 100644 --- a/ot-rfsim/src/event-sim.h +++ b/ot-rfsim/src/event-sim.h @@ -120,7 +120,9 @@ struct RfSimParamEventData OT_TOOL_PACKED_BEGIN struct UdpAilEventData { + uint16_t mSrcPort; uint16_t mDestPort; + uint8_t mDestIp6[OT_IP6_ADDRESS_SIZE]; } OT_TOOL_PACKED_END; /** @@ -214,6 +216,6 @@ void otSimSendNodeInfoEvent(uint32_t nodeId); void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value); // TODO -void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData); +void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen); #endif // PLATFORM_RFSIM_EVENT_SIM_H diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 9c7479e2..06ed39c2 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -44,6 +44,7 @@ #include #include "common/debug.hpp" +#include "utils/code_utils.h" #include "event-sim.h" #include "utils/uart.h" @@ -168,8 +169,79 @@ void platformUdpForwarder(otMessage *aMessage, uint16_t aSockPort, void *aContext) { + OT_UNUSED_VARIABLE(aContext); + struct UdpAilEventData evData; + uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; // FIXME size + size_t msgLen = otMessageGetLength(aMessage); + + if (msgLen > sizeof(buf)) { + fprintf(stderr, "platformUdpForwarder: buffer too small"); + platformExit(EXIT_FAILURE); + } + evData.mDestPort = aPeerPort; - otSimSendUdpAilEvent(&evData); + evData.mSrcPort = aSockPort; + memcpy(evData.mDestIp6, aPeerAddr, OT_IP6_ADDRESS_SIZE); + otMessageRead(aMessage, 0, buf, msgLen); + + otSimSendUdpAilEvent(&evData, &buf[0], msgLen); +} +#endif + +void platformIp6Receiver(otMessage *aMessage, void *aContext) +{ + OT_UNUSED_VARIABLE(aContext); + + struct UdpAilEventData evData; + uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; + const uint8_t dstAddr[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + size_t msgLen; + + // determine if IPv6 datagram must go to host/AIL. + // otGet + otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage)); + + + msgLen = otMessageGetLength(aMessage); + if (msgLen > sizeof(buf)) { + fprintf(stderr, "platformIp6Receiver: buffer too small"); + platformExit(EXIT_FAILURE); + } + + evData.mDestPort = 0; // FIXME - get from aMessage? + evData.mSrcPort = 0; + memcpy(evData.mDestIp6, dstAddr, OT_IP6_ADDRESS_SIZE); + otMessageRead(aMessage, 0, buf, msgLen); + + otPlatLog(OT_LOG_LEVEL_DEBG,OT_LOG_REGION_PLATFORM, + "Sending IPv6 datagram to simulator"); + otSimSendUdpAilEvent(&evData, &buf[0], msgLen); + +exit: + otMessageFree(aMessage); } + +void platformNetifSetUp(otInstance *aInstance) +{ + assert(aInstance != NULL); + + otIp6SetReceiveFilterEnabled(aInstance, true); // FIXME - needed? + //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_ALL); // TODO + //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_DISABLED); + otIp6SetReceiveCallback(aInstance, platformIp6Receiver, aInstance); +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + // We can use the same function for IPv6 and translated IPv4 messages. + // otNat64SetReceiveIp4Callback(gInstance, processReceive, gInstance); +#endif + //otIp6SetAddressCallback(aInstance, processAddressChange, aInstance); +#if OPENTHREAD_POSIX_MULTICAST_PROMISCUOUS_REQUIRED + //otIp6SetMulticastPromiscuousEnabled(aInstance, true); #endif +#if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE + //nat64Init(); +#endif +#if OPENTHREAD_CONFIG_DNS_UPSTREAM_QUERY_ENABLE + //gResolver.Init(); +#endif +} diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index 0f3aa738..b50d90aa 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -227,7 +227,7 @@ void platformRadioReportStateToSimulator(bool force); * Callback that gets called when OT stack has a UDP message that needs to go to * the host interface. * - * @param aMessage + * @param aMessage TODO * @param aPeerPort * @param aPeerAddr * @param aSockPort @@ -239,6 +239,23 @@ void platformUdpForwarder(otMessage *aMessage, uint16_t aSockPort, void *aContext); +/** + * Callback for node receiving an IPv6 datagram. When the datagram is destined for the upper-layer host + * or to the simulated AIL, this callback is used to send the datagram to the simulator for further processing. + * + * @param aMessage + * @param aContext + */ +void platformIp6Receiver(otMessage *aMessage, void *aContext); + +/** + * Setup any simulated non-Thread interfaces. For example, an interface to a host process or + * an AIL interface. + * + * @param aInstance TODO + */ +void platformNetifSetUp(otInstance *aInstance); + /** * checks if the radio is busy performing some task such as transmission, * actively receiving a frame, returning an ACK, or doing a CCA. Idle listening diff --git a/ot-rfsim/src/system.c b/ot-rfsim/src/system.c index 52bc8b71..3b4b161e 100644 --- a/ot-rfsim/src/system.c +++ b/ot-rfsim/src/system.c @@ -125,10 +125,11 @@ void otSysProcessDrivers(otInstance *aInstance) { } // on the first call, perform any init that requires the aInstance. - if (!sIsInstanceInitDone) { + if (!sIsInstanceInitDone) { // TODO move to own function #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE otUdpForwardSetForwarder(aInstance, platformUdpForwarder, aInstance); #endif + platformNetifSetUp(aInstance); sIsInstanceInitDone = true; } diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index b1dfcb21..223d2a59 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -77,6 +77,11 @@ def testCommissioningOneHop(self): self.assertFormPartitionsIgnoreOrphans(1) self.assertTrue(joins and joins[0][1] > 0) # assert join success + # n2 sends a coap message to AIL, to test AIL connectivity + ns.node_cmd(n2, "coap start") + ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR + self.go(10) + # n3 joins as CCM joiner ns.commissioner_ccm_joiner_add(n1, "*") ns.ifconfig_up(n3) From 7e446bbc59e5cd34b56d501319f3cfa3d8668427 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Wed, 15 May 2024 13:17:59 +0200 Subject: [PATCH 05/36] WIP - extended Udp/Ip6 event structure to contain metadata and selective sending to sim/host. --- dispatcher/dispatcher.go | 3 +- event/event.go | 9 ++- event/event_test.go | 6 +- ot-rfsim/src/CMakeLists.txt | 1 + ot-rfsim/src/event-sim.h | 5 +- ot-rfsim/src/platform-rfsim.c | 73 ++++++++++++---------- ot-rfsim/src/platform-rfsim.cpp | 59 +++++++++++++++++ ot-rfsim/src/platform-rfsim.h | 15 +++++ ot-rfsim/src/system.c | 6 +- pylibs/unittests/test_ccm_commissioning.py | 4 +- 10 files changed, 135 insertions(+), 46 deletions(-) create mode 100644 ot-rfsim/src/platform-rfsim.cpp diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index a617b569..d5714129 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -431,7 +431,8 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.alarmMgr.SetTimestamp(node.Id, Ever) case EventTypeUdpToAil: logger.Warnf("FIXME got UdpToAil: %+v", evt.UdpAilData) - logger.Warnf(" msg = %s", hex.EncodeToString(evt.Data)) + logger.Warnf(" msg = %s", hex.EncodeToString(evt.Data)) + logger.Warnf(" dstIpv6 = %s", hex.EncodeToString(evt.UdpAilData.DestIp6Address[:])) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) diff --git a/event/event.go b/event/event.go index 76790fb9..8bf1ca65 100644 --- a/event/event.go +++ b/event/event.go @@ -123,10 +123,11 @@ type RfSimParamEventData struct { Value int32 } -const udpAilEventDataHeaderLen = 20 // from OT-RFSIM platform +const udpAilEventDataHeaderLen = 36 // from OT-RFSIM platform type UdpAilEventData struct { SrcPort uint16 DestPort uint16 + SrcIp6Address [16]byte DestIp6Address [16]byte } @@ -279,10 +280,14 @@ func deserializeRfSimParamData(data []byte) RfSimParamEventData { func deserializeUdpAilData(data []byte) UdpAilEventData { logger.AssertTrue(len(data) >= udpAilEventDataHeaderLen) ip6Addr := [16]byte{} - copy(ip6Addr[:], data[4:20]) + srcIp6 := [16]byte{} + copy(srcIp6[:], data[4:20]) + copy(ip6Addr[:], data[20:36]) + s := UdpAilEventData{ SrcPort: binary.LittleEndian.Uint16(data[0:2]), DestPort: binary.LittleEndian.Uint16(data[2:4]), + SrcIp6Address: srcIp6, DestIp6Address: ip6Addr, } return s diff --git a/event/event_test.go b/event/event_test.go index 77f510c8..af98fd5a 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -234,8 +234,9 @@ func TestDeserializeRfSimRspEvent(t *testing.T) { } func TestDeserializeUdpToAilEvent(t *testing.T) { - data, _ := hex.DecodeString("00000000000000001304000000000000001900efbe3316fe8000000000000000000000000012340102030405") + data, _ := hex.DecodeString("00000000000000001304000000000000002900efbe3316fe800000000000000000000000001234fe80000000000000000000000000beef0102030405") testIp6Addr, _ := hex.DecodeString("fe800000000000000000000000001234") + testIp6Addr2, _ := hex.DecodeString("fe80000000000000000000000000beef") var ev Event ev.Deserialize(data) assert.True(t, 0 == ev.Delay) @@ -243,7 +244,8 @@ func TestDeserializeUdpToAilEvent(t *testing.T) { assert.Equal(t, uint64(4), ev.MsgId) assert.Equal(t, uint16(48879), ev.UdpAilData.SrcPort) assert.Equal(t, uint16(5683), ev.UdpAilData.DestPort) - assert.Equal(t, testIp6Addr, ev.UdpAilData.DestIp6Address[:]) + assert.Equal(t, testIp6Addr, ev.UdpAilData.SrcIp6Address[:]) + assert.Equal(t, testIp6Addr2, ev.UdpAilData.DestIp6Address[:]) } func TestEventCopy(t *testing.T) { diff --git a/ot-rfsim/src/CMakeLists.txt b/ot-rfsim/src/CMakeLists.txt index 18fa6586..00546a1c 100644 --- a/ot-rfsim/src/CMakeLists.txt +++ b/ot-rfsim/src/CMakeLists.txt @@ -41,6 +41,7 @@ add_library(openthread-rfsim logging.c misc.c platform-rfsim.c + platform-rfsim.cpp radio.c system.c trel.c diff --git a/ot-rfsim/src/event-sim.h b/ot-rfsim/src/event-sim.h index fbdf3fc5..149cef79 100644 --- a/ot-rfsim/src/event-sim.h +++ b/ot-rfsim/src/event-sim.h @@ -121,8 +121,9 @@ OT_TOOL_PACKED_BEGIN struct UdpAilEventData { uint16_t mSrcPort; - uint16_t mDestPort; - uint8_t mDestIp6[OT_IP6_ADDRESS_SIZE]; + uint16_t mDstPort; + uint8_t mSrcIp6[OT_IP6_ADDRESS_SIZE]; + uint8_t mDstIp6[OT_IP6_ADDRESS_SIZE]; } OT_TOOL_PACKED_END; /** diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 06ed39c2..b224633f 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2023, The OpenThread Authors. + * Copyright (c) 2018-2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,13 @@ extern int gSockFd; uint64_t gLastMsgId = 0; struct Event gLastRecvEvent; +static otIp6Address unspecifiedIp6Address; + +void platformRfsimInit(void) { + if(otIp6AddressFromString("::", &unspecifiedIp6Address) != OT_ERROR_NONE) { + platformExit(EXIT_FAILURE); + } +} void platformExit(int exitCode) { gTerminate = true; @@ -74,28 +81,20 @@ void platformReceiveEvent(otInstance *aInstance) perror("recvfrom"); platformExit(EXIT_FAILURE); } - else if ((uint16_t)rval < sizeof(struct EventHeader)) { - fprintf(stderr, "incomplete event received, len=%li", rval); - platformExit(EXIT_FAILURE); - } + OT_ASSERT(rval >= sizeof(struct EventHeader)); // read the rest of data (payload data - optional). uint16_t payloadLen = event.mDataLength; if (payloadLen > 0) { - if (payloadLen > sizeof(event.mData)) { - fprintf(stderr, "too-large event payload detected, len=%u, expected <= %lu", payloadLen, sizeof(event.mData)); - platformExit(EXIT_FAILURE); - } + OT_ASSERT(payloadLen <= OT_EVENT_DATA_MAX_SIZE); + rval = recvfrom(gSockFd, (char *)&event.mData, payloadLen, 0, NULL, NULL); if (rval < 0) { perror("recvfrom"); platformExit(EXIT_FAILURE); } - else if ((uint16_t)rval < payloadLen) { - fprintf(stderr, "incomplete event payload received, len=%li, expected=%u", rval, payloadLen); - platformExit(EXIT_FAILURE); - } + OT_ASSERT(rval == (ssize_t) payloadLen); } gLastRecvEvent = event; @@ -172,50 +171,58 @@ void platformUdpForwarder(otMessage *aMessage, OT_UNUSED_VARIABLE(aContext); struct UdpAilEventData evData; - uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; // FIXME size + uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; size_t msgLen = otMessageGetLength(aMessage); - if (msgLen > sizeof(buf)) { - fprintf(stderr, "platformUdpForwarder: buffer too small"); - platformExit(EXIT_FAILURE); - } + OT_ASSERT(msgLen <= sizeof(buf)); - evData.mDestPort = aPeerPort; evData.mSrcPort = aSockPort; - memcpy(evData.mDestIp6, aPeerAddr, OT_IP6_ADDRESS_SIZE); + evData.mDstPort = aPeerPort; + memcpy(evData.mSrcIp6, &unspecifiedIp6Address, OT_IP6_ADDRESS_SIZE); + memcpy(evData.mDstIp6, aPeerAddr, OT_IP6_ADDRESS_SIZE); otMessageRead(aMessage, 0, buf, msgLen); otSimSendUdpAilEvent(&evData, &buf[0], msgLen); } #endif +static bool isLinkLocal(otIp6Address *addr) +{ + return (addr->mFields.m8[0] == 0xfe && (addr->mFields.m8[1] & 0b11000000) == 0x80) + || (addr->mFields.m8[0] == 0xff && (addr->mFields.m8[1] & 0b00001111) == 0x02); +} + void platformIp6Receiver(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); struct UdpAilEventData evData; uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; - const uint8_t dstAddr[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + const uint8_t dstAddrZero[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; size_t msgLen; + otMessageInfo ip6Info; + otError error = OT_ERROR_NONE; - // determine if IPv6 datagram must go to host/AIL. - // otGet - otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage)); + msgLen = otMessageGetLength(aMessage); + OT_ASSERT(msgLen <= sizeof(buf)); + // parse IPv6 message + error = platformParseIp6(aMessage, &ip6Info); + OT_ASSERT(error == OT_ERROR_NONE); - msgLen = otMessageGetLength(aMessage); - if (msgLen > sizeof(buf)) { - fprintf(stderr, "platformIp6Receiver: buffer too small"); - platformExit(EXIT_FAILURE); - } + // determine if IPv6 datagram must go to host/AIL. + otEXPECT(!isLinkLocal(&ip6Info.mPeerAddr) && !isLinkLocal(&ip6Info.mSockAddr)); + otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage)); - evData.mDestPort = 0; // FIXME - get from aMessage? - evData.mSrcPort = 0; - memcpy(evData.mDestIp6, dstAddr, OT_IP6_ADDRESS_SIZE); + // create simulator event + evData.mSrcPort = ip6Info.mSockPort; + evData.mDstPort = ip6Info.mPeerPort; + memcpy(evData.mSrcIp6, &ip6Info.mSockAddr, OT_IP6_ADDRESS_SIZE); + memcpy(evData.mDstIp6, &ip6Info.mPeerAddr, OT_IP6_ADDRESS_SIZE); otMessageRead(aMessage, 0, buf, msgLen); otPlatLog(OT_LOG_LEVEL_DEBG,OT_LOG_REGION_PLATFORM, - "Sending IPv6 datagram to simulator"); + "FIXME Sending IPv6 datagram to simulator"); otSimSendUdpAilEvent(&evData, &buf[0], msgLen); exit: diff --git a/ot-rfsim/src/platform-rfsim.cpp b/ot-rfsim/src/platform-rfsim.cpp new file mode 100644 index 00000000..9f0a1b24 --- /dev/null +++ b/ot-rfsim/src/platform-rfsim.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * @brief + * This file includes the C++ portions of the OT-RFSIM platform. + */ + +#include "net/ip6.hpp" +#include "net/ip6_address.hpp" + +extern "C" otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info); + +#include "platform-rfsim.h" +#include "utils/uart.h" + +using namespace ot; + +otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info) { + Ip6::Headers headers; + otError error = OT_ERROR_PARSE; + Message msg = AsCoreType(aMessage); + + SuccessOrExit(error = headers.ParseFrom(msg)); + ip6Info->mSockAddr = headers.GetSourceAddress(); + ip6Info->mPeerAddr = headers.GetDestinationAddress(); + ip6Info->mSockPort = headers.GetSourcePort(); + ip6Info->mPeerPort = headers.GetDestinationPort(); + +exit: + return error; +} + diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index b50d90aa..ecf1e210 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -198,6 +198,12 @@ void platformLoggingInit(char *processName); */ void platformUartRestore(void); +/** + * initializes the OT-RFSIM simulator communications. + * + */ +void platformRfsimInit(void); + /** * exits the simulated-node's process with the specific exit code. * @@ -248,6 +254,15 @@ void platformUdpForwarder(otMessage *aMessage, */ void platformIp6Receiver(otMessage *aMessage, void *aContext); +/** + * TODO + * + * @param aMessage + * @param ip6Addr + * @return + */ +otError platformParseIp6(otMessage *aMessage, otMessageInfo *ip6Info); + /** * Setup any simulated non-Thread interfaces. For example, an interface to a host process or * an AIL interface. diff --git a/ot-rfsim/src/system.c b/ot-rfsim/src/system.c index 3b4b161e..4445e0a0 100644 --- a/ot-rfsim/src/system.c +++ b/ot-rfsim/src/system.c @@ -44,17 +44,14 @@ #include extern void platformReceiveEvent(otInstance *aInstance); - extern bool gPlatformPseudoResetWasRequested; static void socket_init(char *socketFilePath); - static void handleSignal(int aSignal); volatile bool gTerminate = false; uint32_t gNodeId = 0; int gSockFd = 0; - static uint16_t sIsInstanceInitDone = false; void otSysInit(int argc, char *argv[]) { @@ -96,10 +93,11 @@ void otSysInit(int argc, char *argv[]) { } platformLoggingInit(argv[0]); + platformRandomInit(randomSeed); socket_init(argv[2]); platformAlarmInit(); platformRadioInit(); - platformRandomInit(randomSeed); + platformRfsimInit(); otSimSendNodeInfoEvent(gNodeId); } diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index 223d2a59..df3e4f5b 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -48,7 +48,7 @@ def setFirstNodeDataset(self, n1) -> None: def testCommissioningOneHop(self): ns = self.ns - ns.web() + # ns.web() ns.coaps_enable() ns.radiomodel = 'MIDisc' # enforce strict line topologies for testing @@ -93,7 +93,7 @@ def testCommissioningOneHop(self): print('counters', c) joins = ns.joins() print('joins', joins) - ns.interactive_cli() + # ns.interactive_cli() self.assertFormPartitions(1) self.assertTrue(joins and joins[0][1] > 0) # assert join success From 69f8ff3747ae640080f7444c93d4a6a60df0ff8a Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Fri, 17 May 2024 17:41:47 +0200 Subject: [PATCH 06/36] Rebased to 'main' branch. WIP generic simulated-hosts feature and test for it - works without the CCM specific OT changes. --- cli/CmdRunner.go | 55 ++++++ cli/README.md | 18 ++ cli/ast.go | 11 ++ cli/cli_test.go | 3 + dispatcher/dispatcher.go | 34 +++- event/event.go | 42 +++-- event/event_test.go | 25 ++- go.mod | 2 +- ot-rfsim/src/event-sim.c | 6 +- ot-rfsim/src/event-sim.h | 48 +++-- ot-rfsim/src/platform-rfsim.c | 30 ++-- ot-rfsim/src/system.c | 15 +- pylibs/unittests/test_ccm_commissioning.py | 5 + pylibs/unittests/test_sim_hosts.py | 120 +++++++++++++ script/install-deps | 2 +- simulation/sim_hosts.go | 200 +++++++++++++++++++++ simulation/simulation.go | 25 +++ 17 files changed, 568 insertions(+), 73 deletions(-) create mode 100755 pylibs/unittests/test_sim_hosts.py create mode 100644 simulation/sim_hosts.go diff --git a/cli/CmdRunner.go b/cli/CmdRunner.go index 2926ac63..a4a150db 100644 --- a/cli/CmdRunner.go +++ b/cli/CmdRunner.go @@ -47,6 +47,7 @@ import ( . "github.com/openthread/ot-ns/types" "github.com/openthread/ot-ns/visualize" "github.com/openthread/ot-ns/web" + "net/netip" ) const ( @@ -321,6 +322,8 @@ func (rt *CmdRunner) execute(cmd *Command, output io.Writer) { rt.executeSave(cc, cmd.Save) } else if cmd.Send != nil { rt.executeSend(cc, cmd.Send) + } else if cmd.Host != nil { + rt.executeHost(cc, cmd.Host) } else { logger.Panicf("unimplemented command: %#v", cmd) } @@ -1467,3 +1470,55 @@ func (rt *CmdRunner) executeSend(cc *CommandContext, cmd *SendCmd) { } }) } + +func (rt *CmdRunner) executeHost(cc *CommandContext, cmd *HostCmd) { + rt.postAsyncWait(cc, func(sim *simulation.Simulation) { + var addr netip.Addr + var err error + + if cmd.IpAddr != nil { + addr, err = netip.ParseAddr(cmd.IpAddr.Addr) + if err != nil { + cc.errorf("invalid IPv6 address argument: '%s'", cmd.IpAddr.Addr) + return + } + } + + host := simulation.SimHostEndpoint{ + HostName: cmd.Hostname, + Ip6Addr: addr, + Port: cmd.Port, + PortMapped: cmd.PortMapped, + } + + switch cmd.SubCmd { + case "add": + if cmd.Port == 0 || cmd.PortMapped == 0 { + cc.errorf("invalid port argument(s)") + return + } + if err = sim.SimHosts().AddHost(host); err != nil { + cc.errorf("could not add host: %v", err) + return + } + case "del": + if len(cmd.Hostname) == 0 { + cc.errorf("missing or argument") + return + } + addr, _ = netip.ParseAddr(cmd.Hostname) + for h := range sim.SimHosts().Hosts { + if cmd.Hostname == h.HostName || addr == h.Ip6Addr { + sim.SimHosts().RemoveHost(h) + } + } + + case "list": + sh := sim.SimHosts() + cc.outputf("Hostname Destination IPv6 address DstPort MapPort RxBytes TxBytes\n") + for h := range sim.SimHosts().Hosts { + cc.outputf("%-40s %-40s % 6d % 6d % 8d % 8d\n", h.HostName, h.Ip6Addr.String(), h.Port, h.PortMapped, sh.GetRxBytes(&h), sh.GetTxBytes(&h)) + } + } + }) +} diff --git a/cli/README.md b/cli/README.md index 87cd4387..96b243db 100644 --- a/cli/README.md +++ b/cli/README.md @@ -409,6 +409,24 @@ Show help text for specific, or all, OTNS CLI commands. help [ ] ``` +### host + +Add a simulated IP host for Thread nodes to communicate with. + +```shell +host add "" "" +host del "" | "" +host list +``` + +Any UDP/TCP packets sent to an off-mesh destination by a Thread node will first be routed over the mesh to a Thread +Border Router (node type `br`). The BR will notify OTNS about such received packets. OTNS then looks up if any +simulated IP host matches the packet's destination address/port and if so, it delivers the packet locally (localhost) +to the mapped port number ``. This enables simulations with the behavior of Thread-external servers +to be done fully locally, without causing any real network traffic. + +Note: currently only IPv6 hosts are supported; IPv4 (via NAT64) may be added later. + ### joins Displays finished joiner sessions. diff --git a/cli/ast.go b/cli/ast.go index 8b525a9e..ae81f35b 100644 --- a/cli/ast.go +++ b/cli/ast.go @@ -51,6 +51,7 @@ type Command struct { Exit *ExitCmd `| @@` //nolint Go *GoCmd `| @@` //nolint Help *HelpCmd `| @@` //nolint + Host *HostCmd `| @@` //nolint Joins *JoinsCmd `| @@` //nolint Kpi *KpiCmd `| @@` //nolint Load *LoadCmd `| @@` //nolint @@ -581,6 +582,16 @@ type SendCmd struct { DataSize *DataSizeFlag `[ @@ ]` //nolint } +// noinspection GoVetStructTag +type HostCmd struct { + Cmd struct{} `"host"` //nolint + SubCmd string `@("add"|"del"|"list")` //nolint + Hostname string `[ @String ]` //nolint + IpAddr *Ipv6Address `[ @@ ]` //nolint + Port uint16 `[ @Int ]` //nolint + PortMapped uint16 `[ @Int ]` //nolint +} + // noinspection GoVetStructTag type FailTimeParams struct { Dummy struct{} `"ft"` //nolint diff --git a/cli/cli_test.go b/cli/cli_test.go index f03c1bff..8d6fbd93 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -109,6 +109,9 @@ func TestParseBytes(t *testing.T) { assert.Nil(t, parseBytes([]byte("go 100 speed 2"), &cmd)) assert.NotNil(t, cmd.Go) + assert.True(t, parseBytes([]byte("help"), &cmd) == nil && cmd.Help != nil) + assert.True(t, parseBytes([]byte("host add \"myhost.example.com\" \"fc00::1234\" 35683 1717"), &cmd) == nil && cmd.Host != nil) + assert.True(t, parseBytes([]byte("joins"), &cmd) == nil && cmd.Joins != nil) assert.True(t, parseBytes([]byte("log"), &cmd) == nil && cmd.LogLevel != nil) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index d5714129..728ff98c 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -40,7 +40,6 @@ import ( "sync" "time" - "encoding/hex" "github.com/openthread/ot-ns/dissectpkt" "github.com/openthread/ot-ns/dissectpkt/wpan" "github.com/openthread/ot-ns/energy" @@ -54,6 +53,7 @@ import ( "github.com/openthread/ot-ns/visualize" ) +// CallbackHandler handles the callbacks from Dispatcher to its managing entity (e.g. a Simulation). type CallbackHandler interface { // OnUartWrite Notifies that the node's UART was written with data. OnUartWrite(nodeid NodeId, data []byte) @@ -66,6 +66,12 @@ type CallbackHandler interface { // OnRfSimEvent Notifies that Dispatcher received an OT-RFSIM platform event that it didn't handle itself. OnRfSimEvent(nodeid NodeId, evt *Event) + + // OnUdpToHost Notifies that the Dispatcher received an off-mesh or to-host UDP packet from a node, to be handled. + OnUdpToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) + + // OnIp6ToHost Notifies that the Dispatcher received an off-mesh or to-host IPv6 packet from a node, to be handled. + OnIp6ToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) } // goDuration represents a particular duration of the simulation at a given speed. @@ -377,7 +383,7 @@ func (d *Dispatcher) goSimulateForDuration(duration goDuration) { } } -// handleRecvEvent is the central handler for all events externally received from OpenThread nodes. +// handleRecvEvent is the central handler for all events externally received from nodes/entities. // It may only process events immediately that are to be executed at time d.CurTime. Future events // will need to be queued (scheduled). func (d *Dispatcher) handleRecvEvent(evt *Event) { @@ -429,10 +435,18 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { logger.Debugf("%s socket disconnected.", node) d.setSleeping(node.Id) d.alarmMgr.SetTimestamp(node.Id, Ever) - case EventTypeUdpToAil: - logger.Warnf("FIXME got UdpToAil: %+v", evt.UdpAilData) - logger.Warnf(" msg = %s", hex.EncodeToString(evt.Data)) - logger.Warnf(" dstIpv6 = %s", hex.EncodeToString(evt.UdpAilData.DestIp6Address[:])) + case EventTypeUdpToHost: + d.Counters.OtherEvents += 1 + d.cbHandler.OnUdpToHost(node.Id, &evt.MsgToHostData, evt.Data) + case EventTypeIp6ToHost: + d.Counters.OtherEvents += 1 + d.cbHandler.OnIp6ToHost(node.Id, &evt.MsgToHostData, evt.Data) + case EventTypeUdpFromHost: + fallthrough + case EventTypeIp6FromHost: + d.Counters.OtherEvents += 1 + evt.MustDispatch = true // asap resend again to the target (BR) node. + d.eventQueue.Add(evt) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) @@ -575,6 +589,10 @@ func (d *Dispatcher) processNextEvent(simSpeed float64) bool { d.sendRadioCommRxStartEvents(node, evt) case EventTypeRadioRxDone: d.sendRadioCommRxDoneEvents(node, evt) + case EventTypeUdpFromHost: + fallthrough + case EventTypeIp6FromHost: + node.sendEvent(evt) // TODO no loss on external network is simulated currently. default: if d.radioModel.OnEventDispatch(node.RadioNode, node.RadioNode, evt) { node.sendEvent(evt) @@ -1152,6 +1170,10 @@ func (d *Dispatcher) PostAsync(task func()) { d.taskChan <- task } +func (d *Dispatcher) PostEventAsync(ev *Event) { + d.eventChan <- ev +} + func (d *Dispatcher) handleTasks() { defer func() { err := recover() diff --git a/event/event.go b/event/event.go index 8bf1ca65..b08d0199 100644 --- a/event/event.go +++ b/event/event.go @@ -36,6 +36,7 @@ import ( "github.com/openthread/ot-ns/logger" "github.com/openthread/ot-ns/types" + "net/netip" ) type EventType = uint8 @@ -61,7 +62,10 @@ const ( EventTypeRadioRfSimParamSet EventType = 17 EventTypeRadioRfSimParamRsp EventType = 18 EventTypeLogWrite EventType = 19 - EventTypeUdpToAil EventType = 20 + EventTypeUdpToHost EventType = 20 + EventTypeIp6ToHost EventType = 21 + EventTypeUdpFromHost EventType = 22 + EventTypeIp6FromHost EventType = 23 ) const ( @@ -88,7 +92,7 @@ type Event struct { RadioStateData RadioStateEventData NodeInfoData NodeInfoEventData RfSimParamData RfSimParamEventData - UdpAilData UdpAilEventData + MsgToHostData MsgToHostEventData } // All ...EventData formats below only used by OT nodes supporting advanced @@ -123,12 +127,12 @@ type RfSimParamEventData struct { Value int32 } -const udpAilEventDataHeaderLen = 36 // from OT-RFSIM platform -type UdpAilEventData struct { - SrcPort uint16 - DestPort uint16 - SrcIp6Address [16]byte - DestIp6Address [16]byte +const msgToHostEventDataHeaderLen = 36 // from OT-RFSIM platform +type MsgToHostEventData struct { + SrcPort uint16 + DstPort uint16 + SrcIp6Address netip.Addr + DstIp6Address netip.Addr } /* @@ -218,9 +222,11 @@ func (e *Event) Deserialize(data []byte) int { case EventTypeRadioRfSimParamRsp: e.RfSimParamData = deserializeRfSimParamData(e.Data) payloadOffset += rfSimParamEventDataHeaderLen - case EventTypeUdpToAil: - e.UdpAilData = deserializeUdpAilData(e.Data) - payloadOffset += udpAilEventDataHeaderLen + case EventTypeUdpToHost: + fallthrough + case EventTypeIp6ToHost: + e.MsgToHostData = deserializeMsgToHostData(e.Data) + payloadOffset += msgToHostEventDataHeaderLen default: break } @@ -277,18 +283,18 @@ func deserializeRfSimParamData(data []byte) RfSimParamEventData { return s } -func deserializeUdpAilData(data []byte) UdpAilEventData { - logger.AssertTrue(len(data) >= udpAilEventDataHeaderLen) +func deserializeMsgToHostData(data []byte) MsgToHostEventData { + logger.AssertTrue(len(data) >= msgToHostEventDataHeaderLen) ip6Addr := [16]byte{} srcIp6 := [16]byte{} copy(srcIp6[:], data[4:20]) copy(ip6Addr[:], data[20:36]) - s := UdpAilEventData{ - SrcPort: binary.LittleEndian.Uint16(data[0:2]), - DestPort: binary.LittleEndian.Uint16(data[2:4]), - SrcIp6Address: srcIp6, - DestIp6Address: ip6Addr, + s := MsgToHostEventData{ + SrcPort: binary.LittleEndian.Uint16(data[0:2]), + DstPort: binary.LittleEndian.Uint16(data[2:4]), + SrcIp6Address: netip.AddrFrom16(srcIp6), + DstIp6Address: netip.AddrFrom16(ip6Addr), } return s } diff --git a/event/event_test.go b/event/event_test.go index af98fd5a..5aab2a4d 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2022-2023, The OTNS Authors. +// Copyright (c) 2022-2024, The OTNS Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -233,19 +233,30 @@ func TestDeserializeRfSimRspEvent(t *testing.T) { assert.Equal(t, int32(1234), ev.RfSimParamData.Value) } -func TestDeserializeUdpToAilEvent(t *testing.T) { +func TestDeserializeMsgToHostEvents(t *testing.T) { data, _ := hex.DecodeString("00000000000000001304000000000000002900efbe3316fe800000000000000000000000001234fe80000000000000000000000000beef0102030405") testIp6Addr, _ := hex.DecodeString("fe800000000000000000000000001234") testIp6Addr2, _ := hex.DecodeString("fe80000000000000000000000000beef") var ev Event ev.Deserialize(data) assert.True(t, 0 == ev.Delay) - assert.Equal(t, EventTypeUdpToAil, ev.Type) + assert.Equal(t, EventTypeUdpToHost, ev.Type) assert.Equal(t, uint64(4), ev.MsgId) - assert.Equal(t, uint16(48879), ev.UdpAilData.SrcPort) - assert.Equal(t, uint16(5683), ev.UdpAilData.DestPort) - assert.Equal(t, testIp6Addr, ev.UdpAilData.SrcIp6Address[:]) - assert.Equal(t, testIp6Addr2, ev.UdpAilData.DestIp6Address[:]) + assert.Equal(t, uint16(48879), ev.MsgToHostData.SrcPort) + assert.Equal(t, uint16(5683), ev.MsgToHostData.DstPort) + assert.Equal(t, testIp6Addr, ev.MsgToHostData.SrcIp6Address.AsSlice()) + assert.Equal(t, testIp6Addr2, ev.MsgToHostData.DstIp6Address.AsSlice()) + + // try other event type with same payload structure + data[8] = EventTypeIp6ToHost + ev.Deserialize(data) + assert.True(t, 0 == ev.Delay) + assert.Equal(t, EventTypeIp6ToHost, ev.Type) + assert.Equal(t, uint64(4), ev.MsgId) + assert.Equal(t, uint16(48879), ev.MsgToHostData.SrcPort) + assert.Equal(t, uint16(5683), ev.MsgToHostData.DstPort) + assert.Equal(t, testIp6Addr, ev.MsgToHostData.SrcIp6Address.AsSlice()) + assert.Equal(t, testIp6Addr2, ev.MsgToHostData.DstIp6Address.AsSlice()) } func TestEventCopy(t *testing.T) { diff --git a/go.mod b/go.mod index e3e41233..e2ebbdf2 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.27.0 + golang.org/x/net v0.22.0 golang.org/x/term v0.21.0 google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.1 @@ -46,7 +47,6 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/multierr v1.10.0 // indirect - golang.org/x/net v0.22.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect diff --git a/ot-rfsim/src/event-sim.c b/ot-rfsim/src/event-sim.c index 0094e39b..6530439d 100644 --- a/ot-rfsim/src/event-sim.c +++ b/ot-rfsim/src/event-sim.c @@ -171,12 +171,12 @@ void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value) { otSimSendEvent(&event); } -void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen) { - const size_t evDataSz = sizeof(struct UdpAilEventData); +void otSimSendMsgToHostEvent(uint8_t evType, struct MsgToHostEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen) { + const size_t evDataSz = sizeof(struct MsgToHostEventData); OT_ASSERT(aMsgLen <= OT_EVENT_DATA_MAX_SIZE - evDataSz); struct Event event; - event.mEvent = OT_SIM_EVENT_UDP_TO_AIL; + event.mEvent = evType; event.mDelay = 0; memcpy(event.mData, aEventData, evDataSz); memcpy(event.mData + evDataSz, aMsgBytes, aMsgLen); diff --git a/ot-rfsim/src/event-sim.h b/ot-rfsim/src/event-sim.h index 149cef79..9222024a 100644 --- a/ot-rfsim/src/event-sim.h +++ b/ot-rfsim/src/event-sim.h @@ -41,8 +41,8 @@ /** * The event types defined for communication with a simulator and/or with other simulated nodes. - * Shared for both 'real' and virtual-time event types. Some types are not used (e.g. historic - * or used by the simulator only.) + * Shared for both 'real' and virtual-time event types. Some types are not used in this project + * (e.g. historic or used by the simulator only.) */ enum { @@ -65,10 +65,13 @@ enum OT_SIM_EVENT_RFSIM_PARAM_SET = 17, OT_SIM_EVENT_RFSIM_PARAM_RSP = 18, OT_SIM_EVENT_LOG_WRITE = 19, - OT_SIM_EVENT_UDP_TO_AIL = 20, + OT_SIM_EVENT_UDP_TO_HOST = 20, + OT_SIM_EVENT_IP6_TO_HOST = 21, + OT_SIM_EVENT_UDP_FROM_HOST = 22, + OT_SIM_EVENT_IP6_FROM_HOST = 23, }; -#define OT_EVENT_DATA_MAX_SIZE 1024 +#define OT_EVENT_DATA_MAX_SIZE 2048 OT_TOOL_PACKED_BEGIN struct EventHeader @@ -82,30 +85,30 @@ struct EventHeader OT_TOOL_PACKED_BEGIN struct Event { - uint64_t mDelay; - uint8_t mEvent; - uint64_t mMsgId; - uint16_t mDataLength; + uint64_t mDelay; // delay in us before execution of the event + uint8_t mEvent; // event type + uint64_t mMsgId; // an ever-increasing event message id + uint16_t mDataLength; // the actual length of following event payload data uint8_t mData[OT_EVENT_DATA_MAX_SIZE]; } OT_TOOL_PACKED_END; OT_TOOL_PACKED_BEGIN struct RadioCommEventData { - uint8_t mChannel; - int8_t mPower; // power value (dBm), RSSI or Tx-power - uint8_t mError; // status code result of radio operation + uint8_t mChannel; // radio channel number (shared for IEEE 802.15.4 / BLE / ... ) + int8_t mPower; // power value (dBm), either RSSI or Tx-power + uint8_t mError; // status code result of radio operation using otError values uint64_t mDuration; // us duration of the radio comm operation } OT_TOOL_PACKED_END; OT_TOOL_PACKED_BEGIN struct RadioStateEventData { - uint8_t mChannel; + uint8_t mChannel; // radio channel (see above comments) int8_t mTxPower; // only valid when mEnergyState == OT_RADIO_STATE_TRANSMIT - int8_t mRxSensitivity; + int8_t mRxSensitivity; // current RX sensitivity in dBm uint8_t mEnergyState; // energy-state of radio (disabled, sleep, actively Tx, actively Rx) - uint8_t mSubState; + uint8_t mSubState; // detailed substate of radio, see enum RadioSubState uint8_t mState; // OT state of radio (disabled, sleep, Tx, Rx) uint64_t mRadioTime; // the radio's time otPlatRadioGetNow() } OT_TOOL_PACKED_END; @@ -118,7 +121,7 @@ struct RfSimParamEventData } OT_TOOL_PACKED_END; OT_TOOL_PACKED_BEGIN -struct UdpAilEventData +struct MsgToHostEventData { uint16_t mSrcPort; uint16_t mDstPort; @@ -210,13 +213,24 @@ void otSimSendExtAddrEvent(const otExtAddress *aExtAddress); /** * Send OT node information to the simulator. This helps the simulator * to identify a new socket connection made by the node. + * + * @param nodeId id of the sending OT node */ void otSimSendNodeInfoEvent(uint32_t nodeId); // TODO void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value); -// TODO -void otSimSendUdpAilEvent(struct UdpAilEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen); +/** + * Send an OT message (e.g. UDP, or IPv6 datagram, etc.) to the simulator to be handled + * by the "host" of the node. This host could be a local process/script, or an AIL network + * interface that can further forward the message to its destination. + * + * @param evType the event type to use + * @param aEventData the event data containing the message's metadata + * @param aMsgBytes the bytes of the message itself (e.g. UDP packet bytes or IPv6 datagram bytes) + * @param aMsgLen the length of the message + */ +void otSimSendMsgToHostEvent(uint8_t evType, struct MsgToHostEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen); #endif // PLATFORM_RFSIM_EVENT_SIM_H diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index b224633f..99ef3e54 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -45,9 +45,9 @@ #include "common/debug.hpp" #include "utils/code_utils.h" +#include "utils/uart.h" #include "event-sim.h" -#include "utils/uart.h" #define VERIFY_EVENT_SIZE(X) OT_ASSERT( (payloadLen >= sizeof(X)) && "received event payload too small" ); @@ -170,7 +170,7 @@ void platformUdpForwarder(otMessage *aMessage, { OT_UNUSED_VARIABLE(aContext); - struct UdpAilEventData evData; + struct MsgToHostEventData evData; uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; size_t msgLen = otMessageGetLength(aMessage); @@ -182,21 +182,30 @@ void platformUdpForwarder(otMessage *aMessage, memcpy(evData.mDstIp6, aPeerAddr, OT_IP6_ADDRESS_SIZE); otMessageRead(aMessage, 0, buf, msgLen); - otSimSendUdpAilEvent(&evData, &buf[0], msgLen); + otSimSendMsgToHostEvent(OT_SIM_EVENT_UDP_TO_HOST, &evData, &buf[0], msgLen); } #endif +// utility function to check IPv6 address for fe80::/10 or ffx2::/16 prefix -> link-local. static bool isLinkLocal(otIp6Address *addr) { return (addr->mFields.m8[0] == 0xfe && (addr->mFields.m8[1] & 0b11000000) == 0x80) || (addr->mFields.m8[0] == 0xff && (addr->mFields.m8[1] & 0b00001111) == 0x02); } +// utility function that returns IPv6 address' multicast scope 0x0-0xf or 0xff for parse-error. +static uint8_t ip6McastScope(otIp6Address *addr) +{ + if (addr->mFields.m8[0] != 0xff) + return 0xff; + return addr->mFields.m8[0] & 0x0f; +} + void platformIp6Receiver(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); - struct UdpAilEventData evData; + struct MsgToHostEventData evData; uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; const uint8_t dstAddrZero[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; size_t msgLen; @@ -210,9 +219,11 @@ void platformIp6Receiver(otMessage *aMessage, void *aContext) error = platformParseIp6(aMessage, &ip6Info); OT_ASSERT(error == OT_ERROR_NONE); - // determine if IPv6 datagram must go to host/AIL. - otEXPECT(!isLinkLocal(&ip6Info.mPeerAddr) && !isLinkLocal(&ip6Info.mSockAddr)); - otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage)); + // determine if IPv6 datagram must go to AIL. This implements simulation-specific BR packet filtering. + otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage) && + !isLinkLocal(&ip6Info.mPeerAddr) && + !isLinkLocal(&ip6Info.mSockAddr) && + ip6McastScope(&ip6Info.mPeerAddr) >= 0x4); // create simulator event evData.mSrcPort = ip6Info.mSockPort; @@ -221,9 +232,8 @@ void platformIp6Receiver(otMessage *aMessage, void *aContext) memcpy(evData.mDstIp6, &ip6Info.mPeerAddr, OT_IP6_ADDRESS_SIZE); otMessageRead(aMessage, 0, buf, msgLen); - otPlatLog(OT_LOG_LEVEL_DEBG,OT_LOG_REGION_PLATFORM, - "FIXME Sending IPv6 datagram to simulator"); - otSimSendUdpAilEvent(&evData, &buf[0], msgLen); + otPlatLog(OT_LOG_LEVEL_INFO, OT_LOG_REGION_PLATFORM, "Delivering msg to host for AIL forwarding"); + otSimSendMsgToHostEvent(OT_SIM_EVENT_IP6_TO_HOST, &evData, &buf[0], msgLen); exit: otMessageFree(aMessage); diff --git a/ot-rfsim/src/system.c b/ot-rfsim/src/system.c index 4445e0a0..6c7dc5ca 100644 --- a/ot-rfsim/src/system.c +++ b/ot-rfsim/src/system.c @@ -43,6 +43,8 @@ #include #include +#include "common/debug.hpp" + extern void platformReceiveEvent(otInstance *aInstance); extern bool gPlatformPseudoResetWasRequested; @@ -89,7 +91,7 @@ void otSysInit(int argc, char *argv[]) { argv[3]); platformExit(EXIT_FAILURE); } - randomSeed = (uint32_t) randomSeedParam; + randomSeed = (int32_t) randomSeedParam; } platformLoggingInit(argv[0]); @@ -175,12 +177,7 @@ static void socket_init(char *socketFilePath) { memset(&sockaddr, 0, sizeof(struct sockaddr_un)); sockaddr.sun_family = AF_UNIX; size_t strLen = strlen(socketFilePath); - if (strLen >= sizeof(sockaddr.sun_path)) { - gTerminate = true; - otPlatLog(OT_LOG_LEVEL_CRIT, OT_LOG_REGION_PLATFORM, - "Unix socket path too long: %s\n", socketFilePath); - platformExit(EXIT_FAILURE); - } + OT_ASSERT(strLen < sizeof(sockaddr.sun_path)); memcpy(sockaddr.sun_path, socketFilePath, strLen); gSockFd = socket(AF_UNIX, SOCK_STREAM, 0); @@ -192,9 +189,7 @@ static void socket_init(char *socketFilePath) { if (connect(gSockFd, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == -1) { gTerminate = true; - otPlatLog(OT_LOG_LEVEL_CRIT, OT_LOG_REGION_PLATFORM, - "Unable to open Unix socket to OT-NS at: %s\n", - sockaddr.sun_path); + fprintf(stderr, "Unable to open Unix socket to OT-NS at: %s\n", sockaddr.sun_path); perror("bind"); platformExit(EXIT_FAILURE); } diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index df3e4f5b..e0760d8c 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -82,6 +82,11 @@ def testCommissioningOneHop(self): ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR self.go(10) + # n2 sends a coap message to n1, to test host receiving of UDP + n2_gua = ns.get_ipaddrs(n1, "mleid") + ns.node_cmd(n2, "coap get %s request/to/br" % n2_gua[0]) + self.go(10) + # n3 joins as CCM joiner ns.commissioner_ccm_joiner_add(n1, "*") ns.ifconfig_up(n3) diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py new file mode 100755 index 00000000..58bc63ee --- /dev/null +++ b/pylibs/unittests/test_sim_hosts.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024, The OTNS Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import aiocoap +import aiocoap.resource as resource +import asyncio +import unittest + +from OTNSTestCase import OTNSTestCase +from otns.cli import errors, OTNS + + +class SimHostsTests(OTNSTestCase): + def testConfigureSimHosts(self): + ns = self.ns + ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') + with self.assertRaises(errors.OTNSCliError): # too long IPv6 address + ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd:1234:5678:abcd:2020:3030" 5684 65300') + with self.assertRaises(errors.OTNSCliError): # missing port-mapped + ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd::5678:abcd:2020" 5684') + ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd:1234:5678:abcd:2020" 5684 65300') + ns.cmd('host add "bad.example.com" "910b::f00d" 3 4') + + hosts_list = ns.cmd('host list') + self.assertEqual(3+1, len(hosts_list)) # includes one header line + + ns.cmd('host del "myserver.example.com"') + hosts_list = ns.cmd('host list') + self.assertEqual(1+1, len(hosts_list)) + + ns.cmd('host del "910b::f00d"') + hosts_list = ns.cmd('host list') + self.assertEqual(0+1, len(hosts_list)) + + def testSendToSimHost(self): + ns = self.ns + ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') + n1=ns.add('br') + ns.go(10) + n2=ns.add('router') + ns.go(10) + + # n2 sends a coap message to AIL, to test AIL connectivity + ns.node_cmd(n2, "coap start") + ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR + self.go(10) + + hosts_list = ns.cmd('host list') + self.assertEqual(1+1, len(hosts_list)) + self.assertEqual("11 0", hosts_list[1][-11:]) # number of Rx bytes == 11 + + def testResponseFromSimHost(self): + asyncio.run(self.asyncResponseFromSimHost()) + + async def asyncResponseFromSimHost(self): + await coap_server_main() + + ns = self.ns + ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') + n1=ns.add('br') + ns.go(10) + n2=ns.add('router') + ns.go(10) + + # n2 sends a coap message to AIL, to test AIL connectivity + ns.node_cmd(n2, "coap start") + ns.node_cmd(n2, "coap get fc00::1234 hello") # dest addr must match an external route of the BR + self.go(10) + + #await asyncio.get_running_loop().create_future() + await asyncio.sleep(10) + + hosts_list = ns.cmd('host list') + self.assertEqual(1+1, len(hosts_list)) + self.assertEqual("12 11", hosts_list[1][-11:]) # number of Rx bytes == 11 + + #await asynciocoap_server_main() + + + + + + +class HelloResource(resource.Resource): + async def render_get(self, request): + return aiocoap.Message(content_format=0, payload="Hello World".encode('utf8')) + + +async def coap_server_main(): + root = resource.Site() + root.add_resource(['hello'], HelloResource()) + await aiocoap.Context.create_server_context(root) + +if __name__ == '__main__': + unittest.main() diff --git a/script/install-deps b/script/install-deps index 70127e14..48a7dded 100755 --- a/script/install-deps +++ b/script/install-deps @@ -56,7 +56,7 @@ install_grpcwebproxy() install_python_libs() { activate_python_venv - python3 -m pip install setuptools wheel + python3 -m pip install setuptools wheel aiocoap } main() diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go new file mode 100644 index 00000000..f8298924 --- /dev/null +++ b/simulation/sim_hosts.go @@ -0,0 +1,200 @@ +package simulation + +import ( + "fmt" + "github.com/openthread/ot-ns/event" + "github.com/openthread/ot-ns/logger" + "golang.org/x/net/ipv6" + "net" + "net/netip" +) + +const ( + udpHeaderLen = 8 + protocolUdp = 17 +) + +// ConnId is a unique identifier/tuple for a TCP or UDP connection between a node and a simulated host. +type ConnId struct { + NodeIp6Addr netip.Addr + ExtIp6Addr netip.Addr + NodePort uint16 + ExtPort uint16 +} + +// SimConn is a two-way connection between a node's port and a simulated host's port. +type SimConn struct { + BrNode *Node // assumes a single BR also handles the return traffic. + Conn net.Conn + Nat66State *event.MsgToHostEventData + PortMapped uint16 + BytesUpstream uint64 // total bytes from node to sim-host (across all BRs) + BytesDownstream uint64 // total bytes from sim-host to node (across all BRs) +} + +// SimHostEndpoint represents a single endpoint (port) of a sim-host, potentially interacting with N >= 0 nodes. +type SimHostEndpoint struct { + HostName string + Ip6Addr netip.Addr + Port uint16 // destination UDP/TCP port as specified by the simulated node. + PortMapped uint16 // actual sim-host port on [::1] to which specified port is mapped. +} + +// SimHosts manages all connections between nodes and simulated hosts. +type SimHosts struct { + sim *Simulation + Hosts map[SimHostEndpoint]struct{} + Conns map[ConnId]*SimConn +} + +func NewSimHosts() *SimHosts { + sh := &SimHosts{ + sim: nil, + Hosts: make(map[SimHostEndpoint]struct{}), + Conns: make(map[ConnId]*SimConn), + } + return sh +} + +func (sh *SimHosts) Init(sim *Simulation) { + sh.sim = sim +} + +func (sh *SimHosts) AddHost(host SimHostEndpoint) error { + sh.Hosts[host] = struct{}{} + // TODO check for conflicts with existing item + return nil +} + +func (sh *SimHosts) RemoveHost(host SimHostEndpoint) { + delete(sh.Hosts, host) + // FIXME close all related connection state +} + +func (sh *SimHosts) GetTxBytes(host *SimHostEndpoint) uint64 { + var n uint64 = 0 + for connId, simConn := range sh.Conns { + if host.Ip6Addr == connId.ExtIp6Addr && host.Port == connId.ExtPort { + n += simConn.BytesDownstream + } + } + return n +} + +func (sh *SimHosts) GetRxBytes(host *SimHostEndpoint) uint64 { + var n uint64 = 0 + for connId, simConn := range sh.Conns { + if host.Ip6Addr == connId.ExtIp6Addr && host.Port == connId.ExtPort { + n += simConn.BytesUpstream + } + } + return n +} + +// handleUdpFromNode handles a UDP message coming from a node and checks to which sim-host to deliver it. +func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEventData, udpData []byte) { + var host SimHostEndpoint + var err error + var ok bool + var simConn *SimConn + + found := false + + // find the first matching simulated host, if any. + for host = range sh.Hosts { + if host.Port == udpMetadata.DstPort && host.Ip6Addr == udpMetadata.DstIp6Address { + found = true + } + } + if !found { + logger.Debugf("SimHosts: UDP from node %d did not reach any sim-host destination", node.Id) + return + } + logger.Debugf("SimHosts: UDP from node %d, to sim server [::1]:%d (%d bytes)", node.Id, host.PortMapped, len(udpData)) + + // fetch existing conn object for the specific Thread node source endpoint, if any. + connId := ConnId{ + NodeIp6Addr: udpMetadata.SrcIp6Address, + ExtIp6Addr: udpMetadata.DstIp6Address, + NodePort: udpMetadata.SrcPort, + ExtPort: udpMetadata.DstPort, + } + if simConn, ok = sh.Conns[connId]; !ok { + // create new connection + simConn = &SimConn{ + Conn: nil, + BrNode: node, + Nat66State: udpMetadata, + PortMapped: host.PortMapped, + BytesUpstream: 0, + BytesDownstream: 0, + } + simConn.Conn, err = net.Dial("udp", fmt.Sprintf("[::1]:%d", host.PortMapped)) + if err != nil { + logger.Warnf("SimHosts could not connect to local UDP port %d: %v", host.PortMapped, err) + if simConn.Conn != nil { + _ = simConn.Conn.Close() + } + return + } + + // create reader thread - to process the sim-host's response traffic. + go sh.udpReaderFunc(simConn) + + // store created connection under its unique tuple ID + sh.Conns[connId] = simConn + } + + var n int + n, err = simConn.Conn.Write(udpData) + if err != nil { + logger.Warnf("SimHosts could not write udp data to [::1]:%d : %v", host.PortMapped, err) + return + } + simConn.BytesUpstream += uint64(n) +} + +// handleUdpFromSimHost handles a UDP message coming from a sim-host and checks to which node to deliver it. +func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { + logger.Debugf("SimHosts: UDP from sim-host [::1]:%d (%d bytes)", simConn.PortMapped, len(udpData)) + simConn.BytesDownstream += uint64(len(udpData)) + ev := &event.Event{ + Delay: 0, + Type: event.EventTypeUdpFromHost, + Data: udpData, + NodeId: simConn.BrNode.Id, + MsgToHostData: event.MsgToHostEventData{ + SrcPort: simConn.Nat66State.DstPort, // simulates response back: ports reversed + DstPort: simConn.Nat66State.SrcPort, + SrcIp6Address: simConn.Nat66State.DstIp6Address, // simulates response: addrs reversed + DstIp6Address: simConn.Nat66State.SrcIp6Address, + }, + } + sh.sim.Dispatcher().PostEventAsync(ev) +} + +func (sh *SimHosts) udpReaderFunc(simConn *SimConn) { + buf := make([]byte, 2048) // FIXME size set + for { + rlen, err := simConn.Conn.Read(buf) + if err != nil { + panic(err) // FIXME + } + sh.handleUdpFromSimHost(simConn, buf[:rlen]) + } +} + +func (sh *SimHosts) handleIp6(ip6Metadata *event.MsgToHostEventData, ip6Data []byte) { + var ip6Header *ipv6.Header + var err error + + // check if header is IPv6 + UDP? + if ip6Header, err = ipv6.ParseHeader(ip6Data); err != nil { + logger.Warnf("SimHosts could not parse as IPv6: %v", err) + return + } + if ip6Header.Version == 6 && ip6Header.NextHeader == protocolUdp && len(ip6Data) > ipv6.HeaderLen+udpHeaderLen { + udpData := ip6Data[ipv6.HeaderLen+udpHeaderLen:] + sh.handleUdp(ip6Metadata, udpData) + } +} diff --git a/simulation/simulation.go b/simulation/simulation.go index f662a230..734b7815 100644 --- a/simulation/simulation.go +++ b/simulation/simulation.go @@ -62,6 +62,7 @@ type Simulation struct { energyAnalyser *energy.EnergyAnalyser nodePlacer *NodeAutoPlacer kpiMgr *KpiManager + simHosts *SimHosts } func NewSimulation(ctx *progctx.ProgCtx, cfg *Config, dispatcherCfg *dispatcher.Config) (*Simulation, error) { @@ -76,6 +77,7 @@ func NewSimulation(ctx *progctx.ProgCtx, cfg *Config, dispatcherCfg *dispatcher. networkInfo: visualize.DefaultNetworkInfo(), nodePlacer: NewNodeAutoPlacer(), kpiMgr: NewKpiManager(), + simHosts: NewSimHosts(), } s.SetLogLevel(cfg.LogLevel) s.networkInfo.Real = cfg.Realtime @@ -108,6 +110,7 @@ func NewSimulation(ctx *progctx.ProgCtx, cfg *Config, dispatcherCfg *dispatcher. s.d.SetEnergyAnalyser(s.energyAnalyser) s.vis.SetEnergyAnalyser(s.energyAnalyser) s.kpiMgr.Init(s) + s.simHosts.Init(s) return s, nil } @@ -395,6 +398,24 @@ func (s *Simulation) OnRfSimEvent(nodeid NodeId, evt *event.Event) { } } +func (s *Simulation) OnUdpToHost(nodeid NodeId, udpMetadata *event.MsgToHostEventData, udpData []byte) { + node := s.nodes[nodeid] + if node == nil { + return + } + + s.simHosts.handleUdpFromNode(node, udpMetadata, udpData) +} + +func (s *Simulation) OnIp6ToHost(nodeid NodeId, ip6Metadata *event.MsgToHostEventData, ip6Data []byte) { + node := s.nodes[nodeid] + if node == nil { + return + } + + s.simHosts.handleIp6(ip6Metadata, ip6Data) +} + // PostAsync will post an asynchronous simulation task in the queue for execution // @return true when post was successful, false if not (e.g. when sim exited) func (s *Simulation) PostAsync(f func()) bool { @@ -413,6 +434,10 @@ func (s *Simulation) Dispatcher() *dispatcher.Dispatcher { return s.d } +func (s *Simulation) SimHosts() *SimHosts { + return s.simHosts +} + func (s *Simulation) VisitNodesInOrder(cb func(node *Node)) { var nodeids []NodeId for nodeid := range s.nodes { From 47d619cf3e807f98e2d07eb30235dfd37dce3f51 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Fri, 17 May 2024 17:47:22 +0200 Subject: [PATCH 07/36] WIP build and sim_hosts unit test fixed. --- dispatcher/dispatcher.go | 10 ++++------ pylibs/unittests/test_sim_hosts.py | 22 +++++++--------------- simulation/sim_hosts.go | 4 ++-- simulation/simulation.go | 2 +- 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 728ff98c..9162bba6 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -441,9 +441,8 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { case EventTypeIp6ToHost: d.Counters.OtherEvents += 1 d.cbHandler.OnIp6ToHost(node.Id, &evt.MsgToHostData, evt.Data) - case EventTypeUdpFromHost: - fallthrough - case EventTypeIp6FromHost: + case EventTypeUdpFromHost, + EventTypeIp6FromHost: d.Counters.OtherEvents += 1 evt.MustDispatch = true // asap resend again to the target (BR) node. d.eventQueue.Add(evt) @@ -589,9 +588,8 @@ func (d *Dispatcher) processNextEvent(simSpeed float64) bool { d.sendRadioCommRxStartEvents(node, evt) case EventTypeRadioRxDone: d.sendRadioCommRxDoneEvents(node, evt) - case EventTypeUdpFromHost: - fallthrough - case EventTypeIp6FromHost: + case EventTypeUdpFromHost, + EventTypeIp6FromHost: node.sendEvent(evt) // TODO no loss on external network is simulated currently. default: if d.radioModel.OnEventDispatch(node.RadioNode, node.RadioNode, evt) { diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 58bc63ee..634af955 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -39,9 +39,9 @@ class SimHostsTests(OTNSTestCase): def testConfigureSimHosts(self): ns = self.ns ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') - with self.assertRaises(errors.OTNSCliError): # too long IPv6 address + with self.assertRaises(errors.OTNSCliError): # too-long IPv6 address ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd:1234:5678:abcd:2020:3030" 5684 65300') - with self.assertRaises(errors.OTNSCliError): # missing port-mapped + with self.assertRaises(errors.OTNSCliError): # missing port-mapped ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd::5678:abcd:2020" 5684') ns.cmd('host add "myserver.example.com" "fd12:1234:5678:abcd:1234:5678:abcd:2020" 5684 65300') ns.cmd('host add "bad.example.com" "910b::f00d" 3 4') @@ -67,12 +67,12 @@ def testSendToSimHost(self): # n2 sends a coap message to AIL, to test AIL connectivity ns.node_cmd(n2, "coap start") - ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR + ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR self.go(10) hosts_list = ns.cmd('host list') self.assertEqual(1+1, len(hosts_list)) - self.assertEqual("11 0", hosts_list[1][-11:]) # number of Rx bytes == 11 + self.assertEqual("11 0", hosts_list[1][-11:]) # number of Rx bytes == 11 def testResponseFromSimHost(self): asyncio.run(self.asyncResponseFromSimHost()) @@ -89,21 +89,13 @@ async def asyncResponseFromSimHost(self): # n2 sends a coap message to AIL, to test AIL connectivity ns.node_cmd(n2, "coap start") - ns.node_cmd(n2, "coap get fc00::1234 hello") # dest addr must match an external route of the BR + ns.node_cmd(n2, "coap get fc00::1234 hello") # dest addr must match an external route of the BR self.go(10) - - #await asyncio.get_running_loop().create_future() - await asyncio.sleep(10) + await asyncio.sleep(3) hosts_list = ns.cmd('host list') self.assertEqual(1+1, len(hosts_list)) - self.assertEqual("12 11", hosts_list[1][-11:]) # number of Rx bytes == 11 - - #await asynciocoap_server_main() - - - - + self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 class HelloResource(resource.Resource): diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index f8298924..94f7b3d7 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -184,7 +184,7 @@ func (sh *SimHosts) udpReaderFunc(simConn *SimConn) { } } -func (sh *SimHosts) handleIp6(ip6Metadata *event.MsgToHostEventData, ip6Data []byte) { +func (sh *SimHosts) handleIp6FromNode(node *Node, ip6Metadata *event.MsgToHostEventData, ip6Data []byte) { var ip6Header *ipv6.Header var err error @@ -195,6 +195,6 @@ func (sh *SimHosts) handleIp6(ip6Metadata *event.MsgToHostEventData, ip6Data []b } if ip6Header.Version == 6 && ip6Header.NextHeader == protocolUdp && len(ip6Data) > ipv6.HeaderLen+udpHeaderLen { udpData := ip6Data[ipv6.HeaderLen+udpHeaderLen:] - sh.handleUdp(ip6Metadata, udpData) + sh.handleUdpFromNode(node, ip6Metadata, udpData) } } diff --git a/simulation/simulation.go b/simulation/simulation.go index 734b7815..be64a96e 100644 --- a/simulation/simulation.go +++ b/simulation/simulation.go @@ -413,7 +413,7 @@ func (s *Simulation) OnIp6ToHost(nodeid NodeId, ip6Metadata *event.MsgToHostEven return } - s.simHosts.handleIp6(ip6Metadata, ip6Data) + s.simHosts.handleIp6FromNode(node, ip6Metadata, ip6Data) } // PostAsync will post an asynchronous simulation task in the queue for execution From 76fec3db54a37e0e4aeaa43a6a508fc4d51053ac Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 18 May 2024 16:35:35 +0200 Subject: [PATCH 08/36] WIP - added UDP return event and bugfixing. --- dispatcher/Node.go | 3 ++- dispatcher/dispatcher.go | 6 ++++- event/event.go | 6 +++++ event/event_test.go | 20 ++++++++++++++++ ot-rfsim/src/misc.c | 9 +++++--- ot-rfsim/src/platform-rfsim.c | 37 ++++++++++++++++++++++++++++++ ot-rfsim/src/platform-rfsim.h | 11 +++++++++ pylibs/unittests/test_sim_hosts.py | 4 +++- simulation/sim_hosts.go | 10 +++++--- 9 files changed, 97 insertions(+), 9 deletions(-) diff --git a/dispatcher/Node.go b/dispatcher/Node.go index b0cc6b76..e62b2159 100644 --- a/dispatcher/Node.go +++ b/dispatcher/Node.go @@ -182,7 +182,6 @@ func (node *Node) sendEvent(evt *Event) { // time keeping - move node's time to the current send-event's time. node.D.alarmMgr.SetNotified(node.Id) - node.D.setAlive(node.Id) node.CurTime += evt.Delay logger.AssertTrue(node.CurTime == node.D.CurTime) @@ -203,6 +202,8 @@ func (node *Node) sendEvent(evt *Event) { if err != nil { node.logger.Error(err) node.err = err + } else { + node.D.setAlive(node.Id) } } diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 9162bba6..55ce901c 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -394,7 +394,9 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { return } - node.conn = evt.Conn // store socket connection for this node. + if node.conn == nil { + node.conn = evt.Conn // store socket connection for this node. + } evt.Timestamp = d.CurTime // timestamp the incoming event // TODO document this use (for alarm messages) @@ -446,6 +448,7 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.Counters.OtherEvents += 1 evt.MustDispatch = true // asap resend again to the target (BR) node. d.eventQueue.Add(evt) + logger.Debugf("FIXME d.eventQueue.Add(evt) to node %d", node.Id) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) @@ -591,6 +594,7 @@ func (d *Dispatcher) processNextEvent(simSpeed float64) bool { case EventTypeUdpFromHost, EventTypeIp6FromHost: node.sendEvent(evt) // TODO no loss on external network is simulated currently. + logger.Debugf("FIXME node.sendEvent(evt) to node %d // TODO no loss on external network is simulated currently.", node.Id) default: if d.radioModel.OnEventDispatch(node.RadioNode, node.RadioNode, evt) { node.sendEvent(evt) diff --git a/event/event.go b/event/event.go index b08d0199..529976e8 100644 --- a/event/event.go +++ b/event/event.go @@ -168,6 +168,12 @@ func (e *Event) Serialize() []byte { case EventTypeRadioRfSimParamGet: extraFields = []byte{byte(e.RfSimParamData.Param), 0, 0, 0, 0} binary.LittleEndian.PutUint32(extraFields[1:], uint32(e.RfSimParamData.Value)) + case EventTypeUdpFromHost: + extraFields = make([]byte, msgToHostEventDataHeaderLen) + binary.LittleEndian.PutUint16(extraFields[0:2], e.MsgToHostData.SrcPort) + binary.LittleEndian.PutUint16(extraFields[2:4], e.MsgToHostData.DstPort) + copy(extraFields[4:20], e.MsgToHostData.SrcIp6Address.AsSlice()) + copy(extraFields[20:36], e.MsgToHostData.DstIp6Address.AsSlice()) default: break } diff --git a/event/event_test.go b/event/event_test.go index 5aab2a4d..1ce77180 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -32,6 +32,7 @@ import ( "github.com/openthread/ot-ns/types" "github.com/stretchr/testify/assert" + "net/netip" ) func TestDeserializeAlarmEvent(t *testing.T) { @@ -259,6 +260,25 @@ func TestDeserializeMsgToHostEvents(t *testing.T) { assert.Equal(t, testIp6Addr2, ev.MsgToHostData.DstIp6Address.AsSlice()) } +func TestSerializeMsgToHostEvents(t *testing.T) { + dataExpected, _ := hex.DecodeString("00000000000000001504000000000000002900efbe3316fe800000000000000000000000001234fe80abcd00000000000000000000abcd0102030405") + evData := MsgToHostEventData{ + SrcPort: 48879, + DstPort: 5683, + SrcIp6Address: netip.MustParseAddr("fe80::1234"), + DstIp6Address: netip.MustParseAddr("fe80:abcd::abcd"), + } + ev := &Event{ + Delay: 0, + Type: EventTypeUdpFromHost, + MsgId: 4, + Data: []byte{1, 2, 3, 4, 5}, + MsgToHostData: evData, + } + data := ev.Serialize() + assert.Equal(t, dataExpected, data) +} + func TestEventCopy(t *testing.T) { ev := &Event{ Type: EventTypeRadioRxDone, diff --git a/ot-rfsim/src/misc.c b/ot-rfsim/src/misc.c index 5408ba0a..08496c0f 100644 --- a/ot-rfsim/src/misc.c +++ b/ot-rfsim/src/misc.c @@ -64,11 +64,14 @@ void otPlatReset(otInstance *aInstance) #if OPENTHREAD_CONFIG_PLATFORM_ASSERT_MANAGEMENT void otPlatAssertFail(const char *aFilename, int aLineNumber) { - fprintf(stderr,"assert failed at %s:%d\n", aFilename, aLineNumber); - fprintf(stderr, "Last sent Event: tp=%i dly=%lu datalen=%u\n", + otLogCritPlat("assert failed at %s:%d\n", aFilename, aLineNumber); + otLogCritPlat( "Last sent Event: tp=%i dly=%lu datalen=%u\n", gLastSentEvent.mEvent, (unsigned long)gLastSentEvent.mDelay, gLastSentEvent.mDataLength); - fprintf(stderr, "Last recv Event: tp=%i dly=%lu datalen=%u\n", + otLogCritPlat( "Last recv Event: tp=%i dly=%lu datalen=%u\n", gLastRecvEvent.mEvent, (unsigned long)gLastRecvEvent.mDelay, gLastRecvEvent.mDataLength); + + fprintf(stderr,"assert failed at %s:%d\n", aFilename, aLineNumber); + // For debug build, use assert to generate a core dump assert(false); exit(1); diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 99ef3e54..7d4e8806 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -42,6 +42,8 @@ #include #include +#include +#include #include "common/debug.hpp" #include "utils/code_utils.h" @@ -75,6 +77,7 @@ void platformReceiveEvent(otInstance *aInstance) struct Event event; ssize_t rval = recvfrom(gSockFd, (char *)&event, sizeof(struct EventHeader), 0, NULL, NULL); const uint8_t *evData = event.mData; + otError error; if (rval < 0) { @@ -146,6 +149,17 @@ void platformReceiveEvent(otInstance *aInstance) platformRadioReportStateToSimulator(true); break; + case OT_SIM_EVENT_UDP_FROM_HOST: + otLogWarnPlat("FIXME start platformUdpFromHost"); + VERIFY_EVENT_SIZE(struct MsgToHostEventData) + error = platformUdpFromHost(aInstance, (struct MsgToHostEventData *)evData, + event.mData + sizeof(struct MsgToHostEventData), payloadLen - sizeof(struct MsgToHostEventData)); + if (error != OT_ERROR_NONE) { + otLogCritPlat("Error handling UDP from host event: %s", otThreadErrorToString(error)); + } + otLogWarnPlat("FIXME done platformUdpFromHost"); + break; + default: OT_ASSERT(false && "Unrecognized event type received"); } @@ -161,6 +175,29 @@ void otPlatOtnsStatus(const char *aStatus) otSimSendOtnsStatusPushEvent(aStatus, statusLength); } +otError platformUdpFromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, uint8_t *msg, size_t msgLen) { + otMessage *message = NULL; + otError error = OT_ERROR_NONE; + + message = otIp6NewMessage(aInstance, NULL); + otEXPECT_ACTION(message != NULL, error = OT_ERROR_NO_BUFS); + + // TODO set IPv6 header + + //otMessageSetOrigin(message, OT_MESSAGE_ORIGIN_HOST_UNTRUSTED); + + // TODO append UDP header + + error = otMessageAppend(message, msg, msgLen - 1); + otEXPECT(error == OT_ERROR_NONE); + + error = otIp6Send(aInstance, message); + otLogWarnPlat("FIXME error = %u", error); + +exit: + return error; +} + #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE void platformUdpForwarder(otMessage *aMessage, uint16_t aPeerPort, diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index ecf1e210..43a345cd 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -245,6 +245,17 @@ void platformUdpForwarder(otMessage *aMessage, uint16_t aSockPort, void *aContext); +/** + * TODO + * + * @param aInstance + * @param evData + * @param msg + * @param msgLen + * @return + */ +otError platformUdpFromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, uint8_t *msg, size_t msgLen); + /** * Callback for node receiving an IPv6 datagram. When the datagram is destined for the upper-layer host * or to the simulated AIL, this callback is used to send the datagram to the simulator for further processing. diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 634af955..c0183e19 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -90,8 +90,10 @@ async def asyncResponseFromSimHost(self): # n2 sends a coap message to AIL, to test AIL connectivity ns.node_cmd(n2, "coap start") ns.node_cmd(n2, "coap get fc00::1234 hello") # dest addr must match an external route of the BR + self.go(0.2) + await asyncio.sleep(0.2) # let the aiocoap server serve the request self.go(10) - await asyncio.sleep(3) + await asyncio.sleep(0.2) hosts_list = ns.cmd('host list') self.assertEqual(1+1, len(hosts_list)) diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index 94f7b3d7..551e3268 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -139,7 +139,7 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv } // create reader thread - to process the sim-host's response traffic. - go sh.udpReaderFunc(simConn) + go sh.udpReaderGoRoutine(simConn) // store created connection under its unique tuple ID sh.Conns[connId] = simConn @@ -155,6 +155,7 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv } // handleUdpFromSimHost handles a UDP message coming from a sim-host and checks to which node to deliver it. +// It performs a form of NAT66 with NPT to achieve this. func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { logger.Debugf("SimHosts: UDP from sim-host [::1]:%d (%d bytes)", simConn.PortMapped, len(udpData)) simConn.BytesDownstream += uint64(len(udpData)) @@ -171,9 +172,10 @@ func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { }, } sh.sim.Dispatcher().PostEventAsync(ev) + logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) for node %d, ev = %+v", simConn.BrNode.Id, ev) } -func (sh *SimHosts) udpReaderFunc(simConn *SimConn) { +func (sh *SimHosts) udpReaderGoRoutine(simConn *SimConn) { buf := make([]byte, 2048) // FIXME size set for { rlen, err := simConn.Conn.Read(buf) @@ -188,13 +190,15 @@ func (sh *SimHosts) handleIp6FromNode(node *Node, ip6Metadata *event.MsgToHostEv var ip6Header *ipv6.Header var err error - // check if header is IPv6 + UDP? + // check if header is IPv6? if ip6Header, err = ipv6.ParseHeader(ip6Data); err != nil { logger.Warnf("SimHosts could not parse as IPv6: %v", err) return } + // if it's UDP - handle the datagram if ip6Header.Version == 6 && ip6Header.NextHeader == protocolUdp && len(ip6Data) > ipv6.HeaderLen+udpHeaderLen { udpData := ip6Data[ipv6.HeaderLen+udpHeaderLen:] sh.handleUdpFromNode(node, ip6Metadata, udpData) } + // TODO TCP } From b0a4c6a8878b411d9405508b46320b14d5efd10e Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sun, 19 May 2024 21:58:45 +0200 Subject: [PATCH 09/36] Rebased to 'main' branch. WIP - source reformat, adding UDP checksum in IPv6 packet gen. --- dispatcher/dispatcher.go | 12 +++-- event/event.go | 3 +- event/event_test.go | 4 +- ot-rfsim/src/event-sim.c | 23 ++++---- ot-rfsim/src/event-sim.h | 5 +- ot-rfsim/src/platform-rfsim.c | 45 ++++++---------- ot-rfsim/src/platform-rfsim.h | 2 +- pylibs/unittests/test_sim_hosts.py | 10 ++-- simulation/sim_hosts.go | 64 +++++++++++----------- simulation/types.go | 7 +++ simulation/utils.go | 87 ++++++++++++++++++++++++++++++ simulation/utils_test.go | 17 ++++++ 12 files changed, 195 insertions(+), 84 deletions(-) create mode 100644 simulation/utils_test.go diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 55ce901c..b664cb69 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -53,7 +53,7 @@ import ( "github.com/openthread/ot-ns/visualize" ) -// CallbackHandler handles the callbacks from Dispatcher to its managing entity (e.g. a Simulation). +// CallbackHandler handles callbacks from Dispatcher to its managing entity (e.g. a Simulation). type CallbackHandler interface { // OnUartWrite Notifies that the node's UART was written with data. OnUartWrite(nodeid NodeId, data []byte) @@ -61,16 +61,16 @@ type CallbackHandler interface { // OnLogWrite Notifies that a log item wsa written to the node's log. OnLogWrite(nodeid NodeId, data []byte) - // OnNextEventTime Notifies that the Dispatcher simulation will move shortly to the next event time. + // OnNextEventTime Notifies that the Dispatcher simulated-time will move to the next event time. OnNextEventTime(nextTimeUs uint64) // OnRfSimEvent Notifies that Dispatcher received an OT-RFSIM platform event that it didn't handle itself. OnRfSimEvent(nodeid NodeId, evt *Event) // OnUdpToHost Notifies that the Dispatcher received an off-mesh or to-host UDP packet from a node, to be handled. - OnUdpToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) + //OnUdpToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) - // OnIp6ToHost Notifies that the Dispatcher received an off-mesh or to-host IPv6 packet from a node, to be handled. + // OnIp6ToHost Notifies that the Dispatcher received an IPv6 packet from a node, to be handled by the node's host. OnIp6ToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) } @@ -439,7 +439,9 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.alarmMgr.SetTimestamp(node.Id, Ever) case EventTypeUdpToHost: d.Counters.OtherEvents += 1 - d.cbHandler.OnUdpToHost(node.Id, &evt.MsgToHostData, evt.Data) + //d.cbHandler.OnUdpToHost(node.Id, &evt.MsgToHostData, evt.Data) + // FIXME + logger.Panicf("not impl") case EventTypeIp6ToHost: d.Counters.OtherEvents += 1 d.cbHandler.OnIp6ToHost(node.Id, &evt.MsgToHostData, evt.Data) diff --git a/event/event.go b/event/event.go index 529976e8..46ec808b 100644 --- a/event/event.go +++ b/event/event.go @@ -168,7 +168,8 @@ func (e *Event) Serialize() []byte { case EventTypeRadioRfSimParamGet: extraFields = []byte{byte(e.RfSimParamData.Param), 0, 0, 0, 0} binary.LittleEndian.PutUint32(extraFields[1:], uint32(e.RfSimParamData.Value)) - case EventTypeUdpFromHost: + case EventTypeUdpFromHost, + EventTypeIp6FromHost: extraFields = make([]byte, msgToHostEventDataHeaderLen) binary.LittleEndian.PutUint16(extraFields[0:2], e.MsgToHostData.SrcPort) binary.LittleEndian.PutUint16(extraFields[2:4], e.MsgToHostData.DstPort) diff --git a/event/event_test.go b/event/event_test.go index 1ce77180..0c0b1f54 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -261,7 +261,7 @@ func TestDeserializeMsgToHostEvents(t *testing.T) { } func TestSerializeMsgToHostEvents(t *testing.T) { - dataExpected, _ := hex.DecodeString("00000000000000001504000000000000002900efbe3316fe800000000000000000000000001234fe80abcd00000000000000000000abcd0102030405") + dataExpected, _ := hex.DecodeString("00000000000000001604000000000000002900efbe3316fe800000000000000000000000001234fe80abcd00000000000000000000abcd0102030405") evData := MsgToHostEventData{ SrcPort: 48879, DstPort: 5683, @@ -270,7 +270,7 @@ func TestSerializeMsgToHostEvents(t *testing.T) { } ev := &Event{ Delay: 0, - Type: EventTypeUdpFromHost, + Type: EventTypeIp6FromHost, MsgId: 4, Data: []byte{1, 2, 3, 4, 5}, MsgToHostData: evData, diff --git a/ot-rfsim/src/event-sim.c b/ot-rfsim/src/event-sim.c index 6530439d..974bf5ea 100644 --- a/ot-rfsim/src/event-sim.c +++ b/ot-rfsim/src/event-sim.c @@ -29,7 +29,7 @@ /** * @file * @brief - * This file includes simulation-event message formatting and parsing functions. + * This file includes simulation-event message formatting, sending and parsing functions. */ #include "common/debug.hpp" @@ -45,8 +45,8 @@ struct Event gLastSentEvent; void otSimSendSleepEvent(void) { OT_ASSERT(platformAlarmGetNext() > 0); - struct Event event; + event.mDelay = platformAlarmGetNext(); event.mEvent = OT_SIM_EVENT_ALARM_FIRED; event.mDataLength = 0; @@ -57,8 +57,8 @@ void otSimSendSleepEvent(void) void otSimSendRadioCommEvent(struct RadioCommEventData *aEventData, const uint8_t *aPayload, size_t aLenPayload) { OT_ASSERT(aLenPayload <= OT_EVENT_DATA_MAX_SIZE); - struct Event event; + event.mEvent = OT_SIM_EVENT_RADIO_COMM_START; memcpy(event.mData, aEventData, sizeof(struct RadioCommEventData)); memcpy(event.mData + sizeof(struct RadioCommEventData), aPayload, aLenPayload); @@ -70,6 +70,7 @@ void otSimSendRadioCommEvent(struct RadioCommEventData *aEventData, const uint8_ void otSimSendRadioCommInterferenceEvent(struct RadioCommEventData *aEventData) { struct Event event; + event.mEvent = OT_SIM_EVENT_RADIO_COMM_START; memcpy(event.mData, aEventData, sizeof(struct RadioCommEventData)); event.mData[sizeof(struct RadioCommEventData)] = aEventData->mChannel; // channel is stored twice TODO @@ -81,6 +82,7 @@ void otSimSendRadioCommInterferenceEvent(struct RadioCommEventData *aEventData) void otSimSendRadioChanSampleEvent(struct RadioCommEventData *aChanData) { struct Event event; + event.mEvent = OT_SIM_EVENT_RADIO_CHAN_SAMPLE; event.mDelay = 0; memcpy(event.mData, aChanData, sizeof(struct RadioCommEventData)); @@ -92,6 +94,7 @@ void otSimSendRadioChanSampleEvent(struct RadioCommEventData *aChanData) void otSimSendRadioStateEvent(struct RadioStateEventData *aStateData, uint64_t aDeltaUntilNextRadioState) { struct Event event; + event.mEvent = OT_SIM_EVENT_RADIO_STATE; event.mDelay = aDeltaUntilNextRadioState; memcpy(event.mData, aStateData, sizeof(struct RadioStateEventData)); @@ -102,8 +105,8 @@ void otSimSendRadioStateEvent(struct RadioStateEventData *aStateData, uint64_t a void otSimSendUartWriteEvent(const uint8_t *aData, uint16_t aLength) { OT_ASSERT(aLength <= OT_EVENT_DATA_MAX_SIZE); - struct Event event; + event.mEvent = OT_SIM_EVENT_UART_WRITE; event.mDelay = 0; event.mDataLength = aLength; @@ -126,8 +129,8 @@ void otSimSendLogWriteEvent(const uint8_t *aData, uint16_t aLength) { void otSimSendOtnsStatusPushEvent(const char *aStatus, uint16_t aLength) { OT_ASSERT(aLength <= OT_EVENT_DATA_MAX_SIZE); - struct Event event; + memcpy(event.mData, aStatus, aLength); event.mEvent = OT_SIM_EVENT_OTNS_STATUS_PUSH; event.mDelay = 0; @@ -138,8 +141,8 @@ void otSimSendOtnsStatusPushEvent(const char *aStatus, uint16_t aLength) { void otSimSendExtAddrEvent(const otExtAddress *aExtAddress) { OT_ASSERT(aExtAddress != NULL); - struct Event event; + memcpy(event.mData, aExtAddress, sizeof(otExtAddress)); event.mEvent = OT_SIM_EVENT_EXT_ADDR; event.mDelay = 0; @@ -149,9 +152,9 @@ void otSimSendExtAddrEvent(const otExtAddress *aExtAddress) { } void otSimSendNodeInfoEvent(uint32_t nodeId) { + struct Event event; OT_ASSERT(nodeId > 0); - struct Event event; memcpy(event.mData, &nodeId, sizeof(uint32_t)); event.mEvent = OT_SIM_EVENT_NODE_INFO; event.mDelay = 0; @@ -162,6 +165,7 @@ void otSimSendNodeInfoEvent(uint32_t nodeId) { void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value) { struct Event event; + event.mData[0] = param; memcpy(event.mData + 1, &value, sizeof(int32_t)); event.mEvent = OT_SIM_EVENT_RFSIM_PARAM_RSP; @@ -173,9 +177,9 @@ void otSimSendRfSimParamRespEvent(uint8_t param, int32_t value) { void otSimSendMsgToHostEvent(uint8_t evType, struct MsgToHostEventData *aEventData, uint8_t *aMsgBytes, size_t aMsgLen) { const size_t evDataSz = sizeof(struct MsgToHostEventData); + struct Event event; OT_ASSERT(aMsgLen <= OT_EVENT_DATA_MAX_SIZE - evDataSz); - struct Event event; event.mEvent = evType; event.mDelay = 0; memcpy(event.mData, aEventData, evDataSz); @@ -187,12 +191,13 @@ void otSimSendMsgToHostEvent(uint8_t evType, struct MsgToHostEventData *aEventDa void otSimSendEvent(struct Event *aEvent) { + ssize_t rval; + aEvent->mMsgId = gLastMsgId; gLastSentEvent = *aEvent; if (gSockFd == 0) // don't send events if socket invalid. return; - ssize_t rval; // send header and data. rval = write(gSockFd, aEvent, offsetof(struct Event, mData) + aEvent->mDataLength); diff --git a/ot-rfsim/src/event-sim.h b/ot-rfsim/src/event-sim.h index 9222024a..4e9fc75f 100644 --- a/ot-rfsim/src/event-sim.h +++ b/ot-rfsim/src/event-sim.h @@ -29,8 +29,8 @@ /** * @file * @brief -* This file includes simulation-event message definitions, and formatting and - * parsing functions for events. +* This file includes simulation-event message definitions, and sending, +* formatting and parsing functions for events. */ #ifndef PLATFORM_RFSIM_EVENT_SIM_H @@ -140,6 +140,7 @@ void otSimSendEvent(struct Event *aEvent); /** * Send a sleep event to the simulator. The amount of time to sleep * for this node is determined by the alarm timer, by calling platformAlarmGetNext(). + * */ void otSimSendSleepEvent(void); diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 7d4e8806..60e6840c 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -55,8 +55,8 @@ extern int gSockFd; -uint64_t gLastMsgId = 0; -struct Event gLastRecvEvent; +uint64_t gLastMsgId = 0; +struct Event gLastRecvEvent; static otIp6Address unspecifiedIp6Address; void platformRfsimInit(void) { @@ -67,18 +67,18 @@ void platformRfsimInit(void) { void platformExit(int exitCode) { gTerminate = true; - otPlatLog(OT_LOG_LEVEL_NOTE,OT_LOG_REGION_PLATFORM, - "Exiting with exit code %d.", exitCode); + otLogNotePlat("Exiting with exit code %d.", exitCode); exit(exitCode); } void platformReceiveEvent(otInstance *aInstance) { - struct Event event; - ssize_t rval = recvfrom(gSockFd, (char *)&event, sizeof(struct EventHeader), 0, NULL, NULL); + struct Event event; + ssize_t rval; const uint8_t *evData = event.mData; - otError error; + otError error; + rval = recvfrom(gSockFd, (char *)&event, sizeof(struct EventHeader), 0, NULL, NULL); if (rval < 0) { perror("recvfrom"); @@ -149,15 +149,13 @@ void platformReceiveEvent(otInstance *aInstance) platformRadioReportStateToSimulator(true); break; - case OT_SIM_EVENT_UDP_FROM_HOST: - otLogWarnPlat("FIXME start platformUdpFromHost"); + case OT_SIM_EVENT_IP6_FROM_HOST: VERIFY_EVENT_SIZE(struct MsgToHostEventData) - error = platformUdpFromHost(aInstance, (struct MsgToHostEventData *)evData, + error = platformIp6FromHost(aInstance, (struct MsgToHostEventData *)evData, event.mData + sizeof(struct MsgToHostEventData), payloadLen - sizeof(struct MsgToHostEventData)); if (error != OT_ERROR_NONE) { - otLogCritPlat("Error handling UDP from host event: %s", otThreadErrorToString(error)); + otLogCritPlat("Error handling IP6_FROM_HOST event, dropping datagram: %s", otThreadErrorToString(error)); } - otLogWarnPlat("FIXME done platformUdpFromHost"); break; default: @@ -170,29 +168,18 @@ void otPlatOtnsStatus(const char *aStatus) uint16_t statusLength = (uint16_t)strlen(aStatus); if (statusLength > OT_EVENT_DATA_MAX_SIZE){ statusLength = OT_EVENT_DATA_MAX_SIZE; - OT_ASSERT(statusLength <= OT_EVENT_DATA_MAX_SIZE); } otSimSendOtnsStatusPushEvent(aStatus, statusLength); } -otError platformUdpFromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, uint8_t *msg, size_t msgLen) { - otMessage *message = NULL; - otError error = OT_ERROR_NONE; +otError platformIp6FromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { + otMessage *ip6; + otError error; - message = otIp6NewMessage(aInstance, NULL); - otEXPECT_ACTION(message != NULL, error = OT_ERROR_NO_BUFS); + ip6 = otIp6NewMessageFromBuffer(aInstance, msg, msgLen, NULL); + otEXPECT_ACTION(ip6 != NULL, error = OT_ERROR_NO_BUFS); - // TODO set IPv6 header - - //otMessageSetOrigin(message, OT_MESSAGE_ORIGIN_HOST_UNTRUSTED); - - // TODO append UDP header - - error = otMessageAppend(message, msg, msgLen - 1); - otEXPECT(error == OT_ERROR_NONE); - - error = otIp6Send(aInstance, message); - otLogWarnPlat("FIXME error = %u", error); + error = otIp6Send(aInstance, ip6); exit: return error; diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index 43a345cd..c0637722 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -254,7 +254,7 @@ void platformUdpForwarder(otMessage *aMessage, * @param msgLen * @return */ -otError platformUdpFromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, uint8_t *msg, size_t msgLen); +otError platformIp6FromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); /** * Callback for node receiving an IPv6 datagram. When the datagram is destined for the upper-layer host diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index c0183e19..251c7429 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -89,7 +89,7 @@ async def asyncResponseFromSimHost(self): # n2 sends a coap message to AIL, to test AIL connectivity ns.node_cmd(n2, "coap start") - ns.node_cmd(n2, "coap get fc00::1234 hello") # dest addr must match an external route of the BR + ns.node_cmd(n2, "coap get fc00::1234 hello con") # dest addr must match an external route of the BR self.go(0.2) await asyncio.sleep(0.2) # let the aiocoap server serve the request self.go(10) @@ -100,12 +100,12 @@ async def asyncResponseFromSimHost(self): self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 -class HelloResource(resource.Resource): - async def render_get(self, request): - return aiocoap.Message(content_format=0, payload="Hello World".encode('utf8')) - async def coap_server_main(): + class HelloResource(resource.Resource): + async def render_get(self, request): + return aiocoap.Message(content_format=0, payload="Hello World".encode('utf8')) + root = resource.Site() root.add_resource(['hello'], HelloResource()) await aiocoap.Context.create_server_context(root) diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index 551e3268..011eb60a 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -22,14 +22,14 @@ type ConnId struct { ExtPort uint16 } -// SimConn is a two-way connection between a node's port and a simulated host's port. +// SimConn is a two-way connection between a node's port and a sim-host's port. type SimConn struct { - BrNode *Node // assumes a single BR also handles the return traffic. - Conn net.Conn - Nat66State *event.MsgToHostEventData - PortMapped uint16 - BytesUpstream uint64 // total bytes from node to sim-host (across all BRs) - BytesDownstream uint64 // total bytes from sim-host to node (across all BRs) + Node *Node // assumes a single BR also handles the return traffic. + Conn net.Conn + Nat66State *event.MsgToHostEventData + PortMapped uint16 // real localhost ::1 port on simulator machine, on which sim-host's port is mapped. + UdpBytesUpstream uint64 // total bytes UDP payload from node to sim-host (across all BRs) + UdpBytesDownstream uint64 // total bytes UDP payload from sim-host to node (across all BRs) } // SimHostEndpoint represents a single endpoint (port) of a sim-host, potentially interacting with N >= 0 nodes. @@ -75,7 +75,7 @@ func (sh *SimHosts) GetTxBytes(host *SimHostEndpoint) uint64 { var n uint64 = 0 for connId, simConn := range sh.Conns { if host.Ip6Addr == connId.ExtIp6Addr && host.Port == connId.ExtPort { - n += simConn.BytesDownstream + n += simConn.UdpBytesDownstream } } return n @@ -85,13 +85,13 @@ func (sh *SimHosts) GetRxBytes(host *SimHostEndpoint) uint64 { var n uint64 = 0 for connId, simConn := range sh.Conns { if host.Ip6Addr == connId.ExtIp6Addr && host.Port == connId.ExtPort { - n += simConn.BytesUpstream + n += simConn.UdpBytesUpstream } } return n } -// handleUdpFromNode handles a UDP message coming from a node and checks to which sim-host to deliver it. +// handleUdpFromNode handles a UDP datagram coming from a node and checks to which sim-host to deliver it. func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEventData, udpData []byte) { var host SimHostEndpoint var err error @@ -107,12 +107,12 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv } } if !found { - logger.Debugf("SimHosts: UDP from node %d did not reach any sim-host destination", node.Id) + logger.Debugf("SimHosts: IPv6/UDP from node %d did not reach a sim-host destination: %+v", node.Id, udpMetadata) return } - logger.Debugf("SimHosts: UDP from node %d, to sim server [::1]:%d (%d bytes)", node.Id, host.PortMapped, len(udpData)) + logger.Debugf("SimHosts: IPv6/UDP from node %d, to sim-host [::1]:%d (%d bytes)", node.Id, host.PortMapped, len(udpData)) - // fetch existing conn object for the specific Thread node source endpoint, if any. + // fetch existing conn object for the specific node/sim-host IP and port combo, if any. connId := ConnId{ NodeIp6Addr: udpMetadata.SrcIp6Address, ExtIp6Addr: udpMetadata.DstIp6Address, @@ -122,12 +122,12 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv if simConn, ok = sh.Conns[connId]; !ok { // create new connection simConn = &SimConn{ - Conn: nil, - BrNode: node, - Nat66State: udpMetadata, - PortMapped: host.PortMapped, - BytesUpstream: 0, - BytesDownstream: 0, + Conn: nil, + Node: node, + Nat66State: udpMetadata, + PortMapped: host.PortMapped, + UdpBytesUpstream: 0, + UdpBytesDownstream: 0, } simConn.Conn, err = net.Dial("udp", fmt.Sprintf("[::1]:%d", host.PortMapped)) if err != nil { @@ -148,22 +148,27 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv var n int n, err = simConn.Conn.Write(udpData) if err != nil { - logger.Warnf("SimHosts could not write udp data to [::1]:%d : %v", host.PortMapped, err) + logger.Warnf("SimHosts could not write UDP data to [::1]:%d : %v", host.PortMapped, err) return } - simConn.BytesUpstream += uint64(n) + simConn.UdpBytesUpstream += uint64(n) } -// handleUdpFromSimHost handles a UDP message coming from a sim-host and checks to which node to deliver it. -// It performs a form of NAT66 with NPT to achieve this. +// handleUdpFromSimHost handles a UDP message coming from a sim-host and checks to which (BR) node to deliver it +// as an IPv6+UDP datagram. It performs a form of NAT66 with NPT to achieve this. func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { - logger.Debugf("SimHosts: UDP from sim-host [::1]:%d (%d bytes)", simConn.PortMapped, len(udpData)) - simConn.BytesDownstream += uint64(len(udpData)) + logger.Debugf("SimHosts: UDP datagram from sim-host [::1]:%d (%d bytes)", simConn.PortMapped, len(udpData)) + simConn.UdpBytesDownstream += uint64(len(udpData)) + + ip6Datagram := CreateIp6UdpDatagram(simConn.Nat66State.DstPort, simConn.Nat66State.SrcPort, + simConn.Nat66State.DstIp6Address, simConn.Nat66State.SrcIp6Address, udpData) + + // send IPv6+UDP datagram as event to node ev := &event.Event{ Delay: 0, - Type: event.EventTypeUdpFromHost, - Data: udpData, - NodeId: simConn.BrNode.Id, + Type: event.EventTypeIp6FromHost, + Data: ip6Datagram, + NodeId: simConn.Node.Id, MsgToHostData: event.MsgToHostEventData{ SrcPort: simConn.Nat66State.DstPort, // simulates response back: ports reversed DstPort: simConn.Nat66State.SrcPort, @@ -172,7 +177,6 @@ func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { }, } sh.sim.Dispatcher().PostEventAsync(ev) - logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) for node %d, ev = %+v", simConn.BrNode.Id, ev) } func (sh *SimHosts) udpReaderGoRoutine(simConn *SimConn) { @@ -195,7 +199,7 @@ func (sh *SimHosts) handleIp6FromNode(node *Node, ip6Metadata *event.MsgToHostEv logger.Warnf("SimHosts could not parse as IPv6: %v", err) return } - // if it's UDP - handle the datagram + // if it's UDP - attempt to handle the datagram by a sim-host if ip6Header.Version == 6 && ip6Header.NextHeader == protocolUdp && len(ip6Data) > ipv6.HeaderLen+udpHeaderLen { udpData := ip6Data[ipv6.HeaderLen+udpHeaderLen:] sh.handleUdpFromNode(node, ip6Metadata, udpData) diff --git a/simulation/types.go b/simulation/types.go index bc33ffac..5353366e 100644 --- a/simulation/types.go +++ b/simulation/types.go @@ -129,3 +129,10 @@ func (ys *YamlScriptConfig) BuildBrScript() []string { script := ys.Ftd + "\n" + ys.Br + "\n" + ys.All return strings.Split(script, "\n") } + +// UdpHeader represents the header of a UDP datagram. +type UdpHeader struct { + SrcPort, DstPort uint16 + Length uint16 + Checksum uint16 +} diff --git a/simulation/utils.go b/simulation/utils.go index 11c946e8..c0009310 100644 --- a/simulation/utils.go +++ b/simulation/utils.go @@ -27,7 +27,10 @@ package simulation import ( + "encoding/binary" + "golang.org/x/net/ipv6" "math/rand" + "net/netip" "os" "path/filepath" "strings" @@ -76,3 +79,87 @@ func randomString(length int) string { } return string(b) } + +// SerializeIp6Header serializes an IPv6 header. +func SerializeIp6Header(ipv6 *ipv6.Header, payloadLen int) []byte { + data := make([]byte, 40) + data[0] = uint8((ipv6.Version)<<4) | uint8(ipv6.TrafficClass>>4) + data[1] = uint8(ipv6.TrafficClass<<4) | uint8(ipv6.FlowLabel>>16) + binary.BigEndian.PutUint16(data[2:], uint16(ipv6.FlowLabel)) + binary.BigEndian.PutUint16(data[4:], uint16(payloadLen)) + data[6] = byte(ipv6.NextHeader) + data[7] = byte(ipv6.HopLimit) + copy(data[8:], ipv6.Src) + copy(data[24:], ipv6.Dst) + + return data +} + +// SerializeUdpHeader serializes a UDP datagram header and calculates the checksum. +func SerializeUdpHeader(udpHdr *UdpHeader) []byte { + data := make([]byte, 8) + binary.BigEndian.PutUint16(data, uint16(udpHdr.SrcPort)) + binary.BigEndian.PutUint16(data[2:], uint16(udpHdr.DstPort)) + binary.BigEndian.PutUint16(data[4:], udpHdr.Length) + binary.BigEndian.PutUint16(data[6:], udpHdr.Checksum) + return data +} + +func CalculateUdpChecksum(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, msg []byte) uint16 { + sum := uint32(0) + + pseudoHeader := make([]byte, 48) + copy(pseudoHeader[0:16], srcIp6Addr.AsSlice()) + copy(pseudoHeader[16:32], dstIp6Addr.AsSlice()) + binary.BigEndian.PutUint32(pseudoHeader[32:36], uint32(len(msg)+8)) + pseudoHeader[39] = 17 // UDP next-header + binary.BigEndian.PutUint16(pseudoHeader[40:42], srcPort) + binary.BigEndian.PutUint16(pseudoHeader[42:44], dstPort) + binary.BigEndian.PutUint16(pseudoHeader[44:46], uint16(len(msg))) + + data := append(pseudoHeader, msg...) + + for ; len(data) >= 2; data = data[2:] { + sum += uint32(data[0])<<8 | uint32(data[1]) + } + if len(data) > 0 { + sum += uint32(data[0]) << 8 + } + for sum > 0xffff { + sum = (sum >> 16) + (sum & 0xffff) + } + csum := ^uint16(sum) + if csum == 0 { + csum = 0xffff + } + return csum +} + +// CreateIp6UdpDatagram creates an IPv6+UDP datagram, including UDP payload. +func CreateIp6UdpDatagram(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, udpPayload []byte) []byte { + udpHeader := &UdpHeader{ + SrcPort: srcPort, + DstPort: dstPort, + Length: uint16(len(udpPayload)), + Checksum: CalculateUdpChecksum(srcPort, dstPort, srcIp6Addr, dstIp6Addr, udpPayload), + } + udpHeaderSer := SerializeUdpHeader(udpHeader) + + var ip6Header *ipv6.Header + payloadLen := len(udpPayload) + 8 + ip6Header = &ipv6.Header{ + Version: 6, + TrafficClass: 0, + FlowLabel: 0, + PayloadLen: payloadLen, + NextHeader: 17, // UDP next-header id + HopLimit: 64, // FIXME + Src: srcIp6Addr.AsSlice(), + Dst: dstIp6Addr.AsSlice(), + } + ip6Datagram := SerializeIp6Header(ip6Header, payloadLen) + ip6Datagram = append(ip6Datagram, udpHeaderSer...) + ip6Datagram = append(ip6Datagram, udpPayload...) + + return ip6Datagram +} diff --git a/simulation/utils_test.go b/simulation/utils_test.go new file mode 100644 index 00000000..cd6feb56 --- /dev/null +++ b/simulation/utils_test.go @@ -0,0 +1,17 @@ +package simulation + +import ( + "net/netip" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCreateIp6UdpDatagram(t *testing.T) { + src := netip.MustParseAddr("fc00::1234") + dst := netip.MustParseAddr("fe80::abcd") + udpPayload := []byte{1, 2, 3, 4, 5} + + ip6 := CreateIp6UdpDatagram(5683, 5683, src, dst, udpPayload) + assert.Equal(t, 64, len(ip6)) +} From 344713ffa75b12e8bb23907f3f39b7826bf73cf6 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Mon, 20 May 2024 20:02:17 +0200 Subject: [PATCH 10/36] WIP: added UDP checksum calculation in OTNS; make-pretty --- cli/CmdRunner.go | 3 +- dispatcher/FailureCtrl_test.go | 3 ++ dispatcher/dispatcher.go | 22 ++++++++--- event/event.go | 3 +- event/event_test.go | 3 +- ot-rfsim/src/platform-rfsim.c | 22 ++++++----- ot-rfsim/src/platform-rfsim.cpp | 2 +- ot-rfsim/src/platform-rfsim.h | 47 ++++++++++------------- ot-rfsim/src/system.c | 2 +- pylibs/unittests/test_sim_hosts.py | 2 +- simulation/sim_hosts.go | 53 ++++++++++++++++++++------ simulation/utils.go | 61 ++++++++++++++++-------------- simulation/utils_test.go | 22 ++++++++++- types/ot_types.go | 5 +++ 14 files changed, 161 insertions(+), 89 deletions(-) diff --git a/cli/CmdRunner.go b/cli/CmdRunner.go index a4a150db..fda233b2 100644 --- a/cli/CmdRunner.go +++ b/cli/CmdRunner.go @@ -39,6 +39,8 @@ import ( "github.com/pkg/errors" "gopkg.in/yaml.v3" + "net/netip" + "github.com/openthread/ot-ns/dispatcher" "github.com/openthread/ot-ns/logger" "github.com/openthread/ot-ns/progctx" @@ -47,7 +49,6 @@ import ( . "github.com/openthread/ot-ns/types" "github.com/openthread/ot-ns/visualize" "github.com/openthread/ot-ns/web" - "net/netip" ) const ( diff --git a/dispatcher/FailureCtrl_test.go b/dispatcher/FailureCtrl_test.go index 3c3e9235..4b498370 100644 --- a/dispatcher/FailureCtrl_test.go +++ b/dispatcher/FailureCtrl_test.go @@ -53,6 +53,9 @@ func (m mockDispatcherCallback) OnNextEventTime(nextTimeUs uint64) { func (m mockDispatcherCallback) OnRfSimEvent(nodeid NodeId, evt *event.Event) { } +func (m mockDispatcherCallback) OnIp6ToHost(nodeid NodeId, udpMetadata *event.MsgToHostEventData, udpData []byte) { +} + func mockNode1() *Node { return &Node{ Id: 0x1, diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index b664cb69..4373077b 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -440,17 +440,16 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { case EventTypeUdpToHost: d.Counters.OtherEvents += 1 //d.cbHandler.OnUdpToHost(node.Id, &evt.MsgToHostData, evt.Data) - // FIXME - logger.Panicf("not impl") + logger.Panicf("FIXME not implemented yet") case EventTypeIp6ToHost: - d.Counters.OtherEvents += 1 + d.Counters.OtherEvents += 1 // TODO - counter for host or AIL events? + d.sendIp6PacketToAil(node, evt) d.cbHandler.OnIp6ToHost(node.Id, &evt.MsgToHostData, evt.Data) case EventTypeUdpFromHost, EventTypeIp6FromHost: d.Counters.OtherEvents += 1 evt.MustDispatch = true // asap resend again to the target (BR) node. d.eventQueue.Add(evt) - logger.Debugf("FIXME d.eventQueue.Add(evt) to node %d", node.Id) default: d.Counters.OtherEvents += 1 d.cbHandler.OnRfSimEvent(node.Id, evt) @@ -596,7 +595,6 @@ func (d *Dispatcher) processNextEvent(simSpeed float64) bool { case EventTypeUdpFromHost, EventTypeIp6FromHost: node.sendEvent(evt) // TODO no loss on external network is simulated currently. - logger.Debugf("FIXME node.sendEvent(evt) to node %d // TODO no loss on external network is simulated currently.", node.Id) default: if d.radioModel.OnEventDispatch(node.RadioNode, node.RadioNode, evt) { node.sendEvent(evt) @@ -872,6 +870,20 @@ func (d *Dispatcher) sendOneRadioFrame(evt *Event, srcnode *Node, dstnode *Node) } } +func (d *Dispatcher) sendIp6PacketToAil(node *Node, evt *Event) { + if d.cfg.PcapEnabled { + /** TODO write IPv6 packet to a separate pcap file + d.pcapFrameChan <- pcap.Frame{ + Timestamp: evt.Timestamp, + Data: evt.Data, + Channel: 0, + Rssi: RssiInvalid, + } + */ + node.logger.Debugf("sendIp6PacketToAil: %v", evt) + } +} + func (d *Dispatcher) setAlive(nodeid NodeId) { logger.AssertFalse(d.isDeleted(nodeid)) d.aliveNodes[nodeid] = struct{}{} diff --git a/event/event.go b/event/event.go index 46ec808b..262e01ff 100644 --- a/event/event.go +++ b/event/event.go @@ -34,9 +34,10 @@ import ( "strings" "unicode" + "net/netip" + "github.com/openthread/ot-ns/logger" "github.com/openthread/ot-ns/types" - "net/netip" ) type EventType = uint8 diff --git a/event/event_test.go b/event/event_test.go index 0c0b1f54..b0d141dc 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -30,9 +30,10 @@ import ( "encoding/hex" "testing" + "net/netip" + "github.com/openthread/ot-ns/types" "github.com/stretchr/testify/assert" - "net/netip" ) func TestDeserializeAlarmEvent(t *testing.T) { diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 60e6840c..d949c8ea 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -151,8 +151,9 @@ void platformReceiveEvent(otInstance *aInstance) case OT_SIM_EVENT_IP6_FROM_HOST: VERIFY_EVENT_SIZE(struct MsgToHostEventData) - error = platformIp6FromHost(aInstance, (struct MsgToHostEventData *)evData, - event.mData + sizeof(struct MsgToHostEventData), payloadLen - sizeof(struct MsgToHostEventData)); + error = platformIp6FromHostToNode(aInstance, (struct MsgToHostEventData *) evData, + event.mData + sizeof(struct MsgToHostEventData), + payloadLen - sizeof(struct MsgToHostEventData)); if (error != OT_ERROR_NONE) { otLogCritPlat("Error handling IP6_FROM_HOST event, dropping datagram: %s", otThreadErrorToString(error)); } @@ -172,7 +173,8 @@ void otPlatOtnsStatus(const char *aStatus) otSimSendOtnsStatusPushEvent(aStatus, statusLength); } -otError platformIp6FromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { +// TODO remove unused param +otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { otMessage *ip6; otError error; @@ -186,11 +188,11 @@ otError platformIp6FromHost(otInstance *aInstance, const struct MsgToHostEventDa } #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE -void platformUdpForwarder(otMessage *aMessage, - uint16_t aPeerPort, - otIp6Address *aPeerAddr, - uint16_t aSockPort, - void *aContext) +void handleUdpForwarding(otMessage *aMessage, + uint16_t aPeerPort, + otIp6Address *aPeerAddr, + uint16_t aSockPort, + void *aContext) { OT_UNUSED_VARIABLE(aContext); @@ -225,7 +227,7 @@ static uint8_t ip6McastScope(otIp6Address *addr) return addr->mFields.m8[0] & 0x0f; } -void platformIp6Receiver(otMessage *aMessage, void *aContext) +void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); @@ -270,7 +272,7 @@ void platformNetifSetUp(otInstance *aInstance) otIp6SetReceiveFilterEnabled(aInstance, true); // FIXME - needed? //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_ALL); // TODO //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_DISABLED); - otIp6SetReceiveCallback(aInstance, platformIp6Receiver, aInstance); + otIp6SetReceiveCallback(aInstance, handleIp6FromNodeToHost, aInstance); #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE // We can use the same function for IPv6 and translated IPv4 messages. // otNat64SetReceiveIp4Callback(gInstance, processReceive, gInstance); diff --git a/ot-rfsim/src/platform-rfsim.cpp b/ot-rfsim/src/platform-rfsim.cpp index 9f0a1b24..f5cfabd6 100644 --- a/ot-rfsim/src/platform-rfsim.cpp +++ b/ot-rfsim/src/platform-rfsim.cpp @@ -36,6 +36,7 @@ #include "net/ip6_address.hpp" extern "C" otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info); +extern "C" void platformUpdateMessageChecksum( otMessage *aMessage); #include "platform-rfsim.h" #include "utils/uart.h" @@ -56,4 +57,3 @@ otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info) { exit: return error; } - diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index c0637722..89944f39 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -230,23 +230,7 @@ bool platformRadioIsTransmitPending(void); void platformRadioReportStateToSimulator(bool force); /** - * Callback that gets called when OT stack has a UDP message that needs to go to - * the host interface. - * - * @param aMessage TODO - * @param aPeerPort - * @param aPeerAddr - * @param aSockPort - * @param aContext - */ -void platformUdpForwarder(otMessage *aMessage, - uint16_t aPeerPort, - otIp6Address *aPeerAddr, - uint16_t aSockPort, - void *aContext); - -/** - * TODO + * performs the processing of an IPv6 packet that was sent from the (higher-layer) host to the OT node. * * @param aInstance * @param evData @@ -254,25 +238,34 @@ void platformUdpForwarder(otMessage *aMessage, * @param msgLen * @return */ -otError platformIp6FromHost(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); +otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); /** - * Callback for node receiving an IPv6 datagram. When the datagram is destined for the upper-layer host - * or to the simulated AIL, this callback is used to send the datagram to the simulator for further processing. + * parses aMessage as an IPv6 packet, writing the packet-info into ip6Info. * - * @param aMessage - * @param aContext + * @param aMessage the message containing the IPv6 packet to parse + * @param ip6Info the IPv6 packet's metadata, written in case of successful parse. + * + * @retval OT_ERROR_NONE Successfully parsed the message as IPv6 packet. + * @retval OT_ERROR_PARSE Failed to parse the message as IPv6 packet. */ -void platformIp6Receiver(otMessage *aMessage, void *aContext); +otError platformParseIp6(otMessage *aMessage, otMessageInfo *ip6Info); /** - * TODO + * handler called when OT performs UDP-forwarding to the host. This is for UDP datagrams that + * are not going to be sent to the Thread interface, but rather to the host-interface. * * @param aMessage - * @param ip6Addr - * @return + * @param aPeerPort + * @param aPeerAddr + * @param aSockPort + * @param aContext */ -otError platformParseIp6(otMessage *aMessage, otMessageInfo *ip6Info); +void handleUdpForwarding(otMessage *aMessage, + uint16_t aPeerPort, + otIp6Address *aPeerAddr, + uint16_t aSockPort, + void *aContext); /** * Setup any simulated non-Thread interfaces. For example, an interface to a host process or diff --git a/ot-rfsim/src/system.c b/ot-rfsim/src/system.c index 6c7dc5ca..002f6001 100644 --- a/ot-rfsim/src/system.c +++ b/ot-rfsim/src/system.c @@ -127,7 +127,7 @@ void otSysProcessDrivers(otInstance *aInstance) { // on the first call, perform any init that requires the aInstance. if (!sIsInstanceInitDone) { // TODO move to own function #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE - otUdpForwardSetForwarder(aInstance, platformUdpForwarder, aInstance); + otUdpForwardSetForwarder(aInstance, handleUdpForwarding, aInstance); #endif platformNetifSetUp(aInstance); sIsInstanceInitDone = true; diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 251c7429..a3b91fd9 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -100,7 +100,6 @@ async def asyncResponseFromSimHost(self): self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 - async def coap_server_main(): class HelloResource(resource.Resource): async def render_get(self, request): @@ -110,5 +109,6 @@ async def render_get(self, request): root.add_resource(['hello'], HelloResource()) await aiocoap.Context.create_server_context(root) + if __name__ == '__main__': unittest.main() diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index 011eb60a..ff17df2b 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -1,12 +1,17 @@ package simulation import ( + "errors" "fmt" - "github.com/openthread/ot-ns/event" - "github.com/openthread/ot-ns/logger" - "golang.org/x/net/ipv6" + "io" "net" "net/netip" + + "golang.org/x/net/ipv6" + + "github.com/openthread/ot-ns/event" + "github.com/openthread/ot-ns/logger" + "github.com/openthread/ot-ns/types" ) const ( @@ -47,6 +52,12 @@ type SimHosts struct { Conns map[ConnId]*SimConn } +func (sc *SimConn) close() { + if sc.Conn != nil { + _ = sc.Conn.Close() + } +} + func NewSimHosts() *SimHosts { sh := &SimHosts{ sim: nil, @@ -68,7 +79,14 @@ func (sh *SimHosts) AddHost(host SimHostEndpoint) error { func (sh *SimHosts) RemoveHost(host SimHostEndpoint) { delete(sh.Hosts, host) - // FIXME close all related connection state + // delete and close all related connection state + for id, conn := range sh.Conns { + if conn.Nat66State.DstIp6Address == host.Ip6Addr && + conn.Nat66State.DstPort == host.Port { + conn.close() + delete(sh.Conns, id) + } + } } func (sh *SimHosts) GetTxBytes(host *SimHostEndpoint) uint64 { @@ -132,9 +150,7 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv simConn.Conn, err = net.Dial("udp", fmt.Sprintf("[::1]:%d", host.PortMapped)) if err != nil { logger.Warnf("SimHosts could not connect to local UDP port %d: %v", host.PortMapped, err) - if simConn.Conn != nil { - _ = simConn.Conn.Close() - } + simConn.close() return } @@ -159,9 +175,10 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { logger.Debugf("SimHosts: UDP datagram from sim-host [::1]:%d (%d bytes)", simConn.PortMapped, len(udpData)) simConn.UdpBytesDownstream += uint64(len(udpData)) + hopLim := 63 // assume sim-host used 64 and BR decreases by 1. TODO: the local sim-host case (must be 64?) - ip6Datagram := CreateIp6UdpDatagram(simConn.Nat66State.DstPort, simConn.Nat66State.SrcPort, - simConn.Nat66State.DstIp6Address, simConn.Nat66State.SrcIp6Address, udpData) + ip6Datagram := createIp6UdpDatagram(simConn.Nat66State.DstPort, simConn.Nat66State.SrcPort, + simConn.Nat66State.DstIp6Address, simConn.Nat66State.SrcIp6Address, hopLim, udpData) // send IPv6+UDP datagram as event to node ev := &event.Event{ @@ -180,11 +197,25 @@ func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { } func (sh *SimHosts) udpReaderGoRoutine(simConn *SimConn) { - buf := make([]byte, 2048) // FIXME size set + buf := make([]byte, types.OtMaxIp6DatagramLength) + defer func() { + if simConn.Conn != nil { + _ = simConn.Conn.Close() + } + }() + for { rlen, err := simConn.Conn.Read(buf) if err != nil { - panic(err) // FIXME + if errors.Is(err, io.EOF) { + break + } + logger.Warnf("Error reading from sim-host [::1]:%d, closing : %v", simConn.PortMapped, err) + break + } + if rlen >= types.OtMaxUdpPayloadLength { + logger.Warnf("sim-host sent too large UDP data (%d bytes > %d), dropped", rlen, types.OtMaxUdpPayloadLength) + continue } sh.handleUdpFromSimHost(simConn, buf[:rlen]) } diff --git a/simulation/utils.go b/simulation/utils.go index c0009310..f7a02f30 100644 --- a/simulation/utils.go +++ b/simulation/utils.go @@ -28,12 +28,13 @@ package simulation import ( "encoding/binary" - "golang.org/x/net/ipv6" "math/rand" "net/netip" "os" "path/filepath" "strings" + + "golang.org/x/net/ipv6" ) func removeAllFiles(globPath string) error { @@ -81,7 +82,7 @@ func randomString(length int) string { } // SerializeIp6Header serializes an IPv6 header. -func SerializeIp6Header(ipv6 *ipv6.Header, payloadLen int) []byte { +func serializeIp6Header(ipv6 *ipv6.Header, payloadLen int) []byte { data := make([]byte, 40) data[0] = uint8((ipv6.Version)<<4) | uint8(ipv6.TrafficClass>>4) data[1] = uint8(ipv6.TrafficClass<<4) | uint8(ipv6.FlowLabel>>16) @@ -95,29 +96,33 @@ func SerializeIp6Header(ipv6 *ipv6.Header, payloadLen int) []byte { return data } -// SerializeUdpHeader serializes a UDP datagram header and calculates the checksum. -func SerializeUdpHeader(udpHdr *UdpHeader) []byte { +// serializeUdpHeader serializes a UDP datagram header. +func serializeUdpHeader(udpHdr *UdpHeader) []byte { data := make([]byte, 8) - binary.BigEndian.PutUint16(data, uint16(udpHdr.SrcPort)) - binary.BigEndian.PutUint16(data[2:], uint16(udpHdr.DstPort)) + binary.BigEndian.PutUint16(data, udpHdr.SrcPort) + binary.BigEndian.PutUint16(data[2:], udpHdr.DstPort) binary.BigEndian.PutUint16(data[4:], udpHdr.Length) binary.BigEndian.PutUint16(data[6:], udpHdr.Checksum) return data } -func CalculateUdpChecksum(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, msg []byte) uint16 { +// calcUdpChecksum calculates the UDP checksum per RFC 2460 and writes it into udpHeader.Checksum. +func calcUdpChecksum(srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, udpHeader *UdpHeader, udpPayload []byte) { sum := uint32(0) + udpLen := uint16(len(udpPayload) + 8) + + pseudoHdr := make([]byte, 40) - pseudoHeader := make([]byte, 48) - copy(pseudoHeader[0:16], srcIp6Addr.AsSlice()) - copy(pseudoHeader[16:32], dstIp6Addr.AsSlice()) - binary.BigEndian.PutUint32(pseudoHeader[32:36], uint32(len(msg)+8)) - pseudoHeader[39] = 17 // UDP next-header - binary.BigEndian.PutUint16(pseudoHeader[40:42], srcPort) - binary.BigEndian.PutUint16(pseudoHeader[42:44], dstPort) - binary.BigEndian.PutUint16(pseudoHeader[44:46], uint16(len(msg))) + // IPv6 pseudo-header RFC 2460 + copy(pseudoHdr[0:16], srcIp6Addr.AsSlice()) + copy(pseudoHdr[16:32], dstIp6Addr.AsSlice()) + binary.BigEndian.PutUint32(pseudoHdr[32:36], uint32(udpLen)) // not including UDP header len + pseudoHdr[39] = 17 // UDP next-header - data := append(pseudoHeader, msg...) + // append UDP header (with 0x0000 checksum) and UDP payload + udpHeader.Checksum = 0x0000 + data := append(pseudoHdr, serializeUdpHeader(udpHeader)...) + data = append(data, udpPayload...) for ; len(data) >= 2; data = data[2:] { sum += uint32(data[0])<<8 | uint32(data[1]) @@ -132,32 +137,32 @@ func CalculateUdpChecksum(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, if csum == 0 { csum = 0xffff } - return csum + udpHeader.Checksum = csum } -// CreateIp6UdpDatagram creates an IPv6+UDP datagram, including UDP payload. -func CreateIp6UdpDatagram(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, udpPayload []byte) []byte { +// createIp6UdpDatagram creates an IPv6+UDP datagram, including UDP payload. +func createIp6UdpDatagram(srcPort uint16, dstPort uint16, srcIp6Addr netip.Addr, dstIp6Addr netip.Addr, hopLimit int, udpPayload []byte) []byte { + udpLen := len(udpPayload) + 8 udpHeader := &UdpHeader{ SrcPort: srcPort, DstPort: dstPort, - Length: uint16(len(udpPayload)), - Checksum: CalculateUdpChecksum(srcPort, dstPort, srcIp6Addr, dstIp6Addr, udpPayload), + Length: uint16(udpLen), + Checksum: 0, } - udpHeaderSer := SerializeUdpHeader(udpHeader) + calcUdpChecksum(srcIp6Addr, dstIp6Addr, udpHeader, udpPayload) + udpHeaderSer := serializeUdpHeader(udpHeader) - var ip6Header *ipv6.Header - payloadLen := len(udpPayload) + 8 - ip6Header = &ipv6.Header{ + ip6Header := &ipv6.Header{ Version: 6, TrafficClass: 0, FlowLabel: 0, - PayloadLen: payloadLen, + PayloadLen: udpLen, NextHeader: 17, // UDP next-header id - HopLimit: 64, // FIXME + HopLimit: hopLimit, Src: srcIp6Addr.AsSlice(), Dst: dstIp6Addr.AsSlice(), } - ip6Datagram := SerializeIp6Header(ip6Header, payloadLen) + ip6Datagram := serializeIp6Header(ip6Header, udpLen) ip6Datagram = append(ip6Datagram, udpHeaderSer...) ip6Datagram = append(ip6Datagram, udpPayload...) diff --git a/simulation/utils_test.go b/simulation/utils_test.go index cd6feb56..eda72a85 100644 --- a/simulation/utils_test.go +++ b/simulation/utils_test.go @@ -4,6 +4,9 @@ import ( "net/netip" "testing" + "encoding/binary" + "encoding/hex" + "github.com/stretchr/testify/assert" ) @@ -12,6 +15,21 @@ func TestCreateIp6UdpDatagram(t *testing.T) { dst := netip.MustParseAddr("fe80::abcd") udpPayload := []byte{1, 2, 3, 4, 5} - ip6 := CreateIp6UdpDatagram(5683, 5683, src, dst, udpPayload) - assert.Equal(t, 64, len(ip6)) + ip6 := createIp6UdpDatagram(5683, 5683, src, dst, 64, udpPayload) + assert.Equal(t, 53, len(ip6)) +} + +func TestUdpChecksum(t *testing.T) { + // see example on https://stackoverflow.com/questions/30858973/udp-checksum-calculation-for-ipv6-packet + src := netip.MustParseAddr("2100::1:abcd:0:0:1") + dst := netip.MustParseAddr("fd00::160") + udpPayload := []byte{0x12, 0x34, 0x56, 0x78} + + ip6 := createIp6UdpDatagram(9874, 9874, src, dst, 64, udpPayload) + assert.Equal(t, 52, len(ip6)) + + ip6HexStr := hex.EncodeToString(ip6) + assert.Equal(t, "60000000000c11402100000000000001abcd000000000001fd00000000000000000000000000016026922692000c7ed512345678", ip6HexStr) + udpChecksum := binary.BigEndian.Uint16(ip6[46:48]) + assert.Equal(t, uint16(0x7ed5), udpChecksum) } diff --git a/types/ot_types.go b/types/ot_types.go index 8444e354..3e2f5c03 100644 --- a/types/ot_types.go +++ b/types/ot_types.go @@ -28,6 +28,11 @@ package types +const ( + OtMaxIp6DatagramLength = 1280 + OtMaxUdpPayloadLength = 1232 // this can be adapted - currently not a precise maximum. +) + // OT_ERROR_* error codes from OpenThread that can be sent by OT-NS to the OT nodes. // (See OpenThread error.h for details) const ( From aaf5771e7269a131bdcee56ce0ed08126251a884 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 21 May 2024 09:10:59 +0200 Subject: [PATCH 11/36] WIP: adding UdpToHost message handling for relay --- dispatcher/FailureCtrl_test.go | 2 +- dispatcher/dispatcher.go | 22 ++++++++-------------- pylibs/unittests/test_ccm_commissioning.py | 15 +++++---------- script/test | 1 + simulation/simulation.go | 18 ++++++++---------- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/dispatcher/FailureCtrl_test.go b/dispatcher/FailureCtrl_test.go index 4b498370..ca9048b5 100644 --- a/dispatcher/FailureCtrl_test.go +++ b/dispatcher/FailureCtrl_test.go @@ -53,7 +53,7 @@ func (m mockDispatcherCallback) OnNextEventTime(nextTimeUs uint64) { func (m mockDispatcherCallback) OnRfSimEvent(nodeid NodeId, evt *event.Event) { } -func (m mockDispatcherCallback) OnIp6ToHost(nodeid NodeId, udpMetadata *event.MsgToHostEventData, udpData []byte) { +func (m mockDispatcherCallback) OnMsgToHost(nodeid NodeId, evt *event.Event) { } func mockNode1() *Node { diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 4373077b..ad2c8f98 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -67,11 +67,8 @@ type CallbackHandler interface { // OnRfSimEvent Notifies that Dispatcher received an OT-RFSIM platform event that it didn't handle itself. OnRfSimEvent(nodeid NodeId, evt *Event) - // OnUdpToHost Notifies that the Dispatcher received an off-mesh or to-host UDP packet from a node, to be handled. - //OnUdpToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) - - // OnIp6ToHost Notifies that the Dispatcher received an IPv6 packet from a node, to be handled by the node's host. - OnIp6ToHost(nodeid NodeId, udpMetadata *MsgToHostEventData, udpData []byte) + // OnMsgToHost Notifies that the Dispatcher received a message from a node, to be handled by the node's host. + OnMsgToHost(nodeid NodeId, event *Event) } // goDuration represents a particular duration of the simulation at a given speed. @@ -437,14 +434,11 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { logger.Debugf("%s socket disconnected.", node) d.setSleeping(node.Id) d.alarmMgr.SetTimestamp(node.Id, Ever) - case EventTypeUdpToHost: - d.Counters.OtherEvents += 1 - //d.cbHandler.OnUdpToHost(node.Id, &evt.MsgToHostData, evt.Data) - logger.Panicf("FIXME not implemented yet") - case EventTypeIp6ToHost: + case EventTypeUdpToHost, + EventTypeIp6ToHost: d.Counters.OtherEvents += 1 // TODO - counter for host or AIL events? - d.sendIp6PacketToAil(node, evt) - d.cbHandler.OnIp6ToHost(node.Id, &evt.MsgToHostData, evt.Data) + d.sendMsgToHost(node, evt) + d.cbHandler.OnMsgToHost(node.Id, evt) case EventTypeUdpFromHost, EventTypeIp6FromHost: d.Counters.OtherEvents += 1 @@ -870,7 +864,7 @@ func (d *Dispatcher) sendOneRadioFrame(evt *Event, srcnode *Node, dstnode *Node) } } -func (d *Dispatcher) sendIp6PacketToAil(node *Node, evt *Event) { +func (d *Dispatcher) sendMsgToHost(node *Node, evt *Event) { if d.cfg.PcapEnabled { /** TODO write IPv6 packet to a separate pcap file d.pcapFrameChan <- pcap.Frame{ @@ -880,7 +874,7 @@ func (d *Dispatcher) sendIp6PacketToAil(node *Node, evt *Event) { Rssi: RssiInvalid, } */ - node.logger.Debugf("sendIp6PacketToAil: %v", evt) + node.logger.Debugf("sendMsgToHost: %v", evt) } } diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index e0760d8c..de59a1ae 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -56,6 +56,11 @@ def testCommissioningOneHop(self): n2 = ns.add("router", x = 100, y = 200, radio_range = 120) n3 = ns.add("router", x = 200, y = 100, radio_range = 120) + # configure sim-host server that acts as BRSKI Registrar + # TODO update IPv6 addr + ns.cmd('host add "masa.example.com" "910b::1234" 5683 5683') + + # n1 is out-of-band configured with initial dataset, and becomes leader+ccm-commissioner self.setFirstNodeDataset(n1) ns.ifconfig_up(n1) ns.thread_start(n1) @@ -77,16 +82,6 @@ def testCommissioningOneHop(self): self.assertFormPartitionsIgnoreOrphans(1) self.assertTrue(joins and joins[0][1] > 0) # assert join success - # n2 sends a coap message to AIL, to test AIL connectivity - ns.node_cmd(n2, "coap start") - ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR - self.go(10) - - # n2 sends a coap message to n1, to test host receiving of UDP - n2_gua = ns.get_ipaddrs(n1, "mleid") - ns.node_cmd(n2, "coap get %s request/to/br" % n2_gua[0]) - self.go(10) - # n3 joins as CCM joiner ns.commissioner_ccm_joiner_add(n1, "*") ns.ifconfig_up(n3) diff --git a/script/test b/script/test index 830a1014..2f7c3385 100755 --- a/script/test +++ b/script/test @@ -97,6 +97,7 @@ py_unittests() python3 "$OTNSDIR"/pylibs/unittests/test_radiomodels.py python3 "$OTNSDIR"/pylibs/unittests/test_csl.py python3 "$OTNSDIR"/pylibs/unittests/test_border_router.py + python3 "$OTNSDIR"/pylibs/unittests/test_sim_hosts.py # Increase number of open files limit to allow large number of nodes. Ubuntu 22.04 has 8192 hard limit. ulimit -Sn 8192 diff --git a/simulation/simulation.go b/simulation/simulation.go index be64a96e..f8206915 100644 --- a/simulation/simulation.go +++ b/simulation/simulation.go @@ -398,22 +398,20 @@ func (s *Simulation) OnRfSimEvent(nodeid NodeId, evt *event.Event) { } } -func (s *Simulation) OnUdpToHost(nodeid NodeId, udpMetadata *event.MsgToHostEventData, udpData []byte) { +func (s *Simulation) OnMsgToHost(nodeid NodeId, evt *event.Event) { node := s.nodes[nodeid] if node == nil { return } - s.simHosts.handleUdpFromNode(node, udpMetadata, udpData) -} - -func (s *Simulation) OnIp6ToHost(nodeid NodeId, ip6Metadata *event.MsgToHostEventData, ip6Data []byte) { - node := s.nodes[nodeid] - if node == nil { - return + switch evt.Type { + case event.EventTypeIp6ToHost: + s.simHosts.handleIp6FromNode(node, &evt.MsgToHostData, evt.Data) + case event.EventTypeUdpToHost: + s.simHosts.handleUdpFromNode(node, &evt.MsgToHostData, evt.Data) + default: + logger.Panicf("Event type not implemented: %d", evt.Type) } - - s.simHosts.handleIp6FromNode(node, ip6Metadata, ip6Data) } // PostAsync will post an asynchronous simulation task in the queue for execution From f82783b4da9282e402664f7a30f579906cf974db Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 21 May 2024 10:39:38 +0200 Subject: [PATCH 12/36] [ot-rfsim] fixed filter on outgoing (off mesh) packets --- ot-rfsim/src/platform-rfsim.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index d949c8ea..625122a3 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -247,6 +247,9 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) // determine if IPv6 datagram must go to AIL. This implements simulation-specific BR packet filtering. otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage) && + ip6Info.mSockPort > 0 && + ip6Info.mPeerPort > 0 && + ip6Info.mPeerPort != 61631 && // drop mesh-local TMF messages !isLinkLocal(&ip6Info.mPeerAddr) && !isLinkLocal(&ip6Info.mSockAddr) && ip6McastScope(&ip6Info.mPeerAddr) >= 0x4); @@ -258,7 +261,7 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) memcpy(evData.mDstIp6, &ip6Info.mPeerAddr, OT_IP6_ADDRESS_SIZE); otMessageRead(aMessage, 0, buf, msgLen); - otPlatLog(OT_LOG_LEVEL_INFO, OT_LOG_REGION_PLATFORM, "Delivering msg to host for AIL forwarding"); + otLogDebgPlat("Delivering msg to host for AIL forwarding"); otSimSendMsgToHostEvent(OT_SIM_EVENT_IP6_TO_HOST, &evData, &buf[0], msgLen); exit: From 8aa745a2c17fbf60c0c08f436e2ac2032faccc48 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 21 May 2024 11:34:23 +0200 Subject: [PATCH 13/36] Rebased to 'main' branch. WIP - unit test failures in version-unittests --- ot-rfsim/src/platform-rfsim.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 625122a3..40e4f259 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -227,6 +227,7 @@ static uint8_t ip6McastScope(otIp6Address *addr) return addr->mFields.m8[0] & 0x0f; } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); @@ -267,15 +268,18 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) exit: otMessageFree(aMessage); } +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void platformNetifSetUp(otInstance *aInstance) { assert(aInstance != NULL); +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE otIp6SetReceiveFilterEnabled(aInstance, true); // FIXME - needed? //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_ALL); // TODO //otIcmp6SetEchoMode(gInstance, OT_ICMP6_ECHO_HANDLER_DISABLED); otIp6SetReceiveCallback(aInstance, handleIp6FromNodeToHost, aInstance); +#endif #if OPENTHREAD_CONFIG_NAT64_TRANSLATOR_ENABLE // We can use the same function for IPv6 and translated IPv4 messages. // otNat64SetReceiveIp4Callback(gInstance, processReceive, gInstance); From 26514e8222b4c3ffb82e43c0032c8f987a76338f Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Wed, 22 May 2024 10:46:49 +0200 Subject: [PATCH 14/36] WIP - Extended SimHosts unit tests and fixed aiocoap async use --- ot-rfsim/src/platform-rfsim.c | 83 ++++++++++++++++++++-- ot-rfsim/src/platform-rfsim.cpp | 13 +++- ot-rfsim/src/platform-rfsim.h | 6 ++ pylibs/unittests/test_ccm_commissioning.py | 7 +- pylibs/unittests/test_sim_hosts.py | 47 +++++++++--- simulation/sim_hosts.go | 67 ++++++++++++----- 6 files changed, 190 insertions(+), 33 deletions(-) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 40e4f259..1d52dcaf 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -43,6 +43,7 @@ #include #include +#include #include #include "common/debug.hpp" @@ -159,7 +160,17 @@ void platformReceiveEvent(otInstance *aInstance) } break; - default: + case OT_SIM_EVENT_UDP_FROM_HOST: + VERIFY_EVENT_SIZE(struct MsgToHostEventData) + error = platformUdpFromHostToNode(aInstance, (struct MsgToHostEventData *) evData, + event.mData + sizeof(struct MsgToHostEventData), + payloadLen - sizeof(struct MsgToHostEventData)); + if (error != OT_ERROR_NONE) { + otLogCritPlat("Error handling IP6_FROM_HOST event, dropping datagram: %s", otThreadErrorToString(error)); + } + break; + + default: OT_ASSERT(false && "Unrecognized event type received"); } } @@ -173,21 +184,83 @@ void otPlatOtnsStatus(const char *aStatus) otSimSendOtnsStatusPushEvent(aStatus, statusLength); } -// TODO remove unused param +// TODO change params to start with 'a' otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { otMessage *ip6; - otError error; + otError error = OT_ERROR_NONE; + otIp6Address *dstIp6; + otIp6Address *srcIp6; ip6 = otIp6NewMessageFromBuffer(aInstance, msg, msgLen, NULL); otEXPECT_ACTION(ip6 != NULL, error = OT_ERROR_NO_BUFS); - error = otIp6Send(aInstance, ip6); - + srcIp6 = (otIp6Address *) evData->mSrcIp6; + dstIp6 = (otIp6Address *) evData->mDstIp6; + otLogDebgPlat("FIXME step 1"); + if(otIp6IsAddressUnspecified(dstIp6)) { + otLogDebgPlat("FIXME step 2"); + validateOtMsg(ip6); + otLogDebgPlat("FIXME validateOtMsg passed"); + + otMessage *testMsg; + otLogDebgPlat("FIXME step 3"); + testMsg = otCoapNewMessage(aInstance, NULL); + otLogDebgPlat("FIXME step 4"); + otLogDebgPlat("FIXME step 5"); + validateOtMsg(testMsg); + + otCoapMessageInit(testMsg, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); + otLogDebgPlat("FIXME step 6"); + uint8_t magic[2] = {0xed, 0xda}; + otCoapMessageSetToken(testMsg, magic, 2); + + otUdpForwardReceive(aInstance, testMsg, evData->mSrcPort, srcIp6, evData->mDstPort); + otLogDebgPlat("FIXME step 7"); + + validateOtMsg(ip6); + otLogDebgPlat("FIXME step 8"); + + otUdpForwardReceive(aInstance, ip6, evData->mSrcPort, srcIp6, evData->mDstPort); + }else { + // non-local: send as IPv6 datagram + error = otIp6Send(aInstance, ip6); + } exit: return error; } #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE + +// TODO change params to start with 'a' +otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { + otMessage *udp; + otError error = OT_ERROR_NONE; + otIp6Address *dstIp6; + otIp6Address *srcIp6; + + otLogDebgPlat("FIXME step 1a"); + udp = otUdpNewMessage(aInstance, NULL); + otLogDebgPlat("FIXME step 2a"); + otEXPECT((error = otMessageAppend(udp, msg, msgLen)) == OT_ERROR_NONE); + otLogDebgPlat("FIXME step 3a"); + otEXPECT_ACTION(udp != NULL, error = OT_ERROR_NO_BUFS); + otLogDebgPlat("FIXME step 4a"); + + srcIp6 = (otIp6Address *) evData->mSrcIp6; + dstIp6 = (otIp6Address *) evData->mDstIp6; + otLogDebgPlat("FIXME step 5a"); + + otLogDebgPlat("FIXME step 6a"); + validateOtMsg(udp); + otLogDebgPlat("FIXME validateOtMsg passed 7a"); + + otUdpForwardReceive(aInstance, udp, evData->mSrcPort, srcIp6, evData->mDstPort); + otLogDebgPlat("FIXME step 4a"); + +exit: + return error; +} + void handleUdpForwarding(otMessage *aMessage, uint16_t aPeerPort, otIp6Address *aPeerAddr, diff --git a/ot-rfsim/src/platform-rfsim.cpp b/ot-rfsim/src/platform-rfsim.cpp index f5cfabd6..a151ac0d 100644 --- a/ot-rfsim/src/platform-rfsim.cpp +++ b/ot-rfsim/src/platform-rfsim.cpp @@ -32,11 +32,13 @@ * This file includes the C++ portions of the OT-RFSIM platform. */ +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE + #include "net/ip6.hpp" #include "net/ip6_address.hpp" extern "C" otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info); -extern "C" void platformUpdateMessageChecksum( otMessage *aMessage); +extern "C" void validateOtMsg( otMessage *aMessage); #include "platform-rfsim.h" #include "utils/uart.h" @@ -57,3 +59,12 @@ otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info) { exit: return error; } + +// FIXME delete +void validateOtMsg( otMessage *aMessage) { + Message msg = AsCoreType(aMessage); + msg.RemoveHeader(msg.GetOffset()); + //OT_ASSERT(msg.GetOffset() == 0); +} + +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE \ No newline at end of file diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index 89944f39..38bc075f 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -240,6 +240,9 @@ void platformRadioReportStateToSimulator(bool force); */ otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); +// FIXME +otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); + /** * parses aMessage as an IPv6 packet, writing the packet-info into ip6Info. * @@ -267,6 +270,9 @@ void handleUdpForwarding(otMessage *aMessage, uint16_t aSockPort, void *aContext); +// FIXME +void validateOtMsg( otMessage *aMessage); + /** * Setup any simulated non-Thread interfaces. For example, an interface to a host process or * an AIL interface. diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index de59a1ae..bdec34e5 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -27,6 +27,7 @@ import tracemalloc import unittest +import logging from OTNSTestCase import OTNSTestCase from otns.cli import OTNS @@ -37,6 +38,7 @@ class CommissioningTests(OTNSTestCase): def setUp(self) -> None: + logging.info("Setting up for test: %s", self.name()) self.ns = OTNS(otns_args=['-ot-script', 'none', '-log', 'debug']) self.ns.speed = float('inf') @@ -83,12 +85,13 @@ def testCommissioningOneHop(self): self.assertTrue(joins and joins[0][1] > 0) # assert join success # n3 joins as CCM joiner + ns.speed = 2 ns.commissioner_ccm_joiner_add(n1, "*") ns.ifconfig_up(n3) ns.ccm_joiner_start(n3) self.go(20) - ns.thread_start(n3) - self.go(100) + #ns.thread_start(n3) + #self.go(100) c = ns.counters() print('counters', c) joins = ns.joins() diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index a3b91fd9..b39554ac 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -36,6 +36,7 @@ class SimHostsTests(OTNSTestCase): + def testConfigureSimHosts(self): ns = self.ns ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') @@ -47,7 +48,7 @@ def testConfigureSimHosts(self): ns.cmd('host add "bad.example.com" "910b::f00d" 3 4') hosts_list = ns.cmd('host list') - self.assertEqual(3+1, len(hosts_list)) # includes one header line + self.assertEqual(3+1, len(hosts_list)) # includes one header line ns.cmd('host del "myserver.example.com"') hosts_list = ns.cmd('host list') @@ -59,7 +60,7 @@ def testConfigureSimHosts(self): def testSendToSimHost(self): ns = self.ns - ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') + ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 55683') n1=ns.add('br') ns.go(10) n2=ns.add('router') @@ -78,27 +79,55 @@ def testResponseFromSimHost(self): asyncio.run(self.asyncResponseFromSimHost()) async def asyncResponseFromSimHost(self): - await coap_server_main() + ctx = await coap_server_main() ns = self.ns - ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 5683') + ns.cmd('host add "myserver.example.com" "fc00::1234" 5683 55683') n1=ns.add('br') ns.go(10) n2=ns.add('router') ns.go(10) # n2 sends a coap message to AIL, to test AIL connectivity + # because CoAP server is real, let simulation also move in real time. + ns.autogo = True + ns.speed = 1 ns.node_cmd(n2, "coap start") ns.node_cmd(n2, "coap get fc00::1234 hello con") # dest addr must match an external route of the BR - self.go(0.2) - await asyncio.sleep(0.2) # let the aiocoap server serve the request - self.go(10) - await asyncio.sleep(0.2) + await asyncio.sleep(1) # let the aiocoap server serve the request + ns.autogo = False hosts_list = ns.cmd('host list') self.assertEqual(1+1, len(hosts_list)) self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + await ctx.shutdown() + + def testRequestFromBrAndResponseFromSimHost(self): + asyncio.run(self.asyncRequestFromBrAndResponseFromSimHost()) + + async def asyncRequestFromBrAndResponseFromSimHost(self): + ctx = await coap_server_main() + + ns = self.ns + ns.cmd('host add "myserver.example.com" "fc00::5678" 5683 55683') + n1=ns.add('br') + ns.go(10) + + # n1 sends a coap message to AIL, to test AIL connectivity. Message does not travel over mesh. + ns.autogo = True + ns.speed = 1 + ns.node_cmd(n1, "coap start") + ns.node_cmd(n1, "coap get fc00::5678 hello con") # dest addr must match an external route of the BR + await asyncio.sleep(1) # let the aiocoap server serve the request + ns.autogo = False + + hosts_list = ns.cmd('host list') + self.assertEqual(1+1, len(hosts_list)) + self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + + await ctx.shutdown() + async def coap_server_main(): class HelloResource(resource.Resource): @@ -107,7 +136,7 @@ async def render_get(self, request): root = resource.Site() root.add_resource(['hello'], HelloResource()) - await aiocoap.Context.create_server_context(root) + return await aiocoap.Context.create_server_context(root, bind=['::1', 55683]) if __name__ == '__main__': diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index ff17df2b..708035a1 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -9,6 +9,7 @@ import ( "golang.org/x/net/ipv6" + "encoding/hex" "github.com/openthread/ot-ns/event" "github.com/openthread/ot-ns/logger" "github.com/openthread/ot-ns/types" @@ -130,6 +131,17 @@ func (sh *SimHosts) handleUdpFromNode(node *Node, udpMetadata *event.MsgToHostEv } logger.Debugf("SimHosts: IPv6/UDP from node %d, to sim-host [::1]:%d (%d bytes)", node.Id, host.PortMapped, len(udpData)) + // FIXME + /* + // TODO if sending node doesn't know its own AIL IPv6 interface address, it uses 'unspecified'. + if udpMetadata.SrcIp6Address == netip.IPv6Unspecified() { + udpMetadata.SrcIp6Address, err = netip.ParseAddr(fmt.Sprintf("fc00::%d", node.Id)) // FIXME make configurable + if err != nil { + logger.Panicf("Unexpected error in IPv6 address generation for BR") + } + } + */ + // fetch existing conn object for the specific node/sim-host IP and port combo, if any. connId := ConnId{ NodeIp6Addr: udpMetadata.SrcIp6Address, @@ -177,23 +189,46 @@ func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { simConn.UdpBytesDownstream += uint64(len(udpData)) hopLim := 63 // assume sim-host used 64 and BR decreases by 1. TODO: the local sim-host case (must be 64?) - ip6Datagram := createIp6UdpDatagram(simConn.Nat66State.DstPort, simConn.Nat66State.SrcPort, - simConn.Nat66State.DstIp6Address, simConn.Nat66State.SrcIp6Address, hopLim, udpData) - - // send IPv6+UDP datagram as event to node - ev := &event.Event{ - Delay: 0, - Type: event.EventTypeIp6FromHost, - Data: ip6Datagram, - NodeId: simConn.Node.Id, - MsgToHostData: event.MsgToHostEventData{ - SrcPort: simConn.Nat66State.DstPort, // simulates response back: ports reversed - DstPort: simConn.Nat66State.SrcPort, - SrcIp6Address: simConn.Nat66State.DstIp6Address, // simulates response: addrs reversed - DstIp6Address: simConn.Nat66State.SrcIp6Address, - }, + if simConn.Nat66State.SrcIp6Address == netip.IPv6Unspecified() { + // send as UDP-event to node itself to handle. + ev := &event.Event{ + Delay: 0, + Type: event.EventTypeUdpFromHost, + Data: udpData, + NodeId: simConn.Node.Id, + MsgToHostData: event.MsgToHostEventData{ + SrcPort: simConn.Nat66State.DstPort, // simulates response back: ports reversed + DstPort: simConn.Nat66State.SrcPort, + SrcIp6Address: simConn.Nat66State.DstIp6Address, // simulates response: addrs reversed + DstIp6Address: simConn.Nat66State.SrcIp6Address, + }, + } + sh.sim.Dispatcher().PostEventAsync(ev) + logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) FIXME-UDP path %v", ev) + logger.Debugf("simConn.Nat66State UDP-path = %v", simConn.Nat66State) + logger.Debugf("udpData = %s", hex.EncodeToString(udpData)) + } else { + // send as IPv6-event to node, to let it forward to others on mesh. + ip6Datagram := createIp6UdpDatagram(simConn.Nat66State.DstPort, simConn.Nat66State.SrcPort, + simConn.Nat66State.DstIp6Address, simConn.Nat66State.SrcIp6Address, hopLim, udpData) + + // send IPv6+UDP datagram as event to node + ev := &event.Event{ + Delay: 0, + Type: event.EventTypeIp6FromHost, + Data: ip6Datagram, + NodeId: simConn.Node.Id, + MsgToHostData: event.MsgToHostEventData{ + SrcPort: simConn.Nat66State.DstPort, // simulates response back: ports reversed + DstPort: simConn.Nat66State.SrcPort, + SrcIp6Address: simConn.Nat66State.DstIp6Address, // simulates response: addrs reversed + DstIp6Address: simConn.Nat66State.SrcIp6Address, + }, + } + sh.sim.Dispatcher().PostEventAsync(ev) + logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) FIXME %v", ev) + logger.Debugf("simConn.Nat66State = %v", simConn.Nat66State) } - sh.sim.Dispatcher().PostEventAsync(ev) } func (sh *SimHosts) udpReaderGoRoutine(simConn *SimConn) { From 44b03d9b5087001495416455023cf057d0345e37 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Fri, 24 May 2024 21:13:13 +0200 Subject: [PATCH 15/36] WIP / Rebase to 'main' branch including 'openthread' submodule different commit. --- dispatcher/coaps.go | 10 ++++------ ot-rfsim/src/platform-rfsim.c | 2 +- pylibs/unittests/test_ccm_commissioning.py | 6 ++++-- pylibs/unittests/test_sim_hosts.py | 14 ++++++++++++++ simulation/sim_hosts.go | 2 +- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/dispatcher/coaps.go b/dispatcher/coaps.go index 6efc4457..a9e5ff0a 100644 --- a/dispatcher/coaps.go +++ b/dispatcher/coaps.go @@ -27,7 +27,6 @@ package dispatcher import ( - "github.com/openthread/ot-ns/logger" . "github.com/openthread/ot-ns/types" ) @@ -81,11 +80,11 @@ func (coaps *coapsHandler) OnSend(curTime uint64, nodeId NodeId, messageId int, func (coaps *coapsHandler) OnRecv(curTime uint64, nodeId NodeId, messageId int, coapType CoapType, coapCode CoapCode, uri string, peerAddr string, peerPort int) { msg := coaps.findMessage(messageId, coapType, coapCode, uri) - if msg == nil { - logger.Warnf("CoAP message %d,%d,%d,%s not sent but received by Node %d", messageId, coapType, coapCode, uri, nodeId) + if msg == nil { // may happen if CoAP message originated from outside of mesh (external process) return } + // keep all received CoAP messages for KPI purposes. msg.Receivers = append(msg.Receivers, CoapMessageRecvInfo{ Timestamp: curTime, DstNode: nodeId, @@ -96,12 +95,11 @@ func (coaps *coapsHandler) OnRecv(curTime uint64, nodeId NodeId, messageId int, func (coaps *coapsHandler) OnSendError(nodeId NodeId, messageId int, coapType CoapType, coapCode CoapCode, uri string, peerAddr string, peerPort int, error string) { msg := coaps.findMessage(messageId, coapType, coapCode, uri) - if msg == nil { - logger.Warnf("CoAP message %d,%d,%d,%s not sent but received by Node %d", messageId, coapType, coapCode, uri, nodeId) + if msg == nil { // may happen if CoAP message originated from outside of mesh (external process) ? return } - msg.Error = error + msg.Error = error // mark the message as having had a send error } func (coaps *coapsHandler) findMessage(id int, coapType CoapType, coapCode CoapCode, uri string) *CoapMessage { diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 1d52dcaf..f29ab699 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -255,7 +255,7 @@ otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostE otLogDebgPlat("FIXME validateOtMsg passed 7a"); otUdpForwardReceive(aInstance, udp, evData->mSrcPort, srcIp6, evData->mDstPort); - otLogDebgPlat("FIXME step 4a"); + otLogDebgPlat("FIXME step 8a"); exit: return error; diff --git a/pylibs/unittests/test_ccm_commissioning.py b/pylibs/unittests/test_ccm_commissioning.py index bdec34e5..7c7e69cd 100755 --- a/pylibs/unittests/test_ccm_commissioning.py +++ b/pylibs/unittests/test_ccm_commissioning.py @@ -85,13 +85,15 @@ def testCommissioningOneHop(self): self.assertTrue(joins and joins[0][1] > 0) # assert join success # n3 joins as CCM joiner - ns.speed = 2 + # because CoAP server is real, let simulation also move in real time. + ns.speed = 5 ns.commissioner_ccm_joiner_add(n1, "*") ns.ifconfig_up(n3) ns.ccm_joiner_start(n3) - self.go(20) + self.go(10) #ns.thread_start(n3) #self.go(100) + c = ns.counters() print('counters', c) joins = ns.joins() diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index b39554ac..0a0fac22 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -92,6 +92,8 @@ async def asyncResponseFromSimHost(self): # because CoAP server is real, let simulation also move in real time. ns.autogo = True ns.speed = 1 + ns.coaps_enable() + ns.node_cmd(n2, "coap start") ns.node_cmd(n2, "coap get fc00::1234 hello con") # dest addr must match an external route of the BR await asyncio.sleep(1) # let the aiocoap server serve the request @@ -101,6 +103,11 @@ async def asyncResponseFromSimHost(self): self.assertEqual(1+1, len(hosts_list)) self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + c = ns.coaps() + self.assertEqual(1, len(c)) + self.assertEqual("fc00:0:0:0:0:0:0:1234", c[0]['dst_addr']) + self.assertEqual(5683, c[0]['dst_port']) + await ctx.shutdown() def testRequestFromBrAndResponseFromSimHost(self): @@ -117,6 +124,8 @@ async def asyncRequestFromBrAndResponseFromSimHost(self): # n1 sends a coap message to AIL, to test AIL connectivity. Message does not travel over mesh. ns.autogo = True ns.speed = 1 + ns.coaps_enable() + ns.node_cmd(n1, "coap start") ns.node_cmd(n1, "coap get fc00::5678 hello con") # dest addr must match an external route of the BR await asyncio.sleep(1) # let the aiocoap server serve the request @@ -126,6 +135,11 @@ async def asyncRequestFromBrAndResponseFromSimHost(self): self.assertEqual(1+1, len(hosts_list)) self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + c = ns.coaps() + self.assertEqual(1, len(c)) + self.assertEqual("fc00:0:0:0:0:0:0:5678", c[0]['dst_addr']) + self.assertEqual(5683, c[0]['dst_port']) + await ctx.shutdown() diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index 708035a1..a05edcc2 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -204,7 +204,7 @@ func (sh *SimHosts) handleUdpFromSimHost(simConn *SimConn, udpData []byte) { }, } sh.sim.Dispatcher().PostEventAsync(ev) - logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) FIXME-UDP path %v", ev) + logger.Debugf("sh.sim.Dispatcher().PostEventAsync(ev) FIXME-UDP path %v, %+v", ev, ev.MsgToHostData) logger.Debugf("simConn.Nat66State UDP-path = %v", simConn.Nat66State) logger.Debugf("udpData = %s", hex.EncodeToString(udpData)) } else { From 4da19bf5a2384cfdccf62fc09e03a9c6f8d280f6 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 25 May 2024 21:16:57 +0200 Subject: [PATCH 16/36] [ot-rfsim] fix build for FTD --- ot-rfsim/src/platform-rfsim.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index f29ab699..ea624b1a 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -152,9 +152,13 @@ void platformReceiveEvent(otInstance *aInstance) case OT_SIM_EVENT_IP6_FROM_HOST: VERIFY_EVENT_SIZE(struct MsgToHostEventData) +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE error = platformIp6FromHostToNode(aInstance, (struct MsgToHostEventData *) evData, event.mData + sizeof(struct MsgToHostEventData), payloadLen - sizeof(struct MsgToHostEventData)); +#else + error = OT_ERROR_NOT_IMPLEMENTED; +#endif if (error != OT_ERROR_NONE) { otLogCritPlat("Error handling IP6_FROM_HOST event, dropping datagram: %s", otThreadErrorToString(error)); } @@ -162,15 +166,19 @@ void platformReceiveEvent(otInstance *aInstance) case OT_SIM_EVENT_UDP_FROM_HOST: VERIFY_EVENT_SIZE(struct MsgToHostEventData) +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE error = platformUdpFromHostToNode(aInstance, (struct MsgToHostEventData *) evData, event.mData + sizeof(struct MsgToHostEventData), payloadLen - sizeof(struct MsgToHostEventData)); +#else + error = OT_ERROR_NOT_IMPLEMENTED; +#endif if (error != OT_ERROR_NONE) { otLogCritPlat("Error handling IP6_FROM_HOST event, dropping datagram: %s", otThreadErrorToString(error)); } break; - default: + default: OT_ASSERT(false && "Unrecognized event type received"); } } @@ -184,6 +192,7 @@ void otPlatOtnsStatus(const char *aStatus) otSimSendOtnsStatusPushEvent(aStatus, statusLength); } +#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE // TODO change params to start with 'a' otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { otMessage *ip6; @@ -229,8 +238,6 @@ otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostE return error; } -#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE - // TODO change params to start with 'a' otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { otMessage *udp; @@ -283,7 +290,6 @@ void handleUdpForwarding(otMessage *aMessage, otSimSendMsgToHostEvent(OT_SIM_EVENT_UDP_TO_HOST, &evData, &buf[0], msgLen); } -#endif // utility function to check IPv6 address for fe80::/10 or ffx2::/16 prefix -> link-local. static bool isLinkLocal(otIp6Address *addr) @@ -300,7 +306,6 @@ static uint8_t ip6McastScope(otIp6Address *addr) return addr->mFields.m8[0] & 0x0f; } -#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) { OT_UNUSED_VARIABLE(aContext); @@ -341,7 +346,7 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) exit: otMessageFree(aMessage); } -#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE void platformNetifSetUp(otInstance *aInstance) { From 7f7df31b2318676d61e5cb0f0ac83722cf861f70 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 16:31:13 +0200 Subject: [PATCH 17/36] make-pretty --- simulation/sim_hosts.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/simulation/sim_hosts.go b/simulation/sim_hosts.go index a05edcc2..81f2aa0d 100644 --- a/simulation/sim_hosts.go +++ b/simulation/sim_hosts.go @@ -1,6 +1,33 @@ +// Copyright (c) 2024, The OTNS Authors. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// 3. Neither the name of the copyright holder nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + package simulation import ( + "encoding/hex" "errors" "fmt" "io" @@ -9,7 +36,6 @@ import ( "golang.org/x/net/ipv6" - "encoding/hex" "github.com/openthread/ot-ns/event" "github.com/openthread/ot-ns/logger" "github.com/openthread/ot-ns/types" From f794237a7fe6c7875e74ca4f841537baedbf73a3 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 16:47:19 +0200 Subject: [PATCH 18/36] [event] fix unit test post-rebase --- event/event_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/event/event_test.go b/event/event_test.go index b0d141dc..eee42ad2 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -236,7 +236,7 @@ func TestDeserializeRfSimRspEvent(t *testing.T) { } func TestDeserializeMsgToHostEvents(t *testing.T) { - data, _ := hex.DecodeString("00000000000000001304000000000000002900efbe3316fe800000000000000000000000001234fe80000000000000000000000000beef0102030405") + data, _ := hex.DecodeString("00000000000000001404000000000000002900efbe3316fe800000000000000000000000001234fe80000000000000000000000000beef0102030405") testIp6Addr, _ := hex.DecodeString("fe800000000000000000000000001234") testIp6Addr2, _ := hex.DecodeString("fe80000000000000000000000000beef") var ev Event @@ -262,7 +262,7 @@ func TestDeserializeMsgToHostEvents(t *testing.T) { } func TestSerializeMsgToHostEvents(t *testing.T) { - dataExpected, _ := hex.DecodeString("00000000000000001604000000000000002900efbe3316fe800000000000000000000000001234fe80abcd00000000000000000000abcd0102030405") + dataExpected, _ := hex.DecodeString("00000000000000001704000000000000002900efbe3316fe800000000000000000000000001234fe80abcd00000000000000000000abcd0102030405") evData := MsgToHostEventData{ SrcPort: 48879, DstPort: 5683, From cc1a7b8a7055c6c5e2b9b68e54bfbce9863aeea4 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 16:49:43 +0200 Subject: [PATCH 19/36] [pylibs][script] fix aiocoap install for unit-tests only --- pylibs/unittests/requirements.txt | 1 + script/install-deps | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pylibs/unittests/requirements.txt b/pylibs/unittests/requirements.txt index 4e262d75..768234ae 100644 --- a/pylibs/unittests/requirements.txt +++ b/pylibs/unittests/requirements.txt @@ -2,3 +2,4 @@ grpcio-tools==1.53.0 grpcio==1.53.0 protobuf==4.21.6 wheel==0.42.0 +aiocoap==0.4.9 \ No newline at end of file diff --git a/script/install-deps b/script/install-deps index 48a7dded..70127e14 100755 --- a/script/install-deps +++ b/script/install-deps @@ -56,7 +56,7 @@ install_grpcwebproxy() install_python_libs() { activate_python_venv - python3 -m pip install setuptools wheel aiocoap + python3 -m pip install setuptools wheel } main() From 2300e09ea594ee941cd6f09868057d9ddd9028f9 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 20:36:07 +0200 Subject: [PATCH 20/36] [ot-rfsim] fix script (remove merge artifacts) --- ot-rfsim/script/build_br | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ot-rfsim/script/build_br b/ot-rfsim/script/build_br index b12b2e77..a8e8ac4f 100755 --- a/ot-rfsim/script/build_br +++ b/ot-rfsim/script/build_br @@ -61,13 +61,7 @@ main() local options=() options+=("${OTBR_OPTIONS[@]}" "$@") -<<<<<<< HEAD - ./script/build "${options[@]}" -======= - #git submodule update - #rm -rf build ./script/build "${options[@]}" ot-cli-ftd ->>>>>>> 3862892 (Added event type for UdpToAil forwarding - sending the Udp pkt to simulator.) cp ./build/bin/ot-cli-ftd ./ot-versions/ot-cli-ftd_br } From 8e1ca1d0bd841b688775690ab9cd7c2c4baff3d9 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 20:58:16 +0200 Subject: [PATCH 21/36] [dispatcher][pylibs] added HostEvents counter --- dispatcher/dispatcher.go | 5 +++-- pylibs/unittests/test_sim_hosts.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index ad2c8f98..5df8f1ce 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -88,6 +88,7 @@ type Dispatcher struct { StatusPushEvents uint64 UartWriteEvents uint64 LogWriteEvents uint64 + HostEvents uint64 OtherEvents uint64 // Packet-related event dispatching counters DispatchByExtAddrSucc uint64 @@ -436,12 +437,12 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.alarmMgr.SetTimestamp(node.Id, Ever) case EventTypeUdpToHost, EventTypeIp6ToHost: - d.Counters.OtherEvents += 1 // TODO - counter for host or AIL events? + d.Counters.HostEvents += 1 // TODO - counter for host or AIL events? d.sendMsgToHost(node, evt) d.cbHandler.OnMsgToHost(node.Id, evt) case EventTypeUdpFromHost, EventTypeIp6FromHost: - d.Counters.OtherEvents += 1 + d.Counters.HostEvents += 1 evt.MustDispatch = true // asap resend again to the target (BR) node. d.eventQueue.Add(evt) default: diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 0a0fac22..1ee27037 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -135,6 +135,9 @@ async def asyncRequestFromBrAndResponseFromSimHost(self): self.assertEqual(1+1, len(hosts_list)) self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + ctr = ns.counters() + self.assertEqual(2, ctr['HostEvents']) + c = ns.coaps() self.assertEqual(1, len(c)) self.assertEqual("fc00:0:0:0:0:0:0:5678", c[0]['dst_addr']) From bb688ae0b10b38f6e613140a39b42573379fafe0 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 21:01:46 +0200 Subject: [PATCH 22/36] [dispatcher][pylibs] added HostEvents counter --- dispatcher/dispatcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 5df8f1ce..251f1b32 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -437,7 +437,7 @@ func (d *Dispatcher) handleRecvEvent(evt *Event) { d.alarmMgr.SetTimestamp(node.Id, Ever) case EventTypeUdpToHost, EventTypeIp6ToHost: - d.Counters.HostEvents += 1 // TODO - counter for host or AIL events? + d.Counters.HostEvents += 1 d.sendMsgToHost(node, evt) d.cbHandler.OnMsgToHost(node.Id, evt) case EventTypeUdpFromHost, From e4ca79ead12a4853244780d268cbdf482fc19bc3 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Tue, 18 Jun 2024 21:45:25 +0200 Subject: [PATCH 23/36] [pylibs] fix unit test (TMF Coap messages may be sent in parallel of the test coap message) --- pylibs/unittests/test_sim_hosts.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 1ee27037..7de09c40 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -138,10 +138,15 @@ async def asyncRequestFromBrAndResponseFromSimHost(self): ctr = ns.counters() self.assertEqual(2, ctr['HostEvents']) - c = ns.coaps() - self.assertEqual(1, len(c)) - self.assertEqual("fc00:0:0:0:0:0:0:5678", c[0]['dst_addr']) - self.assertEqual(5683, c[0]['dst_port']) + cnt=0 + coap_msgs = ns.coaps() + for c in coap_msgs: + if c['dst_port'] == 5683: + self.assertEqual("fc00:0:0:0:0:0:0:5678", c['dst_addr']) + self.assertEqual("hello", c['uri']) + cnt+=1 + + self.assertEqual(1, cnt) await ctx.shutdown() From bdd0548a76d8fe948ddb09556a5b90490e8f2ff9 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Fri, 21 Jun 2024 16:36:33 +0200 Subject: [PATCH 24/36] [pcap] insert a dummy 802.15.4 frame at start of every PCAP file, to create a t=0 simulation time reference. This allows any PCAP entry to be associated to a corresponding radio (trace) log entry with exact (to the microsecond) precision. --- pcap/PcapFile.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pcap/PcapFile.go b/pcap/PcapFile.go index 4c0a425a..8106adfe 100644 --- a/pcap/PcapFile.go +++ b/pcap/PcapFile.go @@ -32,6 +32,7 @@ import ( "os" . "github.com/openthread/ot-ns/types" + "github.com/openthread/ot-ns/logger" ) type FrameType int @@ -58,6 +59,11 @@ const ( pcapFrameHeaderSize = 16 ) +const ( + // 802.15.4 frame that uses a Reserved type and is only included as t=0 simulation time reference for the PCAP file. + timeReference802154frameData string = "\x04\x21This is an OTNS simulation PCAP-start t=0 reference frame.\x61\x3f" +) + // File represents a PCAP file type File interface { AppendFrame(frame Frame) error @@ -79,14 +85,28 @@ type wpanFile struct { // NewFile creates a new PCAP file with all frames using specified frameType func NewFile(filename string, frameType FrameType) (File, error) { + var f File + var err error + switch frameType { case FrameTypeWpan: - return newWpanFile(filename) + f, err = newWpanFile(filename) case FrameTypeWpanTap: - return newWpanTapFile(filename) + f, err = newWpanTapFile(filename) default: - return nil, fmt.Errorf("invalid PCAP frame type: %d", frameType) + f, err = nil, fmt.Errorf("invalid PCAP frame type: %d", frameType) + } + + if err == nil && f != nil { + logger.PanicfIfError(f.AppendFrame(Frame{ + Timestamp: 0, + Data: []byte(timeReference802154frameData), + Channel: 0, + Rssi: 0, + }), "PCAP file time-reference 0 frame could not be written") } + + return f, err } func ParseFrameTypeStr(tp string) FrameType { From bf09f36defdcd126cf50dc8b0a37a66de438c94d Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Fri, 21 Jun 2024 16:37:29 +0200 Subject: [PATCH 25/36] [pcap] insert a dummy 802.15.4 frame at start of every PCAP file, to create a t=0 simulation time reference. This allows any PCAP entry to be associated to a corresponding radio (trace) log entry with exact (to the microsecond) precision (now +make-pretty). --- pcap/PcapFile.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pcap/PcapFile.go b/pcap/PcapFile.go index 8106adfe..6a39e8b9 100644 --- a/pcap/PcapFile.go +++ b/pcap/PcapFile.go @@ -1,4 +1,4 @@ -// Copyright (c) 2020-2023, The OTNS Authors. +// Copyright (c) 2020-2024, The OTNS Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -31,8 +31,8 @@ import ( "fmt" "os" - . "github.com/openthread/ot-ns/types" "github.com/openthread/ot-ns/logger" + . "github.com/openthread/ot-ns/types" ) type FrameType int From e780c694295df33784aeb109d045d8404ff54a44 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 15:56:53 +0200 Subject: [PATCH 26/36] [pylibs] added further comments in partial_dataset case study. --- pylibs/case_studies/partial_dataset.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pylibs/case_studies/partial_dataset.py b/pylibs/case_studies/partial_dataset.py index 36462695..d7c64a74 100755 --- a/pylibs/case_studies/partial_dataset.py +++ b/pylibs/case_studies/partial_dataset.py @@ -36,6 +36,7 @@ NUM_NODES = 1 DX = 50 # pixels spacing +# defines a setup script with partial dataset (no channel info) SCRIPT_PARTIAL_DATASET=""" dataset clear dataset networkkey 00112233445566778899aabbccddeeff @@ -52,6 +53,7 @@ def main(): ns.radiomodel = 'MutualInterference' ns.web() ns.web('stats') + ns.watch_default('trace') # setup of Border Routers for i in range(1, NUM_BR+1): @@ -63,7 +65,7 @@ def main(): nid_br = 1 ns.go(100) - # setup of Router nodes or End devices - + # setup of Router nodes or End devices cx = 100 cy = DX for i in range(1, NUM_NODES+1): @@ -77,6 +79,9 @@ def main(): ns.kpi_start() ns.go(100) ns.kpi_stop() + + # at the end, node's status can be inspected via CLI. KPI files contain activity over channels. + # The PCAP file contains channel info of all frames sent, to see the channel scanning happening. ns.interactive_cli() From a6b4642e8ad577784a8e905b1edb66a8385bd77a Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 16:19:16 +0200 Subject: [PATCH 27/36] [pcap] fix unit test, related to introduction of t=0 time-reference frame. --- pcap/PcapFile.go | 4 ++-- pcap/PcapFile_test.go | 47 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/pcap/PcapFile.go b/pcap/PcapFile.go index 6a39e8b9..0d668498 100644 --- a/pcap/PcapFile.go +++ b/pcap/PcapFile.go @@ -84,7 +84,7 @@ type wpanFile struct { } // NewFile creates a new PCAP file with all frames using specified frameType -func NewFile(filename string, frameType FrameType) (File, error) { +func NewFile(filename string, frameType FrameType, useTimeRefFrame bool) (File, error) { var f File var err error @@ -97,7 +97,7 @@ func NewFile(filename string, frameType FrameType) (File, error) { f, err = nil, fmt.Errorf("invalid PCAP frame type: %d", frameType) } - if err == nil && f != nil { + if useTimeRefFrame && err == nil && f != nil { logger.PanicfIfError(f.AppendFrame(Frame{ Timestamp: 0, Data: []byte(timeReference802154frameData), diff --git a/pcap/PcapFile_test.go b/pcap/PcapFile_test.go index 144c8f9f..18c39695 100644 --- a/pcap/PcapFile_test.go +++ b/pcap/PcapFile_test.go @@ -35,7 +35,7 @@ import ( func TestPcapFile(t *testing.T) { pcapFilename := "test.pcap" - pcap, err := NewFile(pcapFilename, FrameTypeWpan) + pcap, err := NewFile(pcapFilename, FrameTypeWpan, false) if err != nil { t.Fatal(err) } @@ -49,7 +49,7 @@ func TestPcapFile(t *testing.T) { t.Fatal(err) } - assert.True(t, pcapFileHeaderSize == getFileSize(t, pcapFilename)) + assert.Equal(t, pcapFileHeaderSize, getFileSize(t, pcapFilename)) for i := 0; i < 10; i++ { frame := Frame{ @@ -73,7 +73,7 @@ func TestPcapFile(t *testing.T) { func TestPcapTapFile(t *testing.T) { pcapFilename := "test_tap.pcap" - pcap, err := NewFile(pcapFilename, FrameTypeWpanTap) + pcap, err := NewFile(pcapFilename, FrameTypeWpanTap, false) if err != nil { t.Fatal(err) } @@ -87,7 +87,7 @@ func TestPcapTapFile(t *testing.T) { t.Fatal(err) } - assert.True(t, pcapFileHeaderSize == getFileSize(t, pcapFilename)) + assert.Equal(t, pcapFileHeaderSize, getFileSize(t, pcapFilename)) for i := 0; i < 10; i++ { frame := Frame{ @@ -105,7 +105,44 @@ func TestPcapTapFile(t *testing.T) { if err != nil { t.Fatal(err) } - assert.True(t, pcapFileHeaderSize+(pcapFrameHeaderSize+pcapTapFrameHeaderSize+5)*(i+1) == getFileSize(t, pcapFilename)) + assert.Equal(t, pcapFileHeaderSize+(pcapFrameHeaderSize+pcapTapFrameHeaderSize+5)*(i+1), getFileSize(t, pcapFilename)) + } +} + +func TestPcapFileWithTimeRefFrame(t *testing.T) { + pcapFilename := "test_timerefframe.pcap" + pcap, err := NewFile(pcapFilename, FrameTypeWpan, true) + if err != nil { + t.Fatal(err) + } + + defer func() { + _ = pcap.Close() + }() + + err = pcap.Sync() + if err != nil { + t.Fatal(err) + } + pcapStartSize := getFileSize(t, pcapFilename) + + for i := 0; i < 10; i++ { + frame := Frame{ + Timestamp: 500 + uint64(i)*1000, + Data: []byte{0x12, 0x10, 0xa6, 0x80, 0x65}, + Channel: 25, + Rssi: 0.0, + } + err = pcap.AppendFrame(frame) + if err != nil { + t.Fatal(err) + } + + err = pcap.Sync() + if err != nil { + t.Fatal(err) + } + assert.True(t, pcapStartSize+(pcapFrameHeaderSize+5)*(i+1) == getFileSize(t, pcapFilename)) } } From 5e974f7d6a181135e65e45c7ca2218095d34a8df Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 16:24:56 +0200 Subject: [PATCH 28/36] [pcap] fix unit test, related to introduction of t=0 time-reference frame. --- dispatcher/dispatcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dispatcher/dispatcher.go b/dispatcher/dispatcher.go index 251f1b32..d942d73e 100644 --- a/dispatcher/dispatcher.go +++ b/dispatcher/dispatcher.go @@ -173,7 +173,7 @@ func NewDispatcher(ctx *progctx.ProgCtx, cfg *Config, cbHandler CallbackHandler) } d.speed = d.normalizeSpeed(d.speed) if d.cfg.PcapEnabled { - d.pcap, err = pcap.NewFile("current.pcap", cfg.PcapFrameType) + d.pcap, err = pcap.NewFile("current.pcap", cfg.PcapFrameType, true) logger.PanicIfError(err) d.waitGroup.Add(1) go d.pcapFrameWriter() From 9681ccae1e27adf0effeeb688516a9ad48a6ac95 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 16:25:27 +0200 Subject: [PATCH 29/36] [ot-rfsim] fix macos build error (due to warn) --- ot-rfsim/src/platform-rfsim.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index ea624b1a..90fab63e 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -221,7 +221,7 @@ otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostE otCoapMessageInit(testMsg, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); otLogDebgPlat("FIXME step 6"); uint8_t magic[2] = {0xed, 0xda}; - otCoapMessageSetToken(testMsg, magic, 2); + otEXPECT( error = otCoapMessageSetToken(testMsg, magic, 2) == OT_ERROR_NONE); otUdpForwardReceive(aInstance, testMsg, evData->mSrcPort, srcIp6, evData->mDstPort); otLogDebgPlat("FIXME step 7"); From 8afac15ea389d1a4811aa29d23a85a48691b4de6 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 16:49:54 +0200 Subject: [PATCH 30/36] [.github] restrict py-ver-unittests to Ubuntu OS, for the moment (macOS fails?) --- .github/workflows/test.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4c93ddf5..eaf5b1fe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,6 +85,27 @@ jobs: - name: Test single OT-version run: | ./script/test py-unittests + + py-ver-unittests: + name: Unittests-Version (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + python-version: ['3.10'] + os: [ubuntu-22.04] + env: + HOMEBREW_NO_AUTO_UPDATE: 1 + steps: + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - uses: actions/checkout@v3 - name: Test multiple OT-versions run: | ./script/test py-ver-unittests From bc892acaaa9189012ea6822dc75687c55dda740a Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 16:50:26 +0200 Subject: [PATCH 31/36] [ot-rfsim] add C lang definitions for macOS build cpp in ot-rfsim project. --- ot-rfsim/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ot-rfsim/CMakeLists.txt b/ot-rfsim/CMakeLists.txt index 228ba7a0..23446b77 100644 --- a/ot-rfsim/CMakeLists.txt +++ b/ot-rfsim/CMakeLists.txt @@ -29,6 +29,10 @@ cmake_minimum_required(VERSION 3.10.2) project(ot-rfsim VERSION 2.0.0) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_C_STANDARD 99) + set(OT_PLATFORM "external" CACHE STRING "Target platform must be 'external'") set(OT_PLATFORM_LIB "openthread-rfsim") if(DEFINED ENV{OT_DIR}) From 1bfc612e1dfcc1bdad7a895e4bb6e6c67cd3dba1 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 17:25:15 +0200 Subject: [PATCH 32/36] [ot-rfsim] adding 'a' in front of function call parameters; and minor format fixes in C code; adding comments; removing FIXME items. --- ot-rfsim/src/alarm.c | 4 +- ot-rfsim/src/platform-rfsim.c | 93 ++++++++++++--------------------- ot-rfsim/src/platform-rfsim.cpp | 23 +++----- ot-rfsim/src/platform-rfsim.h | 31 ++++++----- 4 files changed, 61 insertions(+), 90 deletions(-) diff --git a/ot-rfsim/src/alarm.c b/ot-rfsim/src/alarm.c index aa2a297e..980dbdcc 100644 --- a/ot-rfsim/src/alarm.c +++ b/ot-rfsim/src/alarm.c @@ -79,9 +79,9 @@ int16_t platformAlarmGetClockDrift() return sClockDriftPpm; } -void platformAlarmSetClockDrift(int16_t drift) +void platformAlarmSetClockDrift(int16_t aDrift) { - sClockDriftPpm = drift; + sClockDriftPpm = aDrift; } uint32_t otPlatAlarmMilliGetNow(void) diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 90fab63e..22b71aff 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -56,8 +56,9 @@ extern int gSockFd; -uint64_t gLastMsgId = 0; -struct Event gLastRecvEvent; +uint64_t gLastMsgId = 0; +struct Event gLastRecvEvent; + static otIp6Address unspecifiedIp6Address; void platformRfsimInit(void) { @@ -193,76 +194,48 @@ void otPlatOtnsStatus(const char *aStatus) } #if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE -// TODO change params to start with 'a' -otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { +otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *aEvData, const uint8_t *aMsg, size_t aMsgLen) { otMessage *ip6; otError error = OT_ERROR_NONE; otIp6Address *dstIp6; otIp6Address *srcIp6; - ip6 = otIp6NewMessageFromBuffer(aInstance, msg, msgLen, NULL); + ip6 = otIp6NewMessageFromBuffer(aInstance, aMsg, aMsgLen, NULL); otEXPECT_ACTION(ip6 != NULL, error = OT_ERROR_NO_BUFS); + srcIp6 = (otIp6Address *) aEvData->mSrcIp6; + dstIp6 = (otIp6Address *) aEvData->mDstIp6; - srcIp6 = (otIp6Address *) evData->mSrcIp6; - dstIp6 = (otIp6Address *) evData->mDstIp6; - otLogDebgPlat("FIXME step 1"); if(otIp6IsAddressUnspecified(dstIp6)) { - otLogDebgPlat("FIXME step 2"); - validateOtMsg(ip6); - otLogDebgPlat("FIXME validateOtMsg passed"); - - otMessage *testMsg; - otLogDebgPlat("FIXME step 3"); - testMsg = otCoapNewMessage(aInstance, NULL); - otLogDebgPlat("FIXME step 4"); - otLogDebgPlat("FIXME step 5"); - validateOtMsg(testMsg); - - otCoapMessageInit(testMsg, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); - otLogDebgPlat("FIXME step 6"); - uint8_t magic[2] = {0xed, 0xda}; - otEXPECT( error = otCoapMessageSetToken(testMsg, magic, 2) == OT_ERROR_NONE); - - otUdpForwardReceive(aInstance, testMsg, evData->mSrcPort, srcIp6, evData->mDstPort); - otLogDebgPlat("FIXME step 7"); - - validateOtMsg(ip6); - otLogDebgPlat("FIXME step 8"); - - otUdpForwardReceive(aInstance, ip6, evData->mSrcPort, srcIp6, evData->mDstPort); + // local: message is from host to node itself. + //otMessage *testMsg; // test message for future CCM/relay testing. + //testMsg = otCoapNewMessage(aInstance, NULL); + //otCoapMessageInit(testMsg, OT_COAP_TYPE_CONFIRMABLE, OT_COAP_CODE_POST); + //uint8_t magic[2] = {0xed, 0xda}; + //otEXPECT( error = otCoapMessageSetToken(testMsg, magic, 2) == OT_ERROR_NONE); + //otUdpForwardReceive(aInstance, testMsg, aEvData->mSrcPort, srcIp6, aEvData->mDstPort); + + otUdpForwardReceive(aInstance, ip6, aEvData->mSrcPort, srcIp6, aEvData->mDstPort); }else { - // non-local: send as IPv6 datagram + // non-local: send as IPv6 datagram to (potentially) other node. error = otIp6Send(aInstance, ip6); } exit: return error; } -// TODO change params to start with 'a' -otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen) { +otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *aEvData, const uint8_t *aMsg, size_t aMsgLen) { otMessage *udp; - otError error = OT_ERROR_NONE; - otIp6Address *dstIp6; + otError error; + //otIp6Address *dstIp6; otIp6Address *srcIp6; - otLogDebgPlat("FIXME step 1a"); udp = otUdpNewMessage(aInstance, NULL); - otLogDebgPlat("FIXME step 2a"); - otEXPECT((error = otMessageAppend(udp, msg, msgLen)) == OT_ERROR_NONE); - otLogDebgPlat("FIXME step 3a"); + otEXPECT((error = otMessageAppend(udp, aMsg, aMsgLen)) == OT_ERROR_NONE); otEXPECT_ACTION(udp != NULL, error = OT_ERROR_NO_BUFS); - otLogDebgPlat("FIXME step 4a"); - - srcIp6 = (otIp6Address *) evData->mSrcIp6; - dstIp6 = (otIp6Address *) evData->mDstIp6; - otLogDebgPlat("FIXME step 5a"); - - otLogDebgPlat("FIXME step 6a"); - validateOtMsg(udp); - otLogDebgPlat("FIXME validateOtMsg passed 7a"); - otUdpForwardReceive(aInstance, udp, evData->mSrcPort, srcIp6, evData->mDstPort); - otLogDebgPlat("FIXME step 8a"); + srcIp6 = (otIp6Address *) aEvData->mSrcIp6; + //dstIp6 = (otIp6Address *) aEvData->mDstIp6; + otUdpForwardReceive(aInstance, udp, aEvData->mSrcPort, srcIp6, aEvData->mDstPort); exit: return error; @@ -292,18 +265,18 @@ void handleUdpForwarding(otMessage *aMessage, } // utility function to check IPv6 address for fe80::/10 or ffx2::/16 prefix -> link-local. -static bool isLinkLocal(otIp6Address *addr) +static bool isLinkLocal(otIp6Address *aAddr) { - return (addr->mFields.m8[0] == 0xfe && (addr->mFields.m8[1] & 0b11000000) == 0x80) - || (addr->mFields.m8[0] == 0xff && (addr->mFields.m8[1] & 0b00001111) == 0x02); + return (aAddr->mFields.m8[0] == 0xfe && (aAddr->mFields.m8[1] & 0b11000000) == 0x80) + || (aAddr->mFields.m8[0] == 0xff && (aAddr->mFields.m8[1] & 0b00001111) == 0x02); } // utility function that returns IPv6 address' multicast scope 0x0-0xf or 0xff for parse-error. -static uint8_t ip6McastScope(otIp6Address *addr) +static uint8_t ip6McastScope(otIp6Address *aAddr) { - if (addr->mFields.m8[0] != 0xff) + if (aAddr->mFields.m8[0] != 0xff) return 0xff; - return addr->mFields.m8[0] & 0x0f; + return aAddr->mFields.m8[0] & 0x0f; } void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) @@ -312,10 +285,10 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) struct MsgToHostEventData evData; uint8_t buf[OPENTHREAD_CONFIG_IP6_MAX_DATAGRAM_LENGTH]; - const uint8_t dstAddrZero[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + //const uint8_t dstAddrZero[OT_IP6_ADDRESS_SIZE] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; size_t msgLen; otMessageInfo ip6Info; - otError error = OT_ERROR_NONE; + otError error; msgLen = otMessageGetLength(aMessage); OT_ASSERT(msgLen <= sizeof(buf)); @@ -328,7 +301,7 @@ void handleIp6FromNodeToHost(otMessage *aMessage, void *aContext) otEXPECT(otMessageIsLoopbackToHostAllowed(aMessage) && ip6Info.mSockPort > 0 && ip6Info.mPeerPort > 0 && - ip6Info.mPeerPort != 61631 && // drop mesh-local TMF messages + ip6Info.mPeerPort != 61631 && // drop mesh-local TMF messages (TODO constant) !isLinkLocal(&ip6Info.mPeerAddr) && !isLinkLocal(&ip6Info.mSockAddr) && ip6McastScope(&ip6Info.mPeerAddr) >= 0x4); diff --git a/ot-rfsim/src/platform-rfsim.cpp b/ot-rfsim/src/platform-rfsim.cpp index a151ac0d..3aed3da5 100644 --- a/ot-rfsim/src/platform-rfsim.cpp +++ b/ot-rfsim/src/platform-rfsim.cpp @@ -37,7 +37,7 @@ #include "net/ip6.hpp" #include "net/ip6_address.hpp" -extern "C" otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info); +extern "C" otError platformParseIp6( otMessage *aMessage, otMessageInfo *aIp6Info); extern "C" void validateOtMsg( otMessage *aMessage); #include "platform-rfsim.h" @@ -45,26 +45,19 @@ extern "C" void validateOtMsg( otMessage *aMessage); using namespace ot; -otError platformParseIp6( otMessage *aMessage, otMessageInfo *ip6Info) { +otError platformParseIp6( otMessage *aMessage, otMessageInfo *aIp6Info) { Ip6::Headers headers; - otError error = OT_ERROR_PARSE; + otError error; Message msg = AsCoreType(aMessage); SuccessOrExit(error = headers.ParseFrom(msg)); - ip6Info->mSockAddr = headers.GetSourceAddress(); - ip6Info->mPeerAddr = headers.GetDestinationAddress(); - ip6Info->mSockPort = headers.GetSourcePort(); - ip6Info->mPeerPort = headers.GetDestinationPort(); + aIp6Info->mSockAddr = headers.GetSourceAddress(); + aIp6Info->mPeerAddr = headers.GetDestinationAddress(); + aIp6Info->mSockPort = headers.GetSourcePort(); + aIp6Info->mPeerPort = headers.GetDestinationPort(); exit: return error; } -// FIXME delete -void validateOtMsg( otMessage *aMessage) { - Message msg = AsCoreType(aMessage); - msg.RemoveHeader(msg.GetOffset()); - //OT_ASSERT(msg.GetOffset() == 0); -} - -#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE \ No newline at end of file +#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE diff --git a/ot-rfsim/src/platform-rfsim.h b/ot-rfsim/src/platform-rfsim.h index 38bc075f..f11c5c7e 100644 --- a/ot-rfsim/src/platform-rfsim.h +++ b/ot-rfsim/src/platform-rfsim.h @@ -132,10 +132,10 @@ void platformAlarmAdvanceNow(uint64_t aDelta); /** * set the clock drift for the node's simulated clock. * - * @param drift actual clock drift in PPM - can be positive (=too fast), negative (=too slow), + * @param aDrift actual clock drift in PPM - can be positive (=too fast), negative (=too slow), * or zero (=perfect clock). */ -void platformAlarmSetClockDrift(int16_t drift); +void platformAlarmSetClockDrift(int16_t aDrift); /** * get the clock drift for the node's simulated clock. @@ -232,16 +232,24 @@ void platformRadioReportStateToSimulator(bool force); /** * performs the processing of an IPv6 packet that was sent from the (higher-layer) host to the OT node. * - * @param aInstance - * @param evData - * @param msg - * @param msgLen + * @param aInstance TODO + * @param aEvData + * @param aMsg + * @param aMsgLen * @return */ -otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); +otError platformIp6FromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *aEvData, const uint8_t *aMsg, size_t aMsgLen); -// FIXME -otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *evData, const uint8_t *msg, size_t msgLen); +/** + * performs the processing of a UDP datagram that was sent from the (higher-layer) host to the OT node. + * + * @param aInstance + * @param aEvData + * @param aMsg + * @param aMsgLen + * @return + */ +otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostEventData *aEvData, const uint8_t *aMsg, size_t aMsgLen); /** * parses aMessage as an IPv6 packet, writing the packet-info into ip6Info. @@ -252,7 +260,7 @@ otError platformUdpFromHostToNode(otInstance *aInstance, const struct MsgToHostE * @retval OT_ERROR_NONE Successfully parsed the message as IPv6 packet. * @retval OT_ERROR_PARSE Failed to parse the message as IPv6 packet. */ -otError platformParseIp6(otMessage *aMessage, otMessageInfo *ip6Info); +otError platformParseIp6(otMessage *aMessage, otMessageInfo *aIp6Info); /** * handler called when OT performs UDP-forwarding to the host. This is for UDP datagrams that @@ -270,9 +278,6 @@ void handleUdpForwarding(otMessage *aMessage, uint16_t aSockPort, void *aContext); -// FIXME -void validateOtMsg( otMessage *aMessage); - /** * Setup any simulated non-Thread interfaces. For example, an interface to a host process or * an AIL interface. From bb6136ef3ff51532ac38668b6cc6b9f766c0b193 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 17:25:41 +0200 Subject: [PATCH 33/36] [pylibs] added more documentation to test_sim_hosts and fixed testing-for-coaps in one test. --- pylibs/unittests/test_sim_hosts.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pylibs/unittests/test_sim_hosts.py b/pylibs/unittests/test_sim_hosts.py index 7de09c40..cf02feee 100755 --- a/pylibs/unittests/test_sim_hosts.py +++ b/pylibs/unittests/test_sim_hosts.py @@ -66,7 +66,7 @@ def testSendToSimHost(self): n2=ns.add('router') ns.go(10) - # n2 sends a coap message to AIL, to test AIL connectivity + # n2 sends a coap message to AIL, to test AIL connectivity. A response isn't sent back. ns.node_cmd(n2, "coap start") ns.node_cmd(n2, "coap get fc00::1234 info") # dest addr must match an external route of the BR self.go(10) @@ -101,12 +101,19 @@ async def asyncResponseFromSimHost(self): hosts_list = ns.cmd('host list') self.assertEqual(1+1, len(hosts_list)) - self.assertEqual("12 19", hosts_list[1][-11:]) # number of Rx bytes == 11, Tx == 19 + # TxBytes is number of bytes sent by aiocoap server for the single CoAP response message. + self.assertEqual("12 19", hosts_list[1][-11:]) # number of RxBytes == 11, TxBytes == 19 + + # check that the 'coaps' status-push event was also seen. + cnt=0 + coap_msgs = ns.coaps() + for c in coap_msgs: + if c['dst_port'] == 5683: + self.assertEqual("fc00:0:0:0:0:0:0:1234", c['dst_addr']) + self.assertEqual("hello", c['uri']) + cnt+=1 - c = ns.coaps() - self.assertEqual(1, len(c)) - self.assertEqual("fc00:0:0:0:0:0:0:1234", c[0]['dst_addr']) - self.assertEqual(5683, c[0]['dst_port']) + self.assertEqual(1, cnt) await ctx.shutdown() From 4ba05777f9c9042a145e73b5e8941f4f38cf5ce6 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sat, 22 Jun 2024 21:30:59 +0200 Subject: [PATCH 34/36] [ot-rfsim] fix build for v1.1, v1.2 for using ot....() platform logging functions. --- ot-rfsim/src/logging.c | 4 ++-- ot-rfsim/src/misc.c | 4 +++- ot-rfsim/src/platform-rfsim.c | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ot-rfsim/src/logging.c b/ot-rfsim/src/logging.c index 19c03291..666e5e04 100644 --- a/ot-rfsim/src/logging.c +++ b/ot-rfsim/src/logging.c @@ -43,7 +43,7 @@ #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED) // specify up to which syslog log level message will still be handled. Normally we rely on log messages being sent -// over the virtual-UART to the simulator; so we don't need everything to go to syslog. +// in events to the simulator; so we don't need everything to go to syslog. //#define SYSLOG_LEVEL LOG_DEBUG #define SYSLOG_LEVEL LOG_WARNING @@ -70,7 +70,7 @@ OT_TOOL_WEAK void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const syslog(convertOtLogLevelToSyslogLevel(aLogLevel), "%s", logString); - // extend logString with newline, and then log this string to virtual UART. + // extend logString with newline, and then log this string in an event. if (!gTerminate) { logString[strLen] = '\n'; logString[strLen + 1] = '\0'; diff --git a/ot-rfsim/src/misc.c b/ot-rfsim/src/misc.c index 08496c0f..525896dd 100644 --- a/ot-rfsim/src/misc.c +++ b/ot-rfsim/src/misc.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023, The OpenThread Authors. + * Copyright (c) 2016-2024, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,8 @@ #include #include "openthread-system.h" +#include "common/logging.hpp" + extern jmp_buf gResetJump; extern struct Event gLastSentEvent, gLastRecvEvent; diff --git a/ot-rfsim/src/platform-rfsim.c b/ot-rfsim/src/platform-rfsim.c index 22b71aff..7a161384 100644 --- a/ot-rfsim/src/platform-rfsim.c +++ b/ot-rfsim/src/platform-rfsim.c @@ -47,6 +47,7 @@ #include #include "common/debug.hpp" +#include "common/logging.hpp" #include "utils/code_utils.h" #include "utils/uart.h" From b990d295d07073574efc01c884ab55f7c10def11 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Sun, 23 Jun 2024 22:34:16 +0200 Subject: [PATCH 35/36] [pylibs] fixes to test_commissioning - test sometimes failed. --- pylibs/otns/cli/OTNS.py | 24 +++++++++++++++++++--- pylibs/unittests/test_commissioning.py | 28 ++++++++++---------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/pylibs/otns/cli/OTNS.py b/pylibs/otns/cli/OTNS.py index b7e1676d..dc416bc5 100644 --- a/pylibs/otns/cli/OTNS.py +++ b/pylibs/otns/cli/OTNS.py @@ -937,10 +937,12 @@ def set_networkkey(self, nodeid: int, key: str) -> None: """ self.node_cmd(nodeid, f'networkkey {key}') - def config_dataset(self, nodeid: int, channel: int = None, panid: int = None, extpanid: str = None, networkkey: str = None, network_name: str = None, - dataset='active'): + def config_dataset(self, nodeid: int, channel: int = None, panid: int = None, extpanid: str = None, + networkkey: str = None, network_name: str = None, active_timestamp: int = None, + set_remaining: bool = False, dataset: str = 'active'): """ - Configure the active/pending dataset + Configure the active or pending dataset. Parameters not provided (or set to None) are not set in the + resulting dataset. :param nodeid: target node ID :param channel: the channel number. @@ -948,6 +950,10 @@ def config_dataset(self, nodeid: int, channel: int = None, panid: int = None, ex :param extpanid: the Extended PAN ID. :param networkkey: the network key (REQUIRED) :param network_name: the network name + :param active_timestamp: the active timestamp. + :param set_remaining: if True, sets remaining dataset items not listed in params here to + default values. If False, does not set these items. + :param dataset: use 'active' for Active Dataset or 'pending' for Pending Dataset. """ assert dataset in ('active', 'pending'), dataset @@ -959,6 +965,9 @@ def config_dataset(self, nodeid: int, channel: int = None, panid: int = None, ex if panid is not None: self.node_cmd(nodeid, f'dataset panid 0x{panid:04x}') + if extpanid is not None: + self.node_cmd(nodeid, f'dataset extpanid {extpanid}') + if networkkey is not None: self.node_cmd(nodeid, f'dataset networkkey {networkkey}') @@ -966,6 +975,15 @@ def config_dataset(self, nodeid: int, channel: int = None, panid: int = None, ex network_name = self._escape_whitespace(network_name) self.node_cmd(nodeid, f'dataset networkname {network_name}') + if active_timestamp is not None: + self.node_cmd(nodeid, f'dataset activetimestamp {active_timestamp}') + + if set_remaining: + self.node_cmd(nodeid, f'dataset channelmask 0x07fff800') + self.node_cmd(nodeid, f'dataset meshlocalprefix fdde:ad00:beef:0::') + self.node_cmd(nodeid, f'dataset pskc 3aa55f91ca47d1e4e71a08cb35e91591') + self.node_cmd(nodeid, f'dataset securitypolicy 672 onrc 0') + self.node_cmd(nodeid, f'dataset commit {dataset}') def web(self, tab_name: str = "") -> None: diff --git a/pylibs/unittests/test_commissioning.py b/pylibs/unittests/test_commissioning.py index c2fa7262..1f14d1b8 100755 --- a/pylibs/unittests/test_commissioning.py +++ b/pylibs/unittests/test_commissioning.py @@ -37,7 +37,7 @@ class CommissioningTests(OTNSTestCase): def setUp(self) -> None: - self.ns = OTNS(otns_args=['-ot-script', 'none', '-log', 'debug', '-pcap', 'wpan-tap']) + self.ns = OTNS(otns_args=['-ot-script', 'none', '-log', 'debug', '-pcap', 'wpan-tap', '-seed', '23']) self.ns.speed = float('inf') def setFirstNodeDataset(self, n1) -> None: @@ -49,7 +49,7 @@ def testRawNoSetup(self): ns = self.ns ns.add("router") ns.add("router") - self.go(10) + self.go(250) # can not form any partition without setting network parameters self.assertTrue(0 in ns.partitions()) @@ -60,25 +60,18 @@ def testRawSetup(self): n3 = ns.add("router") # n1 with full dataset becomes Leader. - ns.node_cmd(n1, "dataset init new") - ns.node_cmd(n1, "dataset panid 0xface") - ns.node_cmd(n1, "dataset extpanid dead00beef00cafe") - ns.node_cmd(n1, "dataset networkkey 00112233445566778899aabbccddeeff") - ns.node_cmd(n1, "dataset networkname test") - ns.node_cmd(n1, "dataset channel 15") - ns.node_cmd(n1, "dataset commit active") + ns.config_dataset(n1, channel=21, panid=0xface, extpanid="dead00beef00cafe", networkkey="00112233445566778899aabbccddeeff", + active_timestamp=1719172243, network_name="test", set_remaining=True) ns.ifconfig_up(n1) ns.thread_start(n1) - # n2, n3 with partial dataset - if channel not given - will scan channels to find n1. - # This can take some time and may fail even then (FIXME: find cause). - # To prevent this failure, channel is provided here. + # n2, n3 with partial dataset - will wait for Leader to join to. for id in (n2, n3): - ns.config_dataset(id, channel=15, panid=0xface, extpanid="dead00beef00cafe", network_name="test", networkkey="00112233445566778899aabbccddeeff") + ns.config_dataset(id, network_name="test", networkkey="00112233445566778899aabbccddeeff", set_remaining=False) ns.ifconfig_up(id) ns.thread_start(id) - self.go(300) + self.go(350) self.assertFormPartitions(1) def testCommissioningOneHop(self): @@ -92,6 +85,7 @@ def testCommissioningOneHop(self): ns.thread_start(n1) self.go(35) self.assertTrue(ns.get_state(n1) == "leader") + ns.commissioner_start(n1) ns.commissioner_joiner_add(n1, "*", "TEST123") @@ -126,19 +120,19 @@ def testCommissioningThreeHop(self): ns.ifconfig_up(n2) ns.joiner_start(n2, "TEST123") - self.go(20) + self.go(40) ns.thread_start(n2) ns.ifconfig_up(n3) ns.joiner_start(n3, "TEST123") - self.go(20) + self.go(40) ns.thread_start(n3) - self.go(20) ns.ifconfig_up(n4) ns.joiner_start(n4, "TEST123") self.go(100) ns.thread_start(n4) + self.go(100) c = ns.counters() print('counters', c) From b182deca946738978caad4e62b8f37645f0c4475 Mon Sep 17 00:00:00 2001 From: Esko Dijk Date: Mon, 24 Jun 2024 09:17:33 +0200 Subject: [PATCH 36/36] [pylibs] narrow test topology to avoid spurious test fails. --- pylibs/unittests/test_border_router.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pylibs/unittests/test_border_router.py b/pylibs/unittests/test_border_router.py index a5556dc1..58d4f810 100755 --- a/pylibs/unittests/test_border_router.py +++ b/pylibs/unittests/test_border_router.py @@ -46,13 +46,13 @@ def testBorderRouterDistributesOmrPrefix(self): ns = self.ns ns.radiomodel = 'MIDisc' # force line topology - nid = ns.add('br', x=100, y=100) + ns.add('br', x=100, y=100) ns.go(10) - ns.add('router', x=300, y=100) + ns.add('router', x=250, y=100) ns.go(10) - ns.add('fed', x=500, y=100) + ns.add('fed', x=400, y=100) ns.go(80) ns.ping(3,1,addrtype='slaac',count=4) @@ -63,6 +63,5 @@ def testBorderRouterDistributesOmrPrefix(self): pings = ns.pings() self.assertTrue(len(pings) == 8) - if __name__ == '__main__': unittest.main()