udp.c 10.1 KB
Newer Older
venaas's avatar
venaas committed
1
/*
2
 * Copyright (C) 2006-2009 Stig Venaas <venaas@uninett.no>
venaas's avatar
venaas committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
 *
 * 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.
 */

#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <limits.h>
#ifdef SYS_SOLARIS9
#include <fcntl.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <sys/select.h>
#include <ctype.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <regex.h>
#include <pthread.h>
27
#include <assert.h>
venaas's avatar
venaas committed
28
#include "radsecproxy.h"
29
#include "hostport.h"
venaas's avatar
venaas committed
30

31 32 33 34
#ifdef RADPROT_UDP
#include "debug.h"
#include "util.h"

venaas's avatar
venaas committed
35 36
static void setprotoopts(struct commonprotoopts *opts);
static char **getlistenerargs();
venaas's avatar
venaas committed
37 38 39 40
void *udpserverrd(void *arg);
int clientradputudp(struct server *server, unsigned char *rad);
void addclientudp(struct client *client);
void addserverextraudp(struct clsrvconf *conf);
venaas's avatar
venaas committed
41
void udpsetsrcres();
venaas's avatar
venaas committed
42 43 44 45 46 47 48 49 50 51 52 53
void initextraudp();

static const struct protodefs protodefs = {
    "udp",
    NULL, /* secretdefault */
    SOCK_DGRAM, /* socktype */
    "1812", /* portdefault */
    REQUEST_RETRY_COUNT, /* retrycountdefault */
    10, /* retrycountmax */
    REQUEST_RETRY_INTERVAL, /* retryintervaldefault */
    60, /* retryintervalmax */
    DUPLICATE_INTERVAL, /* duplicateintervaldefault */
venaas's avatar
venaas committed
54 55
    setprotoopts, /* setprotoopts */
    getlistenerargs, /* getlistenerargs */
venaas's avatar
venaas committed
56 57 58 59 60 61 62 63 64
    udpserverrd, /* listener */
    NULL, /* connecter */
    NULL, /* clientconnreader */
    clientradputudp, /* clientradput */
    addclientudp, /* addclient */
    addserverextraudp, /* addserverextra */
    udpsetsrcres, /* setsrcres */
    initextraudp /* initextra */
};
venaas's avatar
venaas committed
65

venaas's avatar
venaas committed
66 67
static int client4_sock = -1;
static int client6_sock = -1;
68
static struct gqueue *server_replyq = NULL;
venaas's avatar
venaas committed
69

venaas's avatar
venaas committed
70
static struct addrinfo *srcres = NULL;
venaas's avatar
venaas committed
71
static uint8_t handle;
venaas's avatar
venaas committed
72
static struct commonprotoopts *protoopts = NULL;
venaas's avatar
venaas committed
73 74 75 76 77

const struct protodefs *udpinit(uint8_t h) {
    handle = h;
    return &protodefs;
}
venaas's avatar
venaas committed
78

venaas's avatar
venaas committed
79 80 81 82 83 84 85 86 87
static void setprotoopts(struct commonprotoopts *opts) {
    protoopts = opts;
}

static char **getlistenerargs() {
    return protoopts ? protoopts->listenargs : NULL;
}

void udpsetsrcres() {
venaas's avatar
venaas committed
88
    if (!srcres)
89 90 91
	srcres =
            resolvepassiveaddrinfo(protoopts ? protoopts->sourcearg : NULL,
                                   AF_UNSPEC, NULL, protodefs.socktype);
venaas's avatar
venaas committed
92 93
}

venaas's avatar
venaas committed
94 95 96
void removeudpclientfromreplyq(struct client *c) {
    struct list_node *n;
    struct request *r;
97

venaas's avatar
venaas committed
98 99 100 101 102 103 104 105
    /* lock the common queue and remove replies for this client */
    pthread_mutex_lock(&c->replyq->mutex);
    for (n = list_first(c->replyq->entries); n; n = list_next(n)) {
	r = (struct request *)n->data;
	if (r->from == c)
	    r->from = NULL;
    }
    pthread_mutex_unlock(&c->replyq->mutex);
106
}
venaas's avatar
venaas committed
107

108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
static int addr_equal(struct sockaddr *a, struct sockaddr *b) {
    switch (a->sa_family) {
    case AF_INET:
	return !memcmp(&((struct sockaddr_in*)a)->sin_addr,
		       &((struct sockaddr_in*)b)->sin_addr,
		       sizeof(struct in_addr));
    case AF_INET6:
	return IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6*)a)->sin6_addr,
				  &((struct sockaddr_in6*)b)->sin6_addr);
    default:
	/* Must not reach */
	return 0;
    }
}

uint16_t port_get(struct sockaddr *sa) {
    switch (sa->sa_family) {
    case AF_INET:
	return ntohs(((struct sockaddr_in *)sa)->sin_port);
    case AF_INET6:
	return ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
    }
    return 0;
}

