mirror of
https://github.com/open5gs/open5gs.git
synced 2026-08-31 01:45:31 +00:00
400 lines
12 KiB
C
400 lines
12 KiB
C
/*
|
|
* Copyright (C) 2019,2024 by Sukchan Lee <acetcom@gmail.com>
|
|
*
|
|
* This file is part of Open5GS.
|
|
*
|
|
* 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "ogs-sbi.h"
|
|
|
|
extern const ogs_sbi_server_actions_t ogs_mhd_server_actions;
|
|
extern const ogs_sbi_server_actions_t ogs_nghttp2_server_actions;
|
|
|
|
ogs_sbi_server_actions_t ogs_sbi_server_actions;
|
|
bool ogs_sbi_server_actions_initialized = false;
|
|
|
|
static OGS_POOL(server_pool, ogs_sbi_server_t);
|
|
|
|
void ogs_sbi_server_init(int num_of_session_pool, int num_of_stream_pool)
|
|
{
|
|
if (ogs_sbi_server_actions_initialized == false) {
|
|
#if 1 /* Use HTTP2 */
|
|
ogs_sbi_server_actions = ogs_nghttp2_server_actions;
|
|
#else
|
|
ogs_sbi_server_actions = ogs_mhd_server_actions;
|
|
#endif
|
|
}
|
|
|
|
ogs_sbi_server_actions.init(num_of_session_pool, num_of_stream_pool);
|
|
|
|
ogs_list_init(&ogs_sbi_self()->server_list);
|
|
ogs_pool_init(&server_pool, ogs_app()->pool.nf);
|
|
}
|
|
|
|
void ogs_sbi_server_final(void)
|
|
{
|
|
ogs_sbi_server_remove_all();
|
|
|
|
ogs_pool_final(&server_pool);
|
|
|
|
ogs_sbi_server_actions.cleanup();
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_add(
|
|
const char *interface,
|
|
OpenAPI_uri_scheme_e scheme, ogs_sockaddr_t *addr,
|
|
ogs_sockopt_t *option)
|
|
{
|
|
ogs_sbi_server_t *server = NULL;
|
|
|
|
ogs_assert(addr);
|
|
ogs_assert(scheme);
|
|
|
|
ogs_pool_id_calloc(&server_pool, &server);
|
|
ogs_assert(server);
|
|
|
|
if (interface)
|
|
server->interface = ogs_strdup(interface);
|
|
|
|
server->scheme = scheme;
|
|
|
|
if (ogs_sbi_self()->tls.server.private_key)
|
|
server->private_key =
|
|
ogs_strdup(ogs_sbi_self()->tls.server.private_key);
|
|
if (ogs_sbi_self()->tls.server.cert)
|
|
server->cert = ogs_strdup(ogs_sbi_self()->tls.server.cert);
|
|
if (ogs_sbi_self()->tls.server.sslkeylog)
|
|
server->sslkeylog = ogs_strdup(ogs_sbi_self()->tls.server.sslkeylog);
|
|
|
|
server->verify_client = ogs_sbi_self()->tls.server.verify_client;
|
|
if (ogs_sbi_self()->tls.server.verify_client_cacert)
|
|
server->verify_client_cacert =
|
|
ogs_strdup(ogs_sbi_self()->tls.server.verify_client_cacert);
|
|
|
|
ogs_assert(OGS_OK == ogs_copyaddrinfo(&server->node.addr, addr));
|
|
if (option)
|
|
server->node.option = ogs_memdup(option, sizeof *option);
|
|
|
|
ogs_list_add(&ogs_sbi_self()->server_list, server);
|
|
|
|
return server;
|
|
}
|
|
|
|
void ogs_sbi_server_remove(ogs_sbi_server_t *server)
|
|
{
|
|
ogs_assert(server);
|
|
|
|
ogs_list_remove(&ogs_sbi_self()->server_list, server);
|
|
|
|
if (server->interface)
|
|
ogs_free(server->interface);
|
|
|
|
ogs_assert(server->node.addr);
|
|
ogs_freeaddrinfo(server->node.addr);
|
|
if (server->node.option)
|
|
ogs_free(server->node.option);
|
|
if (server->advertise)
|
|
ogs_freeaddrinfo(server->advertise);
|
|
|
|
if (server->verify_client_cacert)
|
|
ogs_free(server->verify_client_cacert);
|
|
if (server->private_key)
|
|
ogs_free(server->private_key);
|
|
if (server->cert)
|
|
ogs_free(server->cert);
|
|
if (server->sslkeylog)
|
|
ogs_free(server->sslkeylog);
|
|
|
|
ogs_pool_id_free(&server_pool, server);
|
|
}
|
|
|
|
void ogs_sbi_server_remove_all(void)
|
|
{
|
|
ogs_sbi_server_t *server = NULL, *next_server = NULL;
|
|
|
|
ogs_list_for_each_safe(&ogs_sbi_self()->server_list, next_server, server)
|
|
ogs_sbi_server_remove(server);
|
|
}
|
|
|
|
void ogs_sbi_server_set_advertise(
|
|
ogs_sbi_server_t *server, int family, ogs_sockaddr_t *advertise)
|
|
{
|
|
ogs_sockaddr_t *addr = NULL;
|
|
|
|
ogs_assert(server);
|
|
ogs_assert(advertise);
|
|
|
|
ogs_assert(OGS_OK == ogs_copyaddrinfo(&addr, advertise));
|
|
if (family != AF_UNSPEC)
|
|
ogs_filteraddrinfo(&addr, family);
|
|
|
|
if (addr)
|
|
server->advertise = addr;
|
|
}
|
|
|
|
int ogs_sbi_server_start_all(
|
|
int (*cb)(ogs_sbi_request_t *request, void *data))
|
|
{
|
|
ogs_sbi_server_t *server = NULL, *next_server = NULL;
|
|
|
|
ogs_list_for_each_safe(&ogs_sbi_self()->server_list, next_server, server)
|
|
if (ogs_sbi_server_actions.start(server, cb) != OGS_OK)
|
|
return OGS_ERROR;
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
void ogs_sbi_server_graceful_shutdown_all(void)
|
|
{
|
|
ogs_sbi_server_t *server = NULL, *next_server = NULL;
|
|
|
|
ogs_list_for_each_safe(&ogs_sbi_self()->server_list, next_server, server)
|
|
ogs_sbi_server_actions.graceful_shutdown(server);
|
|
}
|
|
|
|
void ogs_sbi_server_stop_all(void)
|
|
{
|
|
ogs_sbi_server_t *server = NULL, *next_server = NULL;
|
|
|
|
ogs_list_for_each_safe(&ogs_sbi_self()->server_list, next_server, server)
|
|
ogs_sbi_server_actions.stop(server);
|
|
}
|
|
|
|
bool ogs_sbi_server_send_rspmem_persistent(
|
|
ogs_sbi_stream_t *stream, ogs_sbi_response_t *response)
|
|
{
|
|
return ogs_sbi_server_actions.send_rspmem_persistent(stream, response);
|
|
}
|
|
|
|
bool ogs_sbi_server_send_response(
|
|
ogs_sbi_stream_t *stream, ogs_sbi_response_t *response)
|
|
{
|
|
return ogs_sbi_server_actions.send_response(stream, response);
|
|
}
|
|
|
|
bool ogs_sbi_server_send_problem(
|
|
ogs_sbi_stream_t *stream, OpenAPI_problem_details_t *problem)
|
|
{
|
|
ogs_sbi_message_t message;
|
|
ogs_sbi_response_t *response = NULL;
|
|
|
|
ogs_assert(stream);
|
|
ogs_assert(problem);
|
|
|
|
memset(&message, 0, sizeof(message));
|
|
|
|
message.http.content_type = (char*)"application/problem+json";
|
|
message.ProblemDetails = problem;
|
|
|
|
response = ogs_sbi_build_response(&message, problem->status);
|
|
ogs_assert(response);
|
|
|
|
ogs_sbi_server_send_response(stream, response);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ogs_sbi_server_send_error(ogs_sbi_stream_t *stream,
|
|
int status, ogs_sbi_message_t *message,
|
|
const char *title, const char *detail, const char *cause)
|
|
{
|
|
OpenAPI_problem_details_t problem;
|
|
|
|
ogs_assert(stream);
|
|
|
|
memset(&problem, 0, sizeof(problem));
|
|
|
|
if (message) {
|
|
problem.type = ogs_msprintf("/%s/%s",
|
|
message->h.service.name, message->h.api.version);
|
|
ogs_expect(problem.type);
|
|
if (message->h.resource.component[1])
|
|
problem.instance = ogs_msprintf("/%s/%s",
|
|
message->h.resource.component[0],
|
|
message->h.resource.component[1]);
|
|
else
|
|
problem.instance =
|
|
ogs_msprintf("/%s", message->h.resource.component[0]);
|
|
ogs_expect(problem.instance);
|
|
}
|
|
if (status) {
|
|
problem.is_status = true;
|
|
problem.status = status;
|
|
}
|
|
problem.title = (char*)title;
|
|
problem.detail = (char*)detail;
|
|
problem.cause = (char*)cause;
|
|
|
|
ogs_sbi_server_send_problem(stream, &problem);
|
|
|
|
if (problem.type)
|
|
ogs_free(problem.type);
|
|
if (problem.instance)
|
|
ogs_free(problem.instance);
|
|
|
|
return true;
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_from_stream(ogs_sbi_stream_t *stream)
|
|
{
|
|
return ogs_sbi_server_actions.server_from_stream(stream);
|
|
}
|
|
|
|
ogs_pool_id_t ogs_sbi_id_from_stream(ogs_sbi_stream_t *stream)
|
|
{
|
|
return ogs_sbi_server_actions.id_from_stream(stream);
|
|
}
|
|
|
|
void *ogs_sbi_stream_find_by_id(ogs_pool_id_t id)
|
|
{
|
|
return ogs_sbi_server_actions.stream_find_by_id(id);
|
|
}
|
|
|
|
int ogs_sbi_server_attach_xact(ogs_sbi_xact_t *xact)
|
|
{
|
|
ogs_sbi_stream_t *stream = NULL;
|
|
|
|
ogs_assert(xact);
|
|
|
|
/*
|
|
* Idempotent. NFs may set xact->assoc_stream_id and reach the
|
|
* SBI path multiple times for retries; attaching twice would
|
|
* corrupt the list, so we skip when already attached. This is
|
|
* not an error in itself — return OK so the caller continues.
|
|
*/
|
|
if (xact->to_stream_list) {
|
|
ogs_error("ogs_sbi_server_attach_xact: already attached "
|
|
"[xact:%d,assoc_stream_id:%d,service:%s]",
|
|
(int)xact->id, (int)xact->assoc_stream_id,
|
|
OpenAPI_service_name_ToString(xact->service_name));
|
|
return OGS_OK;
|
|
}
|
|
|
|
/*
|
|
* No associated stream is normal for self-initiated transactions
|
|
* (NRF discovery from the NF itself, status notifications, etc.).
|
|
* Silent skip, OK.
|
|
*/
|
|
if (xact->assoc_stream_id < OGS_MIN_POOL_ID ||
|
|
xact->assoc_stream_id > OGS_MAX_POOL_ID)
|
|
return OGS_OK;
|
|
|
|
/*
|
|
* xact->assoc_stream_id is a "best-effort" linkage: it means
|
|
* "if a response arrives and this stream is still alive,
|
|
* deliver the response there." The stream is not required to
|
|
* be alive at the moment the outbound transaction is sent.
|
|
*
|
|
* NFs are allowed to pass the same stream to discover_and_send()
|
|
* even after that stream has already been used to answer the
|
|
* original peer (and is therefore being closed). The common
|
|
* case is a "respond then start follow-up" pattern, e.g. SMF
|
|
* answering the AMF with HTTP 201 Created and then kicking off
|
|
* Npcf_SMPolicyControl_Create.
|
|
*
|
|
* Two outcomes:
|
|
*
|
|
* - Stream still alive: attach to its xact_list so the
|
|
* transaction is cancelled (and its response timer freed)
|
|
* if the peer later resets the stream. This is the
|
|
* #4472/#4473 protection path. Returns OGS_OK.
|
|
*
|
|
* - Stream already gone: nothing to attach. Returns
|
|
* OGS_ERROR so the caller can log with its own context
|
|
* (NF name, UE/session id, etc.) and decide what to do.
|
|
* The caller may legitimately ignore the error and let
|
|
* the transaction proceed; the response (if any) will be
|
|
* dropped through the existing "STREAM has already been
|
|
* removed" path in the response handler.
|
|
*/
|
|
stream = ogs_sbi_stream_find_by_id(xact->assoc_stream_id);
|
|
if (!stream) {
|
|
ogs_error("ogs_sbi_server_attach_xact: originating SBI stream "
|
|
"already closed "
|
|
"[xact:%d,assoc_stream_id:%d,service:%s]",
|
|
(int)xact->id, (int)xact->assoc_stream_id,
|
|
OpenAPI_service_name_ToString(xact->service_name));
|
|
return OGS_NOTFOUND;
|
|
}
|
|
|
|
ogs_assert(ogs_sbi_server_actions.xact_attach);
|
|
ogs_sbi_server_actions.xact_attach(stream, xact);
|
|
|
|
return OGS_OK;
|
|
}
|
|
|
|
void ogs_sbi_server_detach_xact(ogs_sbi_xact_t *xact)
|
|
{
|
|
ogs_assert(xact);
|
|
|
|
/*
|
|
* Idempotent. No-op when not attached, including transactions
|
|
* that never had an inbound stream (NRF discovery, status
|
|
* notifications) where to_stream_list stays NULL. This is
|
|
* invoked unconditionally from ogs_sbi_xact_remove(), so
|
|
* streamless transactions reach here on every cleanup.
|
|
*/
|
|
if (!xact->to_stream_list)
|
|
return;
|
|
|
|
ogs_assert(ogs_sbi_server_actions.xact_detach);
|
|
ogs_sbi_server_actions.xact_detach(xact);
|
|
}
|
|
|
|
static ogs_sbi_server_t *ogs_sbi_server_find_by_interface(
|
|
ogs_sbi_server_t *current, const char *interface)
|
|
{
|
|
ogs_sbi_server_t *server = NULL;
|
|
|
|
server = current ?
|
|
ogs_list_next(current) : ogs_list_first(&ogs_sbi_self()->server_list);
|
|
|
|
for (; server; server = ogs_list_next(server)) {
|
|
if (interface == NULL) {
|
|
if (server->interface == NULL)
|
|
return server;
|
|
} else {
|
|
if (server->interface && strcmp(interface, server->interface) == 0)
|
|
return server;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_first(void)
|
|
{
|
|
return ogs_sbi_server_find_by_interface(NULL, NULL);
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_next(ogs_sbi_server_t *current)
|
|
{
|
|
ogs_assert(current);
|
|
return ogs_sbi_server_find_by_interface(current, NULL);
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_first_by_interface(const char *interface)
|
|
{
|
|
ogs_assert(interface);
|
|
return ogs_sbi_server_find_by_interface(NULL, interface);
|
|
}
|
|
|
|
ogs_sbi_server_t *ogs_sbi_server_next_by_interface(
|
|
ogs_sbi_server_t *current, const char *interface)
|
|
{
|
|
ogs_assert(current);
|
|
ogs_assert(interface);
|
|
return ogs_sbi_server_find_by_interface(current, interface);
|
|
}
|