Added Stefan's UDP fragmentation fix
[radsecproxy.git] / util.c
1 /*
2  * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  */
8
9 #include <sys/socket.h>
10 #include <netinet/in.h>
11 #include <netdb.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <sys/select.h>
19 #include <stdarg.h>
20 #include "debug.h"
21 #include "util.h"
22
23 char *stringcopy(const char *s, int len) {
24     char *r;
25     if (!s)
26         return NULL;
27     if (!len)
28         len = strlen(s);
29     r = malloc(len + 1);
30     if (!r)
31         debug(DBG_ERR, "stringcopy: malloc failed");
32     memcpy(r, s, len);
33     r[len] = '\0';
34     return r;
35 }
36
37 void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) {
38     int i;
39     unsigned char *s = (unsigned char *)chars;
40     if (prefix)
41         printf(prefixfmt ? prefixfmt : "%s: ", prefix);
42     for (i = 0; i < len; i++)
43         printf(charfmt ? charfmt : "%c", s[i]);
44     printf("\n");
45 }
46
47 void port_set(struct sockaddr *sa, uint16_t port) {
48     switch (sa->sa_family) {
49     case AF_INET:
50         ((struct sockaddr_in *)sa)->sin_port = htons(port);
51         break;
52     case AF_INET6:
53         ((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
54         break;
55     }
56 }
57
58 struct sockaddr *addr_copy(struct sockaddr *in) {
59     struct sockaddr *out = NULL;
60     
61     switch (in->sa_family) {
62     case AF_INET:
63         out = malloc(sizeof(struct sockaddr_in));
64         if (out) {
65             memset(out, 0, sizeof(struct sockaddr_in));
66             ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr;
67         }
68         break;
69     case AF_INET6:
70         out = malloc(sizeof(struct sockaddr_in6));
71         if (out) {
72             memset(out, 0, sizeof(struct sockaddr_in6));
73             ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr;
74         }
75         break;
76     }
77     out->sa_family = in->sa_family;
78 #ifdef SIN6_LEN
79     out->sa_len = in->sa_len;
80 #endif
81     return out;
82 }
83
84 char *addr2string(struct sockaddr *addr) {
85     struct sockaddr_in6 *sa6;
86     struct sockaddr_in sa4;
87     static char addr_buf[2][INET6_ADDRSTRLEN];
88     static int i = 0;
89     i = !i;
90     if (addr->sa_family == AF_INET6) {
91         sa6 = (struct sockaddr_in6 *)addr;
92         if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
93             memset(&sa4, 0, sizeof(sa4));
94             sa4.sin_family = AF_INET;
95             sa4.sin_port = sa6->sin6_port;
96             memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4);
97             addr = (struct sockaddr *)&sa4;
98         }
99     }
100     if (getnameinfo(addr, SOCKADDRP_SIZE(addr), addr_buf[i], sizeof(addr_buf[i]),
101                     NULL, 0, NI_NUMERICHOST)) {
102         debug(DBG_WARN, "getnameinfo failed");
103         return "getnameinfo_failed";
104     }
105     return addr_buf[i];
106 }
107
108 #if 0
109 /* not in use */
110 int connectport(int type, char *host, char *port) {
111     struct addrinfo hints, *res0, *res;
112     int s = -1;
113     
114     memset(&hints, 0, sizeof(hints));
115     hints.ai_socktype = type;
116     hints.ai_family = AF_UNSPEC;
117
118     if (getaddrinfo(host, port, &hints, &res0) != 0) {
119         debug(DBG_ERR, "connectport: can't resolve host %s port %s", host, port);
120         return -1;
121     }
122
123     for (res = res0; res; res = res->ai_next) {
124         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
125         if (s < 0) {
126             debug(DBG_WARN, "connectport: socket failed");
127             continue;
128         }
129         if (connect(s, res->ai_addr, res->ai_addrlen) == 0)
130             break;
131         debug(DBG_WARN, "connectport: connect failed");
132         close(s);
133         s = -1;
134     }
135     freeaddrinfo(res0);
136     return s;
137 }
138 #endif
139
140 /* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized"
141    RADIUS packet to be discarded on first attempt (due to Path MTU discovery).
142 */
143
144 void disable_DF_bit(int socket, struct addrinfo *res) {
145         if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) {
146 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
147         /*
148          * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant.
149          */
150         int r, action;
151         debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)");
152         action = IP_PMTUDISC_DONT;
153         r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action));
154         if (r == -1) 
155                   debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER");
156 #else
157         debug(DBG_INFO, "Non-Linux platform, unable to unset DF bit for UDP. You should check with tcpdump whether radsecproxy will send its UDP packets with DF bit set!");
158 #endif
159         }
160 }
161
162 int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) {
163     int s, on = 1;
164     struct addrinfo *res;
165
166     for (res = addrinfo; res; res = res->ai_next) {
167         if (family != AF_UNSPEC && family != res->ai_family)
168             continue;
169         s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
170         if (s < 0) {
171             debug(DBG_WARN, "bindtoaddr: socket failed");
172             continue;
173         }
174
175         disable_DF_bit(s,res);
176
177         if (reuse)
178             setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
179         #ifdef IPV6_V6ONLY
180         if (v6only)
181             setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
182         #endif
183         if (!bind(s, res->ai_addr, res->ai_addrlen))
184             return s;
185         debug(DBG_WARN, "bindtoaddr: bind failed");
186         close(s);
187     }
188     return -1;
189 }
190
191 int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) {
192     int origflags, error = 0, r = -1;
193     fd_set writefds;
194     socklen_t len;
195     
196     origflags = fcntl(s, F_GETFL, 0);
197     fcntl(s, F_SETFL, origflags | O_NONBLOCK);
198     if (!connect(s, addr, addrlen)) {
199         r = 0;
200         goto exit;
201     }
202     if (errno != EINPROGRESS)
203         goto exit;
204
205     FD_ZERO(&writefds);
206     FD_SET(s, &writefds);
207     if (select(s + 1, NULL, &writefds, NULL, timeout) < 1)
208         goto exit;
209
210     len = sizeof(error);
211     if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) && !error)
212         r = 0;
213
214  exit:    
215     fcntl(s, F_SETFL, origflags);
216     return r;
217 }
218
219 int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) {
220     int s;
221     struct addrinfo *res;
222     struct timeval to;
223
224     s = -1;
225     if (timeout) {
226         if (addrinfo && addrinfo->ai_next && timeout > 5)
227             timeout = 5;
228         to.tv_sec = timeout;
229         to.tv_usec = 0;
230     }
231     
232     for (res = addrinfo; res; res = res->ai_next) {
233         s = bindtoaddr(src, res->ai_family, 1, 1);
234         if (s < 0) {
235             debug(DBG_WARN, "connecttoserver: socket failed");
236             continue;
237         }
238         if ((timeout
239              ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, &to)
240              : connect(s, res->ai_addr, res->ai_addrlen)) == 0)
241             break;
242         debug(DBG_WARN, "connecttoserver: connect failed");
243         close(s);
244         s = -1;
245     }
246     return s;
247 }