From 9e70ef6e93f1e537824924aa617a4fecb5bfd1cf Mon Sep 17 00:00:00 2001 From: Avril Date: Fri, 17 May 2019 22:09:49 +0100 Subject: [PATCH] added timeout, improved error handling, added partial message signing --- README | 3 ++- cl-sipc.asd | 2 +- cl-sipc.lisp | 20 ++++++++++++----- ffi.lisp | 4 +++- libsipc/include/sipc.h | 6 +++++ libsipc/src/cli.c | 47 +++++++++++++++++++++++++++++++++++++- libsipc/src/sipc.c | 51 +++++++++++++++++++++++++++++++++++++++--- tools/test-server.lisp | 8 ++++--- 8 files changed, 125 insertions(+), 16 deletions(-) diff --git a/README b/README index 344b6c0..ed584d2 100644 --- a/README +++ b/README @@ -15,6 +15,7 @@ Additional tools can be built by running `make all-ffi' in the libsipc/ director Functions: ;server functions sipc:bind(file) ;; Bind to socket `file'. Returns the socket descriptor on success and nil on fail (the socket must not already exist) + sipc:timeout(socket sec) ;; Set read timeout (in seconds) to socket. (if value is NIL, nothing happens, if value is 0, set to infinite). (note: when something fails timeout it sends error :MESSAGE (invalid message). sipc:release(socket) ;; Close socket sipc:hook(socket error-callback message-callback) ;; start listening on `socket'. error-callback should be in format (lambda (error) ...). message-callback should be in format (lambda (type message )...). @@ -128,7 +129,7 @@ Terminal 1: --- -TODO: fix potential hanging +TODO: Add proper signing. TODO: Internal error handling. TODO: Have libsipc built on system load TODO: better .so loading diff --git a/cl-sipc.asd b/cl-sipc.asd index d5f2e4e..05649ec 100644 --- a/cl-sipc.asd +++ b/cl-sipc.asd @@ -4,7 +4,7 @@ :description "AF_UNIX IPC for CL" :author "Avril " :license "None" - :version "0.0.2" + :version "0.0.3" :serial t :depends-on ( :cffi ) :components ((:file "package") diff --git a/cl-sipc.lisp b/cl-sipc.lisp index ea47977..826e960 100644 --- a/cl-sipc.lisp +++ b/cl-sipc.lisp @@ -8,15 +8,22 @@ ;; wrappers -(defun bind (file) +(defun timeout (sd value) + "set timeout for this socket (0 is infinite)" + (when value + (si-timeout sd value)) + sd) + +(defun bind (file &key (read-timeout nil)) "bind to the AF_UNIX socket `file' - returns the sd on success, nil on failure + returns the sd on success, nil on failure. + timeout can set a read timeout for the connection. 0 (or nil) is forever. (remember it has to be deleted first!) " (let ((rc (si-bind file))) (if (< rc 0) ;error nil - rc))) + (timeout rc read-timeout)))) (defun hook (sd on-err on-msg) "listen on socket `sd' @@ -30,7 +37,7 @@ (note: this function blocks until the connection closes) " (let ((*on-message* on-msg) - (*on-err* on-err)) + (*on-error* on-err)) (let ((rc (si-listen sd (callback si-error-callback) (callback si-callback)))) (if (< rc 0) nil @@ -41,12 +48,12 @@ (si-close sd) t) -(defun connect (file) +(defun connect (file &key (read-timeout nil)) "connect to socket `file'" (let ((rc (si-connect file))) (if (< rc 0) ;error nil - rc))) + (timeout rc read-timeout)))) (defmacro with-bound-socket (desc &body body) "bind socket, run `body', then close the socket. @@ -82,6 +89,7 @@ :partial - Could not write whole message :error - send() error :failure - send failed + :message - invalid response message :unknown - unknown error code :unknown-type - key argument :type is unknown :type can be: diff --git a/ffi.lisp b/ffi.lisp index c84ecaa..1f415c5 100644 --- a/ffi.lisp +++ b/ffi.lisp @@ -147,4 +147,6 @@ (defcfun "siqr_close" :int (sd :pointer)) - +(defcfun "si_timeout" :void + (sd :int) + (secs :int)) diff --git a/libsipc/include/sipc.h b/libsipc/include/sipc.h index 5257c2a..19ffa53 100644 --- a/libsipc/include/sipc.h +++ b/libsipc/include/sipc.h @@ -24,10 +24,13 @@ typedef enum { #define SI_NORESP (1<<0) #define SI_DISCARD (1<<1) +#define _SI_HEADER_CHECK 0xbeefbeefabad1dea; + typedef struct { si_type type; unsigned int flags; unsigned int data_len; + unsigned long check; unsigned char data[]; } si_message; @@ -49,12 +52,15 @@ int si_bind(const char* file); //Returns sd, or -1 on failure. int si_listen(int sd, si_error_callback on_error, si_callback on_message); //Returns non-zero on clean exit, -1 on error. void si_close(int sd); //Close sock (must be called after si_listen() +void si_timeout(int sd, int seconds); //Set sock timeout (0 for infinite). + char* si_error_string(si_error err); char* si_type_string(si_type ty); int si_connect(const char* file); //Returns sd, or -1 on failure. int si_sendmsg_r(int sd, const si_message* msg, si_message** response); //si_sendmmsg but optionally receive response (make sure to free() *response after you're done, NULL to discard response, if there is no response *response is not modified) int si_sendmsg(int sd, const si_message *msg); //Returns 0 on okay, 1 if whole message could not be sent, -1 on send error, -2 on weird send error. (see SI_SEND_*) +void si_sign(si_message *msg); //Sign message. Use this before sending one. //Quick funcs int siqs_string_r(int sd, const char* string, unsigned int flags, si_message** resp); //quick send string diff --git a/libsipc/src/cli.c b/libsipc/src/cli.c index d31bdce..2456ede 100644 --- a/libsipc/src/cli.c +++ b/libsipc/src/cli.c @@ -2,6 +2,7 @@ #include #include #include +#include #include int silent_level=0; @@ -41,6 +42,7 @@ char* strbin(const unsigned char* data, size_t sz) } int server_aresp =0; +int timeout=0; char* textmessage(const si_message *msg) { @@ -122,6 +124,11 @@ int server(const char* bindto, int secho) Eprintf("error binding\n"); } else { Vprintf("bind ok\n"); + if(timeout) + { + si_timeout(sd, timeout); + Vprintf("timeout set to %d\n", timeout); + } rc = si_listen(sd, &on_error, &on_message); Vprintf("listen stopped with rc %d\n", rc); if(rc>=0) //positive rc is okay @@ -172,7 +179,12 @@ int client(const char* conto, const char* string, int bin) Eprintf("connect error\n"); } else { Vprintf("connect ok\n"); + if(timeout) { + si_timeout(sd, timeout); + Vprintf("timeout set to %d\n", timeout); + } si_message *response = NULL; + si_sign(msg); int rrc = si_sendmsg_r(sd, msg, &response); if(response) { if(silent_level>1) @@ -204,7 +216,12 @@ int client_close(const char* conto) Eprintf("connect error\n"); } else { Vprintf("connect ok\n"); + if(timeout) { + si_timeout(sd, timeout); + Vprintf("timeout set to %d\n", timeout); + } si_message* response = NULL; + si_sign(msg); int rrc = si_sendmsg_r(sd, msg, &response); if(response) { if(silent_level>1) @@ -226,6 +243,7 @@ int pfa(char** argv) { char * carg = argv[1]+1; int rc=0; + int nrc=0; while (*carg) { switch(*carg) { @@ -238,6 +256,10 @@ int pfa(char** argv) case 'v': silent_level =-1; break; + case 't': + nrc+=1; + timeout = atoi(*(argv+1+nrc)); + break; default: carg++; continue; @@ -245,6 +267,23 @@ int pfa(char** argv) rc = 1; carg++; } + return rc+nrc; +} + +int hang(const char* sock) +{ + int sd = si_connect(sock); + int rc =0; + if(sd<0) { + Eprintf("connection failed\n"); + rc=-1; + } else { + Vprintf("connection established, waiting for %d seconds\n", (timeout?timeout:5) ); + //si_timeout(sd, 5); + sleep(timeout?timeout:5); + Vprintf("abandoning\n"); + } + return rc; } @@ -293,6 +332,10 @@ int main(int argc, char** argv) if(rc!=0) Eprintf("client rc %d\n", rc); break; + case 'h': + rc = hang(argv[2]); + Vprintf("hang rc %d\n", rc); + break; default: Eprintf("i don't know how to do that\n"); break; @@ -300,14 +343,16 @@ int main(int argc, char** argv) } else { argv = _av; - printf("usage: %s [-qQv] -l[fe] \nusage: %s [-qQv] -p[b] \nusage: %s [-qQv] -c[f] \n", argv[0], argv[0], argv[0]); + printf("usage: %s [-qQvt] [] -l[fe] \nusage: %s [-qQvt] [] -p[b] \nusage: %s [-qQvt] [] -c[f] \nusage: %s [-qQvt] [] -h \n", argv[0], argv[0], argv[0], argv[0]); printf("\n-l[fe]\tlisten on socket. (f to unlink file first, e to send response)\n"); printf("-p[b]\twrite to socket. (b to send as binary)\n"); printf("-c[f]\tsend cose signal to socket. (f to unlink file after)\n"); + printf("-h\thang this socket (for 5 seconds, or set timeout if there is one)\n"); printf("\nother options:\n"); printf(" -q\tquiet mode, don't print errors\n"); printf(" -Q\tsilent mode, only print responses\n"); printf(" -v\tverbose mode, print additional messages\n"); + printf(" -t\tset socket timeout to next arg (in seconds)\n"); } return rc; } diff --git a/libsipc/src/sipc.c b/libsipc/src/sipc.c index 5f033e2..7eced98 100644 --- a/libsipc/src/sipc.c +++ b/libsipc/src/sipc.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -44,7 +45,7 @@ int si_bind(const char* file) static int _si_valid_header(const si_message *msg) { int kk = (int)msg->type; - return kk>=0 && kk<_SI_ERROR && msg->data_len < SI_MAX_MESSAGE_SIZE; + return kk>=0 && kk<_SI_ERROR && msg->data_len < SI_MAX_MESSAGE_SIZE && msg->check == _SI_HEADER_CHECK; } static int _si_read_rest(int sd, si_message *message) @@ -70,11 +71,31 @@ int si_listen(int sd, si_error_callback on_error, si_callback on_message) return -1; } + struct timeval t; + socklen_t len = sizeof(t); + getsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &t, &len); + + //printf("bind timeout : %ld\n", t.tv_sec); + +#define ISTSET(t) (!!(t.tv_sec+t.tv_usec)) + + if(ISTSET(t)) { + //Do we want to keep the accept timeout? + struct timeval t; + t.tv_usec=0; + t.tv_sec=0; + setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); + } + while(1) { int csd = accept(sd, NULL,NULL); if(csd<0) { rc = on_error(SIE_ACCEPT); if(rc<0) break; + else continue; + } + if(ISTSET(t)) { + setsockopt(csd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); } unsigned char buffer[sizeof(si_message)]; si_message *message = (si_message*)buffer; @@ -96,6 +117,7 @@ int si_listen(int sd, si_error_callback on_error, si_callback on_message) break; } } + //printf("%d: %s\n", errno, strerror(errno)); if(rc<0) { close(csd); break; @@ -137,6 +159,7 @@ int si_listen(int sd, si_error_callback on_error, si_callback on_message) resp->type = SI_BINARY; resp->flags = SI_DISCARD | SI_NORESP; resp->data_len = 0; + si_sign(resp); int rc2 = si_sendmsg(csd, resp); if (rc2 != SI_SEND_OKAY) @@ -155,6 +178,8 @@ int si_listen(int sd, si_error_callback on_error, si_callback on_message) close(csd); if(rc!=0) break; } + if(ISTSET(t)) + setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(t)); return rc; } @@ -268,7 +293,7 @@ int si_read_response(int sd, si_message** resp) break; } } - if(rc==0) + if(rc==0 && _si_valid_header(head)) { si_message *full = malloc(sizeof(si_message)+head->data_len+1); memset(full,0,sizeof(si_message)+head->data_len+1); @@ -288,10 +313,19 @@ int si_read_response(int sd, si_message** resp) free(full); return (int)SIE_PCONCLS; } - } + } else if(rc==0) + rc = (int)SIE_INVALID; return rc; } +void si_timeout(int sd, int seconds) +{ + struct timeval t; + t.tv_sec = seconds; + t.tv_usec=0; + setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, (const char*) &t, sizeof(t)); +} + static int _si_sendmsg(int sd, const si_message* msg) { int rc = send(sd, msg, sizeof(si_message)+msg->data_len, 0); @@ -305,6 +339,11 @@ static int _si_sendmsg(int sd, const si_message* msg) return SI_SEND_OKAY; } +void si_sign(si_message* msg) +{ + msg->check = _SI_HEADER_CHECK; +} + int si_sendmsg_r(int sd, const si_message *msg, si_message** resp) { int rc = _si_sendmsg(sd, msg); @@ -366,6 +405,7 @@ int siqs_string_r(int sd, const char* string, unsigned int flags, si_message** r memcpy(msg->data, string, msg->data_len); + si_sign(msg); int rc = si_sendmsg_r(sd, msg, resp); free(msg); @@ -386,6 +426,7 @@ int siqs_close_r(int sd, unsigned int flags, si_message** resp) msg->data_len=0; msg->flags = flags; + si_sign(msg); int rc = si_sendmsg_r(sd, msg, resp); free(msg); @@ -407,6 +448,7 @@ int siqs_binary_r(int sd, const unsigned char* buffer, size_t size, unsigned int memcpy(msg->data, buffer, msg->data_len); + si_sign(msg); int rc = si_sendmsg_r(sd, msg, resp); free(msg); @@ -447,6 +489,7 @@ int siqr_string(const si_message* sd, const char* string) memcpy(msg->data, string, msg->data_len); + si_sign(msg); int rc = si_response(sd, msg); free(msg); @@ -463,6 +506,7 @@ int siqr_binary(const si_message* sd, const unsigned char* buffer, size_t size) memcpy(msg->data, buffer, msg->data_len); + si_sign(msg); int rc = si_response(sd, msg); free(msg); @@ -477,6 +521,7 @@ int siqr_close(const si_message* sd) msg->type = SI_CLOSE; msg->data_len = 0; + si_sign(msg); int rc = si_response(sd, msg); free(msg); diff --git a/tools/test-server.lisp b/tools/test-server.lisp index 18dade0..b77f908 100644 --- a/tools/test-server.lisp +++ b/tools/test-server.lisp @@ -4,12 +4,13 @@ (ql:quickload :cl-sipc)) (defparameter *socket-file* "sipc.socket") +(defparameter *timeout* 5) ;; read timeout in seconds. (defparameter *respond* t) ;; should the server echo responses to client? (when (probe-file *socket-file*) (delete-file *socket-file*)) -(defparameter *socket* (cl-sipc:bind *socket-file*)) ;;attempt to bind to this file +(defparameter *socket* (cl-sipc:bind *socket-file* :read-timeout *timeout*)) ;;attempt to bind to this file (when (not *socket*) (format t "[e] binding failed ~a~%" *socket-file*) (quit)) @@ -19,8 +20,9 @@ ;;block until the listener is done (let ((rc (cl-sipc:hook *socket* #'(lambda (err) ;; Callback ran if there is an error - (format t "Error: ~a~%" err) - nil) ;;returning NIL to the listener stops + (format t "[e] <- ~a~%" err) + (force-output) + t) ;;returning NIL to the listener stops, t lets it continue #'(lambda (type message) ;; Callback ran when a message is received (when *respond* (format t