created tnc-pdp policy decision point plugin
[strongswan.git] / src / libcharon / plugins / tnc_pdp / tnc_pdp.c
1 /*
2 * Copyright (C) 2010 Andreas Steffen
3 * HSR Hochschule fuer Technik Rapperswil
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "tnc_pdp.h"
17
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include <daemon.h>
22 #include <debug.h>
23 #include <threading/thread.h>
24 #include <processing/jobs/callback_job.h>
25
26 typedef struct private_tnc_pdp_t private_tnc_pdp_t;
27
28 /**
29 * Maximum size of a RADIUS IP packet
30 */
31 #define MAX_PACKET 2048
32
33 /**
34 * private data of tnc_pdp_t
35 */
36 struct private_tnc_pdp_t {
37
38 /**
39 * implements tnc_pdp_t interface
40 */
41 tnc_pdp_t public;
42
43 /**
44 * IPv4 RADIUS socket
45 */
46 int ipv4;
47
48 /**
49 * IPv6 RADIUS socket
50 */
51 int ipv6;
52
53 /**
54 * Callback job dispatching commands
55 */
56 callback_job_t *job;
57
58 };
59
60
61 /**
62 * Open IPv4 or IPv6 UDP RADIUS socket
63 */
64 static int open_socket(private_tnc_pdp_t *this, int family, u_int16_t port)
65 {
66 int on = TRUE;
67 struct sockaddr_storage addr;
68 socklen_t addrlen;
69 int skt;
70
71 memset(&addr, 0, sizeof(addr));
72 addr.ss_family = family;
73
74 /* precalculate constants depending on address family */
75 switch (family)
76 {
77 case AF_INET:
78 {
79 struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
80
81 htoun32(&sin->sin_addr.s_addr, INADDR_ANY);
82 htoun16(&sin->sin_port, port);
83 addrlen = sizeof(struct sockaddr_in);
84 break;
85 }
86 case AF_INET6:
87 {
88 struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
89
90 memcpy(&sin6->sin6_addr, &in6addr_any, sizeof(in6addr_any));
91 htoun16(&sin6->sin6_port, port);
92 addrlen = sizeof(struct sockaddr_in6);
93 break;
94 }
95 default:
96 return 0;
97 }
98
99 /* open the socket */
100 skt = socket(family, SOCK_DGRAM, IPPROTO_UDP);
101 if (skt < 0)
102 {
103 DBG1(DBG_NET, "opening RADIUS socket failed: %s", strerror(errno));
104 return 0;
105 }
106 if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0)
107 {
108 DBG1(DBG_NET, "unable to set SO_REUSEADDR on socket: %s", strerror(errno));
109 close(skt);
110 return 0;
111 }
112
113 /* bind the socket */
114 if (bind(skt, (struct sockaddr *)&addr, addrlen) < 0)
115 {
116 DBG1(DBG_NET, "unable to bind RADIUS socket: %s", strerror(errno));
117 close(skt);
118 return 0;
119 }
120
121 return skt;
122 }
123
124 /**
125 * Process packets received on the RADIUS socket
126 */
127 static job_requeue_t receive(private_tnc_pdp_t *this)
128 {
129 while (TRUE)
130 {
131 char buffer[MAX_PACKET];
132 int max_fd = 0, selected = 0, bytes_read = 0;
133 fd_set rfds;
134 bool oldstate;
135 host_t *source;
136 struct msghdr msg;
137 struct iovec iov;
138 union {
139 struct sockaddr_in in4;
140 struct sockaddr_in6 in6;
141 } src;
142
143 FD_ZERO(&rfds);
144
145 if (this->ipv4)
146 {
147 FD_SET(this->ipv4, &rfds);
148 }
149 if (this->ipv6)
150 {
151 FD_SET(this->ipv6, &rfds);
152 }
153 max_fd = max(this->ipv4, this->ipv6);
154
155 DBG2(DBG_NET, "waiting for data on RADIUS sockets");
156 oldstate = thread_cancelability(TRUE);
157 if (select(max_fd + 1, &rfds, NULL, NULL, NULL) <= 0)
158 {
159 thread_cancelability(oldstate);
160 continue;
161 }
162 thread_cancelability(oldstate);
163
164 if (FD_ISSET(this->ipv4, &rfds))
165 {
166 selected = this->ipv4;
167 }
168 else if (FD_ISSET(this->ipv6, &rfds))
169 {
170 selected = this->ipv6;
171 }
172 else
173 {
174 /* oops, shouldn't happen */
175 continue;
176 }
177
178 /* read received packet */
179 msg.msg_name = &src;
180 msg.msg_namelen = sizeof(src);
181 iov.iov_base = buffer;
182 iov.iov_len = MAX_PACKET;
183 msg.msg_iov = &iov;
184 msg.msg_iovlen = 1;
185 msg.msg_flags = 0;
186
187 bytes_read = recvmsg(selected, &msg, 0);
188 if (bytes_read < 0)
189 {
190 DBG1(DBG_NET, "error reading RADIUS socket: %s", strerror(errno));
191 continue;
192 }
193 if (msg.msg_flags & MSG_TRUNC)
194 {
195 DBG1(DBG_NET, "receive buffer too small, RADIUS packet discarded");
196 continue;
197 }
198 source = host_create_from_sockaddr((sockaddr_t*)&src);
199 DBG2(DBG_NET, "received RADIUS packet from %#H", source);
200 DBG3(DBG_NET, "%b", buffer, bytes_read);
201 source->destroy(source);
202 }
203 return JOB_REQUEUE_FAIR;
204 }
205
206 METHOD(tnc_pdp_t, destroy, void,
207 private_tnc_pdp_t *this)
208 {
209 this->job->cancel(this->job);
210 if (this->ipv4)
211 {
212 close(this->ipv4);
213 }
214 if (this->ipv6)
215 {
216 close(this->ipv6);
217 }
218 free(this);
219 }
220
221 /*
222 * see header file
223 */
224 tnc_pdp_t *tnc_pdp_create(u_int16_t port)
225 {
226 private_tnc_pdp_t *this;
227
228 INIT(this,
229 .public = {
230 .destroy = _destroy,
231 },
232 .ipv4 = open_socket(this, AF_INET, port),
233 .ipv6 = open_socket(this, AF_INET6, port),
234 );
235
236 if (!this->ipv4 && !this->ipv6)
237 {
238 DBG1(DBG_NET, "couldd not create any RADIUS sockets");
239 destroy(this);
240 return NULL;
241 }
242 if (!this->ipv4)
243 {
244 DBG1(DBG_NET, "could not open IPv4 RADIUS socket, IPv4 disabled");
245 }
246 if (!this->ipv6)
247 {
248 DBG1(DBG_NET, "could not open IPv6 RADIUS socket, IPv6 disabled");
249 }
250
251 this->job = callback_job_create_with_prio((callback_job_cb_t)receive,
252 this, NULL, NULL, JOB_PRIO_CRITICAL);
253 lib->processor->queue_job(lib->processor, (job_t*)this->job);
254
255 return &this->public;
256 }
257