X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=udp.c;h=c00f215a40eb74b9c8feefbf301be358595ac493;hb=d2880e067aa9a124d2256599aef81113475abe0c;hp=bc239bcdc3c025c5a9aed2a83b026fd4d6b8d97f;hpb=671b8f12f456f89f0432d983619d68516b8e0cb1;p=radsecproxy.git diff --git a/udp.c b/udp.c index bc239bc..c00f215 100644 --- a/udp.c +++ b/udp.c @@ -1,258 +1,177 @@ -/* - * Copyright (C) 2006-2008 Stig Venaas - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ +/* Copyright 2011 NORDUnet A/S. All rights reserved. + See LICENSE for licensing information. */ -#include -#include -#include -#include -#include -#include -#include -#ifdef SYS_SOLARIS9 -#include +#if defined HAVE_CONFIG_H +#include #endif -#include + +#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "debug.h" -#include "list.h" -#include "util.h" -#include "radsecproxy.h" -#include "tls.h" - -static int client4_sock = -1; -static int client6_sock = -1; -static struct queue *server_replyq = NULL; - -/* exactly one of client and server must be non-NULL */ -/* return who we received from in *client or *server */ -/* return from in sa if not NULL */ -unsigned char *radudpget(int s, struct client **client, struct server **server, uint16_t *port) { - int cnt, len; - unsigned char buf[4], *rad = NULL; - struct sockaddr_storage from; - struct sockaddr *fromcopy; - socklen_t fromlen = sizeof(from); - struct clsrvconf *p; - struct list_node *node; - fd_set readfds; - struct client *c = NULL; - - for (;;) { - if (rad) { - free(rad); - rad = NULL; - } - FD_ZERO(&readfds); - FD_SET(s, &readfds); - if (select(s + 1, &readfds, NULL, NULL, NULL) < 1) - continue; - cnt = recvfrom(s, buf, 4, MSG_PEEK | MSG_TRUNC, (struct sockaddr *)&from, &fromlen); - if (cnt == -1) { - debug(DBG_WARN, "radudpget: recv failed"); - continue; - } - if (cnt < 20) { - debug(DBG_WARN, "radudpget: length too small"); - recv(s, buf, 4, 0); - continue; - } - - p = client - ? find_clconf(RAD_UDP, (struct sockaddr *)&from, NULL) - : find_srvconf(RAD_UDP, (struct sockaddr *)&from, NULL); - if (!p) { - debug(DBG_WARN, "radudpget: got packet from wrong or unknown UDP peer %s, ignoring", addr2string((struct sockaddr *)&from)); - recv(s, buf, 4, 0); - continue; - } - - len = RADLEN(buf); - if (len < 20) { - debug(DBG_WARN, "radudpget: length too small"); - recv(s, buf, 4, 0); - continue; - } - - rad = malloc(len); - if (!rad) { - debug(DBG_ERR, "radudpget: malloc failed"); - recv(s, buf, 4, 0); - continue; - } - - cnt = recv(s, rad, len, MSG_TRUNC); - debug(DBG_DBG, "radudpget: got %d bytes from %s", cnt, addr2string((struct sockaddr *)&from)); - - if (cnt < len) { - debug(DBG_WARN, "radudpget: packet smaller than length field in radius header"); - continue; - } - if (cnt > len) - debug(DBG_DBG, "radudpget: packet was padded with %d bytes", cnt - len); - - if (client) { - pthread_mutex_lock(p->lock); - for (node = list_first(p->clients); node; node = list_next(node)) { - c = (struct client *)node->data; - if (s == c->sock && addr_equal((struct sockaddr *)&from, c->addr)) - break; - } - if (!node) { - fromcopy = addr_copy((struct sockaddr *)&from); - if (!fromcopy) { - pthread_mutex_unlock(p->lock); - continue; - } - c = addclient(p, 0); - if (!c) { - free(fromcopy); - pthread_mutex_unlock(p->lock); - continue; - } - c->sock = s; - c->addr = fromcopy; - } - *client = c; - pthread_mutex_unlock(p->lock); - } else if (server) - *server = p->servers; - break; +#include "event.h" +#include "compat.h" +#include "udp.h" + +/* Send one packet, the first in queue. */ +static int +_send (struct rs_connection *conn, int fd) +{ + ssize_t r = 0; + struct rs_packet *pkt = conn->out_queue; + + assert (pkt->rpkt); + assert (pkt->rpkt->data); + + /* Send. */ + r = compat_send (fd, pkt->rpkt->data, pkt->rpkt->length, 0); + if (r == -1) + { + int sockerr = evutil_socket_geterror (pkt->conn->fd); + if (sockerr != EAGAIN) + return rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, + "%d: send: %d (%s)", fd, sockerr, + evutil_socket_error_to_string (sockerr)); } - if (port) - *port = port_get((struct sockaddr *)&from); - return rad; -} - -int clientradputudp(struct server *server, unsigned char *rad) { - size_t len; - struct sockaddr_storage sa; - struct sockaddr *sap; - struct clsrvconf *conf = server->conf; - uint16_t port; - - len = RADLEN(rad); - port = port_get(conf->addrinfo->ai_addr); - - if (*rad == RAD_Accounting_Request) { - sap = (struct sockaddr *)&sa; - memcpy(sap, conf->addrinfo->ai_addr, conf->addrinfo->ai_addrlen); - port_set(sap, ++port); - } else - sap = conf->addrinfo->ai_addr; - if (sendto(server->sock, rad, len, 0, sap, conf->addrinfo->ai_addrlen) >= 0) { - debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, conf->host, port); - return 1; + assert (r == pkt->rpkt->length); + /* Unlink the packet. */ + conn->out_queue = pkt->next; + + /* If there are more packets in queue, add the write event again. */ + if (pkt->conn->out_queue) + { + r = event_add (pkt->conn->wev, NULL); + if (r < 0) + return rs_err_conn_push_fl (pkt->conn, RSE_EVENT, __FILE__, __LINE__, + "event_add: %s", evutil_gai_strerror (r)); + rs_debug (("%s: re-adding the write event\n", __func__)); } - debug(DBG_WARN, "clientradputudp: send failed"); - return 0; + return RSE_OK; } -void *udpclientrd(void *arg) { - struct server *server; - unsigned char *buf; - int *s = (int *)arg; - - for (;;) { - server = NULL; - buf = radudpget(*s, NULL, &server, NULL); - replyh(server, buf); - } -} +/* Callback for conn->wev and conn->rev. FIXME: Rename. + + USER_DATA contains connection for EV_READ and a packet for + EV_WRITE. This is because we don't have a connect/establish entry + point at the user level -- send implies connect so when we're + connected we need the packet to send. */ +static void +_evcb (evutil_socket_t fd, short what, void *user_data) +{ + int err; + struct rs_packet *pkt = (struct rs_packet *) user_data; + + rs_debug (("%s: fd=%d what =", __func__, fd)); + if (what & EV_TIMEOUT) rs_debug ((" TIMEOUT -- shouldn't happen!")); + if (what & EV_READ) rs_debug ((" READ")); + if (what & EV_WRITE) rs_debug ((" WRITE")); + rs_debug (("\n")); + + assert (pkt); + assert (pkt->conn); + if (what & EV_READ) + { + /* Read a single UDP packet and stick it in USER_DATA. */ + /* TODO: Verify that unsolicited packets are dropped. */ + ssize_t r = 0; + + assert (pkt->rpkt->data); + + r = compat_recv (fd, pkt->rpkt->data, RS_MAX_PACKET_LEN, MSG_TRUNC); + if (r == -1) + { + int sockerr = evutil_socket_geterror (pkt->conn->fd); + if (sockerr == EAGAIN) + { + /* FIXME: Really shouldn't happen since we've been told + that fd is readable! */ + rs_debug (("%s: EAGAIN reading UDP packet -- wot?\n")); + goto err_out; + } -void *udpserverrd(void *arg) { - struct request *rq; - int *sp = (int *)arg; - - for (;;) { - rq = newrequest(); - if (!rq) { - sleep(5); /* malloc failed */ - continue; + /* Hard error. */ + rs_err_conn_push_fl (pkt->conn, RSE_SOCKERR, __FILE__, __LINE__, + "%d: recv: %d (%s)", fd, sockerr, + evutil_socket_error_to_string (sockerr)); + event_del (pkt->conn->tev); + goto err_out; } - rq->buf = radudpget(*sp, &rq->from, NULL, &rq->udpport); - rq->udpsock = *sp; - radsrv(rq); - } - free(sp); -} - -void *udpserverwr(void *arg) { - struct queue *replyq = (struct queue *)arg; - struct request *reply; - struct sockaddr_storage to; - - for (;;) { - pthread_mutex_lock(&replyq->mutex); - while (!(reply = (struct request *)list_shift(replyq->entries))) { - debug(DBG_DBG, "udp server writer, waiting for signal"); - pthread_cond_wait(&replyq->cond, &replyq->mutex); - debug(DBG_DBG, "udp server writer, got signal"); + event_del (pkt->conn->tev); + if (r < 20 || r > RS_MAX_PACKET_LEN) /* Short or long packet. */ + { + rs_err_conn_push (pkt->conn, RSE_INVALID_PKT, + "invalid packet length: %d", r); + goto err_out; } - pthread_mutex_unlock(&replyq->mutex); - - memcpy(&to, reply->from->addr, SOCKADDRP_SIZE(reply->from->addr)); - port_set((struct sockaddr *)&to, reply->udpport); - if (sendto(reply->udpsock, reply->replybuf, RADLEN(reply->replybuf), 0, (struct sockaddr *)&to, SOCKADDR_SIZE(to)) < 0) - debug(DBG_WARN, "udpserverwr: send failed"); - debug(DBG_DBG, "udpserverwr: refcount %d", reply->refcount); - freerq(reply); + pkt->rpkt->length = (pkt->rpkt->data[2] << 8) + pkt->rpkt->data[3]; + err = nr_packet_ok (pkt->rpkt); + if (err) + { + rs_err_conn_push_fl (pkt->conn, -err, __FILE__, __LINE__, + "invalid packet"); + goto err_out; + } + /* Hand over message to user. This changes ownership of pkt. + Don't touch it afterwards -- it might have been freed. */ + if (pkt->conn->callbacks.received_cb) + pkt->conn->callbacks.received_cb (pkt, pkt->conn->user_data); + else + rs_debug (("%s: no received-callback -- dropping packet\n", __func__)); } -} + else if (what & EV_WRITE) + { + if (!pkt->conn->is_connected) + event_on_connect (pkt->conn, pkt); + + if (pkt->conn->out_queue) + if (_send (pkt->conn, fd) == RSE_OK) + if (pkt->conn->callbacks.sent_cb) + pkt->conn->callbacks.sent_cb (pkt->conn->user_data); + } + return; -void addclientudp(struct client *client) { - client->replyq = server_replyq; + err_out: + rs_conn_disconnect (pkt->conn); } -void addserverextraudp(struct clsrvconf *conf) { - switch (conf->addrinfo->ai_family) { - case AF_INET: - if (client4_sock < 0) { - client4_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET, 0, 1); - if (client4_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); +int +udp_init (struct rs_connection *conn, struct rs_packet *pkt) +{ + assert (!conn->bev); + + conn->rev = event_new (conn->evb, conn->fd, EV_READ|EV_PERSIST, _evcb, NULL); + conn->wev = event_new (conn->evb, conn->fd, EV_WRITE, _evcb, NULL); + if (!conn->rev || !conn->wev) + { + if (conn->rev) + { + event_free (conn->rev); + conn->rev = NULL; } - conf->servers->sock = client4_sock; - break; - case AF_INET6: - if (client6_sock < 0) { - client6_sock = bindtoaddr(getsrcprotores(RAD_UDP), AF_INET6, 0, 1); - if (client6_sock < 0) - debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->host); - } - conf->servers->sock = client6_sock; - break; - default: - debugx(1, DBG_ERR, "addserver: unsupported address family"); + /* 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; } -void initextraudp() { - pthread_t cl4th, cl6th, srvth; - - if (client4_sock >= 0) - if (pthread_create(&cl4th, NULL, udpclientrd, (void *)&client4_sock)) - debugx(1, DBG_ERR, "pthread_create failed"); - if (client6_sock >= 0) - if (pthread_create(&cl6th, NULL, udpclientrd, (void *)&client6_sock)) - debugx(1, DBG_ERR, "pthread_create failed"); +int +udp_init_retransmit_timer (struct rs_connection *conn) +{ + assert (conn); - if (find_clconf_type(RAD_UDP, NULL)) { - server_replyq = newqueue(); - if (pthread_create(&srvth, NULL, udpserverwr, (void *)server_replyq)) - debugx(1, DBG_ERR, "pthread_create failed"); - } + if (conn->tev) + event_free (conn->tev); + conn->tev = evtimer_new (conn->evb, event_retransmit_timeout_cb, conn); + if (!conn->tev) + return rs_err_conn_push_fl (conn, RSE_EVENT, __FILE__, __LINE__, + "evtimer_new"); + + return RSE_OK; }