This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
laforge gerrit-no-reply at lists.osmocom.orglaforge has submitted this change. ( https://gerrit.osmocom.org/c/libosmocore/+/21243 ) Change subject: ns2: add support for frame relay ...................................................................... ns2: add support for frame relay Add support for frame relay over dahdi hdlc device. It's supporting lmi by q933 and supports both SGSN and BSS. Change-Id: Id3b49f93d33c271f77cd9c9db03cde6b727a4d30 --- M include/Makefile.am A include/osmocom/gprs/frame_relay.h M include/osmocom/gprs/gprs_ns2.h M src/gb/Makefile.am A src/gb/frame_relay.c M src/gb/gprs_ns2.c A src/gb/gprs_ns2_fr.c M src/gb/gprs_ns2_internal.h M src/gb/gprs_ns2_vty.c M src/gb/libosmogb.map 10 files changed, 1,813 insertions(+), 23 deletions(-) Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved diff --git a/include/Makefile.am b/include/Makefile.am index 44ff378..3173290 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -60,6 +60,7 @@ osmocom/ctrl/control_cmd.h \ osmocom/ctrl/control_if.h \ osmocom/ctrl/ports.h \ + osmocom/gprs/frame_relay.h \ osmocom/gprs/gprs_bssgp.h \ osmocom/gprs/gprs_bssgp_bss.h \ osmocom/gprs/gprs_msgb.h \ diff --git a/include/osmocom/gprs/frame_relay.h b/include/osmocom/gprs/frame_relay.h new file mode 100644 index 0000000..b382ea8 --- /dev/null +++ b/include/osmocom/gprs/frame_relay.h @@ -0,0 +1,136 @@ +/*! \file frame_relay.h */ + +/* (C) 2020 Harald Welte <laforge at gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis at fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/utils.h> + +#include <stdint.h> + +struct osmo_tdef; +struct msgb; + +enum osmo_fr_role { + FR_ROLE_USER_EQUIPMENT, + FR_ROLE_NETWORK_EQUIPMENT, +}; + +extern const struct value_string osmo_fr_role_names[]; + +static inline const char *osmo_fr_role_str(enum osmo_fr_role role) { + return get_value_string(osmo_fr_role_names, role); +} + +struct osmo_fr_network { + struct llist_head links; + + unsigned int n391; /* full status polling counter */ + unsigned int n392; /* error threshold */ + unsigned int n393; /* monitored events count */ + + struct osmo_tdef *T_defs; /* T391, T392 */ +}; + +struct osmo_fr_dlc; + +/* Frame Relay Link */ +struct osmo_fr_link { + /* list in osmo_fr_network.links */ + struct llist_head list; + struct osmo_fr_network *net; + enum osmo_fr_role role; + /* human-readable name */ + const char *name; + + /* value of the last received send sequence number field in the + * link integrity verification information element */ + uint8_t last_rx_seq; + + /* value of the send sequence number field of the last link + * integrity verification information element sent */ + uint8_t last_tx_seq; + + struct osmo_timer_list t391; + struct osmo_timer_list t392; + + unsigned int polling_count; + unsigned int err_count; + unsigned int succeed; + /* the type of the last status enquiry */ + uint8_t expected_rep; + bool state; + + /* list of data link connections at this link */ + struct llist_head dlc_list; + + int (*unknown_dlc_rx_cb)(void *cb_data, struct msgb *msg); + void *unknown_dlc_rx_cb_data; + + int (*tx_cb)(void *data, struct msgb *msg); + void *tx_cb_data; +}; + +/* Frame Relay Data Link Connection */ +struct osmo_fr_dlc { + /* entry in fr_link.dlc_list */ + struct llist_head list; + struct osmo_fr_link *link; + + uint16_t dlci; + + /* is this DLC marked active for traffic? */ + bool active; + /* was this DLC newly added? */ + bool add; + /* is this DLC about to be destroyed */ + bool del; + + /* the local state needs to be transfered to the + * UE. The NET must wait until the UE confirms it implicited by a seq number check */ + bool state_send; + + int (*rx_cb)(void *cb_data, struct msgb *msg); + void *rx_cb_data; +}; + +/* allocate a frame relay network */ +struct osmo_fr_network *osmo_fr_network_alloc(void *ctx); + +/* allocate a frame relay link in a given network */ +struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name); + +/* free a frame link in a given network */ +void osmo_fr_link_free(struct osmo_fr_link *link); + +/* allocate a data link connectoin on a given framerelay link */ +struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci); +void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc); + +struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci); + +int osmo_fr_rx(struct msgb *msg); +int osmo_fr_tx_dlc(struct msgb *msg); diff --git a/include/osmocom/gprs/gprs_ns2.h b/include/osmocom/gprs/gprs_ns2.h index 3b47b3c..dd4f4d5 100644 --- a/include/osmocom/gprs/gprs_ns2.h +++ b/include/osmocom/gprs/gprs_ns2.h @@ -8,9 +8,11 @@ #include <osmocom/core/prim.h> #include <osmocom/gprs/protocol/gsm_08_16.h> +#include <osmocom/gprs/frame_relay.h> struct osmo_sockaddr; struct osmo_sockaddr_str; +struct osmo_fr_network; struct gprs_ns2_inst; struct gprs_ns2_nse; @@ -146,6 +148,23 @@ const struct osmo_sockaddr *sockaddr); void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode); +/* FR VL driver */ +struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif( + struct gprs_ns2_inst *nsi, + const char *netif); +const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind); +int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, + const char *netif, + struct osmo_fr_network *fr_network, + enum osmo_fr_role fr_role, + struct gprs_ns2_vc_bind **result); +int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind); +struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind, uint16_t dlci); +struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind, + uint16_t nsei, + uint16_t nsvci, + uint16_t dlci); + /* create a VC connection */ struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind, const struct osmo_sockaddr *remote, @@ -188,6 +207,7 @@ int dscp, struct gprs_ns2_vc_bind **result); int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind); +uint16_t gprs_ns2_fr_nsvc_dlci(struct gprs_ns2_vc *nsvc); struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse( struct gprs_ns2_nse *nse, diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am index 65c3552..41b6c6d 100644 --- a/src/gb/Makefile.am +++ b/src/gb/Makefile.am @@ -21,9 +21,9 @@ libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c gprs_ns_sns.c \ gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \ gprs_bssgp_bss.c \ - gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \ + gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_fr.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \ gprs_ns2_message.c gprs_ns2_vty.c \ - common_vty.c + common_vty.c frame_relay.c endif EXTRA_DIST = libosmogb.map diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c new file mode 100644 index 0000000..f890209 --- /dev/null +++ b/src/gb/frame_relay.c @@ -0,0 +1,966 @@ +/*! \file frame_relay.c + * Implement frame relay/PVC by Q.933 + */ +/* (C) 2020 Harald Welte <laforge at gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis at fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <errno.h> + +#include <osmocom/gprs/frame_relay.h> +#include <osmocom/core/endian.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> + +#include <osmocom/gsm/tlv.h> + +#define LOGPFRL(frl, lvl, fmt, args ...) \ + LOGP(DFR, lvl, "%s: " fmt, (frl)->name, ## args) + +#define DFR DLNS + +/* Table 4-2/Q.931 */ +enum q931_msgtype { + /* Call establishment message */ + Q931_MSGT_ALERTING = 0x01, + Q931_MSGT_CALL_PROCEEDING = 0x02, + Q931_MSGT_CONNECT = 0x07, + Q931_MSGT_CONNECT_ACK = 0x0f, + Q931_MSGT_PROGRESS = 0x03, + Q931_MSGT_SETUP = 0x05, + Q931_MSGT_SETUP_ACK = 0x0d, + /* Call information phase message */ + Q931_MSGT_RESUME = 0x26, + Q931_MSGT_RESUME_ACK = 0x2e, + Q931_MSGT_RESUME_REJ = 0x22, + Q931_MSGT_SUSPEND = 0x25, + Q931_MSGT_SUSPEND_ACK = 0x2d, + Q931_MSGT_USER_INFO = 0x20, + /* Call clearing message */ + Q931_MSGT_DISCONNECT = 0x45, + Q931_MSGT_RELEASE = 0x4d, + Q931_MSGT_RELEASE_COMPLETE = 0x5a, + Q931_MSGT_RESTART = 0x46, + Q931_MSGT_RESTART_ACK = 0x4e, + /* Miscellaneous messages */ + Q931_MSGT_SEGMENT = 0x60, + Q931_MSGT_CONGESTION_CONTROL = 0x79, + Q931_MSGT_IFORMATION = 0x7b, + Q931_MSGT_NOTIFY = 0x6e, + Q931_MSGT_STATUS = 0x7d, + Q931_MSGT_STATUS_ENQUIRY = 0x75, +}; + + +/* Figure A.1/Q.933 Report type information element */ +enum q933_type_of_report { + Q933_REPT_FULL_STATUS = 0x00, + Q933_REPT_LINK_INTEGRITY_VERIF = 0x01, + Q933_REPT_SINGLE_PVC_ASYNC_STS = 0x02, +}; + +/* Q.933 Section A.3 */ +enum q933_iei { + Q933_IEI_REPORT_TYPE = 0x51, + Q933_IEI_LINK_INT_VERIF = 0x53, + Q933_IEI_PVC_STATUS = 0x57, +}; + +/* Q.933 Section A.3.3 */ +enum q933_pvc_status { + Q933_PVC_STATUS_DLC_ACTIVE = 0x02, + Q933_PVC_STATUS_DLC_DELETE = 0x04, + Q933_PVC_STATUS_DLC_NEW = 0x08, +}; + + + +#define LAPF_UI 0x03 /* UI control word */ +#define Q931_PDISC_CC 0x08 /* protocol discriminator */ +#define LMI_Q933A_CALLREF 0x00 /* NULL call-ref */ + +/* LMI DLCI values */ +#define LMI_Q933A_DLCI 0 /* Q.933A DLCI */ +#define LMI_CISCO_DLCI 1023 /* Cisco DLCI */ + +/* maximum of supported */ +#define MAX_SUPPORTED_PVC 10 + +/* TODO: add counters since good connection */ + +/* Message header of the L3 payload of a Q.933 Annex A message */ +struct q933_a_hdr { + uint8_t prot_disc; + uint8_t call_ref; + uint8_t msg_type; +} __attribute__((packed)); + +/* Value part of the Q.933 Annex A.3.3 IE */ +struct q933_a_pvc_sts { + uint8_t dlci_msb:6, + spare:1, + ext0:1; + uint8_t space1:3, + dlci_lsb:4, + ext1:1; + uint8_t reserved:1, + active:1, + delete:1, + new:1, + spare2:3, + ext2:1; + +} __attribute__((packed)); + +/* RX Message: 14 [ 00 01 03 08 00 75 95 01 01 00 03 02 01 00 ] */ +/* RX Message: 13 [ 00 01 03 08 00 75 51 01 00 53 02 01 00 ] */ + +const struct value_string osmo_fr_role_names[] = { + { FR_ROLE_USER_EQUIPMENT, "USER" }, + { FR_ROLE_NETWORK_EQUIPMENT, "NETWORK" }, + { 0, NULL } +}; + +/* Table A.4/Q.933 */ +struct osmo_tdef fr_tdefs[] = { + { + .T=391, + .default_val = 10, + .min_val = 5, + .max_val = 30, + .desc = "Link integrity verification polling timer", + .unit = OSMO_TDEF_S, + }, { + .T=392, + .default_val = 15, + .min_val = 5, + .max_val = 30, + .desc = "Polling verification timer", + .unit = OSMO_TDEF_S, + }, + {} +}; + +static const struct tlv_definition q933_att_tlvdef = { + .def = { + [Q933_IEI_REPORT_TYPE] = { TLV_TYPE_TLV }, + [Q933_IEI_LINK_INT_VERIF] = { TLV_TYPE_TLV }, + [Q933_IEI_PVC_STATUS] = { TLV_TYPE_TLV }, + }, +}; + +static void check_link_state(struct osmo_fr_link *link, bool valid); + +static inline uint16_t q922_to_dlci(const uint8_t *hdr) +{ + return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4); +} + + +static inline void dlci_to_q922(uint8_t *hdr, uint16_t dlci) +{ + hdr[0] = (dlci >> 2) & 0xFC; + hdr[1] = ((dlci << 4) & 0xF0) | 0x01; +} + +/* allocate a message buffer and put Q.933 Annex A headers (L2 + L3) */ +static struct msgb *q933_msgb_alloc(uint16_t dlci, uint8_t prot_disc, uint8_t msg_type) +{ + struct msgb *msg = msgb_alloc_headroom(1600+64, 64, "FR Q.933 Tx"); + struct q933_a_hdr *qh; + + if (!msg) + return NULL; + + msg->l1h = msgb_put(msg, 2); + dlci_to_q922(msg->l1h, dlci); + + /* LAPF UI control */ + msg->l2h = msgb_put(msg, 1); + *msg->l2h = LAPF_UI; + + msg->l3h = msgb_put(msg, sizeof(*qh)); + qh = (struct q933_a_hdr *) msg->l3h; + qh->prot_disc = prot_disc; + qh->call_ref = LMI_Q933A_CALLREF; + qh->msg_type = msg_type; + + return msg; +} + +/* obtain the [next] transmit sequence number */ +static uint8_t link_get_tx_seq(struct osmo_fr_link *link) +{ + /* The {user equipment, network} increments the send sequence + * counter using modulo 256. The value zero is skipped. */ + link->last_tx_seq++; + if (link->last_tx_seq == 0) + link->last_tx_seq++; + + return link->last_tx_seq; +} + +/* Append PVC Status IE according to Q.933 A.3.2 */ +static void msgb_put_link_int_verif(struct msgb *msg, struct osmo_fr_link *link) +{ + uint8_t link_int_tx[2]; + link_int_tx[0] = link_get_tx_seq(link); + link_int_tx[1] = link->last_rx_seq; + msgb_tlv_put(msg, Q933_IEI_LINK_INT_VERIF, 2, link_int_tx); +} + +static void dlc_destroy(struct osmo_fr_dlc *dlc) +{ + llist_del(&dlc->list); + talloc_free(dlc); +} + +/* Append PVC Status IE according to Q.933 A.3.3 */ +static void msgb_put_pvc_status(struct msgb *msg, struct osmo_fr_dlc *dlc) +{ + uint8_t ie[3]; + + ie[0] = (dlc->dlci >> 4) & 0x3f; + /* extension bits */ + ie[1] = 0x80 | ((dlc->dlci & 0xf) << 3); + /* extension bits */ + ie[2] = 0x80; + + /* FIXME: validate: this status should be added as long it's not yet acked by the remote */ + if (dlc->active) + ie[2] |= Q933_PVC_STATUS_DLC_ACTIVE; + + if (dlc->add) { + ie[2] |= Q933_PVC_STATUS_DLC_NEW; + /* we've reported it as new once, reset the status */ + } + + if (dlc->del) { + ie[2] |= Q933_PVC_STATUS_DLC_DELETE; + /* we've reported it as deleted once, destroy it */ + dlc_destroy(dlc); + } + + msgb_tlv_put(msg, Q933_IEI_PVC_STATUS, 3, ie); +} + +/* Send a Q.933 STATUS ENQUIRY given type over given link */ +static int tx_lmi_q933_status_enq(struct osmo_fr_link *link, uint8_t rep_type) +{ + struct msgb *resp; + + resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS_ENQUIRY); + if (!resp) + return -1; + resp->dst = link; + link->expected_rep = rep_type; + + /* Table A.2/Q.933 */ + msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type); + msgb_put_link_int_verif(resp, link); + + return link->tx_cb(link->tx_cb_data, resp); +} + +/* Send a Q.933 STATUS of given type over given link */ +static int tx_lmi_q933_status(struct osmo_fr_link *link, uint8_t rep_type) +{ + struct osmo_fr_dlc *dlc; + struct msgb *resp; + + resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS); + if (!resp) + return -1; + + resp->dst = link; + + /* Table A.1/Q.933 */ + msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type); + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + msgb_put_link_int_verif(resp, link); + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->add || dlc->del) + dlc->state_send = true; + + msgb_put_pvc_status(resp, dlc); + } + break; + case Q933_REPT_LINK_INTEGRITY_VERIF: + msgb_put_link_int_verif(resp, link); + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->add || dlc->del) { + msgb_put_pvc_status(resp, dlc); + dlc->state_send = true; + } + } + break; + case Q933_REPT_SINGLE_PVC_ASYNC_STS: + llist_for_each_entry(dlc, &link->dlc_list, list) + msgb_put_pvc_status(resp, dlc); + break; + } + + return link->tx_cb(link->tx_cb_data, resp); +} + + +/* Q.933 */ +static int rx_lmi_q933_status_enq(struct msgb *msg, struct tlv_parsed *tp) +{ + struct osmo_fr_link *link = msg->dst; + struct osmo_fr_dlc *dlc; + const uint8_t *link_int_rx; + uint8_t rep_type; + + OSMO_ASSERT(link); + + if (link->role == FR_ROLE_USER_EQUIPMENT) { + LOGPFRL(link, LOGL_ERROR, "STATUS-ENQ are not support for role user\n"); + return -1; + } + + /* check for mandatory IEs */ + if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1) || + !TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) + return -1; + + rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE); + + link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF); + link->last_rx_seq = link_int_rx[0]; + + /* the network checks the receive sequence number received from + * the user equipment against its send sequence counter */ + if (link_int_rx[1] != link->last_tx_seq) { + check_link_state(link, false); + link->err_count++; + } else { + check_link_state(link, true); + /* confirm DLC state changes */ + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (!dlc->state_send) + continue; + + if (dlc->add) { + dlc->active = link->state; + dlc->add = false; + } + + if (dlc->del) { + dlc->del = false; + } + + dlc->state_send = false; + } + } + + + /* The network responds to each STATUS ENQUIRY message with a + * STATUS message and resets the T392 timer */ + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + + return tx_lmi_q933_status(link, rep_type); +} + +/* check if the link become active. + * The link becomes active when enough times a STATUS/STATUS ENQUIRY arrives without any loss. + * Look at the last N393 STATUS/STATUS ENQUIRY PDUs. The link is valid if at least N392 + * got received. + * param[in] valid contains the status of the last packet */ +static void check_link_state(struct osmo_fr_link *link, bool valid) +{ + unsigned int last, i; + unsigned int carry = 0; + struct osmo_fr_dlc *dlc; + + link->succeed <<= 1; + if (valid) + link->succeed |= 1; + + /* count the bits */ + last = link->succeed & ((1 << link->net->n393) - 1); + for (i = 0; i < link->net->n393; i++) + if (last & (1 << i)) + carry++; + + if (link->net->n393 - carry >= link->net->n392) { + /* failing link */ + if (!link->state) + return; + + LOGPFRL(link, LOGL_NOTICE, "Link failed\n"); + link->state = false; + if (link->role == FR_ROLE_USER_EQUIPMENT) + return; + + llist_for_each_entry(dlc, &link->dlc_list, list) { + dlc->active = false; + } + } else { + /* good link */ + if (link->state) + return; + + LOGPFRL(link, LOGL_NOTICE, "Link recovered\n"); + link->state = true; + if (link->role == FR_ROLE_USER_EQUIPMENT) + return; + + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (!dlc->add && !dlc->del) + dlc->active = true; + } + } +} + +static int validate_pvc_status(struct tlv_parsed *tp, size_t tp_len) +{ + size_t i; + uint16_t len = 0; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* PVC status can be 2 or 3 bytes. If the PVC is bigger + * ignore this to be compatible to future extensions. */ + len = TLVP_LEN(&tp[i], Q933_IEI_PVC_STATUS); + if (len <= 1) { + return -EINVAL; + } + /* FIXME: validate correct flags: are some flags invalid at the same time? */ + } + + return 0; +} + +static int parse_full_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len) +{ + size_t i; + int err = 0; + struct osmo_fr_dlc *dlc, *tmp; + struct q933_a_pvc_sts *pvc; + uint16_t dlci = 0; + uint16_t *dlcis = talloc_zero_array(link, uint16_t, tp_len); + if (!dlcis) + return -ENOMEM; + + /* first run validate all PVCs */ + err = validate_pvc_status(tp, tp_len); + if (err < 0) + goto out; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* parse only 3 byte PVCs */ + pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN( + &tp[i], + Q933_IEI_PVC_STATUS, + sizeof(struct q933_a_pvc_sts)); + if (!pvc) + continue; + + dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf); + dlcis[i] = dlci; + dlc = osmo_fr_dlc_by_dlci(link, dlci); + if (!dlc) { + dlc = osmo_fr_dlc_alloc(link, dlci); + if (!dlc) { + LOGPFRL(link, LOGL_ERROR, "Could not create DLC %d\n", dlci); + continue; + } + } + + /* Figure A.3/Q.933: The delete bit is only applicable for timely notification + * using the optional single PVC asynchronous status report. + * Ignoring the delete. */ + dlc->add = pvc->new; + dlc->active = pvc->active; + dlc->del = 0; + } + + /* check if all dlc are present in PVC Status */ + llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) { + bool found = false; + for (i = 0; i < tp_len; i++) { + if (dlcis[i] == dlc->dlci) { + found = true; + break; + } + } + + if (!found) { + dlc->active = false; + dlc->del = true; + } + } + + return 0; +out: + talloc_free(dlcis); + return err; +} + +static int parse_link_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len) +{ + int err; + size_t i; + struct q933_a_pvc_sts *pvc; + struct osmo_fr_dlc *dlc; + uint16_t dlci = 0; + + err = validate_pvc_status(tp, tp_len); + if (err < 0) + return err; + + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + + /* parse only 3 byte PVCs */ + pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN( + &tp[i], + Q933_IEI_PVC_STATUS, + sizeof(struct q933_a_pvc_sts)); + if (!pvc) + continue; + + dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf); + dlc = osmo_fr_dlc_by_dlci(link, dlci); + if (!dlc) { + /* don't create dlc's for the ones which are about to be deleted. */ + if (dlc->del) + continue; + + dlc = osmo_fr_dlc_alloc(link, dlci); + if (!dlc) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Could not create DLC %d\n", dlci); + } + } + + if (pvc->delete) { + dlc->del = 1; + } else { + dlc->add = pvc->new; + dlc->active = pvc->active; + dlc->del = 0; + } + } + + return 0; +} + +static size_t count_pvc_status(struct tlv_parsed *tp, size_t tp_len) +{ + size_t i, count = 0; + for (i = 0; i < tp_len; i++) { + if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS)) + continue; + count++; + } + + return count; +} + +static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp) +{ + struct osmo_fr_link *link = msg->dst; + const uint8_t *link_int_rx; + uint8_t rep_type; + + OSMO_ASSERT(link); + + if (link->role == FR_ROLE_NETWORK_EQUIPMENT) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: STATUS aren't support for role network\n"); + return -1; + } + + /* check for mandatory IEs */ + if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUSL: Missing TLV Q933 Report Type\n"); + return -1; + } + + rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE); + + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + case Q933_REPT_LINK_INTEGRITY_VERIF: + if (rep_type != link->expected_rep) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Unexpected Q933 report type (got 0x%x != exp 0x%x)\n", + rep_type, link->expected_rep); + return -1; + } + + if (!TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) { + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Link Integrety Verification\n"); + return -1; + } + link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF); + link->last_rx_seq = link_int_rx[0]; + /* The received receive sequence number is not valid if + * it is not equal to the last transmitted send sequence + * number. Ignore messages containing this error. As a + * result, timer T391 expires and the user then + * increments the error count. */ + if (link_int_rx[1] != link->last_tx_seq) + return 0; + break; + case Q933_REPT_SINGLE_PVC_ASYNC_STS: + default: + return -1; + } + + check_link_state(link, true); + if (count_pvc_status(tp, MAX_SUPPORTED_PVC + 1) > MAX_SUPPORTED_PVC) { + LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Too many PVC! Only %d are supported!\n", MAX_SUPPORTED_PVC); + } + + switch (rep_type) { + case Q933_REPT_FULL_STATUS: + parse_full_pvc_status(link, tp, MAX_SUPPORTED_PVC); + break; + case Q933_REPT_LINK_INTEGRITY_VERIF: + parse_link_pvc_status(link, tp, MAX_SUPPORTED_PVC); + break; + default: + break; + } + + /* The network responds to each STATUS ENQUIRY message with a + * STATUS message and resets the T392 timer */ + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + + return 0; +} + +static int rx_lmi_q922(struct msgb *msg) +{ + struct osmo_fr_link *link = msg->dst; + struct q933_a_hdr *qh; + /* the + 1 is used to detect more than MAX_SUPPORTED_PVC */ + struct tlv_parsed tp[MAX_SUPPORTED_PVC + 1]; + uint8_t *lapf; + int rc; + + OSMO_ASSERT(link); + + if (msgb_l2len(msg) < 1) + return -1; + lapf = msgb_l2(msg); + + /* we only support LAPF UI frames */ + if (lapf[0] != LAPF_UI) + return -1; + + msg->l3h = msg->l2h + 1; + if (msgb_l3len(msg) < 3) + return -1; + + qh = (struct q933_a_hdr *) msgb_l3(msg); + if (qh->prot_disc != Q931_PDISC_CC) { + LOGPFRL(link, LOGL_NOTICE, + "Rx unsupported LMI protocol discriminator %u\n", qh->prot_disc); + return -1; + } + + rc = tlv_parse2(tp, MAX_SUPPORTED_PVC + 1, &q933_att_tlvdef, + msgb_l3(msg) + sizeof(*qh), + msgb_l3len(msg) - sizeof(*qh), 0, 0); + if (rc < 0) { + LOGPFRL(link, LOGL_NOTICE, + "Failed to parse TLVs in LMI message type %u\n", qh->msg_type); + return rc; + } + + switch (qh->msg_type) { + case Q931_MSGT_STATUS_ENQUIRY: + rc = rx_lmi_q933_status_enq(msg, tp); + break; + case Q931_MSGT_STATUS: + rc = rx_lmi_q933_status(msg, tp); + break; + default: + LOGPFRL(link, LOGL_NOTICE, + "Rx unsupported LMI message type %u\n", qh->msg_type); + rc = -1; + break; + } + msgb_free(msg); + + return rc; +} + +int osmo_fr_rx(struct msgb *msg) +{ + int rc = 0; + uint8_t *frh; + uint16_t dlci; + struct osmo_fr_dlc *dlc; + struct osmo_fr_link *link = msg->dst; + + OSMO_ASSERT(link); + + if (msgb_length(msg) < 2) { + LOGPFRL(link, LOGL_ERROR, "Rx short FR header: %u bytes\n", msgb_length(msg)); + rc = -1; + goto out; + } + + frh = msg->l1h = msgb_data(msg); + if (frh[0] & 0x01) { + LOGPFRL(link, LOGL_NOTICE, "Rx Unsupported single-byte FR address\n"); + rc = -1; + goto out; + } + if ((frh[1] & 0x0f) != 0x01) { + LOGPFRL(link, LOGL_NOTICE, "Rx Unknown second FR octet 0x%02x\n", frh[1]); + rc = -1; + goto out; + } + dlci = q922_to_dlci(frh); + msg->l2h = frh + 2; + + switch (dlci) { + case LMI_Q933A_DLCI: + return rx_lmi_q922(msg); + case LMI_CISCO_DLCI: + LOGPFRL(link, LOGL_ERROR, "Rx Unsupported FR DLCI %u\n", dlci); + goto out; + } + + if (!link->state) { + LOGPFRL(link, LOGL_NOTICE, "Link is not reliable. Discarding Rx PDU on DLCI %d\n", dlci); + goto out; + } + + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->dlci == dlci) { + /* dispatch to handler of respective DLC */ + msg->dst = dlc; + return dlc->rx_cb(dlc->rx_cb_data, msg); + } + } + + if (link->unknown_dlc_rx_cb) + return link->unknown_dlc_rx_cb(link->unknown_dlc_rx_cb_data, msg); + else + LOGPFRL(link, LOGL_NOTICE, "DLCI %u doesn't exist, discarding\n", dlci); + +out: + msgb_free(msg); + + return rc; +} + +int osmo_fr_tx_dlc(struct msgb *msg) +{ + uint8_t *frh; + struct osmo_fr_dlc *dlc = msg->dst; + struct osmo_fr_link *link = dlc->link; + + OSMO_ASSERT(dlc); + OSMO_ASSERT(link); + + if (!link->state) { + LOGPFRL(link, LOGL_NOTICE, "Link is not reliable (yet), discarding Tx\n"); + msgb_free(msg); + return -1; + } + if (!dlc->active) { + LOGPFRL(link, LOGL_NOTICE, "DLCI %u is not active (yet), discarding Tx\n", dlc->dlci); + msgb_free(msg); + return -1; + } + LOGPFRL(link, LOGL_DEBUG, "DLCI %u is active, sending message\n", dlc->dlci); + + if (msgb_headroom(msg) < 2) { + msgb_free(msg); + return -ENOSPC; + } + + frh = msgb_push(msg, 2); + dlci_to_q922(frh, dlc->dlci); + + msg->dst = link; + return link->tx_cb(link->tx_cb_data, msg); +} + +/* Every T391 seconds, the user equipment sends a STATUS ENQUIRY + * message to the network and resets its polling timer (T391). */ +static void fr_t391_cb(void *data) +{ + struct osmo_fr_link *link = data; + + OSMO_ASSERT(link); + + if (link->polling_count % link->net->n391 == 0) + tx_lmi_q933_status_enq(link, Q933_REPT_FULL_STATUS); + else + tx_lmi_q933_status_enq(link, Q933_REPT_LINK_INTEGRITY_VERIF); + link->polling_count++; + osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 10), 0); +} + +static void fr_t392_cb(void *data) +{ + struct osmo_fr_link *link = data; + + OSMO_ASSERT(link); + + /* A.5 The network increments the error count .. Non-receipt of + * a STATUS ENQUIRY within T392, which results in restarting + * T392 */ + link->err_count++; + check_link_state(link, false); + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); +} + +/* allocate a frame relay network */ +struct osmo_fr_network *osmo_fr_network_alloc(void *ctx) +{ + struct osmo_fr_network *net = talloc_zero(ctx, struct osmo_fr_network); + + INIT_LLIST_HEAD(&net->links); + net->T_defs = fr_tdefs; + osmo_tdefs_reset(net->T_defs); + net->n391 = 6; + net->n392 = 3; + net->n393 = 4; + + return net; +} + +void osmo_fr_network_free(struct osmo_fr_network *net) +{ + struct osmo_fr_link *link, *tmp; + + if (!net) + return; + + llist_for_each_entry_safe(link, tmp, &net->links, list) { + osmo_fr_link_free(link); + } +} + +/* allocate a frame relay link in a given network */ +struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name) +{ + struct osmo_fr_link *link = talloc_zero(net, struct osmo_fr_link); + if (!link) + return NULL; + + LOGPFRL(link, LOGL_INFO, "Creating frame relay link with role %s\n", osmo_fr_role_str(role)); + + link->role = role; + link->net = net; + link->name = talloc_strdup(link, name); + INIT_LLIST_HEAD(&link->dlc_list); + llist_add_tail(&link->list, &net->links); + + osmo_timer_setup(&link->t391, fr_t391_cb, link); + osmo_timer_setup(&link->t392, fr_t392_cb, link); + + switch (role) { + case FR_ROLE_USER_EQUIPMENT: + osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 15), 0); + break; + case FR_ROLE_NETWORK_EQUIPMENT: + osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0); + break; + } + + return link; +} + +void osmo_fr_link_free(struct osmo_fr_link *link) +{ + struct osmo_fr_dlc *dlc, *tmp; + + if (!link) + return; + + osmo_timer_del(&link->t391); + osmo_timer_del(&link->t392); + + llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) { + osmo_fr_dlc_free(dlc); + } + + llist_del(&link->list); + talloc_free(link); +} + +/* allocate a data link connectoin on a given framerelay link */ +struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci) +{ + struct osmo_fr_dlc *dlc = talloc_zero(link, struct osmo_fr_dlc); + if (!dlc) + return NULL; + + dlc->link = link; + dlc->dlci = dlci; + dlc->active = false; + + llist_add_tail(&dlc->list, &link->dlc_list); + + dlc->add = true; + tx_lmi_q933_status(link, Q933_IEI_PVC_STATUS); + + return dlc; +} + +void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc) +{ + llist_del(&dlc->list); + talloc_free(dlc); +} + +/* TODO: rework osmo_fr_dlc_alloc/free with handling it's own memory. + * For network role: The dlc have to created by the application (e.g. vty). + * The dlc shouldn't free'd directly. It should be communicated to the + * other side and wait until it's confirmed OR the link go off and free it afterwards. + * For user equpment role: The dlc can be created by the application or the dlc will be created + * by the frame relay because the network is configuring the dlc. + * The dlc shouldn't be free'd. Only the handler should be set to NULL. + */ + +struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci) +{ + struct osmo_fr_dlc *dlc; + + llist_for_each_entry(dlc, &link->dlc_list, list) { + if (dlc->dlci == dlci) + return dlc; + } + return NULL; +} diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c index cf04924..096e80b 100644 --- a/src/gb/gprs_ns2.c +++ b/src/gb/gprs_ns2.c @@ -59,7 +59,6 @@ * This implementation has the following limitations: * - Only one NS-VC for each NSE: No load-sharing function * - NSVCI 65535 and 65534 are reserved for internal use - * - Only UDP is supported as of now, no frame relay support * - There are no BLOCK and UNBLOCK timers (yet?) * * \file gprs_ns2.c */ @@ -258,6 +257,15 @@ case GPRS_NS_LL_E1: snprintf(buf, buf_len, "e1)"); break; + case GPRS_NS_LL_FR: + if (!gprs_ns2_is_frgre_bind(nsvc->bind)) { + buf[0] = '\0'; + return buf; + } + + snprintf(buf, buf_len, "fr)netif: %s dlci: %u", gprs_ns2_fr_bind_netif(nsvc->bind), + gprs_ns2_fr_nsvc_dlci(nsvc)); + break; default: buf[0] = '\0'; break; diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c new file mode 100644 index 0000000..ae54a41 --- /dev/null +++ b/src/gb/gprs_ns2_fr.c @@ -0,0 +1,545 @@ +/*! \file gprs_ns2_fr.c + * NS-over-FR-over-GRE implementation. + * GPRS Networks Service (NS) messages on the Gb interface. + * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05) + * as well as its successor 3GPP TS 48.016 */ + +/* (C) 2009-2010,2014,2017 by Harald Welte <laforge at gnumonks.org> + * (C) 2020 sysmocom - s.f.m.c. GmbH + * Author: Alexander Couzens <lynxis at fe80.eu> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <arpa/inet.h> +#include <net/if.h> + +#include <sys/ioctl.h> +#include <netpacket/packet.h> +#include <linux/if_ether.h> +#include <linux/hdlc.h> +#include <linux/if.h> + +#include <osmocom/gprs/frame_relay.h> +#include <osmocom/core/byteswap.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/talloc.h> +#include <osmocom/gprs/gprs_ns2.h> + +#include "common_vty.h" +#include "gprs_ns2_internal.h" + +#define GRE_PTYPE_FR 0x6559 +#define GRE_PTYPE_IPv4 0x0800 +#define GRE_PTYPE_IPv6 0x86dd +#define GRE_PTYPE_KAR 0x0000 /* keepalive response */ + +#ifndef IPPROTO_GRE +# define IPPROTO_GRE 47 +#endif + +struct gre_hdr { + uint16_t flags; + uint16_t ptype; +} __attribute__ ((packed)); + +static void free_bind(struct gprs_ns2_vc_bind *bind); +static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg); + +struct gprs_ns2_vc_driver vc_driver_fr = { + .name = "GB frame relay", + .free_bind = free_bind, +}; + +struct priv_bind { + struct osmo_fd fd; + char netif[IF_NAMESIZE]; + struct osmo_fr_link *link; +}; + +struct priv_vc { + struct osmo_sockaddr remote; + uint16_t dlci; + struct osmo_fr_dlc *dlc; +}; + +static void free_vc(struct gprs_ns2_vc *nsvc) +{ + OSMO_ASSERT(nsvc); + + if (!nsvc->priv) + return; + + talloc_free(nsvc->priv); + nsvc->priv = NULL; +} + +static void dump_vty(const struct gprs_ns2_vc_bind *bind, struct vty *vty, bool _stats) +{ + struct priv_bind *priv; + struct gprs_ns2_vc *nsvc; + + if (!bind) + return; + + priv = bind->priv; + + vty_out(vty, "FR bind: %s%s", priv->netif, VTY_NEWLINE); + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vty_out(vty, " %s%s", gprs_ns2_ll_str(nsvc), VTY_NEWLINE); + } + + priv = bind->priv; +} + +/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */ +static void free_bind(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (!bind) + return; + + priv = bind->priv; + + OSMO_ASSERT(llist_empty(&bind->nsvc)); + + osmo_fr_link_free(priv->link); + osmo_fd_close(&priv->fd); + talloc_free(priv); +} + +static struct priv_vc *fr_alloc_vc(struct gprs_ns2_vc_bind *bind, + struct gprs_ns2_vc *nsvc, + uint16_t dlci) +{ + struct priv_bind *privb = bind->priv; + struct priv_vc *priv = talloc_zero(bind, struct priv_vc); + if (!priv) + return NULL; + + nsvc->priv = priv; + priv->dlci = dlci; + priv->dlc = osmo_fr_dlc_alloc(privb->link, dlci); + if (!priv->dlc) { + nsvc->priv = NULL; + talloc_free(priv); + return NULL; + } + + priv->dlc->rx_cb_data = nsvc; + priv->dlc->rx_cb = fr_dlci_rx_cb; + + return priv; +} + +int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind, + uint16_t dlci, + struct gprs_ns2_vc **result) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + if (!result) + return -EINVAL; + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + if (vcpriv->dlci != dlci) { + *result = nsvc; + return 0; + } + } + + return 1; +} + +/* PDU from the network interface towards the fr layer (upwards) */ +static int handle_netif_read(struct osmo_fd *bfd) +{ + struct gprs_ns2_vc_bind *bind = bfd->data; + struct priv_bind *priv = bind->priv; + struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx"); + int rc = 0; + + if (!msg) + return -ENOMEM; + + rc = read(bfd->fd, msg->data, NS_ALLOC_SIZE); + if (rc < 0) { + LOGP(DLNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", + strerror(errno)); + goto out_err; + } else if (rc == 0) { + goto out_err; + } + + msgb_put(msg, rc); + msg->dst = priv->link; + return osmo_fr_rx(msg); + +out_err: + msgb_free(msg); + return rc; +} + +/* PDU from the frame relay towards the NS-VC (upwards) */ +static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg) +{ + int rc; + struct gprs_ns2_vc *nsvc = cb_data; + + rc = ns2_recv_vc(nsvc, msg); + + return rc; +} + +static int handle_netif_write(struct osmo_fd *bfd) +{ + /* FIXME */ + return -EIO; +} + +static int fr_fd_cb(struct osmo_fd *bfd, unsigned int what) +{ + int rc = 0; + + if (what & OSMO_FD_READ) + rc = handle_netif_read(bfd); + if (what & OSMO_FD_WRITE) + rc = handle_netif_write(bfd); + + return rc; +} + +/*! determine if given bind is for FR-GRE encapsulation. */ +int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind) +{ + return (bind->driver == &vc_driver_fr); +} + +/* PDU from the NS-VC towards the frame relay layer (downwards) */ +static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) +{ + struct priv_vc *vcpriv = nsvc->priv; + + msg->dst = vcpriv->dlc; + return osmo_fr_tx_dlc(msg); +} + +/* PDU from the frame relay layer towards the network interface (downwards) */ +int fr_tx_cb(void *data, struct msgb *msg) +{ + struct gprs_ns2_vc_bind *bind = data; + struct priv_bind *priv = bind->priv; + int rc; + + /* FIXME half writes */ + rc = write(priv->fd.fd, msg->data, msg->len); + msgb_free(msg); + + return rc; +} + +static int devname2ifindex(const char *ifname) +{ + struct ifreq ifr; + int sk, rc; + + sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (sk < 0) + return sk; + + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + ifr.ifr_name[sizeof(ifr.ifr_name)-1] = 0; + + rc = ioctl(sk, SIOCGIFINDEX, &ifr); + close(sk); + if (rc < 0) + return rc; + + return ifr.ifr_ifindex; +} + +static int open_socket(const char *ifname) +{ + struct sockaddr_ll addr; + int ifindex; + int fd, rc, on = 1; + + ifindex = devname2ifindex(ifname); + if (ifindex < 0) { + LOGP(DLNS, LOGL_ERROR, "Can not get interface index for interface %s\n", ifname); + return ifindex; + } + + memset(&addr, 0, sizeof(addr)); + addr.sll_family = AF_PACKET; + addr.sll_protocol = htons(ETH_P_ALL); + addr.sll_ifindex = ifindex; + + fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (fd < 0) { + LOGP(DLNS, LOGL_ERROR, "Can not get socket for interface %s. Are you root or have CAP_RAW_SOCKET?\n", ifname); + return fd; + } + + if (ioctl(fd, FIONBIO, (unsigned char *)&on) < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, + "cannot set this socket unblocking: %s\n", + strerror(errno)); + close(fd); + fd = -EINVAL; + } + + rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc < 0) { + LOGP(DLNS, LOGL_ERROR, "Can not bind for interface %s\n", ifname); + close(fd); + return rc; + } + + return fd; +} + +/*! Create a new bind for NS over FR. + * \param[in] nsi NS instance in which to create the bind + * \param[in] netif Network interface to bind to + * \param[in] fr_network + * \param[in] fr_role + * \param[out] result pointer to created bind + * \return 0 on success; negative on error */ +int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, + const char *netif, + struct osmo_fr_network *fr_network, + enum osmo_fr_role fr_role, + struct gprs_ns2_vc_bind **result) +{ + struct gprs_ns2_vc_bind *bind = talloc_zero(nsi, struct gprs_ns2_vc_bind); + struct priv_bind *priv; + struct osmo_fr_link *fr_link; + int rc = 0; + + if (!bind) + return -ENOSPC; + + bind->driver = &vc_driver_fr; + bind->send_vc = fr_vc_sendmsg; + bind->free_vc = free_vc; + bind->dump_vty = dump_vty; + bind->nsi = nsi; + priv = bind->priv = talloc_zero(bind, struct priv_bind); + if (!priv) { + rc = -ENOSPC; + goto err_bind; + } + + priv->fd.cb = fr_fd_cb; + priv->fd.data = bind; + if (strlen(netif) > IF_NAMESIZE) { + rc = -EINVAL; + goto err_priv; + } + strncpy(priv->netif, netif, sizeof(priv->netif)); + + ns2_vty_bind_apply(bind); + if (result) + *result = bind; + + /* FIXME: move fd handling into socket.c */ + fr_link = osmo_fr_link_alloc(fr_network, fr_role, netif); + if (!fr_link) { + rc = -EINVAL; + goto err_priv; + } + + fr_link->tx_cb = fr_tx_cb; + fr_link->tx_cb_data = bind; + priv->link = fr_link; + priv->fd.fd = rc = open_socket(netif); + if (rc < 0) + goto err_fr; + + priv->fd.when = OSMO_FD_READ; + rc = osmo_fd_register(&priv->fd); + if (rc < 0) + goto err_fd; + + INIT_LLIST_HEAD(&bind->nsvc); + llist_add(&bind->list, &nsi->binding); + + return rc; + +err_fd: + close(priv->fd.fd); +err_fr: + osmo_fr_link_free(fr_link); +err_priv: + talloc_free(priv); +err_bind: + talloc_free(bind); + + return rc; +} + +/*! Return the network interface of the bind + * \param[in] bind The bind + * \return the network interface + */ +const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind) +{ + struct priv_bind *priv; + + if (bind->driver != &vc_driver_fr) + return NULL; + + priv = bind->priv; + return priv->netif; +} + +/*! Find NS bind for a given network interface + * \param[in] nsi NS instance + * \param[in] netif the network interface to search for + * \return the bind or NULL if not found + */ +struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif( + struct gprs_ns2_inst *nsi, + const char *netif) +{ + struct gprs_ns2_vc_bind *bind; + const char *_netif; + + OSMO_ASSERT(nsi); + OSMO_ASSERT(netif); + + llist_for_each_entry(bind, &nsi->binding, list) { + if (!gprs_ns2_is_fr_bind(bind)) + continue; + + _netif = gprs_ns2_fr_bind_netif(bind); + if (!strncmp(_netif, netif, IF_NAMESIZE)) + return bind; + } + + return NULL; +} + +/*! Create, connect and activate a new FR-based NS-VC + * \param[in] bind bind in which the new NS-VC is to be created + * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created + * \param[in] dlci Data Link connection identifier + * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */ +struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind, + uint16_t nsei, + uint16_t nsvci, + uint16_t dlci) +{ + bool created_nse = false; + struct gprs_ns2_vc *nsvc = NULL; + struct priv_vc *priv = NULL; + struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei); + if (!nse) { + nse = gprs_ns2_create_nse(bind->nsi, nsei); + if (!nse) + return NULL; + created_nse = true; + } + + nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci); + if (nsvc) { + goto err_nse; + } + + nsvc = ns2_vc_alloc(bind, nse, true); + if (!nsvc) + goto err_nse; + + nsvc->priv = priv = fr_alloc_vc(bind, nsvc, dlci); + if (!priv) + goto err; + + nsvc->nsvci = nsvci; + nsvc->nsvci_is_valid = true; + nsvc->ll = GPRS_NS_LL_FR; + + gprs_ns2_vc_fsm_start(nsvc); + + return nsvc; + +err: + gprs_ns2_free_nsvc(nsvc); +err_nse: + if (created_nse) + gprs_ns2_free_nse(nse); + + return NULL; +} + +/*! Return the nsvc by dlci. + * \param[in] bind + * \param[in] dlci Data Link connection identifier + * \return the nsvc or NULL if not found + */ +struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind, + uint16_t dlci) +{ + struct gprs_ns2_vc *nsvc; + struct priv_vc *vcpriv; + + llist_for_each_entry(nsvc, &bind->nsvc, blist) { + vcpriv = nsvc->priv; + + if (dlci == vcpriv->dlci) + return nsvc; + } + + return NULL; +} + +/*! Return the dlci of the nsvc + * \param[in] nsvc + * \return the dlci or 0 on error. 0 is not a valid dlci. + */ +uint16_t gprs_ns2_fr_nsvc_dlci(struct gprs_ns2_vc *nsvc) +{ + struct priv_vc *vcpriv; + + if (!nsvc->bind) + return 0; + + if (nsvc->bind->driver != &vc_driver_fr) + return 0; + + vcpriv = nsvc->priv; + return vcpriv->dlci; +} diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h index 493b391..6ea2fcc 100644 --- a/src/gb/gprs_ns2_internal.h +++ b/src/gb/gprs_ns2_internal.h @@ -60,6 +60,7 @@ enum gprs_ns_ll { GPRS_NS_LL_UDP, /*!< NS/UDP/IP */ GPRS_NS_LL_E1, /*!< NS/E1 */ + GPRS_NS_LL_FR, /*!< NS/FR */ GPRS_NS_LL_FR_GRE, /*!< NS/FR/GRE/IP */ }; diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c index 63331b9..4eb9d37 100644 --- a/src/gb/gprs_ns2_vty.c +++ b/src/gb/gprs_ns2_vty.c @@ -31,6 +31,7 @@ #include <stdint.h> #include <arpa/inet.h> +#include <net/if.h> #include <osmocom/core/msgb.h> #include <osmocom/core/byteswap.h> @@ -42,6 +43,7 @@ #include <osmocom/core/sockaddr_str.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/socket.h> +#include <osmocom/gprs/frame_relay.h> #include <osmocom/gprs/gprs_ns2.h> #include <osmocom/gsm/tlv.h> #include <osmocom/vty/vty.h> @@ -78,12 +80,19 @@ uint16_t nsvci; uint16_t frdlci; + struct { + enum osmo_fr_role role; + } fr; + + char netif[IF_NAMESIZE]; + bool remote_end_is_sgsn; bool configured; }; static struct gprs_ns2_inst *vty_nsi = NULL; static struct ns2_vty_priv priv; +static struct osmo_fr_network *vty_fr_network = NULL; /* FIXME: this should go to some common file as it is copied * in vty_interface.c of the BSC */ @@ -223,6 +232,11 @@ vtyvc->nsei, vtyvc->frdlci, VTY_NEWLINE); break; + case GPRS_NS_LL_FR: + vty_out(vty, " nse %u fr %s dlci %u%s", + vtyvc->nsei, vtyvc->netif, vtyvc->frdlci, + VTY_NEWLINE); + break; default: break; } @@ -387,6 +401,44 @@ #define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n" +DEFUN(cfg_nse_fr, cfg_nse_fr_cmd, + "nse <0-65535> nsvci <0-65535> (fr|frnet) NETIF dlci <0-1023>", + NSE_CMD_STR + "NS Virtual Connection\n" + "NS Virtual Connection ID (NSVCI)\n" + "frame relay\n" + IFNAME_STR + "Data Link connection identifier\n" + "Data Link connection identifier\n" + ) +{ + struct ns2_vty_vc *vtyvc; + + uint16_t nsei = atoi(argv[0]); + uint16_t nsvci = atoi(argv[1]); + const char *role = argv[2]; + const char *name = argv[3]; + uint16_t dlci = atoi(argv[4]); + + vtyvc = vtyvc_by_nsei(nsei, true); + if (!vtyvc) { + vty_out(vty, "Can not allocate space %s", VTY_NEWLINE); + return CMD_WARNING; + } + + if (!strcmp(role, "fr")) + vtyvc->fr.role = FR_ROLE_USER_EQUIPMENT; + else if (!strcmp(role, "frnet")) + vtyvc->fr.role = FR_ROLE_NETWORK_EQUIPMENT; + + osmo_strlcpy(vtyvc->netif, name, sizeof(vtyvc->netif)); + vtyvc->frdlci = dlci; + vtyvc->nsvci = nsvci; + vtyvc->ll = GPRS_NS_LL_FR; + + return CMD_SUCCESS; +} + DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd, "nse <0-65535> nsvci <0-65535>", NSE_CMD_STR @@ -453,13 +505,14 @@ } DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd, - "nse <0-65535> fr-dlci <16-1007>", + "nse <0-65535> nsvci <0-65535> fr-dlci <16-1007>", NSE_CMD_STR "Frame Relay DLCI\n" "Frame Relay DLCI Number\n") { uint16_t nsei = atoi(argv[0]); - uint16_t dlci = atoi(argv[1]); + uint16_t nsvci = atoi(argv[1]); + uint16_t dlci = atoi(argv[2]); struct ns2_vty_vc *vtyvc; vtyvc = vtyvc_by_nsei(nsei, true); @@ -474,6 +527,7 @@ } vtyvc->frdlci = dlci; + vtyvc->nsvci = nsvci; return CMD_SUCCESS; } @@ -736,6 +790,7 @@ install_lib_element(CONFIG_NODE, &cfg_ns_cmd); install_node(&ns_node, config_write_ns); + install_lib_element(L_NS_NODE, &cfg_nse_fr_cmd); install_lib_element(L_NS_NODE, &cfg_nse_nsvci_cmd); install_lib_element(L_NS_NODE, &cfg_nse_remoteip_cmd); install_lib_element(L_NS_NODE, &cfg_nse_remoteport_cmd); @@ -763,10 +818,11 @@ */ int gprs_ns2_vty_create() { struct ns2_vty_vc *vtyvc; - struct gprs_ns2_vc_bind *bind; + struct gprs_ns2_vc_bind *bind, *fr; struct gprs_ns2_nse *nse; struct gprs_ns2_vc *nsvc; struct osmo_sockaddr sockaddr; + int rc = 0; if (!vty_nsi) return -1; @@ -787,18 +843,28 @@ /* create vcs */ llist_for_each_entry(vtyvc, &priv.vtyvc, list) { - if (strlen(vtyvc->remote.ip) == 0) { - /* Invalid IP for VC */ - continue; - } + /* validate settings */ + switch (vtyvc->ll) { + case GPRS_NS_LL_UDP: + if (strlen(vtyvc->remote.ip) == 0) { + /* Invalid IP for VC */ + continue; + } - if (!vtyvc->remote.port) { - /* Invalid port for VC */ - continue; - } + if (!vtyvc->remote.port) { + /* Invalid port for VC */ + continue; + } - if (osmo_sockaddr_str_to_sockaddr(&vtyvc->remote, &sockaddr.u.sas)) { - /* Invalid sockaddr for VC */ + if (osmo_sockaddr_str_to_sockaddr(&vtyvc->remote, &sockaddr.u.sas)) { + /* Invalid sockaddr for VC */ + continue; + } + break; + case GPRS_NS_LL_FR: + break; + case GPRS_NS_LL_FR_GRE: + case GPRS_NS_LL_E1: continue; } @@ -812,15 +878,46 @@ } nse->persistent = true; - nsvc = gprs_ns2_ip_connect(bind, - &sockaddr, - nse, - vtyvc->nsvci); - if (!nsvc) { - /* Could not create NSVC, connect failed */ + switch (vtyvc->ll) { + case GPRS_NS_LL_UDP: + nsvc = gprs_ns2_ip_connect(bind, + &sockaddr, + nse, + vtyvc->nsvci); + if (!nsvc) { + /* Could not create NSVC, connect failed */ + continue; + } + nsvc->persistent = true; + break; + case GPRS_NS_LL_FR: { + if (vty_fr_network == NULL) { + /* TODO: add a switch for BSS/SGSN/gbproxy */ + vty_fr_network = osmo_fr_network_alloc(vty_nsi); + } + fr = gprs_ns2_fr_bind_by_netif( + vty_nsi, + vtyvc->netif); + if (!fr) { + rc = gprs_ns2_fr_bind(vty_nsi, vtyvc->netif, vty_fr_network, vtyvc->fr.role, &fr); + if (rc < 0) { + LOGP(DLNS, LOGL_ERROR, "Can not create fr bind on device %s err: %d\n", vtyvc->netif, rc); + return rc; + } + } + + nsvc = gprs_ns2_fr_connect(fr, vtyvc->nsei, vtyvc->nsvci, vtyvc->frdlci); + if (!nsvc) { + /* Could not create NSVC, connect failed */ + continue; + } + nsvc->persistent = true; + break; + } + case GPRS_NS_LL_FR_GRE: + case GPRS_NS_LL_E1: continue; } - nsvc->persistent = true; } diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map index 72437ab..ddfd071 100644 --- a/src/gb/libosmogb.map +++ b/src/gb/libosmogb.map @@ -45,6 +45,14 @@ bssgp_vty_init; bssgp_nsi; +osmo_fr_network_alloc; +osmo_fr_link_alloc; +osmo_fr_link_free; +osmo_fr_dlc_alloc; +osmo_fr_rx; +osmo_fr_tx_dlc; +osmo_fr_role_names; + gprs_ns_signal_ns_names; gprs_ns_pdu_strings; gprs_ns_cause_str; @@ -86,6 +94,14 @@ gprs_ns2_free_nses; gprs_ns2_free_nsvc; gprs_ns2_frgre_bind; +gprs_ns2_fr_bind; +gprs_ns2_fr_bind_netif; +gprs_ns2_fr_bind_by_netif; +gprs_ns2_fr_connect; +gprs_ns2_fr_nsvc_by_dlci; +gprs_ns2_fr_nsvc_dlci; +gprs_ns2_is_fr_bind; +gprs_ns2_find_vc_by_dlci; gprs_ns2_instantiate; gprs_ns2_ip_bind; gprs_ns2_ip_bind_by_sockaddr; -- To view, visit https://gerrit.osmocom.org/c/libosmocore/+/21243 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: libosmocore Gerrit-Branch: master Gerrit-Change-Id: Id3b49f93d33c271f77cd9c9db03cde6b727a4d30 Gerrit-Change-Number: 21243 Gerrit-PatchSet: 2 Gerrit-Owner: lynxis lazus <lynxis at fe80.eu> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: laforge <laforge at osmocom.org> Gerrit-Reviewer: pespin <pespin at sysmocom.de> Gerrit-MessageType: merged -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20201125/a1be2e2e/attachment.htm>