yparser: add support for link-local IPv6 addresses

This commit is contained in:
Jan Hák 2024-08-29 12:59:15 +02:00 committed by Daniel Salzman
parent ca82621cd0
commit aaff152cfd
3 changed files with 128 additions and 32 deletions

View file

@ -811,11 +811,20 @@ struct sockaddr_storage conf_addr_range(
if (val->code == KNOT_EOK) {
conf_val(val);
assert(val->data);
uint8_t type = *val->data;
out = yp_addr_noport(val->data);
// addr_type, addr, format, formatted_data (port| addr| empty).
const uint8_t *format = val->data + sizeof(uint8_t) +
((out.ss_family == AF_INET) ?
IPV4_PREFIXLEN / 8 : IPV6_PREFIXLEN / 8);
const uint8_t *format = val->data + sizeof(uint8_t);
if (type == 4) {
format += IPV4_PREFIXLEN / 8;
} else if (type == 6) {
format += IPV6_PREFIXLEN / 8;
} else if (type == 7) {
format += IPV6_PREFIXLEN / 8;
format += strlen((const char *)format) + 1;
} else {
format += strlen((const char *)format) + 1;
}
// See addr_range_to_bin.
switch (*format) {
case 1:

View file

@ -17,6 +17,7 @@
#include <arpa/inet.h>
#include <inttypes.h>
#include <limits.h>
#include <net/if.h>
#include <stdlib.h>
#include <stdio.h>
@ -58,6 +59,39 @@ enum {
MULTI_YEAR = MULTI_DAY * 365,
};
// See also conf_addr_range() if changing.
enum {
ADDR_TYPE_UNIX = 0,
ADDR_TYPE_IPV4 = 4,
ADDR_TYPE_IPV6 = 6,
ADDR_TYPE_IPV6_LINKLOCAL = 7,
};
static bool is_addr_unix(uint8_t type)
{
return type == ADDR_TYPE_UNIX;
}
static bool is_addr_ipv4(uint8_t type)
{
return type == ADDR_TYPE_IPV4;
}
static bool is_addr_ipv6(uint8_t type)
{
return type == ADDR_TYPE_IPV6;
}
static inline bool is_addr_ipv6_linklocal(uint8_t type)
{
return type == ADDR_TYPE_IPV6_LINKLOCAL;
}
static bool is_ip_addr(uint8_t type)
{
return is_addr_ipv4(type) || is_addr_ipv6(type) || is_addr_ipv6_linklocal(type);
}
static wire_ctx_t copy_in(
wire_ctx_t *in,
size_t in_len,
@ -357,7 +391,8 @@ int yp_int_to_txt(
static uint8_t sock_type_guess(
const uint8_t *str,
size_t len)
size_t len,
const uint8_t **if_name)
{
size_t dots = 0;
size_t semicolons = 0;
@ -372,11 +407,16 @@ static uint8_t sock_type_guess(
// Guess socket type.
if (semicolons >= 1) {
return 6;
*if_name = (const uint8_t *)strchr((const char *)str, '%');
if (*if_name == NULL) {
return ADDR_TYPE_IPV6;
} else {
return ADDR_TYPE_IPV6_LINKLOCAL;
}
} else if (semicolons == 0 && dots == 3 && digits >= 3) {
return 4;
return ADDR_TYPE_IPV4;
} else {
return 0;
return ADDR_TYPE_UNIX;
}
}
@ -390,12 +430,21 @@ int yp_addr_noport_to_bin(
struct in_addr addr4;
struct in6_addr addr6;
uint8_t type = sock_type_guess(in->position, YP_LEN);
const uint8_t *if_name = NULL;
uint8_t type = sock_type_guess(in->position, YP_LEN, &if_name);
// Copy address to the buffer to limit inet_pton overread.
char buf[INET6_ADDRSTRLEN];
if (type == 4 || type == 6) {
wire_ctx_t buf_ctx = copy_in(in, YP_LEN, buf, sizeof(buf));
if (is_ip_addr(type)) {
size_t len = YP_LEN;
if (if_name != NULL) {
if (if_name + 1 >= stop) { // Missing inteface name.
return KNOT_EINVAL;
}
len = if_name - in->position;
}
wire_ctx_t buf_ctx = copy_in(in, len, buf, sizeof(buf));
if (buf_ctx.error != KNOT_EOK) {
return buf_ctx.error;
}
@ -405,13 +454,19 @@ int yp_addr_noport_to_bin(
wire_ctx_write_u8(out, type);
// Write address as such.
if (type == 4 && inet_pton(AF_INET, buf, &addr4) == 1) {
if (is_addr_ipv4(type) && inet_pton(AF_INET, buf, &addr4) == 1) {
wire_ctx_write(out, (uint8_t *)&(addr4.s_addr),
sizeof(addr4.s_addr));
} else if (type == 6 && inet_pton(AF_INET6, buf, &addr6) == 1) {
} else if ((is_addr_ipv6(type) || is_addr_ipv6_linklocal(type)) &&
inet_pton(AF_INET6, buf, &addr6) == 1) {
wire_ctx_write(out, (uint8_t *)&(addr6.s6_addr),
sizeof(addr6.s6_addr));
} else if (type == 0 && allow_unix) {
sizeof(addr6.s6_addr));
if (if_name != NULL) {
assert(is_addr_ipv6_linklocal(type));
wire_ctx_skip(in, sizeof(uint8_t));
yp_str_to_bin(in, out, stop);
}
} else if (is_addr_unix(type) && allow_unix) {
int ret = yp_str_to_bin(in, out, stop);
if (ret != KNOT_EOK) {
return ret;
@ -434,14 +489,15 @@ int yp_addr_noport_to_txt(
int ret;
switch (wire_ctx_read_u8(in)) {
case 0:
uint8_t type = wire_ctx_read_u8(in);
switch (type) {
case ADDR_TYPE_UNIX:
ret = yp_str_to_txt(in, out);
if (ret != KNOT_EOK) {
return ret;
}
break;
case 4:
case ADDR_TYPE_IPV4:
wire_ctx_read(in, &(addr4.s_addr), sizeof(addr4.s_addr));
if (knot_inet_ntop(AF_INET, &addr4, (char *)out->position,
wire_ctx_available(out)) == NULL) {
@ -449,13 +505,22 @@ int yp_addr_noport_to_txt(
}
wire_ctx_skip(out, strlen((char *)out->position));
break;
case 6:
case ADDR_TYPE_IPV6:
case ADDR_TYPE_IPV6_LINKLOCAL:
wire_ctx_read(in, &(addr6.s6_addr), sizeof(addr6.s6_addr));
if (knot_inet_ntop(AF_INET6, &addr6, (char *)out->position,
wire_ctx_available(out)) == NULL) {
return KNOT_EINVAL;
}
wire_ctx_skip(out, strlen((char *)out->position));
if (is_addr_ipv6_linklocal(type) && *in->position != '\0') {
wire_ctx_write_u8(out, '%');
ret = yp_str_to_txt(in, out);
if (ret != KNOT_EOK) {
return ret;
}
}
break;
default:
return KNOT_EINVAL;
@ -487,7 +552,7 @@ int yp_addr_to_bin(
}
if (pos != NULL) {
if (*type == 0) {
if (is_addr_unix(*type)) {
// Rewrite string terminator.
wire_ctx_skip(out, -1);
// Append the rest (separator and port) as a string.
@ -502,7 +567,7 @@ int yp_addr_to_bin(
if (ret != KNOT_EOK) {
return ret;
}
} else if (*type == 4 || *type == 6) {
} else if (is_ip_addr(*type)) {
wire_ctx_write_u64(out, (uint64_t)-1);
}
@ -525,7 +590,7 @@ int yp_addr_to_txt(
}
// Write port.
if (*type == 4 || *type == 6) {
if (is_ip_addr(*type)) {
int64_t port = wire_ctx_read_u64(in);
if (port >= 0) {
@ -1074,18 +1139,26 @@ struct sockaddr_storage yp_addr_noport(
uint8_t type = *data;
data += sizeof(type);
size_t addr_len;
// Set address.
switch (type) {
case 0:
case ADDR_TYPE_UNIX:
sockaddr_set(&ss, AF_UNIX, (char *)data, 0);
break;
case 4:
sockaddr_set_raw(&ss, AF_INET, data,
sizeof(((struct in_addr *)NULL)->s_addr));
case ADDR_TYPE_IPV4:
addr_len = sizeof(((struct in_addr *)NULL)->s_addr);
sockaddr_set_raw(&ss, AF_INET, data, addr_len);
break;
case 6:
sockaddr_set_raw(&ss, AF_INET6, data,
sizeof(((struct in6_addr *)NULL)->s6_addr));
case ADDR_TYPE_IPV6:
case ADDR_TYPE_IPV6_LINKLOCAL:
addr_len = sizeof(((struct in6_addr *)NULL)->s6_addr);
sockaddr_set_raw(&ss, AF_INET6, data, addr_len);
if (is_addr_ipv6_linklocal(type)) {
struct sockaddr_in6 *sa = (struct sockaddr_in6 *)&ss;
sa->sin6_scope_id = if_nametoindex((const char *)data + addr_len);
// Ignore if such an interface doesn't exist.
}
break;
}
@ -1097,16 +1170,18 @@ struct sockaddr_storage yp_addr(
const uint8_t *data,
bool *no_port)
{
uint8_t type = *data;
struct sockaddr_storage ss = yp_addr_noport(data);
size_t addr_len;
// Get binary address length.
switch (ss.ss_family) {
case AF_INET:
switch (type) {
case ADDR_TYPE_IPV4:
addr_len = sizeof(((struct in_addr *)NULL)->s_addr);
break;
case AF_INET6:
case ADDR_TYPE_IPV6:
case ADDR_TYPE_IPV6_LINKLOCAL:
addr_len = sizeof(((struct in6_addr *)NULL)->s6_addr);
break;
default:
@ -1115,7 +1190,11 @@ struct sockaddr_storage yp_addr(
}
if (addr_len > 0) {
int64_t port = knot_wire_read_u64(data + sizeof(uint8_t) + addr_len);
const uint8_t *port_pos = data + sizeof(uint8_t) + addr_len;
if (is_addr_ipv6_linklocal(type)) {
port_pos += strlen((char *)port_pos) + 1;
}
int64_t port = knot_wire_read_u64(port_pos);
if (port >= 0) {
sockaddr_port_set(&ss, port);
*no_port = false;

View file

@ -366,10 +366,16 @@ int main(int argc, char *argv[])
addr_test("::1@12345", false);
addr_test("/tmp/test.sock", true);
addr_test("eth1@53", true);
addr_test("::1%lo", true);
addr_test("::1%2", true);
addr_test("::1%lo@12345", false);
addr_test("::1%4@12345", false);
addr_bad_test("192.168.123.x", KNOT_EINVAL);
addr_bad_test("192.168.123.1@", KNOT_EINVAL);
addr_bad_test("192.168.123.1@1x", KNOT_EINVAL);
addr_bad_test("192.168.123.1@65536", KNOT_ERANGE);
addr_bad_test("::1%", KNOT_EINVAL);
addr_bad_test("::1%@53", KNOT_EINVAL);
/* Address range tests. */
addr_range_test("/tmp/unix.sock");
@ -380,7 +386,9 @@ int main(int argc, char *argv[])
addr_range_test("::1");
addr_range_test("::1/0");
addr_range_test("::1/32");
addr_range_test("::1%lo/32");
addr_range_test("1::-5::");
addr_range_test("1::%lo-5::%lo");
addr_range_bad_test("unix", KNOT_EINVAL);
addr_range_bad_test("1.1.1", KNOT_EINVAL);
addr_range_bad_test("1.1.1.1/", KNOT_EINVAL);