Commit 9b172d39 authored by Yann Garcia's avatar Yann Garcia
Browse files

Add ICMP reply support

parent abf8e2e2
Loading
Loading
Loading
Loading

.vscode/settings.json

0 → 100644
+3 −0
Changes for .vscode/settings.json: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
{
    "cmake.ignoreCMakeListsMissing": true
}
 No newline at end of file
+124 −7
Changes for ttcn/AtsNGAP/NGAP_Steps.ttcn: 124 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
    import from LibGtpu_TypesAndValues all;
    import from LibGtpu_Templates all;
    import from LibGtpu_Functions all;
    import from LibNGAP_EncdecDeclarations all;
    import from UpperTesterPtcToMtcDefs all;
    import from UpperTesterFunctions all;

@@ -865,12 +866,16 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
         *          session (PX_TRANSPORT_LAYER_ADDRESS, PX_GTP_TEID). With PICS_USE_GTPU, the test system plays that
         *          UPF: each G-PDU received on N3_P meanwhile is checked (f_NGAP_gnb_check_uplink_gpdu), and the
         *          verdict is set to fail if one does not conform, or if none carries an echo request of the UE
         *          although the modem ran the pings. Echo requests are not answered (no downlink user data yet).
         *          although the modem ran the pings.
         *          With PX_GTPU_ANSWER_ECHO_REQUESTS, once the downlink tunnel endpoint of the gNB is known
         *          (f_NGAP_gnb_get_dl_gtp_tunnel), each echo request is answered at once with an echo reply in a
         *          downlink G-PDU (PDU Session Container DL PDU SESSION INFORMATION, QFI PX_QOS_FLOW_IDENTIFIER). The
         *          modem must then report replies received: the verdict is set to fail if none of them reached it.
         *          The UE stays in CM-CONNECTED meanwhile: no NGAP signalling is expected. If the gNB nevertheless
         *          asks for the release of the UE context (UE CONTEXT RELEASE REQUEST, e.g. user inactivity when the
         *          UE sends nothing), it is released (UE CONTEXT RELEASE COMMAND/COMPLETE) and the wait goes on:
         *          left unanswered, the request would end in an NG RESET from the gNB.
         * @note   Lost replies are not a failure. The UT dispatcher (NGAP_UtDispatcher) sets the verdict to inconc
         * @note   Without downlink replies, lost replies are not a failure. The UT dispatcher (NGAP_UtDispatcher) sets the verdict to inconc
         *         if the modem could not run the pings at all (e.g. the context of the data call is not usable by
         *         AT+QPING).
         * @return true if the modem ran the pings and, with PICS_USE_GTPU, the uplink user data was conformant.
@@ -879,6 +884,8 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
            var UT_COMMON_CNF v_cnf;
            var GtpuMessage v_gtpu;
            var integer v_echoRequests := 0; // Conformant G-PDUs carrying an echo request of the UE
            var integer v_echoReplies := 0;  // Echo replies sent in downlink G-PDUs
            var integer v_received;          // Echo replies the modem reports as received
            var boolean v_conformant := true;
            timer t_ping := int2float(PX_UT_PING_COUNT * PX_UT_PING_TIMEOUT) + 30.0;

