From 573a71dfe167c8cf6c1c6ffb17b1772e9c846785 Mon Sep 17 00:00:00 2001 From: Heikki Hannikainen Date: Fri, 28 Jun 2013 08:02:23 +0300 Subject: [PATCH] Use STX/ETX chars in framing IS2 packets. 24 bits for length. Remove unaligned integer access from length decoding. Implement IS2 deframing in a more generic way. Support switching deframing method on the fly. --- src/aprsis2.c | 95 ++++++++++++++++++++++++++++++++++++++++------- src/aprsis2.h | 1 + src/errno_aprsc.c | 3 +- src/errno_aprsc.h | 3 +- src/uplink.c | 5 ++- src/worker.c | 32 ++++++++++------ src/worker.h | 2 +- 7 files changed, 112 insertions(+), 29 deletions(-) diff --git a/src/aprsis2.c b/src/aprsis2.c index f0b8f9c..38bc92f 100644 --- a/src/aprsis2.c +++ b/src/aprsis2.c @@ -5,19 +5,26 @@ #include "aprsis2.pb-c.h" #include "version.h" #include "hlog.h" +#include "config.h" -#define IS2_HEAD_LEN 4 -#define IS2_TAIL_LEN 1 +#define STX 0x02 +#define ETX 0x03 + +#define IS2_HEAD_LEN 4 /* STX + network byte order 24 bits uint to specify body length */ +#define IS2_TAIL_LEN 1 /* ETX */ static void *is2_allocate_buffer(int len) { + /* total length of outgoing buffer */ int nlen = len + IS2_HEAD_LEN + IS2_TAIL_LEN; + char *buf = hmalloc(nlen); - uint32_t *ip = (uint32_t *)buf; + uint32_t *len_p = (uint32_t *)buf; - *ip = htonl(len); + *len_p = htonl(len); - buf[nlen-1] = '\n'; + buf[0] = STX; + buf[nlen-1] = ETX; return (void *)buf; } @@ -33,12 +40,16 @@ int is2_out_server_signature(struct worker_t *self, struct client_t *c) m.type = IS2_MESSAGE__TYPE__SERVER_SIGNATURE; m.serversignature = &sig; + sig.username = serverid; sig.app_name = verstr_progname; sig.app_version = version_build; + sig.n_features = 0; + sig.features = NULL; len = is2_message__get_packed_size(&m); buf = is2_allocate_buffer(len); is2_message__pack(&m, buf + IS2_HEAD_LEN); + c->write(self, c, "#IS2\n", 5); c->write(self, c, buf, len + IS2_HEAD_LEN + IS2_TAIL_LEN); hfree(buf); @@ -62,16 +73,67 @@ static int is2_unpack_server_signature(struct worker_t *self, struct client_t *c return 0; } - hlog(LOG_INFO, "%s/%s: IS2: Server signature received: app %s version %s", - c->addr_rem, c->username, sig->app_name, sig->app_version); + hlog(LOG_INFO, "%s/%s: IS2: Server signature received: username %s app %s version %s", + c->addr_rem, c->username, sig->username, sig->app_name, sig->app_version); is2_message__free_unpacked(m, NULL); return 0; } -#define IS2_MINIMUM_FRAME_LEN 4 + 1 + 1 #define IS2_MINIMUM_FRAME_CONTENT_LEN 4 +#define IS2_MINIMUM_FRAME_LEN (IS2_HEAD_LEN + IS2_MINIMUM_FRAME_CONTENT_LEN + IS2_TAIL_LEN) + +int is2_deframe_input(struct worker_t *self, struct client_t *c, int start_at) +{ + int i; + char *ibuf = c->ibuf; + + for (i = start_at; i < c->ibuf_end; ) { + int left = c->ibuf_end - i; + char *this = &ibuf[i]; + + if (left < IS2_MINIMUM_FRAME_LEN) { + //hlog_packet(LOG_DEBUG, this, left, "%s/%s: IS2: Don't have enough data in buffer yet (%d): ", c->addr_rem, c->username, left); + break; + } + + if (*this != STX) { + hlog_packet(LOG_WARNING, this, left, "%s/%s: IS2: Frame missing STX in beginning: ", + c->addr_rem, c->username); + client_close(self, c, CLIERR_IS2_FRAMING_NO_STX); + return -1; + } + + uint32_t *ip = (uint32_t *)this; + uint32_t clen = ntohl(*ip) & 0xffffff; + + if (clen < IS2_MINIMUM_FRAME_CONTENT_LEN) { + hlog_packet(LOG_WARNING, this, left, "%s/%s: IS2: Too short frame content (%d): ", + c->addr_rem, c->username, clen); + return -1; + } + + if (IS2_HEAD_LEN + clen + IS2_TAIL_LEN > left) { + hlog_packet(LOG_WARNING, this, left, "%s/%s: IS2: Frame length points behind buffer end (%d+%d buflen %d): ", + c->addr_rem, c->username, clen, IS2_HEAD_LEN + IS2_TAIL_LEN, left); + return -1; + } + + if (this[IS2_HEAD_LEN + clen] != ETX) { + hlog_packet(LOG_WARNING, this, left, "%s/%s: IS2: Frame missing terminating ETX: ", + c->addr_rem, c->username); + return -1; + } + + hlog_packet(LOG_DEBUG, this, left, "%s/%s: IS2: framing ok: ", c->addr_rem, c->username); + + is2_unpack_server_signature(self, c, this + IS2_HEAD_LEN, clen); + i += IS2_HEAD_LEN + clen + IS2_TAIL_LEN; + } + + return i; +} static int is2_deframe(struct worker_t *self, struct client_t *c, char *s, int len) { @@ -81,8 +143,14 @@ static int is2_deframe(struct worker_t *self, struct client_t *c, char *s, int l return -1; } + if (s[0] != STX) { + hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Frame missing STX in beginning: ", + c->addr_rem, c->username); + return -1; + } + uint32_t *ip = (uint32_t *)s; - int clen = ntohl(*ip); + uint32_t clen = ntohl(*ip) & 0xffffff; if (clen < IS2_MINIMUM_FRAME_CONTENT_LEN) { hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Too short frame content (%d): ", @@ -91,13 +159,13 @@ static int is2_deframe(struct worker_t *self, struct client_t *c, char *s, int l } if (IS2_HEAD_LEN + clen + IS2_TAIL_LEN > len) { - hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Frame length points behind buffer end (%d): ", - c->addr_rem, c->username, clen); + hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Frame length points behind buffer end (%d+%d buflen %d): ", + c->addr_rem, c->username, clen, IS2_HEAD_LEN + IS2_TAIL_LEN, len); return -1; } - if (s[IS2_HEAD_LEN + clen] != '\n') { - hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Frame missing terminating LF: ", + if (s[IS2_HEAD_LEN + clen] != ETX) { + hlog_packet(LOG_WARNING, s, len, "%s/%s: IS2: Frame missing terminating ETX: ", c->addr_rem, c->username); return -1; } @@ -109,6 +177,7 @@ static int is2_deframe(struct worker_t *self, struct client_t *c, char *s, int l return 0; } + int is2_in_server_signature(struct worker_t *self, struct client_t *c, char *s, int len) { /* this one comes through the CRLF deframing */ diff --git a/src/aprsis2.h b/src/aprsis2.h index 59c58da..2c70fea 100644 --- a/src/aprsis2.h +++ b/src/aprsis2.h @@ -6,6 +6,7 @@ extern int is2_out_server_signature(struct worker_t *self, struct client_t *c); extern int is2_in_server_signature(struct worker_t *self, struct client_t *c, char *s, int len); +extern int is2_deframe_input(struct worker_t *self, struct client_t *c, int start_at); #endif diff --git a/src/errno_aprsc.c b/src/errno_aprsc.c index ef7c9c1..c0e68a4 100644 --- a/src/errno_aprsc.c +++ b/src/errno_aprsc.c @@ -31,7 +31,8 @@ const char *aprsc_errs[] = { "Client login retry count exceeded", "Client login timed out", "Inactivity timeout", - "Uplink server certificate validation failed" + "Uplink server certificate validation failed", + "IS2 framing failure - no STX" }; const char *aprsc_strerror(int er) diff --git a/src/errno_aprsc.h b/src/errno_aprsc.h index 168a669..2d259b2 100644 --- a/src/errno_aprsc.h +++ b/src/errno_aprsc.h @@ -2,7 +2,7 @@ #ifndef APRSC_ERRNO_H #define APRSC_ERRNO_H -#define APRSC_ERRNO_MAX 13 +#define APRSC_ERRNO_MAX 14 #define APRSC_UNKNOWN_ERROR -1 #define CLIOK_PEERS_CLOSING -2 #define CLIOK_THREAD_SHUTDOWN -3 @@ -16,6 +16,7 @@ #define CLIERR_LOGIN_TIMEOUT -11 #define CLIERR_INACTIVITY -12 #define CLIERR_UPLINK_PEER_CERT_FAIL -13 +#define CLIERR_IS2_FRAMING_NO_STX -14 extern const char *aprsc_strerror(int er); diff --git a/src/uplink.c b/src/uplink.c index 90522a1..feb4ca4 100644 --- a/src/uplink.c +++ b/src/uplink.c @@ -312,8 +312,11 @@ int uplink_login_handler(struct worker_t *self, struct client_t *c, int l4proto, } #endif - if (is2_in_server_signature(self, c, s, len)) + if (len >= 4 && memcmp(s, "#IS2", 4) == 0) { + hlog_packet(LOG_INFO, s, len, "%s: Uplink server talks IS2, switching: ", c->addr_rem); + c->handler_consume_input = &is2_deframe_input; return 0; + } hlog_packet(LOG_INFO, s, len, "%s: Uplink server software: ", c->addr_rem); diff --git a/src/worker.c b/src/worker.c index c76a3fb..c1b128f 100644 --- a/src/worker.c +++ b/src/worker.c @@ -1218,22 +1218,21 @@ static int handle_corepeer_readable(struct worker_t *self, struct client_t *c) * Consume CRLF-separated data from an APRSIS stream input buffer */ -static int deframe_aprsis_input_lines(struct worker_t *self, struct client_t *c) -{ - int i = 0; - int row_start = 0; +static int deframe_aprsis_input_lines(struct worker_t *self, struct client_t *c, int start_at) +{ + int i; char *ibuf = c->ibuf; /* parse out rows ending in CR and/or LF and pass them to the handler * without the CRLF (we accept either CR or LF or both, but make sure - * to always output CRLF + * to always output CRLF) */ - for (i = 0; i < c->ibuf_end; i++) { + for (i = start_at; i < c->ibuf_end; i++) { if (ibuf[i] == '\r' || ibuf[i] == '\n') { /* found EOL - if the line is not empty, feed it forward */ - if (i - row_start > 0) { + if (i - start_at > 0) { /* NOTE: handler call CAN destroy the c-> object ! */ - if (c->handler_line_in(self, c, c->ai_protocol, ibuf + row_start, i - row_start) < 0) + if (c->handler_line_in(self, c, c->ai_protocol, ibuf + start_at, i - start_at) < 0) return -1; } @@ -1243,11 +1242,17 @@ static int deframe_aprsis_input_lines(struct worker_t *self, struct client_t *c) /* skip the rest of EOL */ while (i < c->ibuf_end && (ibuf[i] == '\r' || ibuf[i] == '\n')) i++; - row_start = i; + + /* call myself recursively - it's possible that the deframing + * handler has been changed by the line handler + * (protocol switch) + */ + return c->handler_consume_input(self, c, i); } } - return row_start; + /* didn't find anything to process */ + return start_at; } /* @@ -1267,7 +1272,7 @@ int client_postread(struct worker_t *self, struct client_t *c, int r) c->last_read = tick; /* not simulated time */ - int consumed = c->handler_consume_input(self, c); + int consumed = c->handler_consume_input(self, c, 0); /* the client might have been freed, even */ if (consumed == -1) @@ -1571,7 +1576,10 @@ static void collect_new_clients(struct worker_t *self) c->write = &tcp_client_write; } - c->handler_consume_input = &deframe_aprsis_input_lines; + if (c->flags & CLFLAGS_IS2) + c->handler_consume_input = &is2_deframe_input; + else + c->handler_consume_input = &deframe_aprsis_input_lines; /* The new client may end up destroyed right away, never mind it here. * We will notice it later and discard the client. diff --git a/src/worker.h b/src/worker.h index 1857589..85daa01 100644 --- a/src/worker.h +++ b/src/worker.h @@ -360,7 +360,7 @@ struct client_t { /* the current handler function for incoming lines */ int (*handler_line_in) (struct worker_t *self, struct client_t *c, int l4proto, char *s, int len); - int (*handler_consume_input) (struct worker_t *self, struct client_t *c); + int (*handler_consume_input) (struct worker_t *self, struct client_t *c, int start_at); int (*write) (struct worker_t *self, struct client_t *c, char *p, int len); int (*handler_client_readable) (struct worker_t *self, struct client_t *c);