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/.
pespin gerrit-no-reply at lists.osmocom.orgpespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-hnodeb/+/26357 ) Change subject: WIP ...................................................................... WIP Change-Id: Icaabb2206d6f141d4fba47dedf71f8ec37e6257d --- A doc/hnodeb.msc M include/osmocom/hnodeb/Makefile.am A include/osmocom/hnodeb/hnb_prim.h M include/osmocom/hnodeb/hnodeb.h A include/osmocom/hnodeb/llsk.h M include/osmocom/hnodeb/vty.h M src/osmo-hnodeb/Makefile.am M src/osmo-hnodeb/debug.c M src/osmo-hnodeb/hnb.c M src/osmo-hnodeb/hnb_shutdown_fsm.c M src/osmo-hnodeb/hnbap.c A src/osmo-hnodeb/llsk.c A src/osmo-hnodeb/llsk_ctl.c A src/osmo-hnodeb/llsk_iuh.c M src/osmo-hnodeb/main.c M src/osmo-hnodeb/vty.c 16 files changed, 1,041 insertions(+), 2 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-hnodeb refs/changes/57/26357/1 diff --git a/doc/hnodeb.msc b/doc/hnodeb.msc new file mode 100644 index 0000000..a5f7a86 --- /dev/null +++ b/doc/hnodeb.msc @@ -0,0 +1,62 @@ +msc { + hscale="3"; + ue [label="Customer"], trx [label="Lower Layer TRX"], hnodeb [label="osmo-hnodeb"], hnbgw [label="HNBGW"], ggsn [label="GGSN"], mgw [label="MGW"]; + + |||; + --- [ label = "HnodeB starts up" ]; + hnodeb => hnbgw [label="HNBAP HnbRegisterRequest"]; + hnodeb <= hnbgw [label="HNBAP HnbRegisterResponse"]; + trx <= hnodeb [label="REQ(SAPI_IUH, HNB_IUH_PRIM_CONFIGURE)(PLMN,LAC,SAC,RAC,CI,RNC_ID from HnbRegisterResponse)"]; + |||; + ...; + |||; + |||; + --- [ label = "Subscriber Sign Up" ]; + ue => trx [label="..."]; + trx => hnodeb [label="IND(SAPI_IUH, CONN_ESTABLISH)[RANAP?]"]; + hnodeb => hnbgw [label="HNBAP UE Register Req(IMSI?)"]; + hnodeb <= hnbgw [label="HNBAP UE Register Acc(context_id)"]; + trx <= hnodeb [label="CNF(SAPI_IUH, CONN_ESTABLISH)(context_id])"]; + trx => hnodeb [label="IND(SAPI_IUH, CONN_DATA)[RANAP GMM ServiceRequest]"]; + hnodeb => hnbgw [label="RANAP GMM ServiceRequest"]; + hnodeb <= hnbgw [label="RANAP GMM ServiceAccept"]; + trx <= hnodeb [label="REQ(SAPI_IUH, CONN_DATA)[RANAP GMM ServiceAccept]"]; + --- [ label = "Subscriber set up PS data:" ]; + hnodeb <= hnbgw [label="RANAP RAB-Assignment Request(TEI, ADDR)"]; + trx <= hnodeb [label="REQ(SAPI_IUH, CONN_DATA)[RANAP RAB-Assignment Request(remote_ip, remote_port, remote_tei)]"]; + trx => hnodeb [label="IND(SAPI_GTP, CONN_ESTABLISH)(remote_ip,remote_port,remote_tei)"]; + ... [ label = "HnodeB sets up GTP-U connection" ]; + trx <= hnodeb [label="CNF(SAPI_GTP, CONN_ESTABLISH)(local_ip,local_port,local_tei,remote_tei)"]; + |||; + ...; + |||; + --- [ label = "PS data transmission over GTP-U:" ]; + ue => trx [label="..."]; + trx => hnodeb [label="IND(SAPI_GTP, CONN_DATA)[remote_tei,payload]"]; + hnodeb => ggsn [label="GTP-U(remote_tei, local_addr, remote_addr, payload)"]; + hnodeb <= ggsn [label="GTP-U(local_tei, remote_addr, local_addr, payload)"]; + trx <= hnodeb [label="REQ(SAPI_GTP, CONN_DATA)[local_tei,payload]"]; + ue <= trx [label="..."]; + |||; + ...; + |||; + --- [ label = "MO/MT PS data Release:" ]; + ue => trx [label="..."]; + trx => hnodeb [label="IND(SAPI_IUH, CONN_DATA)[RANAP IU Release Request]"]; + hnodeb => hnbgw [label="RANAP IU Release Request"]; + hnodeb <= hnbgw [label="RANAP IU Release Command"]; + trx <= hnodeb [label="REQ(SAPI_IUH, CONN_DATA)[RANAP IU Release Command]"]; + ...; + trx => hnodeb [label="IND(SAPI_GTP, CONN_RELEASE)(remote_tei)"]; + trx <= hnodeb [label="CNF(SAPI_GTP, CONN_RELEASE)(remote_tei)"]; + + trx => hnodeb [label="IND(SAPI_IUH, CONN_RELEASE)[RANAP IU Release Complete]"]; + hnodeb => hnbgw [label="RANAP IU Release Complete"]; + trx => hnodeb [label="CNF(SAPI_GTP, CONN_RELEASE)"]; + + |||; + ...; + |||; + --- [ label = "For voice call (CS): Similar to SAPI_GTP, but using SAPI_AUDIO and osmo-hnodeb sets up RTP stream" ]; + +} diff --git a/include/osmocom/hnodeb/Makefile.am b/include/osmocom/hnodeb/Makefile.am index b1acb7e..56d1033 100644 --- a/include/osmocom/hnodeb/Makefile.am +++ b/include/osmocom/hnodeb/Makefile.am @@ -1,8 +1,10 @@ noinst_HEADERS = \ hnb_shutdown_fsm.h \ + hnb_prim.h \ hnbap.h \ hnodeb.h \ iuh.h \ + llsk.h \ nas.h \ ranap.h \ rua.h \ diff --git a/include/osmocom/hnodeb/hnb_prim.h b/include/osmocom/hnodeb/hnb_prim.h new file mode 100644 index 0000000..8cede29 --- /dev/null +++ b/include/osmocom/hnodeb/hnb_prim.h @@ -0,0 +1,271 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin at sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/lienses/>. + * + */ +#pragma once + +#include <inttypes.h> +#include <unistd.h> +#include <stdint.h> + +#include <osmocom/core/prim.h> + +#define HNB_PRIM_API_VERSION 0 +#define HNB_PRIM_UD_SOCK_DEFAULT "/tmp/hnb_prim_sock" + +#define HNB_PRIM_SAPI_CTL 0 +#define HNB_PRIM_SAPI_IUH 1 +#define HNB_PRIM_SAPI_GTP 2 +#define HNB_PRIM_SAPI_AUDIO 3 + +struct hnb_prim_hdr { + uint32_t sap; /*!< Service Access Point Identifier */ + uint32_t primitive; /*!< Primitive number */ + uint32_t operation; /*! Primitive Operation */ +} __attribute__ ((packed)); + +/*! \brief HNB_CTL primitives */ +enum hnb_ctl_prim_type { + HNB_CTL_PRIM_HELLO, + _HNB_CTL_PRIM_MAX +}; + +/* HNB_CTL_PRIM_HELLO, UL */ +struct hnb_ctl_hello_ind_param { + uint16_t api_version; /* see HNB_PRIM_API_VERSION */ +} __attribute__ ((packed)); + +/* HNB_CTL_PRIM_HELLO, DL */ +struct hnb_ctl_hello_cnf_param { + uint16_t api_version; /* see HNB_PRIM_API_VERSION */ +} __attribute__ ((packed)); + +struct hnb_ctl_prim { + struct hnb_prim_hdr hdr; + union { + struct hnb_ctl_hello_ind_param hello_ind; + struct hnb_ctl_hello_cnf_param hello_cnf; + } u; +} __attribute__ ((packed)); + + +/*! \brief HNB_IUH primitives */ +enum hnb_iuh_prim_type { + HNB_IUH_PRIM_CONFIGURE, + HNB_IUH_PRIM_CONN_ESTABLISH, + HNB_IUH_PRIM_CONN_RELEASE, + HNB_IUH_PRIM_CONN_DATA, + HNB_IUH_PRIM_UNITDATA, + _HNB_IUH_PRIM_MAX +}; + +/* HNB_IUH_PRIM_CONFIGURE, DL */ +struct hnb_iuh_configure_req_param { + uint16_t mcc; + uint16_t mnc; + uint16_t cell_identity; + uint16_t lac; + uint8_t rac; + uint8_t reserved; + uint16_t sac; + uint16_t rnc_id; +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_ESTABLISH, DL */ +struct hnb_iuh_conn_establish_req_param { + uint32_t context_id; + uint8_t domain; + uint8_t cause; + uint8_t csg_membership_status; + uint8_t spare1; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_ESTABLISH, UL */ +struct hnb_iuh_conn_establish_ind_param { + uint32_t context_id; + uint8_t domain; + uint8_t cause; + /* TODO: Check if we can copy it as an encoded buffer RRC <-> RUA + * RRC: 3GPP TS 25.331 10.3.1.6 Intra Domain NAS Node Selector + * RUA: 3GPP TS 25.468 9.2.4 */ + uint16_t nas_node_selector_bitlen; + uint8_t nas_node_selector[128]; /* TODO: check whether we can decrease this buffer size */ + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_RELEASE, DL */ +struct hnb_iuh_conn_release_req_param { + uint32_t context_id; + uint8_t domain; + uint8_t cause; + uint16_t spare1; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_RELEASE, UL */ +struct hnb_iuh_conn_release_ind_param { + uint32_t context_id; + uint8_t domain; + uint8_t cause; + uint16_t spare1; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_DATA, DL */ +struct hnb_iuh_conn_data_req_param { + uint32_t context_id; + uint8_t domain; + uint8_t spare1; + uint16_t spare2; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_CONN_DATA, UL */ +struct hnb_iuh_conn_data_ind_param { + uint32_t context_id; + uint8_t domain; + uint8_t spare1; + uint16_t spare2; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_UNITDATA, DL */ +struct hnb_iuh_unitdata_req_param { + uint8_t domain; + uint8_t spare1; + uint16_t spare2; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +/* HNB_IUH_PRIM_UNITDATA, UL */ +struct hnb_iuh_unitdata_ind_param { + uint8_t domain; + uint8_t spare1; + uint16_t spare2; + uint32_t data_len; /* RANAP message length in bytes */ + char data[0]; /* RANAP message */ +} __attribute__ ((packed)); + +struct hnb_iuh_prim { + struct hnb_prim_hdr hdr; + union { + struct hnb_iuh_configure_req_param configure_req; + struct hnb_iuh_conn_establish_req_param conn_establish_req; + struct hnb_iuh_conn_establish_ind_param conn_establish_ind; + struct hnb_iuh_conn_release_req_param conn_reelase_req; + struct hnb_iuh_conn_release_ind_param conn_release_ind; + struct hnb_iuh_conn_data_req_param conn_data_req; + struct hnb_iuh_conn_data_ind_param conn_data_ind; + struct hnb_iuh_unitdata_req_param unitdata_req; + struct hnb_iuh_unitdata_ind_param unitdata_ind; + } u; +} __attribute__ ((packed)); + +struct hnb_iuh_prim *hnb_iuh_makeprim_conn_establish_req(uint32_t context_id, + uint8_t domain, + uint8_t cause, + uint8_t csg_membership_status, + uint8_t *data, + uint32_t data_len); +struct hnb_iuh_prim *hnb_iuh_makeprim_conn_release_req(uint32_t context_id, + uint8_t domain, + uint8_t cause, + uint8_t *data, + uint32_t data_len); +struct hnb_iuh_prim *hnb_iuh_makeprim_conn_data_req(uint32_t context_id, + uint8_t domain, + uint8_t *data, + uint32_t data_len); +struct hnb_iuh_prim *hnb_iuh_makeprim_unitdata_req(uint8_t domain, + uint8_t *data, + uint32_t data_len); + + +/*! \brief HNB_GTP primitives */ +enum hnb_gtp_prim_type { + HNB_GTP_PRIM_CONN_ESTABLISH, + HNB_GTP_PRIM_CONN_RELEASE, + HNB_GTP_PRIM_CONN_DATA +}; +union u_addr { + struct in_addr v4; + struct in6_addr v6; +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_ESTABLISH, UL */ +struct hnb_gtp_conn_establish_ind_param { + uint32_t remote_tei; + uint16_t remote_gtpu_port; + uint8_t remote_gtpu_address_type; + uint8_t spare1; + union u_addr remote_gtpu_addr; +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_ESTABLISH, DL */ +struct hnb_gtp_conn_establish_cnf_param { + uint32_t remote_tei; + uint32_t local_tei; + uint16_t local_gtpu_port; + uint8_t local_gtpu_address_type; + uint8_t spare1; + union u_addr remote_gtpu_addr; +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_RELEASE, UL */ +struct hnb_gtp_conn_release_ind_param { + uint32_t remote_tei; +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_RELEASE, DL */ +struct hnb_gtp_conn_release_cnf_param { + uint32_t remote_tei; +} __attribute__ ((packed)); +struct hnb_gtp_conn_release_req_param { + uint32_t remote_tei; +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_DATA, DL */ +struct hnb_gtp_conn_data_req_param { + uint32_t local_tei; + uint32_t data_len; /* GTP-U payload length in bytes */ + char data[0]; /* GTP-U payload (aka IP packet) */ +} __attribute__ ((packed)); + +/* HNB_GTP_PRIM_CONN_DATA, UL */ +struct hnb_gtp_conn_data_ind_param { + uint32_t remote_tei; + uint32_t data_len; /* GTP-U payload length in bytes */ + char data[0]; /* GTP-U payload (aka IP packet) */ +} __attribute__ ((packed)); + +struct hnb_gtp_prim { + struct hnb_prim_hdr hdr; + union { + struct hnb_gtp_conn_establish_ind_param conn_establish_ind; + struct hnb_gtp_conn_establish_cnf_param conn_establish_cnf; + struct hnb_gtp_conn_data_req_param conn_data_req; + struct hnb_gtp_conn_data_ind_param conn_data_ind; + } u; +} __attribute__ ((packed)); diff --git a/include/osmocom/hnodeb/hnodeb.h b/include/osmocom/hnodeb/hnodeb.h index 3bc2fb3..186798b 100644 --- a/include/osmocom/hnodeb/hnodeb.h +++ b/include/osmocom/hnodeb/hnodeb.h @@ -28,12 +28,15 @@ #include <osmocom/gsm/gsm23003.h> #include <osmocom/netif/stream.h> +#include <osmocom/hnodeb/llsk.h> + enum { DMAIN, DHNBAP, DRUA, DRANAP, DSCTP, + DLLSK, DNAS, }; extern const struct log_info hnb_log_info; @@ -59,7 +62,14 @@ struct osmo_stream_cli *client; } iuh; + /* Lower Layer UD socket */ + struct { + char *sock_path; + struct llsk_state *state; + } llsk; + uint16_t rnc_id; + bool registered; /* Set to true once HnbRegisterAccept was received from Iuh. rnc_id is valid iif registered==true */ uint32_t ctx_id; diff --git a/include/osmocom/hnodeb/llsk.h b/include/osmocom/hnodeb/llsk.h new file mode 100644 index 0000000..a08c006 --- /dev/null +++ b/include/osmocom/hnodeb/llsk.h @@ -0,0 +1,56 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin at sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/lienses/>. + * + */ +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/linuxlist.h> + +#include <osmocom/hnodeb/hnb_prim.h> + +struct hnb; +struct msgb; + +struct llsk_state { + struct hnb *hnb; + struct osmo_fd listen_bfd; /* fd for listen socket */ + struct osmo_fd conn_bfd; /* fd for connection to lcr */ + struct llist_head upqueue; /* queue for sending messages */ +}; + +struct llsk_state *llsk_alloc(struct hnb *hnb); +int llsk_open(struct llsk_state *state, const char *path); +void llsk_free(struct llsk_state *state); + +int llsk_send(struct llsk_state *state, struct msgb *msg); +bool llsk_connected(const struct llsk_state *state); + + +extern const struct value_string hnb_ctl_prim_type_names[]; +int llsk_rx_ctl(struct hnb *hnb, struct hnb_ctl_prim *ctl, unsigned len); + + +extern const struct value_string hnb_iuh_prim_type_names[]; +int llsk_rx_iuh(struct hnb *hnb, struct hnb_iuh_prim *iuh, unsigned len); +struct msgb *hnb_iuh_makeprim_configure_req(uint16_t mcc, uint16_t mnc, + uint16_t cell_identity, + uint16_t lac, uint8_t rac, + uint16_t sac, uint16_t rnc_id); diff --git a/include/osmocom/hnodeb/vty.h b/include/osmocom/hnodeb/vty.h index 7144d3f..1624bfb 100644 --- a/include/osmocom/hnodeb/vty.h +++ b/include/osmocom/hnodeb/vty.h @@ -27,6 +27,7 @@ enum hnb_vty_nodes { HNODEB_NODE = _LAST_OSMOVTY_NODE, IUH_NODE, + LLSK_NODE, CHAN_NODE, }; diff --git a/src/osmo-hnodeb/Makefile.am b/src/osmo-hnodeb/Makefile.am index 7325728..0117f28 100644 --- a/src/osmo-hnodeb/Makefile.am +++ b/src/osmo-hnodeb/Makefile.am @@ -35,6 +35,9 @@ hnb.c \ hnb_shutdown_fsm.c \ iuh.c \ + llsk.c \ + llsk_ctl.c \ + llsk_iuh.c \ nas.c \ ranap.c \ rua.c \ diff --git a/src/osmo-hnodeb/debug.c b/src/osmo-hnodeb/debug.c index 10d1655..e610ff0 100644 --- a/src/osmo-hnodeb/debug.c +++ b/src/osmo-hnodeb/debug.c @@ -24,7 +24,7 @@ static const struct log_info_cat log_cat[] = { [DMAIN] = { .name = "DMAIN", .loglevel = LOGL_NOTICE, .enabled = 1, - .color = "", + .color = "\033[1;37m", .description = "Main program", }, [DHNBAP] = { @@ -47,6 +47,11 @@ .color = "\033[1;36m", .description = "SCTP connection on the Iuh link", }, + [DLLSK] = { + .name = "DLLSK", .loglevel = LOGL_NOTICE, .enabled = 1, + .color = "\033[1;31m", + .description = "Lower Layer Unix Domain Socket", + }, [DNAS] = { .name = "NAS", .loglevel = LOGL_NOTICE, .enabled = 1, .color = "\033[1;32m", diff --git a/src/osmo-hnodeb/hnb.c b/src/osmo-hnodeb/hnb.c index b7be4ea..d7cf886 100644 --- a/src/osmo-hnodeb/hnb.c +++ b/src/osmo-hnodeb/hnb.c @@ -27,6 +27,7 @@ #include <osmocom/hnodeb/hnodeb.h> #include <osmocom/hnodeb/iuh.h> #include <osmocom/hnodeb/hnb_shutdown_fsm.h> +#include <osmocom/hnodeb/hnb_prim.h> struct hnb *hnb_alloc(void *tall_ctx) @@ -43,6 +44,9 @@ .mnc = 1, }; + hnb->llsk.sock_path = talloc_strdup(hnb, HNB_PRIM_UD_SOCK_DEFAULT); + hnb->llsk.state = llsk_alloc(hnb); + hnb->shutdown_fi = osmo_fsm_inst_alloc(&hnb_shutdown_fsm, hnb, hnb, LOGL_INFO, NULL); @@ -58,5 +62,9 @@ hnb->shutdown_fi = NULL; } hnb_iuh_free(hnb); + + llsk_free(hnb->llsk.state); + hnb->llsk.state = NULL; + talloc_free(hnb); } diff --git a/src/osmo-hnodeb/hnb_shutdown_fsm.c b/src/osmo-hnodeb/hnb_shutdown_fsm.c index 7df591e..14e8b32 100644 --- a/src/osmo-hnodeb/hnb_shutdown_fsm.c +++ b/src/osmo-hnodeb/hnb_shutdown_fsm.c @@ -34,6 +34,11 @@ static void st_none_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) { struct hnb *hnb = (struct hnb *)fi->priv; + + /* Reset state: */ + hnb->registered = false; + hnb->rnc_id = 0; + hnb_iuh_connect(hnb); /* Start reconnect once we are done with shutdown and we didn't exit process */ } diff --git a/src/osmo-hnodeb/hnbap.c b/src/osmo-hnodeb/hnbap.c index 0495ce0..4ea1342 100644 --- a/src/osmo-hnodeb/hnbap.c +++ b/src/osmo-hnodeb/hnbap.c @@ -39,16 +39,31 @@ { int rc; HNBAP_HNBRegisterAcceptIEs_t accept; + struct msgb *llsk_msg; rc = hnbap_decode_hnbregisteraccepties(&accept, in); if (rc < 0) { + hnb_shutdown(hnb, "Failed decoding HnbRegisterAccept IEs", false); + return rc; } hnb->rnc_id = accept.rnc_id; + hnb->registered = true; LOGP(DHNBAP, LOGL_INFO, "Rx HNB Register accept with RNC ID %u\n", hnb->rnc_id); hnbap_free_hnbregisteraccepties(&accept); - return 0; + + if (llsk_connected(hnb->llsk.state)) { + /* We are attached to the HNBGW, configure lower layers: */ + llsk_msg = hnb_iuh_makeprim_configure_req(hnb->plmn.mcc, hnb->plmn.mnc, + hnb->cell_identity, hnb->lac, + hnb->rac, hnb->sac, hnb->rnc_id); + rc = llsk_send(hnb->llsk.state, llsk_msg); + if (rc < 0) { + LOGP(DHNBAP, LOGL_NOTICE, "Failed configuring lower layers after HnbRegisterAccept\n"); + } + } + return rc; } static int hnb_rx_hnb_register_rej(struct hnb *hnb, ANY_t *in) @@ -56,6 +71,8 @@ int rc; HNBAP_HNBRegisterRejectIEs_t reject; + hnb->registered = false; + rc = hnbap_decode_hnbregisterrejecties(&reject, in); if (rc < 0) { LOGP(DHNBAP, LOGL_NOTICE, "Rx HNB Register Reject: parse failure\n"); diff --git a/src/osmo-hnodeb/llsk.c b/src/osmo-hnodeb/llsk.c new file mode 100644 index 0000000..dee7477 --- /dev/null +++ b/src/osmo-hnodeb/llsk.c @@ -0,0 +1,306 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin at sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/lienses/>. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <inttypes.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> + +#include <osmocom/hnodeb/hnodeb.h> +#include <osmocom/hnodeb/llsk.h> +#include <osmocom/hnodeb/hnb_prim.h> + +static void llsk_close(struct llsk_state *state); + +struct llsk_state *llsk_alloc(struct hnb *hnb) +{ + struct llsk_state *state; + state = talloc_zero(NULL, struct llsk_state); + if (!state) + return NULL; + + INIT_LLIST_HEAD(&state->upqueue); + state->hnb = hnb; + state->conn_bfd.fd = -1; + state->listen_bfd.fd = -1; + return state; +} + +void llsk_free(struct llsk_state *state) +{ + struct osmo_fd *bfd, *conn_bfd; + + if (!state) + return; + + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd > 0) + llsk_close(state); + bfd = &state->listen_bfd; + close(bfd->fd); + osmo_fd_unregister(bfd); + talloc_free(state); +} + +static int llsk_rx(struct hnb *hnb, struct hnb_prim_hdr *hdr, unsigned len) +{ + switch (hdr->sap) { + case HNB_PRIM_SAPI_CTL: + return llsk_rx_ctl(hnb, ((struct hnb_ctl_prim *)hdr), len); + case HNB_PRIM_SAPI_IUH: + return llsk_rx_iuh(hnb, ((struct hnb_iuh_prim *)hdr), len); + case HNB_PRIM_SAPI_GTP: + case HNB_PRIM_SAPI_AUDIO: + LOGP(DLLSK, LOGL_ERROR, "Rx SAPI %u not yet implemented (len=%u)\n", hdr->sap, len); + return -EINVAL; + default: + LOGP(DLLSK, LOGL_ERROR, "Rx for unknwon SAPI %u (len=%u)\n", hdr->sap, len); + return -EINVAL; + } +} + +static int llsk_read(struct osmo_fd *bfd) +{ + struct llsk_state *state = (struct llsk_state *)bfd->data; + struct hnb_prim_hdr *hdr; + struct msgb *msg; + int rc; + + msg = msgb_alloc(1000, "llsk_rx"); + if (!msg) + return -ENOMEM; + + hdr = (struct hnb_prim_hdr *) msg->tail; + + rc = recv(bfd->fd, msg->tail, msgb_tailroom(msg), 0); + if (rc == 0) + goto close; + + if (rc < 0) { + if (errno == EAGAIN) { + msgb_free(msg); + return 0; + } + goto close; + } + + if (rc < sizeof(struct hnb_prim_hdr)) { + LOGP(DLLSK, LOGL_ERROR, "Received %d bytes on PCU Socket, but primitive hdr size " + "is %zu, discarding\n", rc, sizeof(struct hnb_prim_hdr)); + msgb_free(msg); + return 0; + } + + rc = llsk_rx(state->hnb, hdr, rc); + + /* as we always synchronously process the message in llsk_rx() and + * its callbacks, we can free the message here. */ + msgb_free(msg); + + return rc; + +close: + msgb_free(msg); + llsk_close(state); + return -1; +} + +static int llsk_write(struct osmo_fd *bfd) +{ + struct llsk_state *state = bfd->data; + int rc; + + while (!llist_empty(&state->upqueue)) { + struct msgb *msg, *msg2; + + /* peek at the beginning of the queue */ + msg = llist_entry(state->upqueue.next, struct msgb, list); + + osmo_fd_write_disable(bfd); + + if (!msgb_length(msg)) { + LOGP(DLLSK, LOGL_ERROR, "message with ZERO " + "bytes!\n"); + goto dontsend; + } + + /* try to send it over the socket */ + rc = write(bfd->fd, msgb_data(msg), msgb_length(msg)); + if (rc == 0) + goto close; + if (rc < 0) { + if (errno == EAGAIN) { + osmo_fd_write_enable(bfd); + break; + } + goto close; + } + +dontsend: + /* _after_ we send it, we can deueue */ + msg2 = msgb_dequeue(&state->upqueue); + assert(msg == msg2); + msgb_free(msg); + } + return 0; + +close: + llsk_close(state); + + return -1; +} + +static int llsk_bfd_cb(struct osmo_fd *bfd, unsigned int flags) +{ + int rc = 0; + + if (flags & OSMO_FD_READ) + rc = llsk_read(bfd); + if (rc < 0) + return rc; + + if (flags & OSMO_FD_WRITE) + rc = llsk_write(bfd); + + return rc; +} + +/* accept connection coming from PCU */ +static int llsk_accept(struct osmo_fd *bfd, unsigned int flags) +{ + struct llsk_state *state = (struct llsk_state *)bfd->data; + struct osmo_fd *conn_bfd = &state->conn_bfd; + struct sockaddr_un un_addr; + socklen_t len; + int rc; + + len = sizeof(un_addr); + rc = accept(bfd->fd, (struct sockaddr *) &un_addr, &len); + if (rc < 0) { + LOGP(DLLSK, LOGL_ERROR, "Failed to accept a new connection\n"); + return -1; + } + + if (conn_bfd->fd >= 0) { + LOGP(DLLSK, LOGL_NOTICE, "LL UD Socket connects but we already have " + "another active connection ?!?\n"); + /* We already have one LLSK connected, this is all we support */ + state->listen_bfd.when &= ~OSMO_FD_READ; + close(rc); + return 0; + } + + osmo_fd_setup(conn_bfd, rc, OSMO_FD_READ, llsk_bfd_cb, state, 0); + + if (osmo_fd_register(conn_bfd) != 0) { + LOGP(DLLSK, LOGL_ERROR, "Failed to register new connection fd\n"); + close(conn_bfd->fd); + conn_bfd->fd = -1; + return -1; + } + + LOGP(DLLSK, LOGL_NOTICE, "LL UD socket connected\n"); + + return 0; +} + +int llsk_open(struct llsk_state *state, const char *path) +{ + struct osmo_fd *bfd = &state->listen_bfd; + int rc; + + rc = osmo_sock_unix_init(SOCK_SEQPACKET, 0, path, OSMO_SOCK_F_BIND); + if (rc < 0) { + LOGP(DLLSK, LOGL_ERROR, "Could not create %s unix socket: %s\n", + path, strerror(errno)); + return -1; + } + + osmo_fd_setup(bfd, rc, OSMO_FD_READ, llsk_accept, state, 0); + + rc = osmo_fd_register(bfd); + if (rc < 0) { + LOGP(DLLSK, LOGL_ERROR, "Could not register listen fd: %d\n", + rc); + close(bfd->fd); + bfd->fd = -1; + return rc; + } + + LOGP(DLLSK, LOGL_INFO, "Started listening on Lower Layer Unix Domain Socket: %s\n", path); + + return 0; +} + +static void llsk_close(struct llsk_state *state) +{ + struct osmo_fd *bfd = &state->conn_bfd; + + close(bfd->fd); + bfd->fd = -1; + osmo_fd_unregister(bfd); + + /* re-enable the generation of ACCEPT for new connections */ + osmo_fd_read_enable(&state->listen_bfd); + + /* flush the queue */ + while (!llist_empty(&state->upqueue)) { + struct msgb *msg = msgb_dequeue(&state->upqueue); + msgb_free(msg); + } +} + +int llsk_send(struct llsk_state *state, struct msgb *msg) +{ + struct osmo_fd *conn_bfd; + + if (!state) { + LOGP(DLLSK, LOGL_INFO, "LL UD socket not created, dropping message\n"); + msgb_free(msg); + return -EINVAL; + } + conn_bfd = &state->conn_bfd; + if (conn_bfd->fd <= 0) { + LOGP(DLLSK, LOGL_NOTICE, "LL UD socket not connected, dropping message\n"); + msgb_free(msg); + return -EIO; + } + msgb_enqueue(&state->upqueue, msg); + osmo_fd_write_enable(conn_bfd); + + return 0; +} + +bool llsk_connected(const struct llsk_state *state) +{ + if (!state) + return false; + if (state->conn_bfd.fd <= 0) + return false; + return true; +} diff --git a/src/osmo-hnodeb/llsk_ctl.c b/src/osmo-hnodeb/llsk_ctl.c new file mode 100644 index 0000000..c6b0f55 --- /dev/null +++ b/src/osmo-hnodeb/llsk_ctl.c @@ -0,0 +1,144 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin at sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/lienses/>. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <inttypes.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> + +#include <osmocom/hnodeb/hnodeb.h> +#include <osmocom/hnodeb/llsk.h> +#include <osmocom/hnodeb/hnb_prim.h> + +static size_t llsk_ctl_prim_size_tbl[4][_HNB_CTL_PRIM_MAX] = { + [PRIM_OP_REQUEST] = {}, + [PRIM_OP_RESPONSE] = {}, + [PRIM_OP_INDICATION] = { + [HNB_CTL_PRIM_HELLO] = sizeof(struct hnb_ctl_hello_ind_param), + }, + [PRIM_OP_CONFIRM] = { + [HNB_CTL_PRIM_HELLO] = sizeof(struct hnb_ctl_hello_cnf_param), + }, +}; +static inline size_t llsk_ctl_prim_size(enum hnb_ctl_prim_type ptype, enum osmo_prim_operation op) +{ + return llsk_ctl_prim_size_tbl[op][ptype]; +} + +const struct value_string hnb_ctl_prim_type_names[] = { + OSMO_VALUE_STRING(HNB_CTL_PRIM_HELLO), + { 0, NULL } +}; + +struct msgb *llsk_ctl_msgb_alloc(enum hnb_ctl_prim_type ptype, enum osmo_prim_operation op) +{ + struct msgb *msg; + struct hnb_ctl_prim *ctl_prim; + size_t len = sizeof(ctl_prim->hdr) + llsk_ctl_prim_size(ptype, op); + + msg = msgb_alloc(len, "llsk_ctl_tx"); + if (!msg) + return NULL; + msgb_put(msg, len); + ctl_prim = (struct hnb_ctl_prim *) msg->data; + ctl_prim->hdr.sap = HNB_PRIM_SAPI_CTL; + ctl_prim->hdr.primitive = ptype; + ctl_prim->hdr.operation = op; + + return msg; +} + +static struct msgb *hnb_ctl_makeprim_hello_cnf(uint16_t api_version) +{ + struct msgb *msg; + struct hnb_ctl_prim *ctl_prim; + + msg = llsk_ctl_msgb_alloc(HNB_CTL_PRIM_HELLO, PRIM_OP_CONFIRM); + ctl_prim = (struct hnb_ctl_prim *)msgb_data(msg); + ctl_prim->u.hello_cnf.api_version = api_version; + + return msg; +} + +static int llsk_rx_ctl_hello_ind(struct hnb *hnb, struct hnb_ctl_hello_ind_param *hello_ind) +{ + struct msgb *llsk_msg; + int rc; + + LOGP(DLLSK, LOGL_NOTICE, "Rx CTL::HELLO.ind API_VERSION=%u\n", hello_ind->api_version); + + LOGP(DLLSK, LOGL_NOTICE, "Tx CTL::HELLO.cnf API_VERSION=%u\n", hello_ind->api_version); + llsk_msg = hnb_ctl_makeprim_hello_cnf(HNB_CTL_PRIM_HELLO); + if ((rc = llsk_send(hnb->llsk.state, llsk_msg)) < 0) + LOGP(DHNBAP, LOGL_ERROR, "Failed sending CTL::HELLO.cnf\n"); + + if (hnb->registered) { + LOGP(DLLSK, LOGL_INFO, "Tx CTL::CONFIGURE.req API_VERSION=%u\n", hello_ind->api_version); + /* We are already registered, so configure the lower layers right now */ + llsk_msg = hnb_iuh_makeprim_configure_req(hnb->plmn.mcc, hnb->plmn.mnc, + hnb->cell_identity, hnb->lac, + hnb->rac, hnb->sac, hnb->rnc_id); + if ((rc = llsk_send(hnb->llsk.state, llsk_msg)) < 0) + LOGP(DHNBAP, LOGL_ERROR, "Failed sending IUH::CONFIGURE.req\n"); + } + return rc; +} + +int llsk_rx_ctl(struct hnb *hnb, struct hnb_ctl_prim *ctl, unsigned len) +{ + size_t prim_size = sizeof(ctl->hdr) + llsk_ctl_prim_size(ctl->hdr.primitive, ctl->hdr.operation); + + if (len < prim_size) { + LOGP(DLLSK, LOGL_ERROR, "Rx llsk-ctl %s::%s with length %u < %zu\n", + get_value_string(hnb_iuh_prim_type_names, ctl->hdr.primitive), + get_value_string(osmo_prim_op_names, ctl->hdr.operation), + len, prim_size); + return -EINVAL; + } + + switch (ctl->hdr.operation) { + case PRIM_OP_INDICATION: + switch (ctl->hdr.primitive) { + case HNB_CTL_PRIM_HELLO: + return llsk_rx_ctl_hello_ind(hnb, &ctl->u.hello_ind); + default: + LOGP(DLLSK, LOGL_ERROR, "Rx llsk-ctl unknown primitive %u (len=%u)\n", ctl->hdr.primitive, len); + return -EINVAL; + } + break; + + case PRIM_OP_RESPONSE: + case PRIM_OP_REQUEST: + case PRIM_OP_CONFIRM: + default: + LOGP(DLLSK, LOGL_ERROR, "Rx llsk-ctl unexpected primitive operation %s::%s (len=%u)\n", + get_value_string(hnb_iuh_prim_type_names, ctl->hdr.primitive), + get_value_string(osmo_prim_op_names, ctl->hdr.operation), len); + return -EINVAL; + } +} diff --git a/src/osmo-hnodeb/llsk_iuh.c b/src/osmo-hnodeb/llsk_iuh.c new file mode 100644 index 0000000..b5c13d7 --- /dev/null +++ b/src/osmo-hnodeb/llsk_iuh.c @@ -0,0 +1,103 @@ +/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info at sysmocom.de> + * Author: Pau Espin Pedrol <pespin at sysmocom.de> + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/lienses/>. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <inttypes.h> + +#include <osmocom/core/talloc.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> + +#include <osmocom/hnodeb/hnodeb.h> +#include <osmocom/hnodeb/llsk.h> +#include <osmocom/hnodeb/hnb_prim.h> + +static size_t llsk_iuh_prim_size_tbl[4][_HNB_IUH_PRIM_MAX] = { + [PRIM_OP_REQUEST] = { + [HNB_IUH_PRIM_CONFIGURE] = sizeof(struct hnb_iuh_configure_req_param), + }, + [PRIM_OP_RESPONSE] = {}, + [PRIM_OP_INDICATION] = {}, + [PRIM_OP_CONFIRM] = {}, +}; +static inline size_t llsk_iuh_prim_size(enum hnb_iuh_prim_type ptype, enum osmo_prim_operation op) +{ + return llsk_iuh_prim_size_tbl[op][ptype]; +} + +const struct value_string hnb_iuh_prim_type_names[] = { + OSMO_VALUE_STRING(HNB_IUH_PRIM_CONFIGURE), + { 0, NULL } +}; + + +struct msgb *llsk_iuh_msgb_alloc(enum hnb_iuh_prim_type ptype, enum osmo_prim_operation op) +{ + struct msgb *msg; + struct hnb_iuh_prim *iuh_prim; + size_t len = sizeof(iuh_prim->hdr) + llsk_iuh_prim_size(ptype, op); + + msg = msgb_alloc(len, "llsk_iuh_tx"); + if (!msg) + return NULL; + msgb_put(msg, len); + iuh_prim = (struct hnb_iuh_prim *) msg->data; + iuh_prim->hdr.sap = HNB_PRIM_SAPI_IUH; + iuh_prim->hdr.primitive = ptype; + iuh_prim->hdr.operation = op; + + return msg; +} + +struct msgb *hnb_iuh_makeprim_configure_req(uint16_t mcc, uint16_t mnc, + uint16_t cell_identity, + uint16_t lac, uint8_t rac, + uint16_t sac, uint16_t rnc_id) +{ + struct msgb *msg; + struct hnb_iuh_prim *iuh_prim; + + msg = llsk_iuh_msgb_alloc(HNB_IUH_PRIM_CONFIGURE, PRIM_OP_REQUEST); + iuh_prim = (struct hnb_iuh_prim *)msgb_data(msg); + iuh_prim->u.configure_req.mcc = mcc; + iuh_prim->u.configure_req.mnc = mnc; + iuh_prim->u.configure_req.cell_identity = cell_identity; + iuh_prim->u.configure_req.lac = lac; + iuh_prim->u.configure_req.rac = rac; + iuh_prim->u.configure_req.sac = sac; + iuh_prim->u.configure_req.rnc_id = rnc_id; + + return msg; +} + +int llsk_rx_iuh(struct hnb *hnb, struct hnb_iuh_prim *iuh, unsigned len) +{ + switch (iuh->hdr.primitive) { + default: + LOGP(DLLSK, LOGL_ERROR, "Rx llsk-iuh unknown primitive %u (len=%u)\n", iuh->hdr.primitive, len); + return -EINVAL; + } +} diff --git a/src/osmo-hnodeb/main.c b/src/osmo-hnodeb/main.c index 12d98ff..b8a883b 100644 --- a/src/osmo-hnodeb/main.c +++ b/src/osmo-hnodeb/main.c @@ -265,6 +265,13 @@ exit(1); } + /* Start listening on lower layer unix domain socket: */ + rc = llsk_open(g_hnb->llsk.state, g_hnb->llsk.sock_path); + if (rc < 0) { + perror("Error opening lower layer socket"); + exit(1); + } + rc = hnb_iuh_connect(g_hnb); if (rc < 0) { perror("Error connecting to Iuh port"); diff --git a/src/osmo-hnodeb/vty.c b/src/osmo-hnodeb/vty.c index e11fc2d..1331e59 100644 --- a/src/osmo-hnodeb/vty.c +++ b/src/osmo-hnodeb/vty.c @@ -47,6 +47,10 @@ vty->node = HNODEB_NODE; vty->index = g_hnb; break; + case LLSK_NODE: + vty->node = HNODEB_NODE; + vty->index = g_hnb; + break; case HNODEB_NODE: vty->node = CONFIG_NODE; vty->index = g_hnb; @@ -247,6 +251,36 @@ return CMD_SUCCESS; } +static struct cmd_node llsk_node = { + LLSK_NODE, + "%s(config-ll-socket)# ", + 1, +}; + +#define LLSK_STR "Configure the Lower Layer Unix Domain Socket\n" + +DEFUN(cfg_hnodeb_llsk, + cfg_hnodeb_llsk_cmd, + "ll-socket", LLSK_STR) +{ + OSMO_ASSERT(g_hnb); + vty->index = g_hnb; + vty->node = LLSK_NODE; + + return CMD_SUCCESS; +} + +DEFUN(cfg_hnodeb_llsk_path, cfg_hnodeb_llsk_path_cmd, + "path PATH", + "Configure the Lower Layer Unix Domain Socket path\n" + "UNIX socket path\n") +{ + osmo_talloc_replace_string(g_hnb, &g_hnb->llsk.sock_path, argv[0]); + + /* FIXME: re-open the interface? */ + return CMD_SUCCESS; +} + static int config_write_hnodeb(struct vty *vty) { @@ -266,6 +300,8 @@ vty_out(vty, " local-port %u%s", g_hnb->iuh.local_port, VTY_NEWLINE); vty_out(vty, " remote-ip %s%s", g_hnb->iuh.remote_addr, VTY_NEWLINE); vty_out(vty, " remote-port %u%s", g_hnb->iuh.remote_port, VTY_NEWLINE); + vty_out(vty, " ll-socket%s", VTY_NEWLINE); + vty_out(vty, " path %s%s", g_hnb->llsk.sock_path, VTY_NEWLINE); return CMD_SUCCESS; } @@ -387,6 +423,9 @@ install_element(IUH_NODE, &cfg_hnodeb_iuh_local_port_cmd); install_element(IUH_NODE, &cfg_hnodeb_iuh_remote_ip_cmd); install_element(IUH_NODE, &cfg_hnodeb_iuh_remote_port_cmd); + install_element(HNODEB_NODE, &cfg_hnodeb_llsk_cmd); + install_node(&llsk_node, NULL); + install_element(LLSK_NODE, &cfg_hnodeb_llsk_path_cmd); install_element_ve(&asn_dbg_cmd); install_element_ve(&hnb_register_cmd); -- To view, visit https://gerrit.osmocom.org/c/osmo-hnodeb/+/26357 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-hnodeb Gerrit-Branch: master Gerrit-Change-Id: Icaabb2206d6f141d4fba47dedf71f8ec37e6257d Gerrit-Change-Number: 26357 Gerrit-PatchSet: 1 Gerrit-Owner: pespin <pespin at sysmocom.de> Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20211124/536b8c0b/attachment.htm>