@@ -909,7 +916,7 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
                    repeat;
                }
                [PICS_USE_GTPU] N3_P.receive(mw_gtpu_any) -> value v_gtpu {
                    fl_count_uplink_gpdu(v_gtpu, v_echoRequests, v_conformant);
                    fl_count_uplink_gpdu(v_gtpu, v_echoRequests, v_echoReplies, v_conformant);
                    repeat;
                }
                [] t_ping.timeout {
@@ -925,7 +932,7 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
            t_ping.start(1.0);
            alt {
                [] N3_P.receive(mw_gtpu_any) -> value v_gtpu {
                    fl_count_uplink_gpdu(v_gtpu, v_echoRequests, v_conformant);
                    fl_count_uplink_gpdu(v_gtpu, v_echoRequests, v_echoReplies, v_conformant);
                    repeat;
                }
                [] t_ping.timeout {
@@ -940,27 +947,137 @@ module NGAP_Steps { // TODO FSCOM f_xxx_UE_SecurityMode to be mmoved into NAG_NG
            if (v_conformant and (v_echoRequests > 0)) {
                log("*** " & __SCOPE__ & ": PASS: uplink user data forwarded by the gNB on N3 ***");
            }
            if (v_echoReplies > 0) {
                v_received := fl_ping_replies_received(v_cnf);
                log("*** " & __SCOPE__ & ": INFO: ", v_echoReplies, " echo reply(ies) sent on N3, ", v_received, " received by the UE ***");
                if (v_received == 0) {
                    log("*** " & __SCOPE__ & ": FAIL: the UE received none of the echo replies sent to the gNB on N3 ***");
                    setverdict(fail, "no downlink user data delivered by the gNB to the UE");
                    return false;
                }
                log("*** " & __SCOPE__ & ": PASS: downlink user data delivered by the gNB to the UE ***");
            }
            return v_cnf.Result and v_conformant and (v_echoRequests > 0);
        } // End of function f_NGAP_gnb_UE_ping

        /**
         * @brief  Check an uplink G-PDU (f_NGAP_gnb_check_uplink_gpdu) and update the counters of f_NGAP_gnb_UE_ping.
         * @brief  Check an uplink G-PDU (f_NGAP_gnb_check_uplink_gpdu), answer it if it is an echo request of the UE,
         *         and update the counters of f_NGAP_gnb_UE_ping.
         * @details The echo reply (f_ipv4_icmpEchoReply) is sent in a downlink G-PDU to the downlink tunnel endpoint
         *          of the gNB (vc_dlGtpTunnel), with PX_GTPU_ANSWER_ECHO_REQUESTS and once that endpoint is known.
         * @param  p_gtpu The received GTP-U message.
         * @param  p_echoRequests Incremented if it carries an echo request of the UE.
         * @param  p_echoReplies Incremented if an echo reply was sent.
         * @param  p_conformant Set to false if its GTP-U encapsulation does not conform.
         */
        function fl_count_uplink_gpdu(in GtpuMessage p_gtpu, inout integer p_echoRequests, inout boolean p_conformant) runs on gNBNGAPComponent {
        function fl_count_uplink_gpdu(
                                      in GtpuMessage p_gtpu,
                                      inout integer p_echoRequests,
                                      inout integer p_echoReplies,
                                      inout boolean p_conformant
                                      ) runs on gNBNGAPComponent {
            log(">>> fl_count_uplink_gpdu: p_gtpu: ", p_gtpu);
            log(">>> fl_count_uplink_gpdu: p_echoRequests: ", p_echoRequests);

            var boolean v_echoRequest;
            var octetstring v_reply;

            if (not f_NGAP_gnb_check_uplink_gpdu(p_gtpu, v_echoRequest)) {
                p_conformant := false;
            } else if (v_echoRequest) {
                return;
            }
            if (not v_echoRequest) {
                return;
            }
            p_echoRequests := p_echoRequests + 1;
            if (not PX_GTPU_ANSWER_ECHO_REQUESTS or not vc_dlGtpTunnelKnown) {
                return;
            }
            v_reply := f_ipv4_icmpEchoReply(p_gtpu.payload);
            if (v_reply == ''O) {
                return;
            }
            N3_P.send(m_gtpu_gPdu_dl(vc_dlGtpTunnel.gTP_TEID, PX_QOS_FLOW_IDENTIFIER, v_reply));
            p_echoReplies := p_echoReplies + 1;
        } // End of function fl_count_uplink_gpdu

        /**
         * @brief  Number of echo replies the modem reports as received, from the result of the PING request.
         * @details The upper tester ends its result with the summary of the modem, "... rcvd=<n> ...", when the modem
         *          gives one; otherwise each reply received is reported as a line "+QPING: 0,"<address>",...".
         * @param  p_cnf The confirmation of the PING request.
         * @return The number of replies received.
         */
        function fl_ping_replies_received(in UT_COMMON_CNF p_cnf) return integer {
            var charstring v_text := "";
            var integer v_count := 0;

            if (ispresent(p_cnf.ResultString)) {
                v_text := p_cnf.ResultString;
            }
            for (var integer i := 0; i + 5 <= lengthof(v_text); i := i + 1) {
                if (substr(v_text, i, 5) == "rcvd=") {
                    var charstring v_digits := "";
                    for (var integer j := i + 5; j < lengthof(v_text) and char2int(v_text[j]) >= char2int("0") and char2int(v_text[j]) <= char2int("9"); j := j + 1) {
                        v_digits := v_digits & v_text[j];
                    }
                    if (v_digits != "") {
                        return str2int(v_digits);
                    }
                }
            }
            for (var integer i := 0; i + 10 <= lengthof(v_text); i := i + 1) {
                if (substr(v_text, i, 10) == "+QPING: 0,") {
                    v_count := v_count + 1;
                }
            }
            return v_count;
        } // End of function fl_ping_replies_received

        /**
         * @brief  Get the downlink tunnel endpoint of the gNB for a PDU session from the PDU SESSION RESOURCE SETUP
         *         RESPONSE just received (vc_recvNGAP_PDU), and keep it in vc_dlGtpTunnel.
         * @details The PDU Session Resource Setup Response Transfer of the item for p_pduSessionId is decoded (aligned
         *          PER, TS 38.413 clause 9.3.4.2): its DL QoS Flow per TNL Information gives the GTP tunnel.
         * @param  p_pduSessionId The PDU session.
         * @return false if the response has no such item, or the tunnel cannot be decoded.
         */
        function f_NGAP_gnb_get_dl_gtp_tunnel(in PDUSessionID p_pduSessionId) runs on gNBNGAPComponent return boolean {
            var PDUSessionResourceSetupResponseTransfer v_transfer;
            var bitstring v_bs;

            vc_dlGtpTunnelKnown := false;
            if (not ischosen(vc_recvNGAP_PDU.successfulOutcome)) {
                return false;
            }
            for (var integer i := 0; i < lengthof(vc_recvNGAP_PDU.successfulOutcome.value_.pDUSessionResourceSetupResponse.protocolIEs); i := i + 1) {
                if (vc_recvNGAP_PDU.successfulOutcome.value_.pDUSessionResourceSetupResponse.protocolIEs[i].id == id_PDUSessionResourceSetupListSURes) {
                    var PDUSessionResourceSetupListSURes v_list := vc_recvNGAP_PDU.successfulOutcome.value_.pDUSessionResourceSetupResponse.protocolIEs[i].value_.pDUSessionResourceSetupListSURes;
                    for (var integer j := 0; j < lengthof(v_list); j := j + 1) {
                        if (v_list[j].pDUSessionID == p_pduSessionId) {
                            v_bs := oct2bit(v_list[j].pDUSessionResourceSetupResponseTransfer);
                            if (fx_dec_PDUSessionResourceSetupResponseTransfer(v_bs, v_transfer) != 0) {
                                log("f_NGAP_gnb_get_dl_gtp_tunnel: decoding failure: ", v_list[j].pDUSessionResourceSetupResponseTransfer);
                                return false;
                            }
                            if (not ischosen(v_transfer.dLQosFlowPerTNLInformation.uPTransportLayerInformation.gTPTunnel)) {
                                log("f_NGAP_gnb_get_dl_gtp_tunnel: no GTP tunnel: ", v_transfer);
                                return false;
                            }
                            vc_dlGtpTunnel := v_transfer.dLQosFlowPerTNLInformation.uPTransportLayerInformation.gTPTunnel;
                            vc_dlGtpTunnelKnown := true;
                            log("f_NGAP_gnb_get_dl_gtp_tunnel: downlink tunnel of the gNB: ",
                                f_ipv4_toString(bit2oct(vc_dlGtpTunnel.transportLayerAddress)), ", TEID ", vc_dlGtpTunnel.gTP_TEID,
                                " (G-PDUs are sent to the dst_ip of system.N3_gNB_1)");
                            return true;
                        }
                    }
                }
            }
            log("f_NGAP_gnb_get_dl_gtp_tunnel: no setup item for PDU session ", p_pduSessionId);
            return false;
        } // End of function f_NGAP_gnb_get_dl_gtp_tunnel

        /**
         * @brief  Check an uplink G-PDU the gNB forwarded on N3 for the PDU session of the UE (TS 29.281, TS 38.415).
         * @details The gNB is responsible for the GTP-U encapsulation, which must conform: a G-PDU, to the uplink
+3 −0
Changes for ttcn/AtsNGAP/NGAP_TCFunctions.ttcn: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -176,6 +176,9 @@ module NGAP_TCFunctions {
                    // With a real UE, the PDU session must also be usable from the UE: the upper tester reports
                    // the data call result once the modem has its IP address (inconc otherwise, see
                    // f_NGAP_gnb_UE_await_data_call)
                    if (PICS_USE_GTPU and v_setup and not f_NGAP_gnb_get_dl_gtp_tunnel(v_pduSessionId)) {
                        log("*** " & __SCOPE__ & ": INFO: downlink tunnel of the gNB unknown, no downlink user data ***");
                    }
                    if (PICS_USE_AT_COMMANDS and v_setup) {
                        if (not f_NGAP_gnb_UE_await_data_call()) {
                            log("*** " & __SCOPE__ & ": INCONC: the PDU session is set up, but the UE data call is not connected. ***");
+54 −0
Changes for ttcn/LibGtpu/lib/LibGtpu_Functions.ttcn: 54 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -125,4 +125,58 @@ module LibGtpu_Functions {
        return v_result;
    } // End of function f_ipv4_toString

    /**
     * @brief  Internet checksum (IETF RFC 1071): one's complement of the one's complement sum of 16-bit words.
     * @param  p_data The data, with its checksum field set to zero; padded with a zero octet if its length is odd.
     * @return The two octets of the checksum.
     */
    function f_inet_checksum(in octetstring p_data) return octetstring {
        var octetstring v_data := p_data;
        var integer v_sum := 0;

        if (lengthof(v_data) mod 2 == 1) {
            v_data := v_data & '00'O;
        }
        for (var integer i := 0; i < lengthof(v_data); i := i + 2) {
            v_sum := v_sum + oct2int(substr(v_data, i, 2));
        }
        while (v_sum > 65535) {
            v_sum := (v_sum mod 65536) + (v_sum / 65536);
        }
        return int2oct(65535 - v_sum, 2);
    } // End of function f_inet_checksum

    /**
     * @brief  Build the ICMP echo reply to an IPv4 ICMP echo request (IETF RFC 792), as its destination would.
     * @details The IPv4 header is copied (options included) with the source and destination addresses swapped, the
     *          TTL set to p_ttl and the header checksum recomputed. The ICMP message keeps the identifier, the sequence
     *          number and the data of the request, with the type 0 (echo reply) and the checksum recomputed.
     * @param  p_request The echo request, a complete IPv4 packet (see f_ipv4_getHeader).
     * @param  p_ttl The TTL of the reply.
     * @return The echo reply, or ''O if p_request is not an IPv4 ICMP echo request.
     */
    function f_ipv4_icmpEchoReply(in octetstring p_request, in integer p_ttl := 64) return octetstring {
        var octetstring v_source;
        var octetstring v_destination;
        var integer v_protocol;
        var octetstring v_icmp;
        var octetstring v_header;
        var integer v_ihl;

        if (not f_ipv4_getHeader(p_request, v_source, v_destination, v_protocol, v_icmp)
            or (v_protocol != 1) or (lengthof(v_icmp) < 8) or (v_icmp[0] != '08'O)) {
            return ''O;
        }
        v_ihl := (oct2int(p_request[0]) mod 16) * 4;
        // Version/IHL, DSCP/ECN, total length, identification, flags/fragment offset | TTL | protocol | checksum 0 |
        // source := destination | destination := source | options
        v_header := substr(p_request, 0, 8) & int2oct(p_ttl, 1) & p_request[9] & '0000'O & v_destination & v_source
                    & substr(p_request, 20, v_ihl - 20);
        v_header := substr(v_header, 0, 10) & f_inet_checksum(v_header) & substr(v_header, 12, v_ihl - 12);
        // Type 0 (echo reply) | code | checksum 0 | identifier, sequence number and data of the request
        v_icmp := '00'O & v_icmp[1] & '0000'O & substr(v_icmp, 4, lengthof(v_icmp) - 4);
        v_icmp := substr(v_icmp, 0, 2) & f_inet_checksum(v_icmp) & substr(v_icmp, 4, lengthof(v_icmp) - 4);
        return v_header & v_icmp;
    } // End of function f_ipv4_icmpEchoReply

} // End of module LibGtpu_Functions
+4 −0
Changes for ttcn/LibNGAP/lib/LibNGAP_Pixits.ttcn: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -760,6 +760,10 @@ module LibNGAP_Pixits {
    /** @desc Time, in seconds, the real UE waits for each echo reply (1..255); see f_NGAP_gnb_UE_ping */
    modulepar integer    PX_UT_PING_TIMEOUT := 4;

    /** @desc Whether the test system, playing the UPF (PICS_USE_GTPU), answers the echo requests of the ping of the real UE
     *        with echo replies sent in downlink G-PDUs to the gNB; see f_NGAP_gnb_UE_ping */
    modulepar boolean    PX_GTPU_ANSWER_ECHO_REQUESTS := true;

    /** @desc Whether, after its restart (AT+CFUN=1,1), the real UE (AT commands) is set to full functionality (AT+CFUN=1) and
     *        forced onto PX_UT_PLMN_MCC/PX_UT_PLMN_MNC (AT+COPS=1); see f_NGAP_gnb_UE_Register.
     *        false: it is only restarted, and registers by itself on the PLMN it selects. */
Loading