venaas's avatar
venaas committed
133 134 135
/* 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 */
venaas's avatar
venaas committed
136
unsigned char *radudpget(int s, struct client **client, struct server **server, uint16_t *port) {
venaas's avatar
venaas committed
137 138 139
    int cnt, len;
    unsigned char buf[4], *rad = NULL;
    struct sockaddr_storage from;
140
    struct sockaddr *fromcopy;
venaas's avatar
venaas committed
141 142 143 144
    socklen_t fromlen = sizeof(from);
    struct clsrvconf *p;
    struct list_node *node;
    fd_set readfds;
145
    struct client *c = NULL;
venaas's avatar
venaas committed
146
    struct timeval now;
147

venaas's avatar
venaas committed
148 149 150 151 152 153 154 155 156 157 158 159 160 161
    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;
	}
162

venaas's avatar
venaas committed
163
	p = client
venaas's avatar
venaas committed
164 165
	    ? find_clconf(handle, (struct sockaddr *)&from, NULL)
	    : find_srvconf(handle, (struct sockaddr *)&from, NULL);
venaas's avatar
venaas committed
166
	if (!p) {
167
	    debug(DBG_WARN, "radudpget: got packet from wrong or unknown UDP peer %s, ignoring", addr2string((struct sockaddr *)&from));
venaas's avatar
venaas committed
168 169 170
	    recv(s, buf, 4, 0);
	    continue;
	}
171

venaas's avatar
venaas committed
172 173 174 175 176 177
	len = RADLEN(buf);
	if (len < 20) {
	    debug(DBG_WARN, "radudpget: length too small");
	    recv(s, buf, 4, 0);
	    continue;
	}
178

venaas's avatar
venaas committed
179 180 181 182 183 184
	rad = malloc(len);
	if (!rad) {
	    debug(DBG_ERR, "radudpget: malloc failed");
	    recv(s, buf, 4, 0);
	    continue;
	}
185

venaas's avatar
venaas committed
186
	cnt = recv(s, rad, len, MSG_TRUNC);
187
	debug(DBG_DBG, "radudpget: got %d bytes from %s", cnt, addr2string((struct sockaddr *)&from));
venaas's avatar
venaas committed
188 189 190 191 192 193 194 195 196

	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) {
venaas's avatar
venaas committed
197
	    *client = NULL;
198
	    pthread_mutex_lock(p->lock);
venaas's avatar
venaas committed
199
	    for (node = list_first(p->clients); node;) {
200
		c = (struct client *)node->data;
venaas's avatar
venaas committed
201 202 203 204 205 206 207 208 209 210
		node = list_next(node);
		if (s != c->sock)
		    continue;
		gettimeofday(&now, NULL);
		if (!*client && addr_equal((struct sockaddr *)&from, c->addr)) {
		    c->expiry = now.tv_sec + 60;
		    *client = c;
		}
		if (c->expiry >= now.tv_sec)
		    continue;
211

venaas's avatar
venaas committed
212 213 214 215 216
		debug(DBG_DBG, "radudpget: removing expired client (%s)", addr2string(c->addr));
		removeudpclientfromreplyq(c);
		c->replyq = NULL; /* stop removeclient() from removing common udp replyq */
		removelockedclient(c);
		break;
217
	    }
venaas's avatar
venaas committed
218
	    if (!*client) {
219 220 221 222 223 224 225 226 227 228 229 230 231
		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;
venaas's avatar
venaas committed
232 233 234
		gettimeofday(&now, NULL);
		c->expiry = now.tv_sec + 60;
		*client = c;
235 236
	    }
	    pthread_mutex_unlock(p->lock);
venaas's avatar
venaas committed
237 238 239 240
	} else if (server)
	    *server = p->servers;
	break;
    }
venaas's avatar
venaas committed
241 242
    if (port)
	*port = port_get((struct sockaddr *)&from);
venaas's avatar
venaas committed
243 244 245 246 247 248
    return rad;
}

int clientradputudp(struct server *server, unsigned char *rad) {
    size_t len;
    struct clsrvconf *conf = server->conf;
249 250
    struct addrinfo *ai;

venaas's avatar
venaas committed
251
    len = RADLEN(rad);
252 253 254
    ai = ((struct hostportres *)list_first(conf->hostports)->data)->addrinfo;
    if (sendto(server->sock, rad, len, 0, ai->ai_addr, ai->ai_addrlen) >= 0) {
	debug(DBG_DBG, "clienradputudp: sent UDP of length %d to %s port %d", len, addr2string(ai->ai_addr), port_get(ai->ai_addr));
venaas's avatar
venaas committed
255 256 257 258 259 260 261 262 263 264 265
	return 1;
    }

    debug(DBG_WARN, "clientradputudp: send failed");
    return 0;
}

void *udpclientrd(void *arg) {
    struct server *server;
    unsigned char *buf;
    int *s = (int *)arg;
266

venaas's avatar
venaas committed
267 268 269
    for (;;) {
	server = NULL;
	buf = radudpget(*s, NULL, &server, NULL);
venaas's avatar
venaas committed
270
	replyh(server, buf);
venaas's avatar
venaas committed
271 272 273 274
    }
}

