util.c 6.43 KB
Newer Older
1
/*
venaas's avatar
venaas committed
2
 * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
3 4 5 6 7 8
 *
 * 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.
 */

kolla's avatar
kolla committed
9 10 11 12 13
/* Code contributions from:
 *
 * Stefan Winter <stefan.winter@restena.lu>
 */

venaas's avatar
venaas committed
14 15
#include <sys/socket.h>
#include <netinet/in.h>
16 17 18 19 20
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
venaas's avatar
venaas committed
21 22 23
#include <fcntl.h>
#include <errno.h>
#include <sys/select.h>
24
#include <stdarg.h>
venaas's avatar
venaas committed
25
#include "debug.h"
26
#include "util.h"
27

28
char *stringcopy(const char *s, int len) {
29
    char *r;
30 31
    if (!s)
	return NULL;
32 33 34 35
    if (!len)
	len = strlen(s);
    r = malloc(len + 1);
    if (!r)
venaas's avatar
venaas committed
36
	debug(DBG_ERR, "stringcopy: malloc failed");
37 38 39 40
    else {
        memcpy(r, s, len);
        r[len] = '\0';
    }
41 42
    return r;
}
43 44 45 46 47 48 49 50 51 52 53

void printfchars(char *prefixfmt, char *prefix, char *charfmt, char *chars, int len) {
    int i;
    unsigned char *s = (unsigned char *)chars;
    if (prefix)
	printf(prefixfmt ? prefixfmt : "%s: ", prefix);
    for (i = 0; i < len; i++)
	printf(charfmt ? charfmt : "%c", s[i]);
    printf("\n");
}

venaas's avatar
venaas committed
54 55 56 57 58 59 60 61 62 63 64
void port_set(struct sockaddr *sa, uint16_t port) {
    switch (sa->sa_family) {
    case AF_INET:
	((struct sockaddr_in *)sa)->sin_port = htons(port);
	break;
    case AF_INET6:
	((struct sockaddr_in6 *)sa)->sin6_port = htons(port);
	break;
    }
}

65 66
struct sockaddr *addr_copy(struct sockaddr *in) {
    struct sockaddr *out = NULL;
67

68 69 70
    switch (in->sa_family) {
    case AF_INET:
	out = malloc(sizeof(struct sockaddr_in));
71 72 73 74
        if (out == NULL)
            return NULL;
        memset(out, 0, sizeof(struct sockaddr_in));
        ((struct sockaddr_in *)out)->sin_addr = ((struct sockaddr_in *)in)->sin_addr;
75 76 77
	break;
    case AF_INET6:
	out = malloc(sizeof(struct sockaddr_in6));
78 79 80 81
        if (out == NULL)
            return NULL;
        memset(out, 0, sizeof(struct sockaddr_in6));
        ((struct sockaddr_in6 *)out)->sin6_addr = ((struct sockaddr_in6 *)in)->sin6_addr;
82 83
	break;
    }
venaas's avatar
venaas committed
84
    out->sa_family = in->sa_family;
85 86 87
#ifdef SIN6_LEN
    out->sa_len = in->sa_len;
#endif
88 89 90
    return out;
}

91
char *addr2string(struct sockaddr *addr) {
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
    struct sockaddr_in6 *sa6;
    struct sockaddr_in sa4;
    static char addr_buf[2][INET6_ADDRSTRLEN];
    static int i = 0;
    i = !i;
    if (addr->sa_family == AF_INET6) {
	sa6 = (struct sockaddr_in6 *)addr;
	if (IN6_IS_ADDR_V4MAPPED(&sa6->sin6_addr)) {
	    memset(&sa4, 0, sizeof(sa4));
	    sa4.sin_family = AF_INET;
	    sa4.sin_port = sa6->sin6_port;
	    memcpy(&sa4.sin_addr, &sa6->sin6_addr.s6_addr[12], 4);
	    addr = (struct sockaddr *)&sa4;
	}
    }
107
    if (getnameinfo(addr, SOCKADDRP_SIZE(addr), addr_buf[i], sizeof(addr_buf[i]),
108
                    NULL, 0, NI_NUMERICHOST)) {
109
        debug(DBG_WARN, "getnameinfo failed");
venaas's avatar
venaas committed
110
        return "getnameinfo_failed";
111 112 113 114
    }
    return addr_buf[i];
}

115 116 117 118 119
/* Disable the "Don't Fragment" bit for UDP sockets. It is set by default, which may cause an "oversized"
   RADIUS packet to be discarded on first attempt (due to Path MTU discovery).
*/

