diff options
-rw-r--r-- | lib/Makefile.am | 3 | ||||
-rw-r--r-- | lib/compat.c | 3 | ||||
-rw-r--r-- | lib/conn.c | 14 | ||||
-rw-r--r-- | lib/examples/client-udp.conf | 6 | ||||
-rw-r--r-- | lib/include/radsec/radsec-impl.h | 13 | ||||
-rw-r--r-- | lib/packet.c | 277 | ||||
-rw-r--r-- | lib/radsec.c | 3 |
7 files changed, 233 insertions, 86 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index 311d3cc..69c1c27 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -10,6 +10,7 @@ lib_LTLIBRARIES = libradsec.la libradsec_la_SOURCES = \ attr.c \ + compat.c \ conf.c \ conn.c \ debug.c \ @@ -32,4 +33,4 @@ libradsec_la_SOURCES += \ endif libradsec_la_LDFLAGS = -version-info 0:0:0 -libradsec_la_CFLAGS = $(AM_CFLAGS) #-DDEBUG -DDEBUG_LEVENT -Werror +libradsec_la_CFLAGS = $(AM_CFLAGS) -DDEBUG -DDEBUG_LEVENT -Werror # -ansi diff --git a/lib/compat.c b/lib/compat.c index 731c071..b00bea1 100644 --- a/lib/compat.c +++ b/lib/compat.c @@ -1,8 +1,9 @@ #include <sys/types.h> #include <sys/socket.h> +#include "compat.h" ssize_t compat_send (int sockfd, const void *buf, size_t len, int flags) { - compat_send (int sockfd, const void *buf, size_t len, int flags); + return send (sockfd, buf, len, flags); } @@ -8,6 +8,7 @@ #include <assert.h> #include <debug.h> #include <event2/event.h> +#include <event2/bufferevent.h> #include <radsec/radsec.h> #include <radsec/radsec-impl.h> @@ -126,25 +127,22 @@ rs_conn_destroy (struct rs_connection *conn) assert (conn); - if (conn->is_connected) - { - err = rs_conn_disconnect (conn); - if (err) - return err; - } - /* NOTE: conn->realm is owned by context. */ /* NOTE: conn->peers is owned by context. */ + if (conn->is_connected) + err = rs_conn_disconnect (conn); if (conn->tev) event_free (conn->tev); + if (conn->bev) + bufferevent_free (conn->bev); if (conn->evb) event_base_free (conn->evb); /* TODO: free tls_ctx */ /* TODO: free tls_ssl */ - return 0; + return err; } int diff --git a/lib/examples/client-udp.conf b/lib/examples/client-udp.conf index a83fb26..8578e8b 100644 --- a/lib/examples/client-udp.conf +++ b/lib/examples/client-udp.conf @@ -1,10 +1,10 @@ config blocking-udp { type = "UDP" + timeout = 2 + retries = 2 server { - hostname = "localhost" + hostname = "127.0.0.1" service = "1820" secret = "sikrit" - timeout = 1 /* optional */ - tries = 10 /* optional */ } } diff --git a/lib/include/radsec/radsec-impl.h b/lib/include/radsec/radsec-impl.h index 963c821..f8904ac 100644 --- a/lib/include/radsec/radsec-impl.h +++ b/lib/include/radsec/radsec-impl.h @@ -69,7 +69,6 @@ struct rs_connection { struct rs_context *ctx; struct rs_realm *realm; /* Owned by ctx. */ struct event_base *evb; /* Event base. */ - struct bufferevent *bev; /* Buffer event. */ struct event *tev; /* Timeout event. */ struct rs_credentials transport_credentials; struct rs_conn_callbacks callbacks; @@ -80,10 +79,17 @@ struct rs_connection { char is_connecting; /* FIXME: replace with a single state member */ char is_connected; /* FIXME: replace with a single state member */ int fd; /* Socket. */ - int tryagain; - int nextid; + int tryagain; /* For server failover. */ + int nextid; /* Next RADIUS packet identifier. */ int user_dispatch_flag : 1; /* User does the dispatching. */ + /* TCP transport specifics. */ + struct bufferevent *bev; /* Buffer event. */ + /* UDP transport specifics. */ + struct event *wev; /* Write event (for UDP). */ + struct event *rev; /* Read event (for UDP). */ + struct rs_packet *out_queue; /* Queue for outgoing UDP packets. */ #if defined(RS_ENABLE_TLS) + /* TLS specifics. */ SSL_CTX *tls_ctx; SSL *tls_ssl; #endif @@ -97,6 +103,7 @@ struct rs_packet { struct rs_packet *original; char valid_flag; char written_flag; + struct rs_packet *next; /* Used for UDP output queue. */ }; struct rs_attr { diff --git a/lib/packet.c b/lib/packet.c index 9874dee..afe2725 100644 --- a/lib/packet.c +++ b/lib/packet.c @@ -8,17 +8,19 @@ #include <string.h> #include <errno.h> #include <sys/time.h> +#include <sys/types.h> #include <assert.h> #include <freeradius/libradius.h> #include <event2/event.h> #include <event2/bufferevent.h> #include <radsec/radsec.h> #include <radsec/radsec-impl.h> -#include "tls.h" +#include "compat.h" #include "debug.h" #if defined (RS_ENABLE_TLS) #include <event2/bufferevent_ssl.h> #include <openssl/err.h> +#include "tls.h" #endif #if defined (DEBUG) #include <netdb.h> @@ -27,6 +29,18 @@ #endif static int +_close_conn (struct rs_connection **connp) +{ + int r; + assert (connp); + assert (*connp); + r = rs_conn_destroy (*connp); + if (!r) + *connp = NULL; + return r; +} + +static int _loopbreak (struct rs_connection *conn) { int err = event_base_loopbreak (conn->evb); @@ -37,11 +51,12 @@ _loopbreak (struct rs_connection *conn) return err; } +/* Badly named helper function for preparing a RADIUS message and + queue it. FIXME: Rename. */ static int _do_send (struct rs_packet *pkt) { - int err; - VALUE_PAIR *vp; + VALUE_PAIR *vp = NULL; assert (pkt->rpkt); assert (!pkt->original); @@ -73,23 +88,39 @@ _do_send (struct rs_packet *pkt) } #endif - err = bufferevent_write (pkt->conn->bev, pkt->rpkt->data, - pkt->rpkt->data_len); - if (err < 0) - return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, - "bufferevent_write: %s", - evutil_gai_strerror (err)); + if (pkt->conn->bev) + { + int err = bufferevent_write (pkt->conn->bev, pkt->rpkt->data, + pkt->rpkt->data_len); + if (err < 0) + return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, + "bufferevent_write: %s", + evutil_gai_strerror (err)); + } + else + { + struct rs_packet **pp = &pkt->conn->out_queue; + + while (*pp && (*pp)->next) + *pp = (*pp)->next; + *pp = pkt; + } + return RSE_OK; } static void -_on_connect (struct rs_connection *conn) +_on_connect (struct rs_connection *conn, struct rs_packet *pkt) { + assert (!conn->is_connecting); conn->is_connected = 1; rs_debug (("%s: %p connected\n", __func__, conn->active_peer)); - evtimer_del (conn->tev); + if (conn->tev) + evtimer_del (conn->tev); if (conn->callbacks.connected_cb) conn->callbacks.connected_cb (conn->user_data); + if (pkt) + _do_send (pkt); } static void @@ -103,9 +134,9 @@ _on_disconnect (struct rs_connection *conn) } static void -_event_cb (struct bufferevent *bev, short events, void *ctx) +_event_cb (struct bufferevent *bev, short events, void *user_data) { - struct rs_packet *pkt = (struct rs_packet *)ctx; + struct rs_packet *pkt = (struct rs_packet *) user_data; struct rs_connection *conn = NULL; struct rs_peer *p = NULL; int sockerr = 0; @@ -122,9 +153,7 @@ _event_cb (struct bufferevent *bev, short events, void *ctx) conn->is_connecting = 0; if (events & BEV_EVENT_CONNECTED) { - _on_connect (conn); - if (_do_send (pkt)) - rs_debug (("%s: error sending\n", __func__)); + _on_connect (conn, pkt); } else if (events & BEV_EVENT_EOF) { @@ -145,15 +174,11 @@ _event_cb (struct bufferevent *bev, short events, void *ctx) } else { + rs_debug (("%s: %d: %d (%s)\n", __func__, conn->fd, sockerr, + evutil_socket_error_to_string (sockerr))); rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, - "%d: socket error %d (%s)", - conn->fd, - sockerr, + "%d: %d (%s)", conn->fd, sockerr, evutil_socket_error_to_string (sockerr)); - rs_debug (("%s: socket error on fd %d: %s (%d)\n", __func__, - conn->fd, - evutil_socket_error_to_string (sockerr), - sockerr)); } #if defined (RS_ENABLE_TLS) if (conn->tls_ssl) /* FIXME: correct check? */ @@ -204,7 +229,7 @@ _read_header (struct rs_packet *pkt) pkt->rpkt->data_len = (pkt->hdr[2] << 8) + pkt->hdr[3]; if (pkt->rpkt->data_len < 20 || pkt->rpkt->data_len > 4096) { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, "invalid packet length: %d", pkt->rpkt->data_len); @@ -212,7 +237,7 @@ _read_header (struct rs_packet *pkt) pkt->rpkt->data = rs_malloc (pkt->conn->ctx, pkt->rpkt->data_len); if (!pkt->rpkt->data) { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push_fl (pkt->conn, RSE_NOMEM, __FILE__, __LINE__, NULL); } @@ -228,7 +253,7 @@ _read_header (struct rs_packet *pkt) } else /* Error: libevent gave us less than the low watermark. */ { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push_fl (pkt->conn, RSE_INTERNAL, __FILE__, __LINE__, "got %d octets reading header", n); } @@ -264,7 +289,7 @@ _read_packet (struct rs_packet *pkt) - attribute sizes adding up correctly */ if (!rad_packet_ok (pkt->rpkt, 0) != 0) { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, "invalid packet: %s", fr_strerror ()); } @@ -280,7 +305,7 @@ _read_packet (struct rs_packet *pkt) if (rad_verify (pkt->rpkt, pkt->original->rpkt, pkt->conn->active_peer->secret)) { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, "rad_verify: %s", fr_strerror ()); } @@ -289,7 +314,7 @@ _read_packet (struct rs_packet *pkt) if (rad_decode (pkt->rpkt, pkt->original->rpkt, pkt->conn->active_peer->secret)) { - bufferevent_free (pkt->conn->bev); /* Close connection. */ + _close_conn (&pkt->conn); return rs_err_conn_push_fl (pkt->conn, RSE_FR, __FILE__, __LINE__, "rad_decode: %s", fr_strerror ()); } @@ -376,7 +401,7 @@ _evlog_cb (int severity, const char *msg) } static int -_init_evb (struct rs_connection *conn) +_init_eventbase (struct rs_connection *conn) { if (conn->evb) return RSE_OK; @@ -465,27 +490,21 @@ _set_timeout (struct rs_connection *conn) } static int -_init_bev (struct rs_connection *conn, struct rs_peer *peer) +_init_bufferevent (struct rs_connection *conn, struct rs_peer *peer) { if (conn->bev) return RSE_OK; - switch (conn->realm->type) + if (conn->realm->type == RS_CONN_TYPE_TCP) { - case RS_CONN_TYPE_UDP: - /* Fall through. */ - /* NOTE: We know this is wrong for several reasons, most notably - because libevent doesn't work as expected with UDP. The - timeout handling is wrong too. */ - case RS_CONN_TYPE_TCP: conn->bev = bufferevent_socket_new (conn->evb, conn->fd, 0); if (!conn->bev) return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, "bufferevent_socket_new"); - break; - + } #if defined (RS_ENABLE_TLS) - case RS_CONN_TYPE_TLS: + else if (conn->realm->type == RS_CONN_TYPE_TLS) + { if (rs_tls_init (conn)) return -1; /* Would be convenient to pass BEV_OPT_CLOSE_ON_FREE but things @@ -497,13 +516,10 @@ _init_bev (struct rs_connection *conn, struct rs_peer *peer) if (!conn->bev) return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, "bufferevent_openssl_socket_new"); - break; - - case RS_CONN_TYPE_DTLS: - return rs_err_conn_push_fl (conn, RSE_NOSYS, __FILE__, __LINE__, NULL); + } #endif /* RS_ENABLE_TLS */ - - default: + else + { return rs_err_conn_push_fl (conn, RSE_INTERNAL, __FILE__, __LINE__, "%s: unknown connection type: %d", __func__, conn->realm->type); @@ -512,11 +528,81 @@ _init_bev (struct rs_connection *conn, struct rs_peer *peer) return RSE_OK; } +/* Callback for conn->wev and conn->rev. FIXME: Rename. */ +static void +_evcb (evutil_socket_t fd, short what, void *user_data) +{ + //rs_debug (("%s: fd=%d what=0x%x\n", __func__, fd, what)); + if (what & EV_TIMEOUT) + { + struct rs_connection *conn = (struct rs_connection *) user_data; + assert (conn); + conn->is_connecting = 0; + rs_debug (("%s: UDP timeout NYI", __func__)); + } + else if (what & EV_READ) + { + struct rs_connection *conn = (struct rs_connection *) user_data; + assert (conn); + /* read a single UDP packet and stick it in a new struct + rs_packet */ + + rs_debug (("%s: UDP read NYI", __func__)); + } + else if (what & EV_WRITE) + { + struct rs_packet *pkt = (struct rs_packet *) user_data; + assert (pkt); + /* Socket ready for writing, possibly as a result of a + successful connect. */ + if (!pkt->conn->is_connected) + _on_connect (pkt->conn, pkt); + if (pkt->conn->out_queue) + { + /* Send one packet, the first. */ + ssize_t r = 0; + struct rs_packet *p = pkt->conn->out_queue; + + assert (p->rpkt); + assert (p->rpkt->data); + r = compat_send (fd, p->rpkt->data, p->rpkt->data_len, 0); + if (r == -1) + { + int sockerr = evutil_socket_geterror (p->conn->fd); + if (sockerr != EAGAIN) + rs_err_conn_push_fl (p->conn, RSE_SOCKERR, __FILE__, __LINE__, + "%d: send: %d (%s)", fd, sockerr, + evutil_socket_error_to_string (sockerr)); + return; /* Don't unlink packet. */ + } + pkt->conn->out_queue = p->next; + } + } +} + +static int +_init_udp (struct rs_connection *conn, struct rs_packet *pkt) +{ + assert (!conn->bev); + + conn->rev = event_new (conn->evb, conn->fd, EV_READ|EV_PERSIST, _evcb, conn); + conn->wev = event_new (conn->evb, conn->fd, EV_WRITE|EV_PERSIST, _evcb, pkt); + if (!conn->rev || !conn->wev) + { + if (conn->rev) + event_free (conn->rev); + /* ENOMEM _or_ EINVAL but EINVAL only if we use EV_SIGNAL, at + least for now (libevent-2.0.5). */ + return rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL); + } + return RSE_OK; +} + static void _do_connect (struct rs_connection *conn) { struct rs_peer *p; - int err; + int err, sockerr; assert (conn); assert (conn->active_peer); @@ -534,21 +620,37 @@ _do_connect (struct rs_connection *conn) } #endif - _set_timeout (conn); - err = bufferevent_socket_connect (p->conn->bev, p->addr->ai_addr, - p->addr->ai_addrlen); - if (err < 0) - rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__, - "bufferevent_socket_connect: %s", - evutil_gai_strerror (err)); - else - p->conn->is_connecting = 1; + if (p->conn->bev) /* TCP */ + { + _set_timeout (conn); + err = bufferevent_socket_connect (p->conn->bev, p->addr->ai_addr, + p->addr->ai_addrlen); + if (err < 0) + rs_err_conn_push_fl (p->conn, RSE_EVENT, __FILE__, __LINE__, + "bufferevent_socket_connect: %s", + evutil_gai_strerror (err)); + else + p->conn->is_connecting = 1; + } + else /* UDP */ + { + err = connect (p->conn->fd, p->addr->ai_addr, p->addr->ai_addrlen); + if (err < 0) + { + sockerr = evutil_socket_geterror (p->conn->fd); + rs_debug (("%s: %d: connect: %d (%s)\n", __func__, p->conn->fd, + sockerr, evutil_socket_error_to_string (sockerr))); + rs_err_conn_push_fl (p->conn, RSE_SOCKERR, __FILE__, __LINE__, + "%d: connect: %d (%s)", p->conn->fd, sockerr, + evutil_socket_error_to_string (sockerr)); + } + } } static int -_conn_open(struct rs_connection *conn, struct rs_packet *pkt) +_conn_open (struct rs_connection *conn, struct rs_packet *pkt) { - if (_init_evb (conn)) + if (_init_eventbase (conn)) return -1; if (!conn->active_peer) @@ -559,8 +661,17 @@ _conn_open(struct rs_connection *conn, struct rs_packet *pkt) if (_init_socket (conn, conn->active_peer)) return -1; - if (_init_bev (conn, conn->active_peer)) - return -1; + if (conn->realm->type == RS_CONN_TYPE_TCP + || conn->realm->type == RS_CONN_TYPE_TLS) + { + if (_init_bufferevent (conn, conn->active_peer)) + return -1; + } + else + { + if (_init_udp (conn, pkt)) + return -1; + } if (!conn->is_connected) if (!conn->is_connecting) @@ -640,7 +751,10 @@ _wcb (void *user_data) struct rs_packet *pkt = (struct rs_packet *) user_data; assert (pkt); pkt->written_flag = 1; - bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); + if (pkt->conn->bev) + bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); + else + event_del (pkt->conn->wev); } int @@ -660,13 +774,24 @@ rs_packet_send (struct rs_packet *pkt, void *user_data) return -1; assert (conn->evb); - assert (conn->bev); assert (conn->active_peer); assert (conn->fd >= 0); conn->user_data = user_data; - bufferevent_setcb (conn->bev, NULL, _write_cb, _event_cb, pkt); - bufferevent_enable (conn->bev, EV_WRITE); + + if (conn->bev) /* TCP */ + { + bufferevent_setcb (conn->bev, NULL, _write_cb, _event_cb, pkt); + bufferevent_enable (conn->bev, EV_WRITE); + } + else /* UDP */ + { + err = event_add (conn->wev, NULL); + if (err < 0) + return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, + "event_add: %s", + evutil_gai_strerror (err)); + } /* Do dispatch, unless the user wants to do it herself. */ if (!conn->user_dispatch_flag) @@ -696,7 +821,10 @@ _rcb (struct rs_packet *packet, void *user_data) struct rs_packet *pkt = (struct rs_packet *) user_data; assert (pkt); pkt->valid_flag = 1; - bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); + if (pkt->conn->bev) + bufferevent_disable (pkt->conn->bev, EV_WRITE|EV_READ); + else + event_del (pkt->conn->rev); } /* Special function used in libradsec blocking dispatching mode, @@ -737,11 +865,22 @@ rs_conn_receive_packet (struct rs_connection *conn, assert (conn->active_peer); assert (conn->fd >= 0); - bufferevent_setwatermark (conn->bev, EV_READ, RS_HEADER_LEN, 0); - bufferevent_setcb (conn->bev, _read_cb, NULL, _event_cb, pkt); - bufferevent_enable (conn->bev, EV_READ); conn->callbacks.received_cb = _rcb; conn->user_data = pkt; + if (conn->bev) + { + bufferevent_setwatermark (conn->bev, EV_READ, RS_HEADER_LEN, 0); + bufferevent_setcb (conn->bev, _read_cb, NULL, _event_cb, pkt); + bufferevent_enable (conn->bev, EV_READ); + } + else + { + err = event_add (conn->rev, NULL); + if (err < 0) + return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, + "event_add: %s", + evutil_gai_strerror (err)); + } /* Dispatch. */ rs_debug (("%s: entering event loop\n", __func__)); diff --git a/lib/radsec.c b/lib/radsec.c index afb871e..f191e73 100644 --- a/lib/radsec.c +++ b/lib/radsec.c @@ -125,7 +125,8 @@ _rs_peer_destroy (struct rs_peer *p) rs_free (p->conn->ctx, p); } -void rs_context_destroy(struct rs_context *ctx) +void +rs_context_destroy (struct rs_context *ctx) { struct rs_realm *r = NULL; struct rs_peer *p = NULL; |