Fancier protocol for control socket

* pass error status back
* pass message boundaries
This commit is contained in:
Scott Lamb 2007-11-07 02:48:33 +00:00
parent b0b5299184
commit 50ad3f2a89
3 changed files with 232 additions and 13 deletions

View file

@ -24,6 +24,7 @@
#include "system.h" #include "system.h"
#include "conf.h" #include "conf.h"
#include "control.h" #include "control.h"
#include "control_common.h"
#include "logger.h" #include "logger.h"
#include "xalloc.h" #include "xalloc.h"
@ -33,17 +34,58 @@ static splay_tree_t *control_socket_tree;
extern char *controlsocketname; extern char *controlsocketname;
static void handle_control_data(struct bufferevent *event, void *data) { static void handle_control_data(struct bufferevent *event, void *data) {
char *line = evbuffer_readline(event->input); tinc_ctl_request_t req;
if(!line) size_t size;
tinc_ctl_request_t res;
struct evbuffer *res_data = NULL;
if(EVBUFFER_LENGTH(event->input) < sizeof(tinc_ctl_request_t))
return; return;
if(!strcasecmp(line, "stop")) { /* Copy the structure to ensure alignment */
memcpy(&req, EVBUFFER_DATA(event->input), sizeof(tinc_ctl_request_t));
if(EVBUFFER_LENGTH(event->input) < req.length)
return;
if(req.length < sizeof(tinc_ctl_request_t))
goto failure;
memset(&res, 0, sizeof res);
res.type = req.type;
res.id = req.id;
res_data = evbuffer_new();
if (res_data == NULL) {
res.res_errno = ENOMEM;
goto respond;
}
if(req.type == REQ_STOP) {
logger(LOG_NOTICE, _("Got stop command")); logger(LOG_NOTICE, _("Got stop command"));
event_loopexit(NULL); event_loopexit(NULL);
return; goto respond;
} }
logger(LOG_DEBUG, _("Malformed control command received")); logger(LOG_DEBUG, _("Malformed control command received"));
res.res_errno = EINVAL;
respond:
res.length = (sizeof res)
+ ((res_data == NULL) ? 0 : EVBUFFER_LENGTH(res_data));
evbuffer_drain(event->input, req.length);
if(bufferevent_write(event, &res, sizeof res) == -1)
goto failure;
if(res_data != NULL) {
if(bufferevent_write_buffer(event, res_data) == -1)
goto failure;
evbuffer_free(res_data);
}
return;
failure:
logger(LOG_INFO, _("Closing control socket on error"));
evbuffer_free(res_data);
close(event->ev_read.ev_fd); close(event->ev_read.ev_fd);
splay_delete(control_socket_tree, event); splay_delete(control_socket_tree, event);
} }
@ -61,6 +103,7 @@ static void handle_control_error(struct bufferevent *event, short what, void *da
static void handle_new_control_socket(int fd, short events, void *data) { static void handle_new_control_socket(int fd, short events, void *data) {
int newfd; int newfd;
struct bufferevent *ev; struct bufferevent *ev;
tinc_ctl_greeting_t greeting;
newfd = accept(fd, NULL, NULL); newfd = accept(fd, NULL, NULL);
@ -77,6 +120,17 @@ static void handle_new_control_socket(int fd, short events, void *data) {
return; return;
} }
memset(&greeting, 0, sizeof greeting);
greeting.version = TINC_CTL_VERSION_CURRENT;
if(bufferevent_write(ev, &greeting, sizeof greeting) == -1) {
logger(LOG_ERR,
_("Cannot send greeting for new control connection: %s"),
strerror(errno));
bufferevent_free(ev);
close(newfd);
return;
}
bufferevent_enable(ev, EV_READ); bufferevent_enable(ev, EV_READ);
splay_insert(control_socket_tree, ev); splay_insert(control_socket_tree, ev);

46
src/control_common.h Normal file
View file

@ -0,0 +1,46 @@
/*
control_protocol.h -- control socket protocol.
Copyright (C) 2007 Scott Lamb <slamb@slamb.org>
This program 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 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, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
$Id$
*/
#ifndef __TINC_CONTROL_PROTOCOL_H__
#define __TINC_CONTROL_PROTOCOL_H__
enum request_type {
REQ_STOP,
REQ_RELOAD,
REQ_RESTART,
};
#define TINC_CTL_VERSION_CURRENT 0
/* This greeting is sent by the server on socket open. */
typedef struct tinc_ctl_greeting_t {
int version;
} tinc_ctl_greeting_t;
/* A single request or response header. */
typedef struct tinc_ctl_request_t {
size_t length; /* total length, including the header */
enum request_type type;
int id;
int res_errno; /* used only for responses */
} tinc_ctl_request_t;
#endif

View file

@ -30,8 +30,9 @@
#include <getopt.h> #include <getopt.h>
#include "protocol.h"
#include "xalloc.h" #include "xalloc.h"
#include "protocol.h"
#include "control_common.h"
/* The name this program was run with. */ /* The name this program was run with. */
char *program_name = NULL; char *program_name = NULL;
@ -327,9 +328,118 @@ static void make_names(void) {
} }
} }
static int fullread(int fd, void *data, size_t datalen) {
int rv, len = 0;
while (len < datalen) {
rv = read(fd, data + len, datalen - len);
if(rv == -1 && errno == EINTR)
continue;
else if (rv == -1)
return rv;
else if (rv == 0) {
errno = ENODATA;
return -1;
}
len += rv;
}
return 0;
}
/*
Send a request (raw)
*/
static int send_ctl_request(int fd, enum request_type type,
void const *outdata, size_t outdatalen,
int *res_errno_p, void **indata_p,
size_t *indatalen_p) {
tinc_ctl_request_t req;
int rv;
struct iovec vector[2] = {
{&req, sizeof(req)},
{(void*) outdata, outdatalen}
};
void *indata;
if(res_errno_p == NULL)
return -1;
memset(&req, 0, sizeof req);
req.length = sizeof req + outdatalen;
req.type = type;
req.res_errno = 0;
while((rv = writev(fd, vector, 2)) == -1 && errno == EINTR) ;
if(rv != req.length)
return -1;
if(fullread(fd, &req, sizeof req) == -1)
return -1;
if(req.length < sizeof req) {
errno = EINVAL;
return -1;
}
if(req.length > sizeof req) {
if (indata_p == NULL) {
errno = EINVAL;
return -1;
}
indata = xmalloc(req.length - sizeof req);
if(fullread(fd, indata, req.length - sizeof req) == -1) {
free(indata);
return -1;
}
*indata_p = indata;
if(indatalen_p != NULL)
*indatalen_p = req.length - sizeof req;
}
*res_errno_p = req.res_errno;
return 0;
}
/*
Send a request (with printfs)
*/
static int send_ctl_request_cooked(int fd, enum request_type type,
void const *outdata, size_t outdatalen)
{
int res_errno = -1;
char *buf = NULL;
size_t buflen = 0;
if(send_ctl_request(fd, type, outdata, outdatalen, &res_errno,
(void**) &buf, &buflen)) {
fprintf(stderr, _("Error sending request: %s\n"), strerror(errno));
return -1;
}
if(buf != NULL) {
printf("%*s", buflen, buf);
free(buf);
}
if(res_errno != 0) {
fprintf(stderr, _("Server reported error: %s\n"), strerror(res_errno));
return -1;
}
return 0;
}
int main(int argc, char *argv[], char *envp[]) { int main(int argc, char *argv[], char *envp[]) {
int fd;
struct sockaddr_un addr; struct sockaddr_un addr;
int fd;
int len;
tinc_ctl_greeting_t greeting;
tinc_ctl_request_t req;
program_name = argv[0]; program_name = argv[0];
setlocale(LC_ALL, ""); setlocale(LC_ALL, "");
@ -399,6 +509,18 @@ int main(int argc, char *argv[], char *envp[]) {
return 1; return 1;
} }
if(fullread(fd, &greeting, sizeof greeting) == -1) {
fprintf(stderr, _("Cannot read greeting from control socket: %s\n"),
strerror(errno));
return 1;
}
if(greeting.version != TINC_CTL_VERSION_CURRENT) {
fprintf(stderr, _("Version mismatch: server %d, client %d\n"),
greeting.version, TINC_CTL_VERSION_CURRENT);
return 1;
}
struct ucred cred; struct ucred cred;
socklen_t credlen = sizeof cred; socklen_t credlen = sizeof cred;
@ -413,18 +535,15 @@ int main(int argc, char *argv[], char *envp[]) {
} }
if(!strcasecmp(argv[optind], "stop")) { if(!strcasecmp(argv[optind], "stop")) {
write(fd, "stop\n", 5); return send_ctl_request_cooked(fd, REQ_STOP, NULL, 0) != -1;
return 0;
} }
if(!strcasecmp(argv[optind], "reload")) { if(!strcasecmp(argv[optind], "reload")) {
write(fd, "reload\n", 7); return send_ctl_request_cooked(fd, REQ_RELOAD, NULL, 0) != -1;
return 0;
} }
if(!strcasecmp(argv[optind], "restart")) { if(!strcasecmp(argv[optind], "restart")) {
write(fd, "restart\n", 8); return send_ctl_request_cooked(fd, REQ_RESTART, NULL, 0) != -1;
return 0;
} }
fprintf(stderr, _("Unknown command `%s'.\n"), argv[optind]); fprintf(stderr, _("Unknown command `%s'.\n"), argv[optind]);