/* NS Provider for NS/UDP/IP * (C) 2020-2021 Harald Welte * contributions by sysmocom - s.f.m.c. GmbH * All rights reserved. * * Released under the terms of GNU General Public License, Version 2 or * (at your option) any later version. * * SPDX-License-Identifier: GPL-2.0-or-later */ /* This provider can be operated in two modes: * * 1) the "classic" mode, where - similar to the NS_Provider_FR - there is * only one NSVC per provider. In this mode, the "NSE" port is used to * exchange data with the next higher level component, such as a NSVC_CT * or a RAW_NS_CT. * * 2) the new "endpoint" mode, where one provider can host a number of different * NSVCs. This is needed in most non-trivial IP-SNS scenarios. The 'NSE' * port of this component is no longer used. Instead, there is a NSVC port * array, one of which will be used for each NSVC. The NSVCs are dynamically * added and removed via the PROC procedure port, controlled by NS_CT. */ module NS_Provider_IPL4 { import from Misc_Helpers all; import from NS_Emulation all; import from RAW_NS all; import from NS_Types all; import from IPL4asp_Types all; import from IPL4asp_PortType all; /* maximum number of NS-VCs within one Provider (== IP endpoint) */ private const integer NUM_MAX_NSVC := 16; type component NS_Provider_IPL4_CT extends NS_Provider_CT { /* down-facing port towards IPL4asp to IUT */ port IPL4asp_PT IPL4; var integer g_conn_id := -1; /* per-NSVC ports and state */ port NS_PROVIDER_PT NSVC[NUM_MAX_NSVC]; var boolean g_nsvc_bound[NUM_MAX_NSVC]; var PerNsvcState g_nsvc[NUM_MAX_NSVC]; /* management port via which */ port NSPIP_PROC_PT PROC; }; type record PerNsvcState { charstring remote_ip, PortNumber remote_port, NSVC_CT vc_nsvc }; signature NSPIP_add_nsvc(charstring remote_ip, PortNumber remote_port, NSVC_CT vc_nsvc) return integer; signature NSPIP_del_nsvc(charstring remote_ip, PortNumber remote_port) return integer; type port NSPIP_PROC_PT procedure { inout NSPIP_add_nsvc, NSPIP_del_nsvc; } with { extension "internal" }; /* add a new NSVC to the provider */ private function f_nsvc_add(PerNsvcState nsvc) runs on NS_Provider_IPL4_CT return integer { for (var integer i := 0; i < sizeof(g_nsvc); i := i+1) { if (g_nsvc_bound[i] == false) { g_nsvc[i] := nsvc; g_nsvc_bound[i] := true; if (isbound(nsvc.vc_nsvc) and nsvc.vc_nsvc != null) { connect(self:NSVC[i], nsvc.vc_nsvc:NSCP); NSVC[i].send(NS_Provider_Evt:{link_status := NS_PROV_LINK_STATUS_UP}); } return i; } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("Overflow of g_nsvc array")); return -1; } private function f_nsvc_del(PerNsvcState nsvc) runs on NS_Provider_IPL4_CT return integer { for (var integer i := 0; i < sizeof(g_nsvc); i := i+1) { if (g_nsvc_bound[i] and g_nsvc[i].remote_ip == nsvc.remote_ip and g_nsvc[i].remote_port == nsvc.remote_port) { g_nsvc[i] := { remote_ip := -, remote_port := -, vc_nsvc := null } g_nsvc_bound[i] := false; NSVC[i].send(NS_Provider_Evt:{link_status := NS_PROV_LINK_STATUS_DOWN}); if (isbound(g_nsvc[i].vc_nsvc) and g_nsvc[i].vc_nsvc != null) { disconnect(self:NSVC[i], nsvc.vc_nsvc:NSCP); } return i; } } Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, log2str("attempt to delete unknown NSVC")); return -1; } private function f_get_nsvc_idx(charstring remote_ip, PortNumber remote_port) runs on NS_Provider_IPL4_CT return integer { for (var integer i := 0; i < sizeof(g_nsvc); i := i+1) { if (g_nsvc_bound[i] and g_nsvc[i].remote_ip == remote_ip and g_nsvc[i].remote_port == remote_port) { return i; } } return -1; } function main(NSVCConfiguration config, NSConfiguration nsconfig, charstring id) runs on NS_Provider_IPL4_CT { for (var integer i := 0; i < sizeof(g_nsvc); i := i+1) { g_nsvc[i].vc_nsvc := null; g_nsvc_bound[i] := false; } /* in order to support any number of NSVC on this endpoiint, we only bind the socket * to its local ip/port, but do not connect it to the remote peer provided in 'config'. */ map(self:IPL4, system:IPL4); var Result res := f_IPL4_listen(IPL4, config.provider.ip.local_ip, config.provider.ip.local_udp_port, { udp := {}}); if (not ispresent(res.connId)) { setverdict(fail, "Could not connect NS UDP socket ", config.provider.ip); mtc.stop; } g_conn_id := res.connId; if (NSE.checkstate("Connected")) { NSE.send(NS_Provider_Evt:{link_status := NS_PROV_LINK_STATUS_UP}); } /* transceive between user-facing port and UDP socket */ while (true) { var ASP_RecvFrom rx_rf; var PDU_NS rx_pdu; var integer rx_idx; var charstring remote_ip; var PortNumber remote_port; var NSVC_CT vc_nsvc; var NS_CT vc_caller; alt { [] IPL4.receive(ASP_RecvFrom:?) -> value rx_rf { /* we have to resolve the NS-VC based on the remote peer */ var integer nsvc_idx := f_get_nsvc_idx(rx_rf.remName, rx_rf.remPort); if (nsvc_idx == -1) { /* backwards compatibility; if there's no NSVC, send to NSE port */ NSE.send(dec_PDU_NS(rx_rf.msg)); } else { /* endpoint mode; send to the per-NSVC component via NSVC port */ NSVC[nsvc_idx].send(dec_PDU_NS(rx_rf.msg)); } } [] IPL4.receive(ASP_ConnId_ReadyToRelease:?) { } [] IPL4.receive(ASP_Event:?) { } [] any from NSVC.receive(PDU_NS:?) -> value rx_pdu @index value rx_idx { /* we can use the port array index directly into the g_nsvc array in order * to resolve the IP + port of the remote peer to which to send */ var ASP_SendTo tx := { connId := g_conn_id, remName := g_nsvc[rx_idx].remote_ip, remPort := g_nsvc[rx_idx].remote_port, proto := { udp := {} }, msg := enc_PDU_NS(rx_pdu) }; IPL4.send(tx); } [] NSE.receive(PDU_NS:?) -> value rx_pdu { /* backwards compatibility: If user uses the NSE port, use the destination * provided during main() initialization */ var ASP_SendTo tx := { connId := g_conn_id, remName := config.provider.ip.remote_ip, remPort := config.provider.ip.remote_udp_port, proto := { udp := {} }, msg := enc_PDU_NS(rx_pdu) }; IPL4.send(tx); } /* procedure port to add/remove NSVCs from this provider */ [] PROC.getcall(NSPIP_add_nsvc:{?,?,?}) -> param (remote_ip, remote_port, vc_nsvc) sender vc_caller { var integer idx; idx := f_nsvc_add(PerNsvcState:{remote_ip, remote_port, vc_nsvc}); PROC.reply(NSPIP_add_nsvc:{remote_ip, remote_port, vc_nsvc} value idx) to vc_caller; } [] PROC.getcall(NSPIP_del_nsvc:{?,?}) -> param (remote_ip, remote_port) sender vc_caller { var integer idx; idx := f_nsvc_del(PerNsvcState:{remote_ip, remote_port}); PROC.reply(NSPIP_del_nsvc:{remote_ip, remote_port} value idx) to vc_caller; } } /* alt */ } /* while */ } /* main */ function f_nspip_add_nsvc(NS_Provider_IPL4_CT vc_ipep, charstring remote_ip, PortNumber remote_port, NSVC_CT vc_nsvc) runs on NS_CT { var integer idx := -1; NSPIP_PROC.call(NSPIP_add_nsvc:{remote_ip, remote_port, vc_nsvc}) to vc_ipep { [] NSPIP_PROC.getreply(NSPIP_add_nsvc:{?,?,?}) -> value idx; } } function f_nspip_add_nsvc2(NS_Provider_IPL4_CT vc_ipep, charstring remote_ip, PortNumber remote_port) runs on RAW_NS_CT return integer { var integer idx := -1; NSPIP_PROC.call(NSPIP_add_nsvc:{remote_ip, remote_port, null}) to vc_ipep { [] NSPIP_PROC.getreply(NSPIP_add_nsvc:{?,?,?}) -> value idx; } return idx; } } /* module */