Files
rtbrick-bngblaster/code/bngblaster/src/bbl_ctrl.c
T

418 lines
15 KiB
C

/*
* BNG Blaster (BBL) - Control Socket
*
* Christian Giese, January 2021
*
* Copyright (C) 2020-2022, RtBrick, Inc.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/stat.h>
#include "bbl.h"
#include "bbl_ctrl.h"
#include "bbl_session.h"
#include "bbl_stream.h"
#include "bbl_dhcp.h"
#include "bbl_dhcpv6.h"
#define BACKLOG 4
int
bbl_ctrl_status(int fd, const char *status, uint32_t code, const char *message)
{
int result = 0;
json_t *root = json_pack("{sssiss*}", "status", status, "code", code, "message", message);
if(root) {
result = json_dumpfd(root, fd, 0);
json_decref(root);
}
return result;
}
int
bbl_ctrl_test_info(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused)))
{
int result = 0;
json_t *root;
root = json_pack("{ss si s{ss si}}",
"status", "ok",
"code", 200,
"test-info",
"state", test_state(),
"duration", test_duration());
if(root) {
result = json_dumpfd(root, fd, 0);
} else {
result = bbl_ctrl_status(fd, "error", 500, "internal error");
}
return result;
}
int
bbl_ctrl_multicast_traffic_start(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused)))
{
g_ctx->multicast_endpoint = ENDPOINT_ACTIVE;
return bbl_ctrl_status(fd, "ok", 200, NULL);
}
int
bbl_ctrl_multicast_traffic_stop(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused)))
{
g_ctx->multicast_endpoint = ENDPOINT_ENABLED;
return bbl_ctrl_status(fd, "ok", 200, NULL);
}
int
bbl_ctrl_traffic_start(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused)))
{
enable_disable_traffic(true);
return bbl_ctrl_status(fd, "ok", 200, NULL);
}
int
bbl_ctrl_traffic_stop(int fd, uint32_t session_id __attribute__((unused)), json_t *arguments __attribute__((unused)))
{
enable_disable_traffic(false);
return bbl_ctrl_status(fd, "ok", 200, NULL);
}
typedef int callback_function(int fd, uint32_t session_id, json_t *arguments);
struct action {
char *name;
callback_function *fn;
bool thread_safe;
};
struct action actions[] = {
{"interfaces", bbl_interface_ctrl, true},
{"access-interfaces", bbl_access_ctrl_interfaces, true},
{"network-interfaces", bbl_network_ctrl_interfaces, true},
{"a10nsp-interfaces", bbl_a10nsp_ctrl_interfaces, true},
{"terminate", bbl_session_ctrl_terminate, false},
{"sessions-pending", bbl_session_ctrl_pending, true},
{"session-counters", bbl_session_ctrl_counters, true},
{"session-info", bbl_session_ctrl_info, true},
{"session-start", bbl_session_ctrl_start, true},
{"session-traffic", bbl_session_ctrl_traffic_stats, true},
{"session-traffic-enabled", bbl_session_ctrl_traffic_start, false},
{"session-traffic-start", bbl_session_ctrl_traffic_start, false},
{"session-traffic-disabled", bbl_session_ctrl_traffic_stop, false},
{"session-traffic-stop", bbl_session_ctrl_traffic_stop, false},
{"session-streams", bbl_stream_ctrl_session, true},
{"stream-traffic-enabled", bbl_stream_ctrl_traffic_start, false},
{"stream-traffic-start", bbl_stream_ctrl_traffic_start, false},
{"stream-traffic-disabled", bbl_stream_ctrl_traffic_stop, false},
{"stream-traffic-stop", bbl_stream_ctrl_traffic_stop, false},
{"stream-info", bbl_stream_ctrl_info, true},
{"stream-summary", bbl_stream_ctrl_summary, true},
{"stream-stats", bbl_stream_ctrl_stats, true},
{"stream-reset", bbl_stream_ctrl_reset, false},
{"multicast-traffic-start", bbl_ctrl_multicast_traffic_start, false},
{"multicast-traffic-stop", bbl_ctrl_multicast_traffic_stop, false},
{"igmp-join", bbl_igmp_ctrl_join, false},
{"igmp-join-iter", bbl_igmp_ctrl_join_iter, false},
{"igmp-leave", bbl_igmp_ctrl_leave, false},
{"igmp-leave-all", bbl_igmp_ctrl_leave_all, false},
{"igmp-info", bbl_igmp_ctrl_info, true},
{"zapping-start", bbl_igmp_ctrl_zapping_start, true},
{"zapping-stop", bbl_igmp_ctrl_zapping_stop, false},
{"zapping-stats", bbl_igmp_ctrl_zapping_stats, true},
{"li-flows", bbl_li_ctrl_flows, true},
{"l2tp-tunnels", bbl_l2tp_ctrl_tunnels, true},
{"l2tp-sessions", bbl_l2tp_ctrl_sessions, true},
{"l2tp-csurq", bbl_l2tp_ctrl_csurq, false},
{"l2tp-tunnel-terminate", bbl_l2tp_ctrl_tunnel_terminate, false},
{"l2tp-session-terminate", bbl_l2tp_ctrl_session_terminate, false},
{"ipcp-open", bbl_session_ctrl_ipcp_open, false},
{"ipcp-close", bbl_session_ctrl_ipcp_close, false},
{"ip6cp-open", bbl_session_ctrl_ip6cp_open, false},
{"ip6cp-close", bbl_session_ctrl_ip6cp_close, false},
{"cfm-cc-start", bbl_cfm_ctrl_cc_start, false},
{"cfm-cc-stop", bbl_cfm_ctrl_cc_stop, false},
{"cfm-cc-rdi-on", bbl_cfm_ctrl_cc_rdi_on, false},
{"cfm-cc-rdi-off", bbl_cfm_ctrl_cc_rdi_off, false},
{"traffic-start", bbl_ctrl_traffic_start, false},
{"traffic-stop", bbl_ctrl_traffic_stop, false},
{"isis-adjacencies", isis_ctrl_adjacencies, true},
{"isis-database", isis_ctrl_database, true},
{"isis-load-mrt", isis_ctrl_load_mrt, false},
{"isis-lsp-update", isis_ctrl_lsp_update, false},
{"isis-teardown", isis_ctrl_teardown, false},
{"bgp-sessions", bgp_ctrl_sessions, true},
{"bgp-disconnect", bgp_ctrl_disconnect, false},
{"bgp-teardown", bgp_ctrl_teardown, true},
{"bgp-raw-update-list", bgp_ctrl_raw_update_list, true},
{"bgp-raw-update", bgp_ctrl_raw_update, false},
{"monkey-start", bbl_session_ctrl_monkey_start, false},
{"monkey-stop", bbl_session_ctrl_monkey_stop, false},
{"lag-info", bbl_lag_ctrl_info, true},
{"test-info", bbl_ctrl_test_info, true},
{NULL, NULL, false},
};
static void
bbl_ctrl_socket_main(bbl_ctrl_thread_s *ctrl)
{
if(ctrl->main.fd) {
pthread_mutex_lock(&ctrl->mutex);
actions[ctrl->main.action].fn(ctrl->main.fd, ctrl->main.session_id, (json_t*)ctrl->main.arguments);
ctrl->main.action = 0;
ctrl->main.fd = 0;
ctrl->main.session_id = 0;
ctrl->main.arguments = NULL;
pthread_cond_signal(&ctrl->cond);
pthread_mutex_unlock(&ctrl->mutex);
}
}
void
bbl_ctrl_socket_main_job(timer_s *timer)
{
bbl_ctrl_socket_main(timer->data);
}
void *
bbl_ctrl_socket_thread(void *thread_data)
{
bbl_ctrl_thread_s *ctrl = thread_data;
size_t i;
size_t flags = JSON_DISABLE_EOF_CHECK;
json_error_t error;
json_t *root = NULL;
json_t* arguments = NULL;
json_t* value = NULL;
const char *command = NULL;
uint32_t session_id = 0;
bbl_access_interface_s *access_interface;
vlan_session_key_t key = {0};
bbl_session_s *session;
void **search;
struct timespec sleep, rem;
sleep.tv_sec = 0;
sleep.tv_nsec = 100 * MSEC;
/* ToDo: Add connection manager!
* This is just a temporary workaround! Finally we need
* to create a connection manager. */
static int fd = 0;
ctrl->active = true;
while(ctrl->active) {
fd = accept(ctrl->socket, 0, 0);
if(fd > 0) {
/* New connection */
root = json_loadfd(fd, flags, &error);
if(!root) {
LOG(DEBUG, "Invalid json via ctrl socket: line %d: %s\n", error.line, error.text);
bbl_ctrl_status(fd, "error", 400, "invalid json");
} else {
/* Each command request should be formatted as shown in the example below
* with a mandatory command element and optional arguments.
* {
* "command": "session-info",
* "arguments": {
* "outer-vlan": 1,
* "inner-vlan": 2
* }
* }
*/
command = NULL;
arguments = NULL;
session_id = 0;
key.ifindex = 0;
key.inner_vlan_id = 0;
key.outer_vlan_id = 0;
if(json_unpack(root, "{s:s, s?o}", "command", &command, "arguments", &arguments) != 0) {
LOG_NOARG(DEBUG, "Invalid command via ctrl socket\n");
bbl_ctrl_status(fd, "error", 400, "invalid request");
} else {
if(arguments) {
value = json_object_get(arguments, "session-id");
if(value) {
if(json_is_number(value)) {
session_id = json_number_value(value);
} else {
bbl_ctrl_status(fd, "error", 400, "invalid session-id");
goto CLOSE;
}
} else {
/* Deprecated!
* For backward compatibility with version 0.4.X, we still
* support per session commands using VLAN index instead of
* new session-id. */
value = json_object_get(arguments, "ifindex");
if(value) {
if(json_is_number(value)) {
key.ifindex = json_number_value(value);
} else {
bbl_ctrl_status(fd, "error", 400, "invalid ifindex");
goto CLOSE;
}
} else {
/* Use first interface as default. */
access_interface = bbl_access_interface_get(NULL);
if(access_interface) {
key.ifindex = access_interface->ifindex;
}
}
value = json_object_get(arguments, "outer-vlan");
if(value) {
if(json_is_number(value)) {
key.outer_vlan_id = json_number_value(value);
} else {
bbl_ctrl_status(fd, "error", 400, "invalid outer-vlan");
goto CLOSE;
}
}
value = json_object_get(arguments, "inner-vlan");
if(value) {
if(json_is_number(value)) {
key.inner_vlan_id = json_number_value(value);
} else {
bbl_ctrl_status(fd, "error", 400, "invalid inner-vlan");
goto CLOSE;
}
}
if(key.outer_vlan_id) {
search = dict_search(g_ctx->vlan_session_dict, &key);
if(search) {
session = *search;
session_id = session->session_id;
} else {
bbl_ctrl_status(fd, "warning", 404, "session not found");
goto CLOSE;
}
}
}
}
for(i = 0; true; i++) {
if(actions[i].name == NULL) {
bbl_ctrl_status(fd, "error", 400, "unknown command");
break;
} else if(strcmp(actions[i].name, command) == 0) {
if(actions[i].thread_safe) {
actions[i].fn(fd, session_id, arguments);
} else {
pthread_mutex_lock(&ctrl->mutex);
ctrl->main.fd = fd;
ctrl->main.action = i;
ctrl->main.session_id = session_id;
ctrl->main.arguments = (void*)arguments;
pthread_cond_wait(&ctrl->cond, &ctrl->mutex);
pthread_mutex_unlock(&ctrl->mutex);
}
break;
}
}
}
}
CLOSE:
if(root) {
json_decref(root);
root = NULL;
}
shutdown(fd, SHUT_WR);
}
nanosleep(&sleep, &rem);
if(fd > 0) {
close(fd);
}
}
return NULL;
}
bool
bbl_ctrl_socket_init()
{
bbl_ctrl_thread_s *ctrl;
struct sockaddr_un addr = {0};
if(!g_ctx->ctrl_socket_path) {
return true;
}
ctrl = calloc(1, sizeof(bbl_ctrl_thread_s));
if(!ctrl) {
fprintf(stderr, "Error: Failed to init ctrl socket memory\n");
return false;
}
g_ctx->ctrl_thread = ctrl;
ctrl->socket = socket(AF_UNIX, SOCK_STREAM, 0);
if(ctrl->socket < 0) {
fprintf(stderr, "Error: Failed to create ctrl socket\n");
return false;
}
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, g_ctx->ctrl_socket_path, sizeof(addr.sun_path)-1);
unlink(g_ctx->ctrl_socket_path);
if(bind(ctrl->socket, (struct sockaddr *)&addr, SUN_LEN(&addr)) != 0) {
fprintf(stderr, "Error: Failed to bind ctrl socket %s (error %d)\n", g_ctx->ctrl_socket_path, errno);
return false;
}
if(listen(ctrl->socket, BACKLOG) != 0) {
fprintf(stderr, "Error: Failed to listen on ctrl socket %s (error %d)\n", g_ctx->ctrl_socket_path, errno);
return false;
}
/* Change socket to non-blocking */
fcntl(ctrl->socket, F_SETFL, O_NONBLOCK);
/* Create ctrl thread */
if(pthread_mutex_init(&ctrl->mutex, NULL) != 0) {
LOG_NOARG(ERROR, "Failed to init ctrl mutex\n");
return false;
}
if(pthread_cond_init(&ctrl->cond, NULL) != 0) {
LOG_NOARG(ERROR, "Failed to init ctrl condition\n");
return false;
}
if(pthread_create(&ctrl->thread, NULL, bbl_ctrl_socket_thread, (void *)ctrl) != 0) {
LOG_NOARG(ERROR, "Failed to create ctrl thread\n");
return false;
}
/* Start ctrl main job */
timer_add_periodic(&g_ctx->timer_root, &ctrl->main.timer, "CTRL Socket Main Timer", 0, 1000 * MSEC, ctrl, &bbl_ctrl_socket_main_job);
LOG(INFO, "Opened control socket %s\n", g_ctx->ctrl_socket_path);
return true;
}
bool
bbl_ctrl_socket_close()
{
bbl_ctrl_thread_s *ctrl;
if(g_ctx->ctrl_thread) {
ctrl = g_ctx->ctrl_thread;
if(ctrl->active) {
ctrl->active = false;
bbl_ctrl_socket_main(ctrl);
pthread_join(ctrl->thread, NULL);
pthread_mutex_destroy(&ctrl->mutex);
pthread_cond_destroy(&ctrl->cond);
}
if(ctrl->socket) {
close(ctrl->socket);
}
unlink(g_ctx->ctrl_socket_path);
free(g_ctx->ctrl_thread);
g_ctx->ctrl_thread = NULL;
}
return true;
}