cla_mtcp: Enable ION interoperability

ION uses a 32-bit unsigned integer length field as header for the MTCP
packets. To be compatible with that, we add a compile-time setting
`CLA_MTCP_ION_STCP_COMPATIBILITY`.

Closes: #202

Signed-off-by: Felix Walter <felix.walter@d3tn.com>
This commit is contained in:
Felix Walter 2026-02-03 10:37:57 +01:00
parent 5288e71691
commit 81b7060ba5
7 changed files with 191 additions and 5 deletions

View file

@ -441,6 +441,16 @@ ion-dtn-bpv7-interoperability-test:
- source /ud3tn_venv/bin/activate
- bash test/ion_interoperability/minimal_forwarding_test/run.sh 7 dtn
ion-bpv7-mtcp-interoperability-test:
stage: functional_test
when: always
image: registry.gitlab.com/d3tn/ud3tn-docker-images/ion-interop:4.1.3s
script:
- bash test/dockerfiles/prepare_for_test.sh . /ud3tn_build sanitize=yes "CFLAGS=-DCLA_MTCP_ION_STCP_COMPATIBILITY=1"
- cd /ud3tn_build
- source /ud3tn_venv/bin/activate
- bash test/ion_interoperability/mtcp_minimal_forwarding_test/run.sh
ione-dtn-bpv7-interoperability-test:
extends: ion-dtn-bpv7-interoperability-test
image: registry.gitlab.com/d3tn/ud3tn-docker-images/ione-interop:1.1.0-1

View file

