/* (C) 2021 by sysmocom - s.f.m.c. GmbH * Author: Pau Espin Pedrol * 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 . * */ #include #include #include #include #include #include /* 3GPP TS 29.281 */ struct gtp1u_hdr { /* 3GPP TS 29.281 */ #if OSMO_IS_LITTLE_ENDIAN uint8_t pn:1, /* N-PDU Number flag */ s:1, /* Sequence number flag */ e:1, /* Extension header flag */ spare:1, pt:1, /* Protocol Type: GTP=1, GTP'=0 */ version:3; /* Version: 1 */ #elif OSMO_IS_BIG_ENDIAN uint8_t version:3, pt:1, spare:1, e:1, s:1, pn:1; #endif uint8_t msg_type; uint16_t length; uint32_t tei; /* 05 - 08 Tunnel Endpoint ID */ uint8_t data[0]; } __attribute__((packed)); static uint32_t next_gtp_conn_id = 1; struct gtp_conn *gtp_conn_alloc(struct hnb_ue *ue) { struct gtp_conn *conn; conn = talloc_zero(ue, struct gtp_conn); if (!conn) return NULL; conn->ue = ue; llist_add(&conn->list, &ue->conn_ps.conn_list); return conn; } void gtp_conn_free(struct gtp_conn *conn) { if (!conn) return; llist_del(&conn->list); talloc_free(conn); } int gtp_conn_setup(struct gtp_conn *conn, const struct osmo_sockaddr *rem_addr, uint32_t rem_tei) { struct hnb_ue *ue = conn->ue; struct hnb *hnb = ue->hnb; LOGUE(ue, DGTP, LOGL_INFO, "Creating PDP context\n"); if (rem_addr->u.sa.sa_family != AF_INET) { LOGUE(ue, DGTP, LOGL_ERROR, "Failed creating PDP context: unsupported proto family %u\n", rem_addr->u.sa.sa_family); return -ENOTSUP; } conn->id = next_gtp_conn_id++; /* TODO: validate next one is not already taken due to wrap-around */ conn->loc_addr = hnb->gtp.local_addr; conn->rem_addr = *rem_addr; conn->rem_tei = rem_tei; conn->loc_tei = conn->id; /* We use conn ID as local TEI */ return 0; } int gtp_conn_tx(struct gtp_conn *conn, const uint8_t *gtpu_payload, unsigned gtpu_payload_len) { int rc; struct hnb_ue *ue; struct hnb *hnb; struct msgb *msg; struct gtp1u_hdr *hdr; struct osmo_sockaddr *sa; const uint8_t opt_hdr_len = 4; unsigned msg_len = sizeof(*hdr) + opt_hdr_len + gtpu_payload_len; if (!conn) { LOGP(DGTP, LOGL_ERROR, "Tx: GTP-U conn not available\n"); return -EINVAL; } ue = conn->ue; hnb = ue->hnb; if (hnb->gtp.wq1u.bfd.fd == -1) { LOGP(DGTP, LOGL_ERROR, "Tx: GTP-U socket not available\n"); return -EINVAL; } if (gtpu_payload_len == 0) { LOGP(DGTP, LOGL_ERROR, "Tx: GTP-U len=0\n"); return -EINVAL; } msg = msgb_alloc_c(hnb, sizeof(*sa) + msg_len, "gtp-ul"); sa = (struct osmo_sockaddr *)msgb_put(msg, sizeof(*sa)); *sa = conn->rem_addr; hdr = (struct gtp1u_hdr *)msgb_put(msg, msg_len); hdr->pn = 0; hdr->s = 1; hdr->e = 0; hdr->spare = 0; hdr->pt = 1; hdr->version = 1; hdr->msg_type = 0xff; osmo_store16be(gtpu_payload_len + opt_hdr_len, &hdr->length); osmo_store32be(conn->rem_tei, &hdr->tei); osmo_store16be(conn->seq_nr, &hdr->data[0]); conn->seq_nr++; /* byes 2 and 3 are set to 0 */ memcpy(&hdr->data[opt_hdr_len], gtpu_payload, gtpu_payload_len); rc = osmo_wqueue_enqueue(&hnb->gtp.wq1u, msg); if (rc < 0) msgb_free(msg); return rc; } /* Called whenever we receive a DATA packet */ static int hnb_gtp_rx_gtp(struct hnb *hnb, struct msgb *msg, const struct osmo_sockaddr *from) { struct gtp_conn *conn; struct hnb_ue *ue; struct gtp1u_hdr *hdr; uint32_t tei; struct hnb_gtp_prim *gtp_prim; int rc; hdr = (struct gtp1u_hdr *)msgb_data(msg); tei = osmo_load32be(&hdr->tei); /* The local TEI is the GTP conn_id: */ conn = hnb_find_gtp_conn_by_id(hnb, tei); if (!conn) { LOGP(DGTP, LOGL_NOTICE, "Tx GTP-CONN_DATA.ind len=%u with no conn!\n", msgb_l3len(msg)); rc = -EINVAL; goto free_ret; } ue = conn->ue; if (osmo_sockaddr_cmp(from, &conn->rem_addr)) { LOGUE(ue, DGTP, LOGL_NOTICE, "Rx DL GTP-U loc_tei=0x%08x from unexpected addr=%s!\n", tei, osmo_sockaddr_to_str(from)); rc = -EINVAL; goto free_ret; } ue = conn->ue; if (!ue->conn_ps.active) { LOGUE(ue, DGTP, LOGL_NOTICE, "Tx GTP-CONN_DATA.ind len=%u but UE conn_ps is not active!\n", msgb_l3len(msg)); rc = -EINVAL; goto free_ret; } hnb = ue->hnb; LOGUE(ue, DGTP, LOGL_DEBUG, "Tx GTP-CONN_DATA.ind len=%u\n", msgb_l3len(msg)); gtp_prim = hnb_gtp_makeprim_conn_data_ind(conn->id, msgb_l3(msg), msgb_l3len(msg)); if ((rc = osmo_prim_srv_send(hnb->llsk.srv, gtp_prim->hdr.msg)) < 0) { LOGUE(ue, DGTP, LOGL_ERROR, "Failed Tx GTP-CONN_DATA.ind len=%u\n", msgb_l3len(msg)); } free_ret: msgb_free(msg); return rc; } static int hnb_gtp_wq_read_cb(struct osmo_fd *fd) { struct hnb *hnb = (struct hnb *)fd->data; struct msgb *msg; struct gtp1u_hdr *hdr; int rc; struct osmo_sockaddr peer; socklen_t peerlen = sizeof(peer.u.sas); unsigned int opt_hdr_len; uint16_t pl_len; msg = msgb_alloc_c(hnb, 4096, "gtp-dl"); rc = recvfrom(hnb->gtp.wq1u.bfd.fd, msgb_data(msg), msg->data_len, 0, &peer.u.sa, &peerlen); if (rc <= 0) { LOGP(DGTP, LOGL_ERROR, "recvfrom() failed: rc = %d error = %s\n", rc, rc ? strerror(errno) : "No error"); goto free_ret; } msgb_put(msg, rc); /* Do some sanity checks: */ if (msgb_length(msg) < sizeof(*hdr)) { LOGP(DGTP, LOGL_ERROR, "Rx GTP-U packet with size %u < %zu (header)\n", msgb_length(msg), sizeof(*hdr)); rc = -EINVAL; goto free_ret; } hdr = (struct gtp1u_hdr *)msgb_data(msg); pl_len = osmo_load16be(&hdr->length); /* Do some sanity checks: */ if (hdr->version != 1) { LOGP(DGTP, LOGL_ERROR, "Rx GTP-U version %u != 1\n", hdr->version); rc = -ENOTSUP; goto free_ret; } if (hdr->e == 1) { LOGP(DGTP, LOGL_ERROR, "Rx GTP-U with Extension Header not supported\n"); rc = -ENOTSUP; goto free_ret; } if (hdr->s || hdr->pn || hdr->e) opt_hdr_len = 4; else opt_hdr_len = 0; if (msgb_length(msg) < sizeof(*hdr) + opt_hdr_len) { LOGP(DGTP, LOGL_ERROR, "Rx GTP-U packet with size %u < %zu (header + opt)\n", msgb_length(msg), sizeof(*hdr) + opt_hdr_len); rc = -EINVAL; goto free_ret; } msg->l3h = msgb_data(msg) + sizeof(*hdr) + opt_hdr_len; if (pl_len < opt_hdr_len || msgb_l3len(msg) != (pl_len - opt_hdr_len)) { LOGP(DGTP, LOGL_ERROR, "Rx GTP-U packet with payload size %u != %u (header)\n", msgb_length(msg), pl_len - opt_hdr_len); rc = -EINVAL; goto free_ret; } return hnb_gtp_rx_gtp(hnb, msg, &peer); free_ret: msgb_free(msg); return rc; } static int hnb_gtp_wq_write_cb(struct osmo_fd *fd, struct msgb *msg) { struct hnb *hnb = (struct hnb *)fd->data; struct osmo_sockaddr *rem_addr; int rc; rem_addr = (struct osmo_sockaddr *)msgb_data(msg); msgb_pull(msg, sizeof(*rem_addr)); rc = sendto(hnb->gtp.wq1u.bfd.fd, msgb_data(msg), msgb_length(msg), 0, &rem_addr->u.sa, sizeof(*rem_addr)); if (rc < 0) { int err = errno; LOGP(DGTP, LOGL_ERROR, "GTP1-U sendto(len=%d, to=%s) failed: Error = %s\n", msgb_length(msg), osmo_sockaddr_to_str(rem_addr), strerror(err)); } return rc; } /* Get osa of locally bound GTP-U socket */ static int sk_get_bound_addr(int fd, struct osmo_sockaddr *osa) { int rc; socklen_t alen = sizeof(*osa); rc = getsockname(fd, (struct sockaddr *)&osa->u.sa, &alen); if (rc < 0) return rc; return 0; } int hnb_gtp_bind(struct hnb *hnb) { int rc; OSMO_ASSERT(hnb->gtp.wq1u.bfd.fd == -1); hnb->gtp.wq1u.read_cb = hnb_gtp_wq_read_cb; hnb->gtp.wq1u.write_cb = hnb_gtp_wq_write_cb; rc = osmo_sock_init2_ofd(&hnb->gtp.wq1u.bfd, AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, hnb->gtp.cfg_local_addr, 2152, NULL, 0, OSMO_SOCK_F_BIND); if (rc < 0) { LOGP(DGTP, LOGL_ERROR, "Failed to set up GTP socket: %s\n", strerror(-rc)); return rc; } rc = sk_get_bound_addr(hnb->gtp.wq1u.bfd.fd, &hnb->gtp.local_addr); if (rc < 0) { LOGP(DGTP, LOGL_ERROR, "Failed to get GTP-U socket bound address: %s\n", strerror(-rc)); goto free_ret; } return 0; free_ret: if (hnb->gtp.wq1u.bfd.fd != -1) { close(hnb->gtp.wq1u.bfd.fd); hnb->gtp.wq1u.bfd.fd = -1; } return rc; } void hnb_gtp_unbind(struct hnb *hnb) { if (hnb->gtp.wq1u.bfd.fd != -1) { osmo_wqueue_clear(&hnb->gtp.wq1u); osmo_fd_unregister(&hnb->gtp.wq1u.bfd); close(hnb->gtp.wq1u.bfd.fd); hnb->gtp.wq1u.bfd.fd = -1; } }