/**
* @file user_inet_types.c
* @author Michal Vasko <mvasko@cesnet.cz>
* @brief ietf-inet-types typedef conversion to canonical format
*
* Copyright (c) 2018 CESNET, z.s.p.o.
*
* This source code is licensed under BSD 3-Clause License (the "License").
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "compat.h"
#include "../user_types.h"
/**
* @brief Storage for ID used to check plugin API version compatibility.
*/
LYTYPE_VERSION_CHECK
#ifdef __GNUC__
# define UNUSED(x) UNUSED_ ## x __attribute__((__unused__))
#else
# define UNUSED(x) UNUSED_ ## x
#endif
static char *
convert_ipv6_addr(const char *ipv6_addr, char **err_msg)
{
char buf[sizeof(struct in6_addr)], *str;
str = malloc(INET6_ADDRSTRLEN);
if (!str) {
*err_msg = NULL;
return NULL;
}
if (!inet_pton(AF_INET6, ipv6_addr, buf)) {
if (asprintf(err_msg, "Failed to convert IPv6 address \"%s\".", ipv6_addr) == -1) {
*err_msg = NULL;
}
free(str);
return NULL;
}
if (!inet_ntop(AF_INET6, buf, str, INET6_ADDRSTRLEN)) {
if (asprintf(err_msg, "Failed to convert IPv6 address (%s).", strerror(errno)) == -1) {
*err_msg = NULL;
}
free(str);
return NULL;
}
return str;
}
static int
ip_store_clb(struct ly_ctx *ctx, const char *UNUSED(type_name), const char **value_str, lyd_val *value, char **err_msg)
{
char *ptr, *ipv6_addr, *result, *tmp;
if (!strchr(*value_str, ':')) {
/* not an IPv6 address */
return 0;
}
if ((ptr = strchr(*value_str, '%'))) {
/* there is a zone index */
ipv6_addr = strndup(*value_str, ptr - *value_str);
} else {
ipv6_addr = (char *)*value_str;
}
/* convert to canonical format */
result = convert_ipv6_addr(ipv6_addr, err_msg);
if (ptr) {
free(ipv6_addr);
}
/* failure */
if (!result) {
return 1;
}
if (strncmp(*value_str, result, strlen(result))) {
/* some conversion took place, update the value */
if (ptr) {
tmp = result;
if (asprintf(&result, "%s%s", tmp, ptr) == -1) {
free(tmp);
*err_msg = NULL;
return 1;
}
free(tmp);
}
lydict_remove(ctx, *value_str);
*value_str = lydict_insert_zc(ctx, result);
value->string = *value_str;
} else {
free(result);
}
return 0;
}
static int
ipv4_prefix_store_clb(struct ly_ctx *ctx, const char *UNUSED(type_name), const char **value_str, lyd_val *value, char **err_msg)
{
char *pref_str, *ptr, *result;
uint32_t pref, addr_bin, i, mask;
if (sizeof addr_bin < sizeof(struct in_addr)) {
if (asprintf(err_msg, "Internal error (buffer too small for an ip address).") == -1) {
*err_msg = NULL;
}
return 1;
}
pref_str = strchr(*value_str, '/');
if (!pref_str) {
if (asprintf(err_msg, "Invalid IPv4 prefix \"%s\".", *value_str) == -1) {
*err_msg = NULL;
}
return 1;
}
/* learn prefix */
pref = strtoul(pref_str + 1, &ptr, 10);
if (ptr[0] || (pref > 32)) {
if (asprintf(err_msg, "Invalid IPv4 prefix \"%s\".", *value_str) == -1) {
*err_msg = NULL;
}
return 1;
}
result = malloc(INET_ADDRSTRLEN + 3);
if (!result) {
*err_msg = NULL;
return 1;
}
/* copy just the network prefix */
strncpy(result, *value_str, pref_str - *value_str);
result[pref_str - *value_str] = '\0';
/* convert it to binary form */
if (inet_pton(AF_INET, result, (void *)&addr_bin) != 1) {
if (asprintf(err_msg, "Failed to convert IPv4 address \"%s\".", result) == -1) {
*err_msg = NULL;
}
free(result);
return 1;
}
/* zero host bits */
mask = 0;
for (i = 0; i < 32; ++i) {
mask <<= 1;
if (pref > i) {
mask |= 1;
}
}
mask = htonl(mask);
addr_bin &= mask;
/* convert back to string */
if (!inet_ntop(AF_INET, (void *)&addr_bin, result, INET_ADDRSTRLEN)) {
if (asprintf(err_msg, "Failed to convert IPv4 address (%s).", strerror(errno)) == -1) {
*err_msg = NULL;
}
free(result);
return 1;
}
/* add the prefix */
strcat(result, pref_str);
if (strcmp(result, *value_str)) {
/* some conversion took place, update the value */
lydict_remove(ctx, *value_str);
*value_str = lydict_insert_zc(ctx, result);
value->string = *value_str;
} else {
free(result);
}
return 0;
}
static int
ipv6_prefix_store_clb(struct ly_ctx *ctx, const char *UNUSED(type_name), const char **value_str, lyd_val *value, char **err_msg)
{
char *pref_str, *ptr, *result;
unsigned long int pref, i, j;
union {
struct in6_addr s;
uint32_t a[4];
} addr_bin;
uint32_t mask;
pref_str = strchr(*value_str, '/');
if (!pref_str) {
if (asprintf(err_msg, "Invalid IPv6 prefix \"%s\".", *value_str) == -1) {
*err_msg = NULL;
}
return 1;
}
/* learn prefix */
pref = strtoul(pref_str + 1, &ptr, 10);
if (ptr[0] || (pref > 128)) {
if (asprintf(err_msg, "Invalid IPv6 prefix \"%s\".", *value_str) == -1) {
*err_msg = NULL;
}
return 1;
}
result = malloc(INET6_ADDRSTRLEN + 4);
if (!result) {
*err_msg = NULL;
return 1;
}
/* copy just the network prefix */
strncpy(result, *value_str, pref_str - *value_str);
result[pref_str - *value_str] = '\0';
/* convert it to binary form */
if (inet_pton(AF_INET6, result, (void *)&addr_bin.s) != 1) {
if (asprintf(err_msg, "Failed to convert IPv6 address \"%s\".", result) == -1) {
*err_msg = NULL;
}
free(result);
return 1;
}
/* zero host bits */
for (i = 0; i < 4; ++i) {
mask = 0;
for (j = 0; j < 32; ++j) {
mask <<= 1;
if (pref > (i * 32) + j) {
mask |= 1;
}
}
mask = htonl(mask);
addr_bin.a[i] &= mask;
}
/* convert back to string */
if (!inet_ntop(AF_INET6, (void *)&addr_bin.s, result, INET6_ADDRSTRLEN)) {
if (asprintf(err_msg, "Failed to convert IPv6 address (%s).", strerror(errno)) == -1) {
*err_msg = NULL;
}
free(result);
return 1;
}
/* add the prefix */
strcat(result, pref_str);
if (strcmp(result, *value_str)) {
/* some conversion took place, update the value */
lydict_remove(ctx, *value_str);
*value_str = lydict_insert_zc(ctx, result);
value->string = *value_str;
} else {
free(result);
}
return 0;
}
static int
ip_prefix_store_clb(struct ly_ctx *ctx, const char *type_name, const char **value_str, lyd_val *value, char **err_msg)
{
if (strchr(*value_str, ':')) {
return ipv6_prefix_store_clb(ctx, type_name, value_str, value, err_msg);
}
return ipv4_prefix_store_clb(ctx, type_name, value_str, value, err_msg);
}
/* Name of this array must match the file name! */
struct lytype_plugin_list user_inet_types[] = {
{"ietf-inet-types", "2013-07-15", "ip-address", ip_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ipv6-address", ip_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ip-address-no-zone", ip_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ipv6-address-no-zone", ip_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ip-prefix", ip_prefix_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ipv4-prefix", ipv4_prefix_store_clb, NULL},
{"ietf-inet-types", "2013-07-15", "ipv6-prefix", ipv6_prefix_store_clb, NULL},
{NULL, NULL, NULL, NULL, NULL} /* terminating item */
};