void disable_DF_bit(int socket, struct addrinfo *res) {
120
    if ((res->ai_family == AF_INET) && (res->ai_socktype == SOCK_DGRAM)) {
121 122 123 124 125 126 127 128
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
        /*
         * Turn off Path MTU discovery on IPv4/UDP sockets, Linux variant.
         */
	int r, action;
        debug(DBG_INFO, "disable_DF_bit: disabling DF bit (Linux variant)");
        action = IP_PMTUDISC_DONT;
        r = setsockopt(socket, IPPROTO_IP, IP_MTU_DISCOVER, &action, sizeof(action));
129 130
        if (r == -1)
	    debug(DBG_WARN, "Failed to set IP_MTU_DISCOVER");
131 132 133
#else
	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!");
#endif
134
    }
135 136
}

137 138 139 140 141 142 143 144 145 146 147 148
int bindtoaddr(struct addrinfo *addrinfo, int family, int reuse, int v6only) {
    int s, on = 1;
    struct addrinfo *res;

    for (res = addrinfo; res; res = res->ai_next) {
	if (family != AF_UNSPEC && family != res->ai_family)
	    continue;
	s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
	if (s < 0) {
	    debug(DBG_WARN, "bindtoaddr: socket failed");
	    continue;
	}
149 150 151

	disable_DF_bit(s,res);

152
	if (reuse)
153 154
	    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
                debugerrno(errno, DBG_WARN, "Failed to set SO_REUSEADDR");
155
#ifdef IPV6_V6ONLY
156
	if (v6only)
157
	    if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1)
158
                debugerrno(errno, DBG_INFO, "Failed to set IPV6_V6ONLY");
159
#endif
160 161 162 163 164 165 166 167
	if (!bind(s, res->ai_addr, res->ai_addrlen))
	    return s;
	debug(DBG_WARN, "bindtoaddr: bind failed");
	close(s);
    }
    return -1;
}

venaas's avatar
venaas committed
168 169 170 171
int connectnonblocking(int s, const struct sockaddr *addr, socklen_t addrlen, struct timeval *timeout) {
    int origflags, error = 0, r = -1;
    fd_set writefds;
    socklen_t len;
172

venaas's avatar
venaas committed
173
    origflags = fcntl(s, F_GETFL, 0);
174 175 176 177 178 179 180 181
    if (origflags == -1) {
        debugerrno(errno, DBG_WARN, "Failed to get flags");
        return -1;
    }
    if (fcntl(s, F_SETFL, origflags | O_NONBLOCK) == -1) {
        debugerrno(errno, DBG_WARN, "Failed to set O_NONBLOCK");
        return -1;
    }
venaas's avatar
venaas committed
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    if (!connect(s, addr, addrlen)) {
	r = 0;
	goto exit;
    }
    if (errno != EINPROGRESS)
	goto exit;

    FD_ZERO(&writefds);
    FD_SET(s, &writefds);
    if (select(s + 1, NULL, &writefds, NULL, timeout) < 1)
	goto exit;

    len = sizeof(error);
    if (!getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&error, &len) && !error)
	r = 0;

198
exit:
199 200
    if (fcntl(s, F_SETFL, origflags) == -1)
        debugerrno(errno, DBG_WARN, "Failed to set original flags back");
venaas's avatar
venaas committed
201 202 203 204
    return r;
}

int connecttcp(struct addrinfo *addrinfo, struct addrinfo *src, uint16_t timeout) {
205 206
    int s;
    struct addrinfo *res;
venaas's avatar
venaas committed
207
    struct timeval to;
208 209

    s = -1;
venaas's avatar
venaas committed
210
    if (timeout) {
211
	if (addrinfo && addrinfo->ai_next && timeout > 5)
venaas's avatar
venaas committed
212 213 214 215
	    timeout = 5;
	to.tv_sec = timeout;
	to.tv_usec = 0;
    }
216

217 218 219 220 221 222
    for (res = addrinfo; res; res = res->ai_next) {
	s = bindtoaddr(src, res->ai_family, 1, 1);
	if (s < 0) {
	    debug(DBG_WARN, "connecttoserver: socket failed");
	    continue;
	}
venaas's avatar
venaas committed
223 224 225
	if ((timeout
	     ? connectnonblocking(s, res->ai_addr, res->ai_addrlen, &to)
	     : connect(s, res->ai_addr, res->ai_addrlen)) == 0)
226 227 228 229 230 231 232
	    break;
	debug(DBG_WARN, "connecttoserver: connect failed");
	close(s);
	s = -1;
    }
    return s;
}
233 234 235 236

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