mirror of https://github.com/sipwise/kamailio.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
579 lines
15 KiB
579 lines
15 KiB
/*
|
|
*
|
|
* Copyright (C) 2011 Flowroute LLC (flowroute.com)
|
|
*
|
|
* This file is part of Kamailio, a free SIP server.
|
|
*
|
|
* This file is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version
|
|
*
|
|
*
|
|
* This file 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <event.h>
|
|
#include <sys/timerfd.h>
|
|
|
|
#include "../../sr_module.h"
|
|
#include "../../route.h"
|
|
#include "../../route_struct.h"
|
|
#include "../../lvalue.h"
|
|
#include "../tm/tm_load.h"
|
|
|
|
#include "jsonrpc_io.h"
|
|
#include "jsonrpc.h"
|
|
#include "netstring.h"
|
|
|
|
#define CHECK_MALLOC_VOID(p) if(!p) {LM_ERR("Out of memory!"); return;}
|
|
#define CHECK_MALLOC(p) if(!p) {LM_ERR("Out of memory!"); return -1;}
|
|
|
|
struct jsonrpc_server {
|
|
char *host;
|
|
int port, socket, status, conn_attempts;
|
|
struct jsonrpc_server *next;
|
|
struct event *ev;
|
|
struct itimerspec *timer;
|
|
};
|
|
|
|
struct jsonrpc_server_group {
|
|
struct jsonrpc_server *next_server;
|
|
int priority;
|
|
struct jsonrpc_server_group *next_group;
|
|
};
|
|
|
|
struct tm_binds tmb;
|
|
|
|
struct jsonrpc_server_group *server_group;
|
|
|
|
void socket_cb(int fd, short event, void *arg);
|
|
void cmd_pipe_cb(int fd, short event, void *arg);
|
|
int set_non_blocking(int fd);
|
|
int parse_servers(char *_servers, struct jsonrpc_server_group **group_ptr);
|
|
int connect_servers(struct jsonrpc_server_group *group);
|
|
int connect_server(struct jsonrpc_server *server);
|
|
int handle_server_failure(struct jsonrpc_server *server);
|
|
|
|
/* module config from jsonrpc_mod.c */
|
|
int _jsonrpcc_max_conn_retry = 0; /* max retries to connect. -1 forever 0 none */
|
|
|
|
int jsonrpc_io_child_process(int cmd_pipe, char* _servers)
|
|
{
|
|
if (parse_servers(_servers, &server_group) != 0)
|
|
{
|
|
LM_ERR("servers parameter could not be parsed\n");
|
|
return -1;
|
|
}
|
|
|
|
event_init();
|
|
|
|
struct event pipe_ev;
|
|
set_non_blocking(cmd_pipe);
|
|
event_set(&pipe_ev, cmd_pipe, EV_READ | EV_PERSIST, cmd_pipe_cb, &pipe_ev);
|
|
event_add(&pipe_ev, NULL);
|
|
|
|
if (!connect_servers(server_group))
|
|
{
|
|
LM_WARN("failed to connect to any servers\n");
|
|
}
|
|
|
|
event_dispatch();
|
|
return 0;
|
|
}
|
|
|
|
void timeout_cb(int fd, short event, void *arg)
|
|
{
|
|
LM_ERR("message timeout\n");
|
|
jsonrpc_request_t *req = (jsonrpc_request_t*)arg;
|
|
json_object *error = json_object_new_string("timeout");
|
|
void_jsonrpc_request(req->id);
|
|
close(req->timerfd);
|
|
event_del(req->timer_ev);
|
|
pkg_free(req->timer_ev);
|
|
req->cbfunc(error, req->cbdata, 1);
|
|
pkg_free(req);
|
|
}
|
|
|
|
int result_cb(json_object *result, char *data, int error)
|
|
{
|
|
struct jsonrpc_pipe_cmd *cmd = (struct jsonrpc_pipe_cmd*)data;
|
|
|
|
pv_spec_t *dst = cmd->cb_pv;
|
|
pv_value_t val;
|
|
|
|
const char* res = json_object_get_string(result);
|
|
|
|
val.rs.s = (char*)res;
|
|
val.rs.len = strlen(res);
|
|
val.flags = PV_VAL_STR;
|
|
|
|
dst->setf(0, &dst->pvp, (int)EQ_T, &val);
|
|
|
|
int n;
|
|
if (error) {
|
|
n = route_get(&main_rt, cmd->err_route);
|
|
} else {
|
|
n = route_get(&main_rt, cmd->cb_route);
|
|
}
|
|
|
|
struct action *a = main_rt.rlist[n];
|
|
tmb.t_continue(cmd->t_hash, cmd->t_label, a);
|
|
|
|
free_pipe_cmd(cmd);
|
|
return 0;
|
|
}
|
|
|
|
|
|
int (*res_cb)(json_object*, char*, int) = &result_cb;
|
|
|
|
|
|
void cmd_pipe_cb(int fd, short event, void *arg)
|
|
{
|
|
struct jsonrpc_pipe_cmd *cmd;
|
|
char *ns = 0;
|
|
size_t bytes;
|
|
json_object *payload = NULL;
|
|
jsonrpc_request_t *req = NULL;
|
|
json_object *params;
|
|
/* struct event *ev = (struct event*)arg; */
|
|
|
|
if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd)) {
|
|
LM_ERR("failed to read from command pipe: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
|
|
params = json_tokener_parse(cmd->params);
|
|
|
|
if (cmd->notify_only) {
|
|
payload = build_jsonrpc_notification(cmd->method, params);
|
|
} else {
|
|
req = build_jsonrpc_request(cmd->method, params, (char*)cmd, res_cb);
|
|
if (req)
|
|
payload = req->payload;
|
|
}
|
|
|
|
if (!payload) {
|
|
LM_ERR("Failed to build jsonrpc_request_t (method: %s, params: %s)\n", cmd->method, cmd->params);
|
|
goto error;
|
|
}
|
|
char *json = (char*)json_object_get_string(payload);
|
|
|
|
bytes = netstring_encode_new(&ns, json, (size_t)strlen(json));
|
|
|
|
struct jsonrpc_server_group *g;
|
|
int sent = 0;
|
|
for (g = server_group; g != NULL; g = g->next_group)
|
|
{
|
|
struct jsonrpc_server *s, *first = NULL;
|
|
for (s = g->next_server; s != first; s = s->next)
|
|
{
|
|
if (first == NULL) first = s;
|
|
if (s->status == JSONRPC_SERVER_CONNECTED) {
|
|
if (send(s->socket, ns, bytes, 0) == bytes)
|
|
{
|
|
sent = 1;
|
|
break;
|
|
} else {
|
|
handle_server_failure(s);
|
|
}
|
|
}
|
|
g->next_server = s->next;
|
|
}
|
|
if (sent) {
|
|
break;
|
|
} else {
|
|
LM_WARN("Failed to send on priority group %d... proceeding to next priority group.\n", g->priority);
|
|
}
|
|
}
|
|
|
|
if (sent && req) {
|
|
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
|
|
if (timerfd == -1) {
|
|
LM_ERR("Could not create timerfd.");
|
|
goto error;
|
|
}
|
|
|
|
req->timerfd = timerfd;
|
|
struct itimerspec *itime = pkg_malloc(sizeof(struct itimerspec));
|
|
CHECK_MALLOC_VOID(itime);
|
|
itime->it_interval.tv_sec = 0;
|
|
itime->it_interval.tv_nsec = 0;
|
|
|
|
itime->it_value.tv_sec = JSONRPC_TIMEOUT/1000;
|
|
itime->it_value.tv_nsec = (JSONRPC_TIMEOUT % 1000) * 1000000;
|
|
if (timerfd_settime(timerfd, 0, itime, NULL) == -1)
|
|
{
|
|
LM_ERR("Could not set timer.");
|
|
goto error;
|
|
}
|
|
pkg_free(itime);
|
|
struct event *timer_ev = pkg_malloc(sizeof(struct event));
|
|
CHECK_MALLOC_VOID(timer_ev);
|
|
event_set(timer_ev, timerfd, EV_READ, timeout_cb, req);
|
|
if(event_add(timer_ev, NULL) == -1) {
|
|
LM_ERR("event_add failed while setting request timer (%s).", strerror(errno));
|
|
goto error;
|
|
}
|
|
req->timer_ev = timer_ev;
|
|
} else if (!sent) {
|
|
LM_ERR("Request could not be sent... no more failover groups.\n");
|
|
if (req) {
|
|
json_object *error = json_object_new_string("failure");
|
|
void_jsonrpc_request(req->id);
|
|
req->cbfunc(error, req->cbdata, 1);
|
|
}
|
|
}
|
|
|
|
pkg_free(ns);
|
|
json_object_put(payload);
|
|
if (cmd->notify_only) free_pipe_cmd(cmd);
|
|
return;
|
|
|
|
error:
|
|
if(ns) pkg_free(ns);
|
|
if(payload) json_object_put(payload);
|
|
if (cmd->notify_only) free_pipe_cmd(cmd);
|
|
return;
|
|
}
|
|
|
|
void socket_cb(int fd, short event, void *arg)
|
|
{
|
|
struct jsonrpc_server *server = (struct jsonrpc_server*)arg;
|
|
|
|
if (event != EV_READ) {
|
|
LM_ERR("unexpected socket event (%d)\n", event);
|
|
handle_server_failure(server);
|
|
return;
|
|
}
|
|
|
|
char *netstring;
|
|
|
|
int retval = netstring_read_fd(fd, &netstring);
|
|
|
|
if (retval != 0) {
|
|
LM_ERR("bad netstring (%d)\n", retval);
|
|
handle_server_failure(server);
|
|
return;
|
|
}
|
|
|
|
struct json_object *res = json_tokener_parse(netstring);
|
|
|
|
if (res) {
|
|
handle_jsonrpc_response(res);
|
|
json_object_put(res);
|
|
} else {
|
|
LM_ERR("netstring could not be parsed: (%s)\n", netstring);
|
|
handle_server_failure(server);
|
|
}
|
|
pkg_free(netstring);
|
|
}
|
|
|
|
int set_non_blocking(int fd)
|
|
{
|
|
int flags;
|
|
|
|
flags = fcntl(fd, F_GETFL);
|
|
if (flags < 0)
|
|
return flags;
|
|
flags |= O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flags) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int parse_servers(char *_servers, struct jsonrpc_server_group **group_ptr)
|
|
{
|
|
char cpy[strlen(_servers)+1];
|
|
char *servers = strcpy(cpy, _servers);
|
|
|
|
struct jsonrpc_server_group *group = NULL;
|
|
|
|
/* parse servers string */
|
|
char *token = strtok(servers, ":");
|
|
while (token != NULL)
|
|
{
|
|
char *host, *port_s, *priority_s, *tail;
|
|
int port, priority;
|
|
host = token;
|
|
|
|
/* validate domain */
|
|
if (!(isalpha(host[0]) || isdigit(host[0]))) {
|
|
LM_ERR("invalid domain (1st char is '%c')\n", host[0]);
|
|
return -1;
|
|
}
|
|
int i;
|
|
for (i=1; i<strlen(host)-1; i++)
|
|
{
|
|
if(!(isalpha(host[i]) || isdigit(host[i]) || host[i] == '-' || host[i] == '.'))
|
|
{
|
|
LM_ERR("invalid domain (char %d is %c)\n", i, host[i]);
|
|
return -1;
|
|
}
|
|
}
|
|
if (!(isalpha(host[i]) || isdigit(host[i]))) {
|
|
LM_ERR("invalid domain (char %d (last) is %c)\n", i, host[i]);
|
|
return -1;
|
|
}
|
|
|
|
/* convert/validate port */
|
|
port_s = strtok(NULL, ",");
|
|
if (port_s == NULL || !(port = strtol(port_s, &tail, 0)) || strlen(tail))
|
|
{
|
|
LM_ERR("invalid port: %s\n", port_s);
|
|
return -1;
|
|
}
|
|
|
|
/* convert/validate priority */
|
|
priority_s = strtok(NULL, " ");
|
|
if (priority_s == NULL || !(priority = strtol(priority_s, &tail, 0)) || strlen(tail))
|
|
{
|
|
LM_ERR("invalid priority: %s\n", priority_s);
|
|
return -1;
|
|
}
|
|
|
|
|
|
struct jsonrpc_server *server = pkg_malloc(sizeof(struct jsonrpc_server));
|
|
CHECK_MALLOC(server);
|
|
memset(server, 0, sizeof(struct jsonrpc_server));
|
|
char *h = pkg_malloc(strlen(host)+1);
|
|
CHECK_MALLOC(h);
|
|
|
|
strcpy(h,host);
|
|
server->host = h;
|
|
server->port = port;
|
|
server->status = JSONRPC_SERVER_DISCONNECTED;
|
|
server->socket = 0;
|
|
server->conn_attempts = _jsonrpcc_max_conn_retry;
|
|
|
|
int group_cnt = 0;
|
|
|
|
/* search for a server group with this server's priority */
|
|
struct jsonrpc_server_group *selected_group = NULL;
|
|
for (selected_group=group; selected_group != NULL; selected_group=selected_group->next_group)
|
|
{
|
|
if (selected_group->priority == priority) break;
|
|
}
|
|
|
|
if (selected_group == NULL) {
|
|
group_cnt++;
|
|
LM_INFO("Creating group for priority %d\n", priority);
|
|
|
|
/* this is the first server for this priority... link it to itself */
|
|
server->next = server;
|
|
|
|
selected_group = pkg_malloc(sizeof(struct jsonrpc_server_group));
|
|
CHECK_MALLOC(selected_group);
|
|
memset(selected_group, 0, sizeof(struct jsonrpc_server_group));
|
|
selected_group->priority = priority;
|
|
selected_group->next_server = server;
|
|
|
|
/* insert the group properly in the linked list */
|
|
struct jsonrpc_server_group *x, *pg;
|
|
pg = NULL;
|
|
if (group == NULL)
|
|
{
|
|
group = selected_group;
|
|
group->next_group = NULL;
|
|
} else {
|
|
for (x = group; x != NULL; x = x->next_group)
|
|
{
|
|
if (priority > x->priority)
|
|
{
|
|
if (pg == NULL)
|
|
{
|
|
group = selected_group;
|
|
} else {
|
|
pg->next_group = selected_group;
|
|
}
|
|
selected_group->next_group = x;
|
|
break;
|
|
} else if (x->next_group == NULL) {
|
|
x->next_group = selected_group;
|
|
break;
|
|
} else {
|
|
pg = x;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
LM_ERR("Using existing group for priority %d\n", priority);
|
|
server->next = selected_group->next_server->next;
|
|
selected_group->next_server->next = server;
|
|
}
|
|
|
|
token = strtok(NULL, ":");
|
|
}
|
|
|
|
*group_ptr = group;
|
|
return 0;
|
|
}
|
|
|
|
int connect_server(struct jsonrpc_server *server)
|
|
{
|
|
struct sockaddr_in server_addr;
|
|
struct hostent *hp;
|
|
|
|
server_addr.sin_family = AF_INET;
|
|
server_addr.sin_port = htons(server->port);
|
|
|
|
hp = gethostbyname(server->host);
|
|
if (hp == NULL) {
|
|
LM_ERR("gethostbyname(%s) failed with h_errno=%d.\n", server->host, h_errno);
|
|
handle_server_failure(server);
|
|
return -1;
|
|
}
|
|
memcpy(&(server_addr.sin_addr.s_addr), hp->h_addr, hp->h_length);
|
|
|
|
int sockfd = socket(AF_INET,SOCK_STREAM,0);
|
|
|
|
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in))) {
|
|
LM_WARN("Failed to connect to %s on port %d... %s\n", server->host, server->port, strerror(errno));
|
|
handle_server_failure(server);
|
|
return -1;
|
|
}
|
|
|
|
if (set_non_blocking(sockfd) != 0)
|
|
{
|
|
LM_WARN("Failed to set socket (%s:%d) to non blocking.\n", server->host, server->port);
|
|
handle_server_failure(server);
|
|
return -1;
|
|
}
|
|
|
|
server->socket = sockfd;
|
|
server->status = JSONRPC_SERVER_CONNECTED;
|
|
server->conn_attempts = _jsonrpcc_max_conn_retry;
|
|
|
|
struct event *socket_ev = pkg_malloc(sizeof(struct event));
|
|
CHECK_MALLOC(socket_ev);
|
|
event_set(socket_ev, sockfd, EV_READ | EV_PERSIST, socket_cb, server);
|
|
event_add(socket_ev, NULL);
|
|
server->ev = socket_ev;
|
|
return 0;
|
|
}
|
|
|
|
int connect_servers(struct jsonrpc_server_group *group)
|
|
{
|
|
int connected_servers = 0;
|
|
for (;group != NULL; group = group->next_group)
|
|
{
|
|
struct jsonrpc_server *s, *first = NULL;
|
|
LM_INFO("Connecting to servers for priority %d:\n", group->priority);
|
|
for (s=group->next_server;s!=first;s=s->next)
|
|
{
|
|
if (connect_server(s) == 0)
|
|
{
|
|
connected_servers++;
|
|
LM_INFO("Connected to host %s on port %d\n", s->host, s->port);
|
|
}
|
|
if (first == NULL) first = s;
|
|
}
|
|
}
|
|
return connected_servers;
|
|
}
|
|
|
|
void reconnect_cb(int fd, short event, void *arg)
|
|
{
|
|
LM_INFO("Attempting to reconnect now.");
|
|
struct jsonrpc_server *server = (struct jsonrpc_server*)arg;
|
|
|
|
if (server->status == JSONRPC_SERVER_CONNECTED) {
|
|
LM_WARN("Trying to connect an already connected server.");
|
|
return;
|
|
}
|
|
|
|
if (server->ev != NULL) {
|
|
event_del(server->ev);
|
|
pkg_free(server->ev);
|
|
server->ev = NULL;
|
|
}
|
|
|
|
close(fd);
|
|
pkg_free(server->timer);
|
|
|
|
connect_server(server);
|
|
}
|
|
|
|
int handle_server_failure(struct jsonrpc_server *server)
|
|
{
|
|
LM_INFO("Setting timer to reconnect to %s on port %d in %d seconds.\n", server->host, server->port, JSONRPC_RECONNECT_INTERVAL);
|
|
|
|
if (server->socket)
|
|
close(server->socket);
|
|
server->socket = 0;
|
|
if (server->ev != NULL) {
|
|
event_del(server->ev);
|
|
pkg_free(server->ev);
|
|
server->ev = NULL;
|
|
}
|
|
server->status = JSONRPC_SERVER_FAILURE;
|
|
server->conn_attempts--;
|
|
if(_jsonrpcc_max_conn_retry!=-1 && server->conn_attempts<0) {
|
|
LM_ERR("max reconnect attempts. No further attempts will be made to reconnect this server.");
|
|
return -1;
|
|
}
|
|
|
|
int timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
|
|
if (timerfd == -1) {
|
|
LM_ERR("Could not create timerfd to reschedule connection. No further attempts will be made to reconnect this server.");
|
|
return -1;
|
|
}
|
|
|
|
struct itimerspec *itime = pkg_malloc(sizeof(struct itimerspec));
|
|
CHECK_MALLOC(itime);
|
|
itime->it_interval.tv_sec = 0;
|
|
itime->it_interval.tv_nsec = 0;
|
|
|
|
itime->it_value.tv_sec = JSONRPC_RECONNECT_INTERVAL;
|
|
itime->it_value.tv_nsec = 0;
|
|
|
|
if (timerfd_settime(timerfd, 0, itime, NULL) == -1)
|
|
{
|
|
LM_ERR("Could not set timer to reschedule connection. No further attempts will be made to reconnect this server.");
|
|
return -1;
|
|
}
|
|
LM_INFO("timerfd value is %d\n", timerfd);
|
|
struct event *timer_ev = pkg_malloc(sizeof(struct event));
|
|
CHECK_MALLOC(timer_ev);
|
|
event_set(timer_ev, timerfd, EV_READ, reconnect_cb, server);
|
|
if(event_add(timer_ev, NULL) == -1) {
|
|
LM_ERR("event_add failed while rescheduling connection (%s). No further attempts will be made to reconnect this server.", strerror(errno));
|
|
return -1;
|
|
}
|
|
server->ev = timer_ev;
|
|
server->timer = itime;
|
|
return 0;
|
|
}
|
|
|
|
|
|
void free_pipe_cmd(struct jsonrpc_pipe_cmd *cmd)
|
|
{
|
|
if (cmd->method)
|
|
shm_free(cmd->method);
|
|
if (cmd->params)
|
|
shm_free(cmd->params);
|
|
if (cmd->cb_route)
|
|
shm_free(cmd->cb_route);
|
|
if (cmd->err_route)
|
|
shm_free(cmd->err_route);
|
|
if (cmd->cb_pv)
|
|
shm_free(cmd->cb_pv);
|
|
shm_free(cmd);
|
|
}
|