diff --git a/src/knot/conf/conf.c b/src/knot/conf/conf.c index 5e9ed8393..7d566498e 100644 --- a/src/knot/conf/conf.c +++ b/src/knot/conf/conf.c @@ -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: diff --git a/src/libknot/yparser/yptrafo.c b/src/libknot/yparser/yptrafo.c index 764a5d1ba..c7f198253 100644 --- a/src/libknot/yparser/yptrafo.c +++ b/src/libknot/yparser/yptrafo.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -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; diff --git a/tests/libknot/test_yptrafo.c b/tests/libknot/test_yptrafo.c index d76dbc499..cf9b07966 100644 --- a/tests/libknot/test_yptrafo.c +++ b/tests/libknot/test_yptrafo.c @@ -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);