Files
ocserv/src/ip-util.c
Nikos Mavrogiannopoulos 982348df88 Reworked MTU discovery
Disable MTU discovery when not requested, set the minimum packet size
to 1280 for IPv6 and 800 bytes for IPv4. When MTU discovery fails to
calculate an MTU over the minimum, it disables itself and ocserv will rely
on packet fragmentation. This also enhances DTLS connection detection
(due to MTU issues), by setting the DPD packet size to equal to the current
data MTU.
2016-08-04 07:57:37 +02:00

299 lines
6.4 KiB
C

/*
* Copyright (C) 2013-2016 Nikos Mavrogiannopoulos
* Copyright (C) 2015-2016 Red Hat, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "ip-util.h"
#include <string.h>
#include <stdio.h>
#include <talloc.h>
/* for inet_ntop */
#include <arpa/inet.h>
#include <syslog.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/types.h>
#include <sys/socket.h>
int ip_cmp(const struct sockaddr_storage *s1, const struct sockaddr_storage *s2)
{
if (((struct sockaddr*)s1)->sa_family == AF_INET) {
return memcmp(SA_IN_P(s1), SA_IN_P(s2), sizeof(struct in_addr));
} else { /* inet6 */
return memcmp(SA_IN6_P(s1), SA_IN6_P(s2), sizeof(struct in6_addr));
}
}
/* returns an allocated string with the mask to apply for the prefix
*/
char* ipv4_prefix_to_strmask(void *pool, unsigned prefix)
{
struct in_addr in;
char str[MAX_IP_STR];
if (prefix == 0 || prefix > 32)
return NULL;
in.s_addr = ntohl(((uint32_t)0xFFFFFFFF) << (32 - prefix));
if (inet_ntop(AF_INET, &in, str, sizeof(str)) == NULL)
return NULL;
return talloc_strdup(pool, str);
}
unsigned ipv6_prefix_to_mask(struct in6_addr *in6, unsigned prefix)
{
int i, j;
if (prefix == 0 || prefix > 128)
return 0;
memset(in6, 0x0, sizeof(*in6));
for (i = prefix, j = 0; i > 0; i -= 8, j++) {
if (i >= 8) {
in6->s6_addr[j] = 0xff;
} else {
in6->s6_addr[j] = (unsigned long)(0xffU << ( 8 - i ));
}
}
return 1;
}
/* check whether a route is on the expected format, and if it cannot be
* fixed, then returns a negative code.
*
* The expected format by clients for IPv4 is xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx, i.e.,
* this function converts xxx.xxx.xxx.xxx/prefix to the above for IPv4.
*/
int ip_route_sanity_check(void *pool, char **_route)
{
char *p;
unsigned prefix;
char *route = *_route, *n;
char *slash_ptr, *pstr;
/* this check is valid for IPv4 only */
p = strchr(route, '.');
if (p == NULL)
return 0;
p = strchr(p, '/');
if (p == NULL) {
fprintf(stderr, "route '%s' in wrong format, use xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx\n", route);
return -1;
}
slash_ptr = p;
p++;
/* if we are in dotted notation exit */
if (strchr(p, '.') != 0)
return 0;
/* we are most likely in the xxx.xxx.xxx.xxx/prefix format */
prefix = atoi(p);
pstr = ipv4_prefix_to_strmask(pool, prefix);
if (pstr == NULL) {
fprintf(stderr, "cannot figure format of route '%s'\n", route);
return -1;
}
*slash_ptr = 0;
n = talloc_asprintf(pool, "%s/%s", route, pstr);
if (n == NULL) {
fprintf(stderr, "memory error\n");
return -1;
}
*_route = n;
talloc_free(pstr);
talloc_free(route);
return 0;
}
static
int bit_count(uint32_t i)
{
int c = 0;
unsigned int seen_one = 0;
while (i > 0) {
if (i & 1) {
seen_one = 1;
c++;
} else {
if (seen_one) {
return -1;
}
}
i >>= 1;
}
return c;
}
static int mask2prefix(struct in_addr mask)
{
return bit_count(ntohl(mask.s_addr));
}
static
int ipv4_mask_to_int(const char *prefix)
{
int ret;
struct in_addr in;
ret = inet_pton(AF_INET, prefix, &in);
if (ret == 0)
return -1;
return mask2prefix(in);
}
/* Converts a route from xxx.xxx.xxx.xxx/xxx.xxx.xxx.xxx format, to
* xxx.xxx.xxx.xxx/prefix format.
*/
char *ipv4_route_to_cidr(void *pool, const char *route)
{
int prefix;
int len;
const char *p;
/* this check is valid for IPv4 only */
p = strchr(route, '.');
if (p == NULL)
return talloc_strdup(pool, route);
p = strchr(p, '/');
if (p == NULL) {
return NULL;
}
len = (ptrdiff_t)(p-route);
p++;
/* if we are in CIDR format exit */
if (strchr(p, '.') == 0)
return talloc_strdup(pool, route);
prefix = ipv4_mask_to_int(p);
if (prefix <= 0 || prefix > 32)
return NULL;
return talloc_asprintf(pool, "%.*s/%d", len, route, prefix);
}
char *human_addr2(const struct sockaddr *sa, socklen_t salen,
void *_buf, size_t buflen, unsigned full)
{
char *save_buf = _buf;
char *buf = _buf;
size_t l;
const char *ret;
unsigned port;
if (!buf || !buflen)
return NULL;
if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6) {
return NULL;
}
if (salen == sizeof(struct sockaddr_in6)) {
port = (unsigned)ntohs(((struct sockaddr_in6*)sa)->sin6_port);
if (full != 0 && port != 0 && buflen > 0) {
*buf = '[';
buf++;
buflen--;
}
ret = inet_ntop(AF_INET6, &((struct sockaddr_in6*)sa)->sin6_addr, buf, buflen);
} else {
port = (unsigned)ntohs(((struct sockaddr_in*)sa)->sin_port);
ret = inet_ntop(AF_INET, &((struct sockaddr_in*)sa)->sin_addr, buf, buflen);
}
if (ret == NULL) {
return NULL;
}
if (full == 0)
goto finish;
l = strlen(buf);
buf += l;
buflen -= l;
if (salen == sizeof(struct sockaddr_in6) && port != 0 && buflen > 0) {
*buf = ']';
buf++;
buflen--;
}
if (port != 0 && buflen > 0) {
*buf = ':';
buf++;
buflen--;
snprintf(buf, buflen, "%u", port);
}
finish:
return save_buf;
}
void set_mtu_disc(int fd, int family, int val)
{
int y;
if (family == AF_INET6) {
y = val;
#if defined(IPV6_DONTFRAG)
if (setsockopt(fd, IPPROTO_IPV6, IPV6_DONTFRAG,
(const void *) &y, sizeof(y)) < 0)
syslog(LOG_INFO, "setsockopt(IPV6_DF) failed");
#elif defined(IPV6_MTU_DISCOVER)
if (val)
y = IP_PMTUDISC_DO;
else
y = IP_PMTUDISC_DONT;
if (setsockopt(fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER,
(const void *) &y, sizeof(y)) < 0)
syslog(LOG_INFO, "setsockopt(IPV6_MTU_DISCOVER) failed");
#endif
} else {
y = val;
#if defined(IP_DONTFRAG)
if (setsockopt(fd, IPPROTO_IP, IP_DONTFRAG,
(const void *) &y, sizeof(y)) < 0)
syslog(LOG_INFO, "setsockopt(IP_DF) failed");
#elif defined(IP_MTU_DISCOVER)
if (val)
y = IP_PMTUDISC_DO;
else
y = IP_PMTUDISC_DONT;
if (setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER,
(const void *) &y, sizeof(y)) < 0)
syslog(LOG_INFO, "setsockopt(IP_MTU_DISCOVER) failed");
#endif
}
}