@ -331,9 +331,27 @@ size_t mtcp_forward_to_specific_parser(struct cla_link *link,
size_t result = 0;
// Decode MTCP CBOR byte string header if not done already
if (!(mtcp_link->mtcp_parser.flags & PARSER_FLAG_DATA_SUBPARSER))
return mtcp_parser_parse(&mtcp_link->mtcp_parser,
buffer, length);
if (!(mtcp_link->mtcp_parser.flags & PARSER_FLAG_DATA_SUBPARSER)) {
// Override MTCP parser usage for ION "simple TCP" compatibility.
if (CLA_MTCP_ION_STCP_COMPATIBILITY) {
if (length < 4)
return 0; // Need more bytes!
const uint32_t payload_len = (
(uint32_t)buffer[0] << 24 |
(uint32_t)buffer[1] << 16 |
(uint32_t)buffer[2] << 8 |
(uint32_t)buffer[3]
);
mtcp_link->mtcp_parser.flags = PARSER_FLAG_DATA_SUBPARSER;
mtcp_link->mtcp_parser.next_bytes = payload_len;
return 4;
}
return mtcp_parser_parse(&mtcp_link->mtcp_parser, buffer, length);
}
// We do not allow to parse more than the stated length...
if (length > mtcp_link->mtcp_parser.next_bytes)
@ -551,8 +569,17 @@ enum cla_begin_packet_result mtcp_begin_packet(struct cla_link *link,
enum { BUFFER_SIZE = 9U }; // max. for uint64_t
uint8_t buffer[BUFFER_SIZE];
size_t hdr_len;
const size_t hdr_len = mtcp_encode_header(buffer, BUFFER_SIZE, length);
if (CLA_MTCP_ION_STCP_COMPATIBILITY) {
buffer[0] = (uint8_t)((length >> 24) & 0xFF);
buffer[1] = (uint8_t)((length >> 16) & 0xFF);
buffer[2] = (uint8_t)((length >> 8) & 0xFF);
buffer[3] = (uint8_t)(length & 0xFF);
hdr_len = 4;
} else {
hdr_len = mtcp_encode_header(buffer, BUFFER_SIZE, length);
}
if (tcp_send_all(tcp_link->connection_socket, buffer, hdr_len) == -1) {
LOG_WARN("MTCP: Error during sending. Data discarded.");

View file

@ -171,6 +171,9 @@
# setting this to zero may lead to dead connections being used for some time.
#CPPFLAGS += -DCLA_MTCP_CLOSE_AFTER_CONTACT=1
# Use the ION STCP header format (a 32-bit unsigned integer length field).
#CPPFLAGS += -DCLA_MTCP_ION_STCP_COMPATIBILITY=0
# The size of the chunked-read buffer of the CLA RX task.
#CPPFLAGS += -DCLA_RX_BUFFER_SIZE=64

View file

@ -15,6 +15,11 @@
#define CLA_MTCP_CLOSE_AFTER_CONTACT 1
#endif // CLA_MTCP_CLOSE_AFTER_CONTACT
// Use the ION STCP header format (a 32-bit unsigned integer length field)
#ifndef CLA_MTCP_ION_STCP_COMPATIBILITY
#define CLA_MTCP_ION_STCP_COMPATIBILITY 0
#endif // CLA_MTCP_ION_STCP_COMPATIBILITY
struct cla_config *mtcp_create(
const char *const options[], const size_t option_count,
const struct bundle_agent_interface *bundle_agent_interface);

View file

@ -7,7 +7,7 @@ set -euo pipefail
# It is recommended to run this script in a Docker image based on the
# `ion-interop` Dockerfile provided with uD3TN as follows:
# $ docker run -it -v "$(pwd):/ud3tn" ud3tn-ion-interop:<tag> \
# bash -c '/ud3tn/test/ion_interoperability/prepare_for_test.sh /ud3tn /ud3tn_build && cd /ud3tn_build && source /ud3tn_venv/bin/activate && test/ion_interoperability/minimal_forwarding_test/run.sh 7'
# bash -c '/ud3tn/test/dockerfiles/prepare_for_test.sh /ud3tn /ud3tn_build && cd /ud3tn_build && source /ud3tn_venv/bin/activate && test/ion_interoperability/minimal_forwarding_test/run.sh 7 dtn'
BP_VERSION=$1

View file

@ -0,0 +1,49 @@
## begin ionadmin
# Initialization command
1 1 ''
# Start ION
s
# Add contacts
a contact +1 +3600 1 1 100000
a contact +1 +3600 2 1 100000
a contact +1 +3600 1 3 100000
# Assign ranges
a range +1 +3600 1 1 1
a range +1 +3600 2 1 1
a range +1 +3600 1 3 1
# Assign production/consumption rates (dummy)
m production 100000
m consumption 100000
## end ionadmin
## begin ionsecadmin
1
## end ionsecadmin
## begin bpadmin
1
# watch: print bundle actions, see manpage
# you should see "ybc" in the test; you should not see '~' or '!'
w 1
a scheme ipn 'ipnfw' 'ipnadminep'
a endpoint ipn:1.0 q
a endpoint ipn:1.1 q
# add the stcp convergence layer and in/outducts
a protocol stcp 1400 100 -1
a induct stcp 0.0.0.0:7001 stcpcli
a outduct stcp 127.0.0.1:7003 stcpclo
# add egress plan
a plan ipn:3.0
a planduct ipn:3.0 stcp 127.0.0.1:7003
# start daemons
s
## end bpadmin

View file

@ -0,0 +1,92 @@
#!/bin/bash
# SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0
set -euo pipefail
# uD3TN + ION Interoperability test
# It is recommended to run this script in a Docker image based on the
# `ion-interop` Dockerfile provided with uD3TN as follows:
# $ docker run -it -v "$(pwd):/ud3tn" ud3tn-ion-interop:<tag> \
# bash -c '/ud3tn/test/dockerfiles/prepare_for_test.sh /ud3tn /ud3tn_build "CFLAGS=-DCLA_MTCP_ION_STCP_COMPATIBILITY=1" && cd /ud3tn_build && source /ud3tn_venv/bin/activate && test/ion_interoperability/mtcp_minimal_forwarding_test/run.sh'
BP_VERSION=7
ION_SCRIPT_NAME=ipn.rc
ION_EID=ipn:1.0
UD3TN1_EID=ipn:2.0
UD3TN2_EID=ipn:3.0
SOURCE_AGENTID=1
SINK_AGENTID=1
SINK_EID=ipn:3.1
ION_SINK_EID=ipn:1.1
# This assumes you are running the command from within the "ud3tn" directory.
UD3TN_DIR="$(pwd)"
cd "$UD3TN_DIR"
exit_handler() {
cd "$UD3TN_DIR"
kill -TERM $UD3TN1_PID || true
kill -TERM $UD3TN2_PID || true
echo "Terminating ION (timeout 20s)..."
(ionstop || true) &
sleep 20
echo
echo ">>> ION LOGFILE"
cat "ion.log" || true
echo
echo ">>> uD3TN1 LOGFILE"
cat "/tmp/ud3tn1.log" || true
echo
echo ">>> uD3TN2 LOGFILE"
cat "/tmp/ud3tn2.log" || true
echo
echo "Waiting for uD3TN to exit gracefully - if it doesn't, check for sanitizer warnings."
wait $UD3TN1_PID
wait $UD3TN2_PID
}
rm -f ion.log
rm -f /tmp/ion*log /tmp/ud3tn*.log
# Start first uD3TN instance (uD3TN1)
"$UD3TN_DIR/build/posix/ud3tn" -s "$UD3TN_DIR/ud3tn1.socket" -S "$UD3TN_DIR/ud3tn1.aap2.socket" -c "sqlite:ud3tn1.sqlite;mtcp:127.0.0.1,7002" -b $BP_VERSION -e "$UD3TN1_EID" -L 4 --allow-remote-config > /tmp/ud3tn1.log 2>&1 &
UD3TN1_PID=$!
# Start second uD3TN instance (uD3TN2)
"$UD3TN_DIR/build/posix/ud3tn" -s "$UD3TN_DIR/ud3tn2.socket" -S "$UD3TN_DIR/ud3tn2.aap2.socket" -c "sqlite:ud3tn2.sqlite;mtcp:127.0.0.1,7003" -b $BP_VERSION -e "$UD3TN2_EID" -L 4 --allow-remote-config > /tmp/ud3tn2.log 2>&1 &
UD3TN2_PID=$!
# Start ION instance
ulimit -n 512 # fix behavior on systems with a huge limit (e.g. if the container runtime does not change the kernel default), see: #121
ionstart -I test/ion_interoperability/mtcp_minimal_forwarding_test/$ION_SCRIPT_NAME
# UD3TN1_PID and UD3TN2_PID must be defined for this
trap exit_handler EXIT
# Configure a contact to ION in uD3TN1 which allows to reach uD3TN2
sleep 0.5
aap2-configure-link --socket "$UD3TN_DIR/ud3tn1.aap2.socket" "$ION_EID" mtcp:127.0.0.1:7001
aap2-configure-link --socket "$UD3TN_DIR/ud3tn1.aap2.socket" "$UD3TN2_EID" mtcp:127.0.0.1:7001
# Send a bundle to uD3TN1, addressed to uD3TN2
PAYLOAD="THISISTHEBUNDLEPAYLOAD"
(sleep 0.5 && aap2-send --socket "$UD3TN_DIR/ud3tn1.aap2.socket" --agentid "$SOURCE_AGENTID" "$SINK_EID" "$PAYLOAD") &
timeout -v 10 stdbuf -oL aap2-receive --socket "$UD3TN_DIR/ud3tn2.aap2.socket" --agentid "$SINK_AGENTID" --count 1 --verify-pl "$PAYLOAD" --newline -vv
# Test forwarding from ION directly
(sleep 0.5 && bpsource "$SINK_EID" "$PAYLOAD") &
timeout -v 10 stdbuf -oL aap2-receive --socket "$UD3TN_DIR/ud3tn2.aap2.socket" --agentid "$SINK_AGENTID" --count 1 --verify-pl "$PAYLOAD" --newline -vv
(sleep 0.5 && aap2-send --socket "$UD3TN_DIR/ud3tn1.aap2.socket" --agentid "$SOURCE_AGENTID" "$ION_SINK_EID" "$PAYLOAD") &
timeout -v 10 bprecvfile "$ION_SINK_EID" 1
RECEIVED_PAYLOAD="$(cat testfile1)"
echo "bprecvfile: $RECEIVED_PAYLOAD"
if [[ "$RECEIVED_PAYLOAD" != "$PAYLOAD" ]]; then
echo "Received payload does not match: $RECEIVED_PAYLOAD"
exit 1
fi