diff --git a/TODO-RELEASE b/TODO-RELEASE index 0ed7189..c2470bd 100644 --- a/TODO-RELEASE +++ b/TODO-RELEASE @@ -7,3 +7,5 @@ # If any interfaces have been added since the last public release: c:r:a + 1. # If any interfaces have been removed or changed since the last public release: c:r:0. #library what description / commit summary line +libgtp new API SGSN Ctx Req/Resp/Ack, gtp_encode_pdp_ctx, gtp_decode_pdp_ctx, gtp_set_cb_sgsn_context_request_ind, ... +libgtp new dependency libosmogsm diff --git a/configure.ac b/configure.ac index cc5a459..8d40d66 100644 --- a/configure.ac +++ b/configure.ac @@ -157,6 +157,7 @@ AM_INIT_AUTOMAKE([foreign]) PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.11.0) PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.11.0) +PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.11.0) AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( diff --git a/gtp/Makefile.am b/gtp/Makefile.am index fe5abe8..af75fe5 100644 --- a/gtp/Makefile.am +++ b/gtp/Makefile.am @@ -12,6 +12,7 @@ AM_CFLAGS = \ -DSBINDIR='"$(sbindir)"' \ -I$(top_srcdir)/include \ $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ $(NULL) libgtp_la_SOURCES = \ @@ -19,6 +20,8 @@ libgtp_la_SOURCES = \ gsn_internal.h \ gtp.c \ gtp_internal.h \ + gtp_sgsn_ctx.c \ + gtp_sgsn_ctx.h \ gtpie.c \ lookupa.c \ lookupa.h \ @@ -28,4 +31,4 @@ libgtp_la_SOURCES = \ $(NULL) libgtp_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS) +libgtp_la_LIBADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) diff --git a/gtp/gsn.c b/gtp/gsn.c index 14c86ad..bfbc694 100644 --- a/gtp/gsn.c +++ b/gtp/gsn.c @@ -64,6 +64,7 @@ #include "queue.h" #include "gsn_internal.h" #include "gtp_internal.h" +#include "gtp_sgsn_ctx.h" /* Error reporting functions */ @@ -244,6 +245,30 @@ int gtp_set_cb_data_ind(struct gsn_t *gsn, return 0; } +int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)) +{ + gsn->cb_sgsn_context_request_ind = cb; + return 0; +} + +int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)) +{ + gsn->cb_sgsn_context_response_ind = cb; + return 0; +} + +int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)) +{ + gsn->cb_sgsn_context_ack_ind = cb; + return 0; +} + static int queue_timer_retrans(struct gsn_t *gsn) { /* Retransmit any outstanding packets */ @@ -525,6 +550,10 @@ int gtp_new(struct gsn_t **gsn, char *statedir, struct in_addr *listen, /* Start internal queue timer */ gtp_queue_timer_start(*gsn); + (*gsn)->sgsn_ctx = sgsn_ctx_reqs_init(*gsn, 2048, 4095); + if (!(*gsn)->sgsn_ctx) + goto error; + return 0; error: gtp_free(*gsn); diff --git a/gtp/gtp.c b/gtp/gtp.c index f5d398e..fa65575 100644 --- a/gtp/gtp.c +++ b/gtp/gtp.c @@ -25,6 +25,8 @@ #include #include +#include +#include #if defined(__FreeBSD__) #include @@ -61,6 +63,7 @@ #include "queue.h" #include "gsn_internal.h" +#include "gtp_sgsn_ctx.h" #include "gtp_internal.h" /* Error reporting functions */ @@ -393,6 +396,18 @@ static int gtp_req_transmit(struct gsn_t *gsn, uint8_t version, const struct in_ return 0; } +static int gtp_reqv1c(struct gsn_t *gsn, uint32_t teic, + union gtp_packet *packet, int len, + const struct in_addr *inetaddr, void *cbp) +{ + packet->flags |= GTP1HDR_F_SEQ; + packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT); + packet->gtp1l.h.seq = hton16(gsn->seq_next); + packet->gtp1l.h.tei = hton32(teic); + + return gtp_req_transmit(gsn, 1, inetaddr, packet, len, NULL, cbp); +} + static int gtp_req(struct gsn_t *gsn, uint8_t version, struct pdp_t *pdp, union gtp_packet *packet, int len, const struct in_addr *inetaddr, void *cbp) @@ -552,6 +567,21 @@ static int gtp_resp_pdp(uint8_t version, struct gsn_t *gsn, struct pdp_t *pdp, return gtp_resp(gsn, packet, len, peer, fd, seq, tid, flow, tei); } +static int gtp_respv1c(struct gsn_t *gsn, uint16_t seq, uint32_t remote_teic, + union gtp_packet *packet, int len, + const struct in_addr *inetaddr) +{ + struct sockaddr_in peer = {}; + peer.sin_addr = *inetaddr; + peer.sin_port = htons(GTP1C_PORT); + peer.sin_family = AF_INET; + + packet->flags |= GTP1HDR_F_SEQ | GTP1HDR_F_GTP1; + packet->gtp1l.h.length = hton16(len - GTP1_HEADER_SIZE_SHORT); + + return gtp_resp(gsn, packet, len, &peer, gsn->fd1c, seq, 0, 0, remote_teic); +} + static int gtp_notification(struct gsn_t *gsn, uint8_t version, union gtp_packet *packet, int len, const struct sockaddr_in *peer, int fd, uint16_t seq) @@ -851,6 +881,640 @@ int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *peer, return gtp_notification(gsn, 1, &packet, length, peer, gsn->fd1c, 0); } +#define GSM_MI_TYPE_TLLI 0x05 + +/* Send an SGSN Context Request */ +int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref, + const struct sockaddr_in *peer, union gtpie_member **ie) +{ + union gtp_packet packet = {}; + unsigned int packet_len; + int rc; + uint32_t local_teic; + union gtpie_member req_ie_elem[2] = {}; + void *pack; + unsigned encoded_len; + + rc = sgsn_ctx_req_fsm_tx_req(gsn, peer, ie, GTPIE_SIZE, &local_teic, gsn->seq_next); + if (rc) + return rc; + + packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_REQ, &packet); + req_ie_elem[0].tv4.t = GTPIE_TEI_C; + req_ie_elem[0].tv4.v = htonl(local_teic); + ie[GTPIE_TEI_C] = &req_ie_elem[0]; + + req_ie_elem[1].tlv.t = GTPIE_GSN_ADDR; + req_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc)); + memcpy(&req_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc)); + ie[GTPIE_GSN_ADDR] = &req_ie_elem[1]; + + pack = &packet; + pack += packet_len; + rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len); + if (rc) + return -EINVAL; + + packet_len += encoded_len; + *local_ref = local_teic; + return gtp_reqv1c(gsn, 0, &packet, packet_len, &peer->sin_addr, NULL); +} + +/* Send an SGSN Context Request, extends ies */ +int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref, + union gtpie_member **ie) +{ + uint32_t local_teic = local_ref; + uint32_t remote_teic; + uint16_t seq; + + union gtpie_member resp_ie_elem[2] = {}; + union gtp_packet packet = {}; + unsigned int packet_len; + int rc; + void *pack; + unsigned encoded_len; + struct sockaddr_in peer; + + rc = sgsn_ctx_req_fsm_tx_resp(gsn, &peer, &seq, local_teic, &remote_teic, ie, GTPIE_SIZE); + if (rc) + return rc; + + packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_RSP, &packet); + + resp_ie_elem[0].tv4.t = GTPIE_TEI_C; + resp_ie_elem[0].tv4.v = htonl(local_teic); + ie[GTPIE_TEI_C] = &resp_ie_elem[0]; + + resp_ie_elem[1].tlv.t = GTPIE_GSN_ADDR; + resp_ie_elem[1].tlv.l = htons(sizeof(gsn->gsnc)); + memcpy(&resp_ie_elem[1].tlv.v[0], &gsn->gsnc, sizeof(gsn->gsnc)); + ie[GTPIE_GSN_ADDR] = &resp_ie_elem[1]; + + pack = &packet; + pack += packet_len; + rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len); + if (rc) + return -EINVAL; + + packet_len += encoded_len; + return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr); +} + +int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref, + uint8_t cause) +{ + union gtpie_member *ie[GTP_MAX] = {}; + union gtpie_member causetlv = {}; + causetlv.tv1.t = GTPIE_CAUSE; + causetlv.tv1.v = cause; + ie[0] = &causetlv; + + return gtp_sgsn_context_resp(gsn, local_ref, ie); +} + +/* Send an SGSN Context Ack/Nack. + * Argument **ie could be const, but the other functions take a non-const ie + * to be consistent and allow future modification, keep ie in this case non-const. + */ +int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref, + union gtpie_member **ie) +{ + uint32_t local_teic = local_ref; + uint32_t remote_teic; + uint16_t seq; + union gtp_packet packet = {}; + unsigned int packet_len; + int rc; + void *pack; + unsigned encoded_len; + struct sockaddr_in peer; + + rc = sgsn_ctx_req_fsm_tx_ack(gsn, &peer, &seq, local_teic, &remote_teic, ie, GTPIE_SIZE); + if (rc) + return rc; + + packet_len = get_default_gtp(1, GTP_SGSN_CONTEXT_ACK, &packet); + pack = &packet; + pack += packet_len; + + rc = gtpie_encaps3(ie, GTPIE_SIZE, pack, GTP_MAX - packet_len, &encoded_len); + if (rc) + return -EINVAL; + + packet_len += encoded_len; + return gtp_respv1c(gsn, seq, remote_teic, &packet, packet_len, &peer.sin_addr); +} + +int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref, + uint8_t cause) +{ + union gtpie_member *ie[GTPIE_SIZE] = {}; + union gtpie_member causetlv = {}; + causetlv.tv1.t = GTPIE_CAUSE; + causetlv.tv1.v = cause; + ie[GTPIE_CAUSE] = &causetlv; + + return gtp_sgsn_context_ack(gsn, local_ref, ie); +} + +/* Handle an SGSN Context Request */ +static int gtp_sgsn_context_req_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, + void *pack, unsigned len) +{ + union gtpie_member *ie[GTPIE_SIZE] = {}; + uint16_t seq; + + if (version != 1) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer, + "Sent SGSN Context Request with wrong version (exp 1, rx %u)\n", version); + return -EINVAL; + } + + /* the GTP code ensures the package contains a full GTPv1 header */ + int hlen = get_hlen(pack); + + /* Decode information elements */ + if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + GTP_LOGPKG(LOGL_ERROR, peer, pack, len, + "Sent SGSN Context Request with Invalid message format\n"); + return -EINVAL; + } + + seq = get_seq(pack); + return sgsn_ctx_req_fsm_rx_req(gsn, peer, seq, ie, GTPIE_SIZE); +} + +static bool is_ext_ua(const struct pdp_t *pdp) +{ + return pdp->eua.l > 0 && pdp->eua.v[0] == PDP_EUA_TYPE_v4v6; +} + +/* Check if length of the PDP */ +int decode_pdp_ctx_len_check(const uint8_t *buf, unsigned int size) +{ +#define CHECK_SIZE() do { if (ptr >= end) return -EINVAL; } while (0) + uint8_t tmp1; + const uint8_t *ptr = buf; + const uint8_t *end = ptr + size; + bool ext_ua = false; + + /* FIXME what is the minimal length ? */ + if (size < 20) + return -EINVAL; + + ext_ua = !!((*ptr) & 0x80); + + ptr += 2; + /* Qos Sub Length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* Qos Req Length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* Qos Neg Length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + ptr += 17; + + /* PDP Address length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* GGSN Address for control plane length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* GGSN Address for User traffic plane length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* APN length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + + /* Transaction Id */ + ptr += 2; + + /* Second PDP Address is only present in IPv4v6 PDP Context */ + if (ext_ua) { + ptr++; + /* PDP Address Length */ + CHECK_SIZE(); + tmp1 = *ptr; + ptr += tmp1 + 1; + } + + if (ptr == end) + return 0; + +#undef CHECK_SIZE + return 1; +} + +/** Decode a PDP Ctx from a buffer into struct pdp_t + * + * @param buf input buffer + * @param size size of buf + * @param pdp the decoded pdp buffer + * @param sapi the decoded sapi + * @return 0 on success, 1 if buffer not completely consumed, < 0 on syntactical errors + */ +int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp, uint16_t *sapi) +{ + uint8_t pdp_type, pdp_len; + const uint8_t *ptr = buf; + const uint8_t *end = ptr + size; + + if (decode_pdp_ctx_len_check(buf, size)) { + LOGP(DLGTP, LOGL_ERROR, "GTP: PDP Context Decode: length check failed."); + return -EINVAL; + } + + memset(pdp, 0, sizeof(*pdp)); + bool ext_ua = (*ptr & (1 << 7)); + + pdp->version = 1; + pdp->vplmn_allow = (*ptr) & (1 << 6); + pdp->reorder = (*ptr) & (1 << 4); + pdp->nsapi = (*ptr) & 0x0f; + ptr++; + + /* SAPI */ + *sapi = *ptr & 0x0f; + ptr++; + + /* QoS Sub */ + pdp->qos_sub.l = *ptr; + ptr++; + memcpy(pdp->qos_sub.v, ptr, pdp->qos_sub.l); + ptr += pdp->qos_sub.l; + + /* QoS Req */ + pdp->qos_req.l = *ptr; + ptr++; + memcpy(pdp->qos_req.v, ptr, pdp->qos_req.l); + ptr += pdp->qos_req.l; + + /* QoS Neg */ + pdp->qos_neg.l = *ptr; + ptr++; + memcpy(pdp->qos_neg.v, ptr, pdp->qos_neg.l); + ptr += pdp->qos_neg.l; + + /* SND */ + pdp->pdcpsndd = osmo_load16be(ptr); + ptr += 2; + + /* SNU */ + pdp->pdcpsndu = osmo_load16be(ptr); + ptr += 2; + + /* Send N-PDU */ + pdp->gtpsntx = *ptr; + ptr++; + + /* Recv N-PDU */ + pdp->gtpsnrx = *ptr; + ptr++; + + /* Uplink TEIC */ + pdp->teic_gn = osmo_load32be(ptr); + ptr += 4; + + /* Uplink TEIDI */ + pdp->teid_gn = osmo_load32be(ptr); + ptr += 4; + + /* PDP Ctx Id */ + pdp->pdp_id = *ptr; + ptr++; + + /* PDP Type Org */ + pdp->eua.v[0] = (*ptr) & 0x0f; + ptr++; + + /* PDP Type Number */ + pdp_type = *ptr; + ptr++; + + /* PDP Type length */ + pdp_len = *ptr; + ptr++; + + /* PDP Address */ + switch (pdp_type) { + case PDP_EUA_TYPE_v4: + /* v4v6 expects an IPv4 addr first followed by an IPv6 addr*/ + pdp->eua.v[1] = ext_ua ? PDP_EUA_TYPE_v4v6 : PDP_EUA_TYPE_v4; + pdp->eua.l = pdp_len + 2; + if (pdp_len != 4) { + LOGP(DLGTP, LOGL_ERROR, + "PDP Context Decode: Failed with invalid PDP length for IPv4 (%d != 4)", pdp_len); + return -EINVAL; + } + memcpy(&pdp->eua.v[2], ptr, pdp_len); + break; + case PDP_EUA_TYPE_v6: + pdp->eua.v[1] = PDP_EUA_TYPE_v6; + pdp->eua.l = pdp_len + 2; + if (pdp_len != 16) { + LOGP(DLGTP, LOGL_ERROR, + "PDP Context Decode: Failed with invalid PDP length for IPv6 (%d != 16)", pdp_len); + return -EINVAL; + } + memcpy(&pdp->eua.v[2], ptr, pdp_len); + break; + default: + return -EINVAL; + } + ptr += pdp_len; + + /* GGSN Address Ctrl */ + pdp->gsnrc.l = *ptr; + ptr++; + memcpy(pdp->gsnrc.v, ptr, pdp->gsnrc.l); + ptr += pdp->gsnrc.l; + + /* GGSN Address User */ + pdp->gsnru.l = *ptr; + ptr++; + memcpy(pdp->gsnru.v, ptr, pdp->gsnru.l); + ptr += pdp->gsnru.l; + + /* APN */ + pdp->apn_use.l = *ptr; + ptr++; + memcpy(pdp->apn_use.v, ptr, pdp->apn_use.l); + ptr += pdp->apn_use.l; + + /* TransId 4 or 12 bit, if the second octet is 0x0, it is a 4 bit transaction id */ + if (*(ptr + 1) == 0x00) + pdp->ti = (*ptr) & 0x0f; + else + pdp->ti = osmo_load16be(ptr) & 0x0fff; + + ptr += 2; + + if (is_ext_ua(pdp)) { + pdp_len = *ptr; + if (pdp_len != 16) { + LOGP(DLGTP, LOGL_ERROR, + "PDP Context Decode: Failed with invalid PDP address length for IPv6 (%d != 16) in the second PDP address field", pdp_len); + return -EINVAL; + } + + pdp->eua.l += pdp_len; + memcpy(&pdp->eua.v[6], ptr, pdp_len); + ptr += pdp_len; + } + + if (ptr == end) + return 0; + + return 1; +} + +/** Encode a PDP Ctx into a buffer from a struct pdp_t + * + * @param buf output buffer + * @param size size of buf + * @param pdp to be encoded PDP context + * @param sapi to be encoded sapi + * @return on error < 0, on success the used bytes in buf + */ +int gtp_encode_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp, uint16_t sapi) +{ + uint32_t tmp32; + uint16_t tmp16; + uint8_t *ptr = buf; +#define CHECK_SPACE_ERR(bytes) do { \ + if ((ptr - buf) + (bytes) > size) { \ + LOGP(DLGTP, LOGL_ERROR, "PDP encode: Failed size check: %lu + %lu > %lu\n", (ptr - buf), (unsigned long) (bytes), (unsigned long) size); \ + return -1; \ + } \ + } while (0) + +#define MEMCPY_CHK(dst, src, len) do { \ + CHECK_SPACE_ERR((len)); \ + memcpy((dst), (uint8_t *)(src), (len)); \ + (dst) += (len); \ + } while (0) + + /* Flags - FIXME: No ASI */ + *ptr++ = (is_ext_ua(pdp) << 7) | ((!!pdp->vplmn_allow) << 6) | ((!!pdp->reorder) << 4) | (pdp->nsapi & 0x0f); + /* SAPI */ + *ptr++ = sapi & 0x0f; + + /* QoS Sub */ + if (pdp->qos_sub.l < 4) { + /* Work around qos_sub never being set */ + *ptr++ = 4; + *ptr++ = 0; + *ptr++ = 0x23; + *ptr++ = 0x02; + *ptr++ = 0x00; + } else { + *ptr++ = pdp->qos_sub.l; + MEMCPY_CHK(ptr, pdp->qos_sub.v, pdp->qos_sub.l); + } + /* QoS Req */ + *ptr++ = pdp->qos_req.l; + MEMCPY_CHK(ptr, pdp->qos_req.v, pdp->qos_req.l); + /* QoS Neg */ + *ptr++ = pdp->qos_neg.l; + MEMCPY_CHK(ptr, pdp->qos_neg.v, pdp->qos_neg.l); + + /* SND */ + tmp16 = osmo_htons(pdp->pdcpsndd); + MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16)); + /* SNU */ + tmp16 = osmo_htons(pdp->pdcpsndu); + MEMCPY_CHK(ptr, &tmp16, sizeof(tmp16)); + /* Send N-PDU */ + *ptr++ = pdp->gtpsntx; + /* Recv N-PDU */ + *ptr++ = pdp->gtpsnrx; + /* Uplink TEIC */ + tmp32 = osmo_htonl(pdp->teic_gn); + MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32)); + /* Uplink TEIDI */ + tmp32 = osmo_htonl(pdp->teid_gn); + MEMCPY_CHK(ptr, &tmp32, sizeof(tmp32)); + /* PDP Ctx Id */ + *ptr++ = pdp->pdp_id; + /* PDP Type Org */ + *ptr++ = PDP_EUA_ORG_IETF; + + /* PDP Type No. */ + /* PDP Address */ + switch (pdp->eua.v[1]) { + case PDP_EUA_TYPE_v4: + case PDP_EUA_TYPE_v4v6: + /* v4v6 expects an IPv4 addr first followed by an IPv6 addr */ + *ptr++ = PDP_EUA_TYPE_v4; + if (pdp->eua.l < 6) + return -1; + *ptr++ = 4; + MEMCPY_CHK(ptr, &pdp->eua.v[2], 4); + break; + case PDP_EUA_TYPE_v6: + *ptr++ = PDP_EUA_TYPE_v6; + if (pdp->eua.l < 18) + return -1; + *ptr++ = 16; + MEMCPY_CHK(ptr, &pdp->eua.v[2], 16); + break; + default: + return -EINVAL; + } + /* GGSN Address Ctrl */ + *ptr++ = pdp->gsnrc.l; + MEMCPY_CHK(ptr, pdp->gsnrc.v, pdp->gsnrc.l); + /* GGSN Address User */ + *ptr++ = pdp->gsnru.l; + MEMCPY_CHK(ptr, pdp->gsnru.v, pdp->gsnru.l); + /* APN */ + *ptr++ = pdp->apn_use.l; + MEMCPY_CHK(ptr, pdp->apn_use.v, pdp->apn_use.l); + /* TransId */ + *ptr++ = (pdp->ti >> 8) & 0x0f; + *ptr++ = pdp->ti & 0xff; + + if (is_ext_ua(pdp)) { + *ptr++ = PDP_EUA_TYPE_v6; + if (pdp->eua.l < 22) + return -1; + *ptr++ = 16; + MEMCPY_CHK(ptr, &pdp->eua.v[6], 16); + } + return ptr - buf; +#undef CHECK_SPACE_ERR +#undef MEMCPY_CHK +} + +/* Handle an SGSN Context Response */ +static int gtp_sgsn_context_resp_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, + void *pack, unsigned len) +{ + /** If cause is "Request accepted": Send ACK, + else: Log and handle error */ + + /* Check if we have a context with TLLI/P-TMSI/IMSI in the RAI */ + + void *cbp = NULL; + uint8_t type = 0; + uint8_t cause; + uint16_t seq; + uint32_t local_teic; + int hlen; + union gtpie_member *ie[GTPIE_SIZE] = {}; + union gtp_packet *packet = (union gtp_packet *)pack; + + if (version != 1) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer, + "Sent SGSN Context Response with wrong version (exp 1, rx %u)\n", version); + return -EINVAL; + } + + if (!(packet->flags & GTP1HDR_F_SEQ)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + LOGP(DLGTP, LOGL_NOTICE, + "Sent SGSN Context Response without Sequence flag set. Flags 0x%x\n", packet->flags); + return -EINVAL; + } + + /* the GTP code ensures the package contains a full GTPv1 header */ + seq = get_seq(pack); + hlen = get_hlen(pack); + local_teic = get_tei(pack); + + /* FIXME: retransmission need to be implemented: + * - Check if an Ack has been already transmitted (if so re-transmit) and return early + * - Check if a Req is in re-transmit queue, if, confirm it + */ + /* Remove packet from queue */ + if (gtp_conf(gsn, version, peer, pack, len, &type, &cbp)) + return EOF; + + /* Extract information elements into a pointer array */ + if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + GTP_LOGPKG(LOGL_ERROR, peer, pack, len, + "Sent SGSN Context Request with Invalid message format\n"); + /* FIXME: Error Indication */ + return EOF; + } + + /* Extract cause value (mandatory) */ + if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_MISSING); + GTP_LOGPKG(LOGL_ERROR, peer, pack, len, + "Missing mandatory information field\n"); + /* FIXME: Error Indication */ + return EOF; + } + + return sgsn_ctx_req_fsm_rx_resp(gsn, peer, + seq, local_teic, + ie, GTPIE_SIZE); +} + +/* Handle an SGSN Context Acknowledge */ +static int gtp_sgsn_context_ack_ind(struct gsn_t *gsn, int version, struct sockaddr_in *peer, + void *pack, unsigned len) +{ + union gtpie_member *ie[GTPIE_SIZE] = {}; + uint16_t seq; + uint32_t local_teic; + const union gtp_packet *packet = (union gtp_packet *)pack; + + if (version != 1) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + LOGP_WITH_ADDR(DLGTP, LOGL_NOTICE, peer, + "Sent SGSN Context Ack with wrong version (exp 1, rx %u)\n", version); + return -EINVAL; + } + + if (!(packet->flags & GTP1HDR_F_SEQ)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + LOGP(DLGTP, LOGL_NOTICE, + "Sent SGSN Context Ack without Sequence flag set. Flags 0x%x\n", packet->flags); + return -EINVAL; + } + + /* the GTP code ensures the package contains a full GTPv1 header */ + int hlen = get_hlen(pack); + + /* Decode information elements */ + if (gtpie_decaps(ie, version, pack + hlen, len - hlen)) { + rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_INVALID); + GTP_LOGPKG(LOGL_ERROR, peer, pack, len, + "Sent SGSN Context Ack with Invalid message format\n"); + return -EINVAL; + } + + seq = get_seq(pack); + local_teic = get_tei(pack); + + return sgsn_ctx_req_fsm_rx_ack(gsn, peer, + seq, local_teic, + ie, GTPIE_SIZE); +} + /* *********************************************************** * Conversion functions *************************************************************/ @@ -2949,6 +3613,15 @@ int gtp_decaps1c(struct gsn_t *gsn) case GTP_RAN_INFO_RELAY: gtp_ran_info_relay_ind(gsn, version, &peer, buffer, status); break; + case GTP_SGSN_CONTEXT_REQ: + gtp_sgsn_context_req_ind(gsn, version, &peer, buffer, status); + break; + case GTP_SGSN_CONTEXT_RSP: + gtp_sgsn_context_resp_ind(gsn, version, &peer, buffer, status); + break; + case GTP_SGSN_CONTEXT_ACK: + gtp_sgsn_context_ack_ind(gsn, version, &peer, buffer, status); + break; default: rate_ctr_inc2(gsn->ctrg, GSN_CTR_PKT_UNKNOWN); GTP_LOGPKG(LOGL_ERROR, &peer, buffer, status, diff --git a/gtp/gtp_sgsn_ctx.c b/gtp/gtp_sgsn_ctx.c new file mode 100644 index 0000000..6ade6ef --- /dev/null +++ b/gtp/gtp_sgsn_ctx.c @@ -0,0 +1,512 @@ +/* + * OsmoGGSN - Gateway GPRS Support Node + * Copyright (C) 2024 sysmocom s.f.m.c. GmbH + * + * Author: Alexander Couzens + * + * The contents of this file may be used under the terms of the GNU + * General Public License Version 2, provided that the above copyright + * notice and this permission notice is included in all copies or + * substantial portions of the software. + * + */ + +#include + +#include +#include + +#include +#include +#include + +#include "gtp_sgsn_ctx.h" + +#define X(s) (1 << (s)) + +/* Handle the logic part of the SGSN Context Req/Resp/Ack */ + +const struct value_string sgsn_ctx_event_names[] = { + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_REQ), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_RESP_FAIL), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_ACK), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_NACK), + + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_REQ), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_RX_RESP_FAIL), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_ACK), + OSMO_VALUE_STRING(SGSN_CTX_REQ_E_TX_NACK), + { 0, NULL } +}; + +static const struct osmo_tdef_state_timeout sgsn_ctx_fsm_timeouts[32] = { + [SGSN_CTX_REQ_ST_START] = { }, + + [SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = { }, + [SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = { }, + + [SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = { }, + [SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = { }, +}; + +struct osmo_tdef gtp_tdefs_sgsn_ctx[] = { + { /* terminator */ } +}; + +/* Used to pass all relevant data to the fsm via data */ +struct ctx_msg { + /* Message type */ + uint8_t msg; + union gtpie_member * const *ie; + unsigned int ie_size; +}; + +#define sgsn_ctx_fsm_state_chg(fi, NEXT_STATE) \ + osmo_tdef_fsm_inst_state_chg(fi, NEXT_STATE, sgsn_ctx_fsm_timeouts, gtp_tdefs_sgsn_ctx, 10) + +/* Direction of the SGSN context. + * Outgoing Ctx: From local to remote. + * Incoming Ctx: From remote to local. + */ +static void sgsn_ctx_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_ctx_req *req = fi->priv; + struct gsn_t *gsn = req->gsn; + struct ctx_msg *msg = data; + + switch (event) { + case SGSN_CTX_REQ_E_RX_REQ: + /* remote SGSN ask this peer */ + sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP); + if (gsn->cb_sgsn_context_request_ind) + gsn->cb_sgsn_context_request_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size); + break; + case SGSN_CTX_REQ_E_TX_REQ: + /* local SGSN ask remote peer */ + sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP); + break; + default: + OSMO_ASSERT(0); + } +} + +/* Outgoing Ctx: remote SGSN ask this peer, waiting for a resp from this node (SGSN) */ +static void sgsn_ctx_wait_local_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case SGSN_CTX_REQ_E_TX_RESP: + sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK); + break; + case SGSN_CTX_REQ_E_TX_RESP_FAIL: + sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP); + break; + default: + OSMO_ASSERT(0); + } +} + +/* Outgoing Ctx: This node (SGSN) answered with a Ctx Resp, waiting for a remote Ack or Nack */ +static void sgsn_ctx_wait_remote_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_ctx_req *req = fi->priv; + struct gsn_t *gsn = req->gsn; + struct ctx_msg *msg = data; + + switch (event) { + case SGSN_CTX_REQ_E_RX_ACK: + if (gsn->cb_sgsn_context_ack_ind) + gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL); + break; + case SGSN_CTX_REQ_E_RX_NACK: + if (gsn->cb_sgsn_context_ack_ind) + gsn->cb_sgsn_context_ack_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size); + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + break; + default: + OSMO_ASSERT(0); + } +} + +/* Incoming Ctx: This node requested a remote SGSN. Waiting for a remote response */ +static void sgsn_ctx_wait_remote_resp(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct sgsn_ctx_req *req = fi->priv; + struct gsn_t *gsn = req->gsn; + struct ctx_msg *msg = data; + + switch (event) { + case SGSN_CTX_REQ_E_RX_RESP: + sgsn_ctx_fsm_state_chg(fi, SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK); + if (gsn->cb_sgsn_context_response_ind) + gsn->cb_sgsn_context_response_ind(gsn, &req->peer, req->local_teic, msg->ie, msg->ie_size); + break; + case SGSN_CTX_REQ_E_RX_RESP_FAIL: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + break; + default: + OSMO_ASSERT(0); + } +} + +/* Incoming Ctx: Received a Response, waiting for this node to respond with an Ack or Nack */ +static void sgsn_ctx_local_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + switch (event) { + case SGSN_CTX_REQ_E_TX_ACK: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL); + break; + case SGSN_CTX_REQ_E_TX_NACK: + osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL); + break; + default: + OSMO_ASSERT(0); + } +} + +int sgsn_ctx_timer_cb(struct osmo_fsm_inst *fi) +{ + /* Terminating the FSM when a timeout happens */ + return 1; +} + +static struct osmo_fsm_state sgsn_ctx_req_states[] = { + [SGSN_CTX_REQ_ST_START] = { + .in_event_mask = + X(SGSN_CTX_REQ_E_RX_REQ) | + X(SGSN_CTX_REQ_E_TX_REQ), + .out_state_mask = + X(SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP) | + X(SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP), + .name = "Init", + .action = sgsn_ctx_init, + }, + + [SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP] = { + .in_event_mask = + X(SGSN_CTX_REQ_E_TX_RESP) | + X(SGSN_CTX_REQ_E_TX_RESP_FAIL), + .out_state_mask = + X(SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK), + .name = "Wait Local Response", + .action = sgsn_ctx_wait_local_resp, + }, + [SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK] = { + .in_event_mask = + X(SGSN_CTX_REQ_E_RX_ACK) | + X(SGSN_CTX_REQ_E_RX_NACK), + .out_state_mask = 0, + .name = "Wait Remote Ack", + .action = sgsn_ctx_wait_remote_ack, + }, + + [SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP] = { + .in_event_mask = + X(SGSN_CTX_REQ_E_RX_RESP) | + X(SGSN_CTX_REQ_E_RX_RESP_FAIL), + .out_state_mask = + X(SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK), + .name = "Wait Remote Response", + .action = sgsn_ctx_wait_remote_resp, + }, + [SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK] = { + .in_event_mask = + X(SGSN_CTX_REQ_E_TX_ACK) | + X(SGSN_CTX_REQ_E_TX_NACK), + .out_state_mask = 0, + .name = "Wait Local Ack", + .action = sgsn_ctx_local_ack, + }, +}; + +struct osmo_fsm sgsn_ctx_req_fsm = { + .name = "SGSNCtxReq", + .states = sgsn_ctx_req_states, + .num_states = ARRAY_SIZE(sgsn_ctx_req_states), + .event_names = sgsn_ctx_event_names, + .log_subsys = DLGTP, + .timer_cb = sgsn_ctx_timer_cb, +}; + +static __attribute__((constructor)) void sgsn_ctx_register(void) +{ + OSMO_ASSERT(osmo_fsm_register(&sgsn_ctx_req_fsm) == 0); +} + +struct sgsn_ctx_reqs *sgsn_ctx_reqs_init(void *ctx, uint32_t lowest_teic, uint32_t highest_teic) +{ + struct sgsn_ctx_reqs *reqs; + + if (lowest_teic >= highest_teic) + return NULL; + + reqs = talloc_zero(ctx, struct sgsn_ctx_reqs); + if (!reqs) + return reqs; + + reqs->lowest_teic = reqs->next_teic = lowest_teic; + reqs->high_teic = highest_teic; + + INIT_LLIST_HEAD(&reqs->local_reqs); + INIT_LLIST_HEAD(&reqs->remote_reqs); + + return reqs; +} + +static uint32_t get_next_teic(struct gsn_t *gsn) +{ + uint32_t teic = gsn->sgsn_ctx->next_teic++; + + if (gsn->sgsn_ctx->next_teic > gsn->sgsn_ctx->high_teic) + gsn->sgsn_ctx->next_teic = gsn->sgsn_ctx->lowest_teic; + + /* FIXME: check if this is already assigned! */ + + return teic; +} + +static struct sgsn_ctx_req *_sgsn_ctx_req_alloc(struct gsn_t *gsn) +{ + struct sgsn_ctx_req *req = talloc_zero(gsn, struct sgsn_ctx_req); + + if (!req) + return NULL; + + req->fi = osmo_fsm_inst_alloc(&sgsn_ctx_req_fsm, req, req, LOGL_INFO, NULL); + if (!req->fi) + goto out; + + req->gsn = gsn; + req->local_teic = get_next_teic(gsn); + return req; + +out: + osmo_fsm_inst_free(req->fi); + talloc_free(req); + return NULL; +} + +/* Alloc a SGSN_CTX for an outgoing Context (Remote SGSN requested) */ +static struct sgsn_ctx_req *sgsn_ctx_req_alloc_outgoing(struct gsn_t *gsn, const struct sockaddr_in *peer, uint16_t seq) +{ + struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn); + if (!req) + return req; + + req->seq = seq; + req->peer = *peer; + llist_add_tail(&req->list, &gsn->sgsn_ctx->remote_reqs); + + return req; +} + +/* Alloc a SGSN_CTX for an incoming Context (Local SGSN requested) */ +static struct sgsn_ctx_req *sgsn_ctx_req_alloc_incoming(struct gsn_t *gsn, const struct sockaddr_in *peer, uint16_t seq) +{ + struct sgsn_ctx_req *req = _sgsn_ctx_req_alloc(gsn); + if (!req) + return req; + + req->seq = seq; + req->peer = *peer; + llist_add_tail(&req->list, &gsn->sgsn_ctx->local_reqs); + + return req; +} + +static void sgsn_ctx_free(struct sgsn_ctx_req *req) +{ + if (!req) + return; + + osmo_fsm_inst_free(req->fi); + llist_del(&req->list); + talloc_free(req); +} + +static struct sgsn_ctx_req *sgsn_ctx_by_teic(struct gsn_t *gsn, uint32_t teic, bool local_req) +{ + struct sgsn_ctx_req *req; + struct llist_head *head; + + if (local_req) + head = &gsn->sgsn_ctx->local_reqs; + else + head = &gsn->sgsn_ctx->remote_reqs; + + llist_for_each_entry(req, head, list) { + if (req->local_teic == teic) + return req; + } + + return NULL; +} + +int sgsn_ctx_req_fsm_rx_req(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, + union gtpie_member * const *ie, unsigned int ie_size) +{ + struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_outgoing(gsn, peer, seq); + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_REQ, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOMEM; + + if (!gsn->cb_sgsn_context_request_ind) + goto err; + + if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic)) + goto err; + + if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_REQ, &ctx_msg)) + goto err; + + return 0; +err: + sgsn_ctx_free(req); + return -EINVAL; +} + +int sgsn_ctx_req_fsm_rx_resp(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, uint32_t local_teic, + union gtpie_member * const *ie, unsigned int ie_size) +{ + struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true); + uint8_t cause; + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_RSP, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOENT; + + if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) + return -EINVAL; + + if (gtpie_gettv4(ie, GTPIE_TEI_C, 0, &req->remote_teic) && cause == GTPCAUSE_ACC_REQ) + return -EINVAL; + + if (osmo_fsm_inst_dispatch(req->fi, + cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_RX_RESP : SGSN_CTX_REQ_E_RX_RESP_FAIL, + &ctx_msg)) + return -EINVAL; + + return 0; +} + +int sgsn_ctx_req_fsm_rx_ack(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, uint32_t local_teic, + union gtpie_member * const *ie, unsigned int ie_size) +{ + struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false); + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_ACK, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOENT; + + if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_RX_ACK, &ctx_msg)) + return -EINVAL; + + return 0; +} + +int sgsn_ctx_req_fsm_tx_req(struct gsn_t *gsn, const struct sockaddr_in *peer, + union gtpie_member * const *ie, unsigned int ie_size, + uint32_t *local_teic, uint16_t seq) +{ + struct sgsn_ctx_req *req = sgsn_ctx_req_alloc_incoming(gsn, peer, seq); + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_REQ, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOMEM; + + /* TODO: move tx GTPv1 into the fsm */ + *local_teic = req->local_teic; + + if (osmo_fsm_inst_dispatch(req->fi, SGSN_CTX_REQ_E_TX_REQ, &ctx_msg)) + goto err; + + return 0; + +err: + sgsn_ctx_free(req); + return -EINVAL; +} + +int sgsn_ctx_req_fsm_tx_resp(struct gsn_t *gsn, struct sockaddr_in *peer, + uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic, + union gtpie_member * const *ie, unsigned int ie_size) +{ + struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, false); + uint8_t cause; + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_RSP, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOENT; + + /* TODO: move tx GTPv1 into the fsm */ + *seq = req->seq; + *peer = req->peer; + *remote_teic = req->remote_teic; + + if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) + return -EINVAL; + + if (osmo_fsm_inst_dispatch(req->fi, + cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_RESP : SGSN_CTX_REQ_E_TX_RESP_FAIL, + &ctx_msg)) + return -EINVAL; + + return 0; +} + +int sgsn_ctx_req_fsm_tx_ack(struct gsn_t *gsn, struct sockaddr_in *peer, + uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic, + union gtpie_member * const *ie, unsigned int ie_size) +{ + struct sgsn_ctx_req *req = sgsn_ctx_by_teic(gsn, local_teic, true); + uint8_t cause; + struct ctx_msg ctx_msg = { + .msg = GTP_SGSN_CONTEXT_ACK, + .ie = ie, + .ie_size = ie_size, + }; + + if (!req) + return -ENOENT; + + /* TODO: move tx GTPv1 into the fsm */ + *seq = req->seq; + *peer = req->peer; + *remote_teic = req->remote_teic; + + if (gtpie_gettv1(ie, GTPIE_CAUSE, 0, &cause)) + return -EINVAL; + + if (osmo_fsm_inst_dispatch(req->fi, + cause == GTPCAUSE_ACC_REQ ? SGSN_CTX_REQ_E_TX_ACK : SGSN_CTX_REQ_E_TX_NACK, + &ctx_msg)) + return -EINVAL; + + return 0; +} diff --git a/gtp/gtp_sgsn_ctx.h b/gtp/gtp_sgsn_ctx.h new file mode 100644 index 0000000..6d2ea0e --- /dev/null +++ b/gtp/gtp_sgsn_ctx.h @@ -0,0 +1,104 @@ +/* + * OsmoGGSN - Gateway GPRS Support Node + * Copyright (C) 2024 sysmocom s.f.m.c. GmbH + * + * Author: Alexander Couzens + * + * The contents of this file may be used under the terms of the GNU + * General Public License Version 2, provided that the above copyright + * notice and this permission notice is included in all copies or + * substantial portions of the software. + * + */ + +#include + +#include + +struct gsn_t; +struct osmo_fsm_inst; +union gtpie_member; + + +enum gtp_sgsn_ctx_states { + SGSN_CTX_REQ_ST_START, + + /* remote SGSN/MME request a Ctx from this peer */ + SGSN_CTX_REQ_ST_WAIT_LOCAL_RESP, /*! wait for this peer to tx a SGSN Ctx Respond */ + SGSN_CTX_REQ_ST_WAIT_REMOTE_ACK, /*! wait for remote peer SGSN Ctx Ack */ + + /* local SGSN request a Ctx from a remote peer (SGSN/MME) */ + SGSN_CTX_REQ_ST_WAIT_REMOTE_RESP, /*! wait for remote peer to send this peer a SGSN Ctx Respond */ + SGSN_CTX_REQ_ST_WAIT_LOCAL_ACK, /*! wait for the local peer to ack */ +}; + +enum gtp_sgsn_ctx_req_event { + /* remote SGSN/MME request a Ctx from this peer */ + SGSN_CTX_REQ_E_RX_REQ, + SGSN_CTX_REQ_E_TX_RESP, /* a response with a success reason */ + SGSN_CTX_REQ_E_TX_RESP_FAIL, + SGSN_CTX_REQ_E_RX_ACK, + SGSN_CTX_REQ_E_RX_NACK, /* a nack with a reason != success */ + + /* local SGSN requests a Context from a remote peer (SGSN/MME) */ + SGSN_CTX_REQ_E_TX_REQ, + SGSN_CTX_REQ_E_RX_RESP, + SGSN_CTX_REQ_E_RX_RESP_FAIL, + SGSN_CTX_REQ_E_TX_ACK, + SGSN_CTX_REQ_E_TX_NACK, +}; + +struct sgsn_ctx_reqs { + /*! contains SGSN Context Request which this peer started by requesting a Ctx */ + struct llist_head local_reqs; + /*! contains SGSN Context Requests which the remote peer started by sending a Ctx */ + struct llist_head remote_reqs; + + /* TEID-C */ + uint32_t lowest_teic; /*! lowest valid teic for Gn interface */ + uint32_t next_teic; + uint32_t high_teic; /*! highest valid teic for Gn interface */ +}; + +struct sgsn_ctx_req { + /*! entry in sgsn_ctx_reqs local or remote reqs */ + struct llist_head list; + struct gsn_t *gsn; + + struct osmo_fsm_inst *fi; + + struct sockaddr_in peer; + + uint32_t local_teic; + uint32_t remote_teic; + + uint16_t seq; +}; + +struct sgsn_ctx_reqs *sgsn_ctx_reqs_init(void *ctx, uint32_t lowest_teic, uint32_t highest_teic); + +/*! Received a SGSN Context Request from a peeer */ +int sgsn_ctx_req_fsm_rx_req(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, + union gtpie_member * const *ie, unsigned int ie_size); + +int sgsn_ctx_req_fsm_rx_resp(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, uint32_t local_teic, + union gtpie_member * const *ie, unsigned int ie_size); + +int sgsn_ctx_req_fsm_rx_ack(struct gsn_t *gsn, const struct sockaddr_in *peer, + uint16_t seq, uint32_t local_teic, + union gtpie_member * const *ie, unsigned int ie_size); + + +int sgsn_ctx_req_fsm_tx_req(struct gsn_t *gsn, const struct sockaddr_in *peer, + union gtpie_member * const *ie, unsigned int ie_size, + uint32_t *local_teic, uint16_t seq); + +int sgsn_ctx_req_fsm_tx_resp(struct gsn_t *gsn, struct sockaddr_in *peer, + uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic, + union gtpie_member * const *ie, unsigned int ie_size); + +int sgsn_ctx_req_fsm_tx_ack(struct gsn_t *gsn, struct sockaddr_in *peer, + uint16_t *seq, uint32_t local_teic, uint32_t *remote_teic, + union gtpie_member * const *ie, unsigned int ie_size); diff --git a/include/osmocom/gtp/gsn.h b/include/osmocom/gtp/gsn.h index 772e845..8ccae14 100644 --- a/include/osmocom/gtp/gsn.h +++ b/include/osmocom/gtp/gsn.h @@ -17,7 +17,10 @@ #include #include #include +#include +#include +#include "gtpie.h" #include "pdp.h" #define GTP_MODE_GGSN 1 @@ -25,6 +28,8 @@ #define RESTART_FILE "gsn_restart" +struct sgsn_ctx_reqs; + extern struct osmo_tdef gtp_T_defs[]; /* *********************************************************** @@ -108,12 +113,17 @@ struct gsn_t { int (*cb_recovery)(struct sockaddr_in *peer, uint8_t recovery); int (*cb_recovery2)(struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery); int (*cb_recovery3)(struct gsn_t *gsn, struct sockaddr_in *peer, struct pdp_t *pdp, uint8_t recovery); + int (*cb_sgsn_context_request_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size); + int (*cb_sgsn_context_response_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size); + int (*cb_sgsn_context_ack_ind)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size); /* Counters */ struct rate_ctr_group *ctrg; /* Timers: */ struct osmo_tdef *tdef; + + struct sgsn_ctx_reqs *sgsn_ctx; }; /* External API functions */ @@ -152,6 +162,15 @@ extern int gtp_set_cb_extheader_ind(struct gsn_t *gsn, extern int gtp_set_cb_ran_info_relay_ind(struct gsn_t *gsn, int (*cb)(struct sockaddr_in *peer, union gtpie_member **ie)); +extern int gtp_set_cb_sgsn_context_request_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)); + +extern int gtp_set_cb_sgsn_context_response_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)); + +extern int gtp_set_cb_sgsn_context_ack_ind(struct gsn_t *gsn, + int (*cb)(struct gsn_t *gsn, const struct sockaddr_in *peer, uint32_t local_ref, union gtpie_member * const *ie, unsigned int ie_size)); + extern int gtp_set_cb_conf(struct gsn_t *gsn, int (*cb)(int type, int cause, struct pdp_t *pdp, void *cbp)); diff --git a/include/osmocom/gtp/gtp.h b/include/osmocom/gtp/gtp.h index 801837a..3e37e73 100644 --- a/include/osmocom/gtp/gtp.h +++ b/include/osmocom/gtp/gtp.h @@ -270,6 +270,27 @@ extern int gtp_ran_info_relay_req(struct gsn_t *gsn, const struct sockaddr_in *p const uint8_t *rim_route_addr, size_t rim_route_addr_len, uint8_t rim_route_addr_discr); +/* Tx a SGSN Context Request */ +extern int gtp_sgsn_context_req(struct gsn_t *gsn, uint32_t *local_ref, + const struct sockaddr_in *peer, union gtpie_member **ie); + +/* Tx a SGSN Context Response */ +extern int gtp_sgsn_context_resp(struct gsn_t *gsn, uint32_t local_ref, + union gtpie_member **ie); + +/* Tx a SGSN Context Response, simplified when returning an error */ +int gtp_sgsn_context_resp_error(struct gsn_t *gsn, uint32_t local_ref, + uint8_t cause); + +/* Tx a SGSN Context Ack */ +extern int gtp_sgsn_context_ack(struct gsn_t *gsn, uint32_t local_ref, + union gtpie_member **ie); + +/* Tx a SGSN Context Ack, simplified when returning an error */ +int gtp_sgsn_context_ack_error(struct gsn_t *gsn, uint32_t local_ref, + uint8_t cause); + + extern int gtp_decaps0(struct gsn_t *gsn); extern int gtp_decaps1c(struct gsn_t *gsn); extern int gtp_decaps1u(struct gsn_t *gsn); @@ -279,6 +300,9 @@ extern int gtp_echo_req(struct gsn_t *gsn, int version, void *cbp, extern int gsna2in_addr(struct in_addr *dst, struct ul16_t *gsna); +extern int gtp_encode_pdp_ctx(uint8_t *buf, unsigned int size, const struct pdp_t *pdp, uint16_t sapi); +extern int gtp_decode_pdp_ctx(const uint8_t *buf, unsigned int size, struct pdp_t *pdp, uint16_t *sapi); + extern const char *imsi_gtp2str(const uint64_t *imsi); /*! Set the talloc context for internal objects */