/* (C) 2018-2019 by sysmocom s.f.m.c. GmbH * All Rights Reserved * * Author: Harald Welte, Philipp Maier * * 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 #include #include #include #define LOGSGC(sgc, lvl, fmt, args...) \ LOGP(DSGS, lvl, "%s: " fmt, (sgc)->sockname, ## args) /* call-back when data arrives on SGs */ static int sgs_conn_readable_cb(struct osmo_stream_srv *conn) { struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn); struct sgs_connection *sgc = osmo_stream_srv_get_data(conn); struct msgb *msg = gsm29118_msgb_alloc(); struct sctp_sndrcvinfo sinfo; int flags = 0; int rc = 0; /* we cannot use osmo_stream_srv_recv() here, as we might get some out-of-band info from * SCTP. FIXME: add something like osmo_stream_srv_recv_sctp() to libosmo-netif and use * it here as well as in libosmo-sigtran */ rc = sctp_recvmsg(ofd->fd, msgb_data(msg), msgb_tailroom(msg), NULL, NULL, &sinfo, &flags); if (rc < 0) { osmo_stream_srv_destroy(conn); rc = -EBADF; goto out; } else if (rc == 0) { osmo_stream_srv_destroy(conn); rc = -EBADF; goto out; } else { msgb_put(msg, rc); } if (flags & MSG_NOTIFICATION) { union sctp_notification *notif = (union sctp_notification *)msgb_data(msg); switch (notif->sn_header.sn_type) { case SCTP_SHUTDOWN_EVENT: osmo_stream_srv_destroy(conn); rc = -EBADF; break; case SCTP_ASSOC_CHANGE: /* FIXME: do we have to notify the SGs code about this? */ break; default: break; } goto out; } /* set l2 header, as that's what we use in SGs code */ msg->l2h = msgb_data(msg); if (msgb_sctp_ppid(msg) != 0) { LOGSGC(sgc, LOGL_NOTICE, "Ignoring SCTP PPID %ld (spec violation)\n", msgb_sctp_ppid(msg)); msgb_free(msg); return 0; } /* handle message */ sgs_iface_rx(sgc, msg); return 0; out: msgb_free(msg); return rc; } /* call-back when new connection is closed ed on SGs */ static int sgs_conn_closed_cb(struct osmo_stream_srv *conn) { struct sgs_connection *sgc = osmo_stream_srv_get_data(conn); LOGSGC(sgc, LOGL_NOTICE, "Connection lost\n"); if (sgc->mme) { /* unlink ourselves from the MME context */ if (sgc->mme->conn == sgc) sgc->mme->conn = NULL; } llist_del(&sgc->entry); return 0; } /* call-back when new connection is accept() ed on SGs */ static int sgs_accept_cb(struct osmo_stream_srv_link *link, int fd) { struct sgs_state *sgs = osmo_stream_srv_link_get_data(link); struct sgs_connection *sgc = talloc_zero(link, struct sgs_connection); OSMO_ASSERT(sgc); sgc->sgs = sgs; osmo_sock_get_name_buf(sgc->sockname, sizeof(sgc->sockname), fd); sgc->srv = osmo_stream_srv_create(sgc, link, fd, sgs_conn_readable_cb, sgs_conn_closed_cb, sgc); if (!sgc->srv) { talloc_free(sgc); return -1; } LOGSGC(sgc, LOGL_INFO, "Accepted new SGs connection\n"); llist_add_tail(&sgc->entry, &sgs->conn_list); return 0; } static struct sgs_state *sgs_state_alloc(void *ctx) { struct sgs_state *sgs = talloc_zero(ctx, struct sgs_state); INIT_LLIST_HEAD(&sgs->mme_list); INIT_LLIST_HEAD(&sgs->conn_list); memcpy(sgs->cfg.timer, sgs_state_timer_defaults, sizeof(sgs->cfg.timer)); memcpy(sgs->cfg.counter, sgs_state_counter_defaults, sizeof(sgs->cfg.counter)); sgs->cfg.local_port = SGS_PORT_DEFAULT; osmo_strlcpy(sgs->cfg.local_addr, DEFAULT_SGS_SERVER_IP, sizeof(sgs->cfg.local_addr)); osmo_strlcpy(sgs->cfg.vlr_name, DEFAULT_SGS_SERVER_VLR_NAME, sizeof(sgs->cfg.vlr_name)); return sgs; } /*! allocate SGs new sgs state * \param[in] ctx talloc context * \returns returns allocated sgs state, NULL in case of error. */ struct sgs_state *sgs_server_alloc(void *ctx) { struct sgs_state *sgs; struct osmo_stream_srv_link *link; sgs = sgs_state_alloc(ctx); if (!sgs) return NULL; sgs->srv_link = link = osmo_stream_srv_link_create(ctx); if (!sgs->srv_link) return NULL; osmo_stream_srv_link_set_nodelay(link, true); osmo_stream_srv_link_set_addr(link, sgs->cfg.local_addr); osmo_stream_srv_link_set_port(link, sgs->cfg.local_port); osmo_stream_srv_link_set_proto(link, IPPROTO_SCTP); osmo_stream_srv_link_set_data(link, sgs); osmo_stream_srv_link_set_accept_cb(link, sgs_accept_cb); return sgs; } /*! (re)open SGs interface (SCTP) * \param[in] sgs associated sgs state * \returns 0 in case of success, -EINVAL in case of error. */ int sgs_server_open(struct sgs_state *sgs) { int rc; struct osmo_fd *ofd = osmo_stream_srv_link_get_ofd(sgs->srv_link); rc = osmo_stream_srv_link_open(sgs->srv_link); if (rc < 0) { LOGP(DSGS, LOGL_ERROR, "SGs socket cannot be opened: %s\n", strerror(errno)); return -EINVAL; } LOGP(DSGS, LOGL_NOTICE, "SGs socket bound to %s\n", osmo_sock_get_name2(ofd->fd)); return 0; }