void *udpserverrd(void *arg) {
275
    struct request *rq;
venaas's avatar
venaas committed
276
    int *sp = (int *)arg;
277

venaas's avatar
venaas committed
278
    for (;;) {
279 280 281 282 283
	rq = newrequest();
	if (!rq) {
	    sleep(5); /* malloc failed */
	    continue;
	}
venaas's avatar
venaas committed
284 285
	rq->buf = radudpget(*sp, &rq->from, NULL, &rq->udpport);
	rq->udpsock = *sp;
286
	radsrv(rq);
venaas's avatar
venaas committed
287 288
    }
    free(sp);
289
    return NULL;
venaas's avatar
venaas committed
290 291 292
}

void *udpserverwr(void *arg) {
293
    struct gqueue *replyq = (struct gqueue *)arg;
venaas's avatar
venaas committed
294 295
    struct request *reply;
    struct sockaddr_storage to;
296

venaas's avatar
venaas committed
297 298
    for (;;) {
	pthread_mutex_lock(&replyq->mutex);
venaas's avatar
venaas committed
299
	while (!(reply = (struct request *)list_shift(replyq->entries))) {
venaas's avatar
venaas committed
300 301 302 303
	    debug(DBG_DBG, "udp server writer, waiting for signal");
	    pthread_cond_wait(&replyq->cond, &replyq->mutex);
	    debug(DBG_DBG, "udp server writer, got signal");
	}
venaas's avatar
venaas committed
304 305 306
	/* do this with lock, udpserverrd may set from = NULL if from expires */
	if (reply->from)
	    memcpy(&to, reply->from->addr, SOCKADDRP_SIZE(reply->from->addr));
venaas's avatar
venaas committed
307
	pthread_mutex_unlock(&replyq->mutex);
venaas's avatar
venaas committed
308 309 310 311 312
	if (reply->from) {
	    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");
	}
313
	debug(DBG_DBG, "udpserverwr: refcount %d", reply->refcount);
venaas's avatar
venaas committed
314
	freerq(reply);
venaas's avatar
venaas committed
315 316
    }
}
venaas's avatar
venaas committed
317 318 319 320 321 322

void addclientudp(struct client *client) {
    client->replyq = server_replyq;
}

void addserverextraudp(struct clsrvconf *conf) {
323
    assert(list_first(conf->hostports) != NULL);
324
    switch (((struct hostportres *)list_first(conf->hostports)->data)->addrinfo->ai_family) {
venaas's avatar
venaas committed
325 326
    case AF_INET:
	if (client4_sock < 0) {
venaas's avatar
venaas committed
327
	    client4_sock = bindtoaddr(srcres, AF_INET, 0, 1);
venaas's avatar
venaas committed
328
	    if (client4_sock < 0)
329
		debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name);
venaas's avatar
venaas committed
330 331 332 333 334
	}
	conf->servers->sock = client4_sock;
	break;
    case AF_INET6:
	if (client6_sock < 0) {
venaas's avatar
venaas committed
335
	    client6_sock = bindtoaddr(srcres, AF_INET6, 0, 1);
venaas's avatar
venaas committed
336
	    if (client6_sock < 0)
337
		debugx(1, DBG_ERR, "addserver: failed to create client socket for server %s", conf->name);
venaas's avatar
venaas committed
338 339 340 341 342 343 344 345 346 347
	}
	conf->servers->sock = client6_sock;
	break;
    default:
	debugx(1, DBG_ERR, "addserver: unsupported address family");
    }
}

void initextraudp() {
    pthread_t cl4th, cl6th, srvth;
venaas's avatar
venaas committed
348 349 350 351 352

    if (srcres) {
	freeaddrinfo(srcres);
	srcres = NULL;
    }
353

venaas's avatar
venaas committed
354
    if (client4_sock >= 0)
355
	if (pthread_create(&cl4th, &pthread_attr, udpclientrd, (void *)&client4_sock))
venaas's avatar
venaas committed
356 357
	    debugx(1, DBG_ERR, "pthread_create failed");
    if (client6_sock >= 0)
358
	if (pthread_create(&cl6th, &pthread_attr, udpclientrd, (void *)&client6_sock))
venaas's avatar
venaas committed
359 360
	    debugx(1, DBG_ERR, "pthread_create failed");

venaas's avatar
venaas committed
361
    if (find_clconf_type(handle, NULL)) {
venaas's avatar
venaas committed
362
	server_replyq = newqueue();
363
	if (pthread_create(&srvth, &pthread_attr, udpserverwr, (void *)server_replyq))
venaas's avatar
venaas committed
364 365 366
	    debugx(1, DBG_ERR, "pthread_create failed");
    }
}
367 368 369 370 371
#else
const struct protodefs *udpinit(uint8_t h) {
    return NULL;
}
#endif
372 373 374 375

/* Local Variables: */
/* c-file-style: "stroustrup" */
/* End: */