From 58ceae2104c76d9a43561a8311a9e98dfe5e0876 Mon Sep 17 00:00:00 2001 From: kaies Chaouch Date: Mon, 28 Oct 2024 17:32:35 +0100 Subject: [PATCH 1/5] interface-ip: add dns server expiry time During the testing process for the IOL INTACT cetification for IPv6, we discovered some issues: 'Test v6LC.2.2.25 (C): Recursive DNS (RDNSS) Option Expired' failed because netifd doesn't support dns server expiry time (lifetime) Test Description: - TR1 to transmit Router Advertisement A with a lifetime 60 in the RDNSS Option. - Configure the HUT to transmit an Echo Request with a destination of 'node1.test.example.com'. - Observe the HUT transmitting a DNS Query to DNS-Server. - Wait 65 seconds. - Configure the HUT to transmit an Echo Request with a destination of node1.test.example.com. - Observe the HUT doesn't transmit a DNS Query to DNS-Server Solution: - netifd will receive a list of DNS Server json objects instead of a list of DNS Server string. each json object contain the dns server string in "dns" and dns server lifetime in "lifetime". - the idea is to save expiration time for each server in new element "valid_until" in struct 'dns_server'. the function interface_ip_valid_until_handler is called each 1 second to remove expired element the dns_server list. If at least one dns server is expired , we have to update dnsmasq config file using function : interface_write_resolv_conf. Signed-off-by: kaies Chaouch --- interface-ip.c | 66 +++++++++++++++++++++++++++-------------- interface-ip.h | 3 +- interface.c | 17 +++++++++++ proto-shell.c | 21 ++++++++++++- scripts/netifd-proto.sh | 17 +++++++++-- utils.h | 3 ++ 6 files changed, 100 insertions(+), 27 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index 7e60f64..9d028c0 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -90,6 +90,16 @@ const struct uci_blob_param_list neighbor_attr_list = { .params = neighbor_attr, }; +enum { + DNS_HOST, + DNS_LIFETIME, + __DNS_MAX +}; + +static const struct blobmsg_policy dns_attr[__DNS_MAX]= { + [DNS_HOST]= { .name = "dns", .type = BLOBMSG_TYPE_STRING}, + [DNS_LIFETIME]= { .name = "lifetime", .type = BLOBMSG_TYPE_INT32}, +}; struct list_head prefixes = LIST_HEAD_INIT(prefixes); static struct device_prefix *ula_prefix = NULL; @@ -1411,47 +1421,47 @@ interface_ip_set_ula_prefix(const char *prefix) } } -static void -interface_add_dns_server(struct interface_ip_settings *ip, const char *str) +void +interface_add_dns_server(struct interface_ip_settings *ip, struct blob_attr *attr) { + struct blob_attr *tb[__DNS_MAX], *cur; struct dns_server *s; + blobmsg_parse(dns_attr, __DNS_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + cur = tb[DNS_HOST]; + if (cur == NULL) + return; + s = calloc(1, sizeof(*s)); if (!s) return; s->af = AF_INET; - if (inet_pton(s->af, str, &s->addr.in)) + if (inet_pton(s->af, (char *)blobmsg_data(cur), &s->addr.in)) goto add; s->af = AF_INET6; - if (inet_pton(s->af, str, &s->addr.in)) + if (inet_pton(s->af, (char *)blobmsg_data(cur), &s->addr.in)) goto add; free(s); return; add: - D(INTERFACE, "Add IPv%c DNS server: %s", - s->af == AF_INET6 ? '6' : '4', str); - vlist_simple_add(&ip->dns_servers, &s->node); -} - -void -interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list) -{ - struct blob_attr *cur; - size_t rem; - - blobmsg_for_each_attr(cur, list, rem) { - if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) - continue; - - if (!blobmsg_check_attr(cur, false)) - continue; - - interface_add_dns_server(ip, blobmsg_data(cur)); + s->valid_until = 0; + cur = tb[DNS_LIFETIME]; + if (cur != NULL) { + int64_t lifetime = blobmsg_get_u32(cur); + if (lifetime > 0) { + int64_t valid_until = lifetime + (int64_t)system_get_rtime(); + if (valid_until > 0) /* Catch overflow */ + s->valid_until = valid_until; + } } + D(INTERFACE, "Add IPv%c DNS server: %s Expiry Time: %lld\n", + s->af == AF_INET6 ? '6' : '4', (char *)blobmsg_data(tb[DNS_HOST]), s->valid_until); + vlist_simple_add(&ip->dns_servers, &s->node); } static void @@ -1837,6 +1847,8 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) struct device_addr *addr, *addrp; struct device_route *route, *routep; struct device_prefix *pref, *prefp; + struct dns_server *srv, *tmpsrv; + bool dns_expired = false; vlist_for_each_element_safe(&iface->proto_ip.addr, addr, node, addrp) if (addr->valid_until && addr->valid_until < now) @@ -1850,6 +1862,14 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) if (pref->valid_until && pref->valid_until < now) vlist_delete(&iface->proto_ip.prefix, &pref->node); + vlist_simple_for_each_element_safe(&iface->proto_ip.dns_servers, srv, node, tmpsrv) + if (srv->valid_until && srv->valid_until < now) { + vlist_simple_delete(&iface->proto_ip.dns_servers, &srv->node); + dns_expired = true; + } + + if (dns_expired) + interface_write_resolv_conf(iface->jail); } uloop_timeout_set(t, 1000); diff --git a/interface-ip.h b/interface-ip.h index cc7efbd..917c961 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -162,6 +162,7 @@ struct device_source_table { struct dns_server { struct vlist_simple_node node; int af; + time_t valid_until; union if_addr addr; }; @@ -175,12 +176,12 @@ extern const struct uci_blob_param_list neighbor_attr_list; extern struct list_head prefixes; void interface_ip_init(struct interface *iface); -void interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list); void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list); void interface_write_resolv_conf(const char *jail); void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6); void interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6); +void interface_add_dns_server(struct interface_ip_settings *ip, struct blob_attr *attr); void interface_ip_update_start(struct interface_ip_settings *ip); void interface_ip_update_complete(struct interface_ip_settings *ip); void interface_ip_flush(struct interface_ip_settings *ip); diff --git a/interface.c b/interface.c index 60b1807..7951dc8 100644 --- a/interface.c +++ b/interface.c @@ -520,6 +520,23 @@ interface_remove_user(struct interface_user *dep) dep->iface = NULL; } +static void +interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr *list) +{ + struct blob_attr *cur; + size_t rem; + + blobmsg_for_each_attr(cur, list, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) + continue; + + if (!blobmsg_check_attr(cur, false)) + continue; + + interface_add_dns_server(ip, cur); + } +} + static void interface_add_assignment_classes(struct interface *iface, struct blob_attr *list) { diff --git a/proto-shell.c b/proto-shell.c index 931a59e..81045fb 100644 --- a/proto-shell.c +++ b/proto-shell.c @@ -430,6 +430,25 @@ proto_shell_parse_neighbor_list(struct interface *iface, struct blob_attr *attr, } } +static void +proto_shell_parse_dns_list(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *cur; + size_t rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) { + DPRINTF("Ignore wrong dns type: %d\n", blobmsg_type(cur)); + continue; + } + + if (!blobmsg_check_attr(cur, false)) + continue; + + interface_add_dns_server(&iface->proto_ip, cur); + } +} + static void proto_shell_parse_data(struct interface *iface, struct blob_attr *attr) { @@ -574,7 +593,7 @@ proto_shell_update_link(struct proto_shell_state *state, struct blob_attr *data, proto_shell_parse_neighbor_list(state->proto.iface, cur, true); if ((cur = tb[NOTIFY_DNS])) - interface_add_dns_server_list(&iface->proto_ip, cur); + proto_shell_parse_dns_list(state->proto.iface, cur); if ((cur = tb[NOTIFY_DNS_SEARCH])) interface_add_dns_search_list(&iface->proto_ip, cur); diff --git a/scripts/netifd-proto.sh b/scripts/netifd-proto.sh index c25aa9f..63450ae 100644 --- a/scripts/netifd-proto.sh +++ b/scripts/netifd-proto.sh @@ -106,8 +106,9 @@ proto_close_data() { proto_add_dns_server() { local address="$1" + local lifetime="$2" - append PROTO_DNS "$address" + append PROTO_DNS "$address/$lifetime" } proto_add_dns_search() { @@ -302,6 +303,18 @@ _proto_push_route() { json_close_object } +_proto_push_dns() { + local str="$1"; + local dns="${str%%/*}" + str="${str#*/}" + local lifetime="${str%%/*}" + + json_add_object "" + json_add_string dns "$dns" + [ -n "$lifetime" ] && json_add_int lifetime "$lifetime" + json_close_object +} + _proto_push_array() { local name="$1" local val="$2" @@ -332,7 +345,7 @@ proto_send_update() { _proto_push_array "routes" "$PROTO_ROUTE" _proto_push_route _proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route _proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string - _proto_push_array "dns" "$PROTO_DNS" _proto_push_string + _proto_push_array "dns" "$PROTO_DNS" _proto_push_dns _proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string _proto_push_array "neighbor" "$PROTO_NEIGHBOR" _proto_push_ipv4_neighbor _proto_push_array "neighbor6" "$PROTO_NEIGHBOR6" _proto_push_ipv6_neighbor diff --git a/utils.h b/utils.h index f40e14f..3d347e3 100644 --- a/utils.h +++ b/utils.h @@ -71,6 +71,9 @@ static inline void vlist_simple_add(struct vlist_simple_tree *tree, struct vlist #define vlist_simple_for_each_element(tree, element, node_member) \ list_for_each_entry(element, &(tree)->list, node_member.list) +#define vlist_simple_for_each_element_safe(tree, element, node_member, tmp) \ + list_for_each_entry_safe(element, tmp, &(tree)->list, node_member.list) + #define vlist_simple_empty(tree) \ list_empty(&(tree)->list) From 7d7cde974f8adee3381eee65898c08c2d3ae0582 Mon Sep 17 00:00:00 2001 From: kaies Chaouch Date: Wed, 30 Oct 2024 11:42:05 +0100 Subject: [PATCH 2/5] interface-ip: search domain should be saved the correct dns file : /etc/resolv.conf During the testing process for the IOL INTACT cetification for IPv6, we discovered some issues: 'Test v6LC.2.2.25 (D) : Search List Option' Failed because domain list are saved under /tmp/resolv.conf/resolv.conf.auto , they are not taken in consideration by dnsmasq Test Description: - TR1 to transmit Router Advertisement B with a lifetime of 60 in the DNSSL Option. - Configure the HUT to transmit an Echo Request with a destination of 'node1'. - Observe the HUT transmitting a DNS Query to DNS-Server with the Search List. Solution: -netifd is adding search domain '/tmp/resolv.conf.d/resolv.conf.auto'. dnsmasq didn't use this config file to read search domains. Also dnsmasq work as a dnsqueries forwarder and never change in dns queries. the resolver library use /etc/resolv.conf to prepare queries to add add search domain to the request Domain name. ( example : if the request is 'node1' it change it to become 'node1.example.com'). -The idea is implement a function that add/remove dns_search list in/from /etc/resolv.conf and call it when an update is required (Interface down , up , config updated). Signed-off-by: kaies Chaouch --- interface-ip.c | 207 +++++++++++++++++++++++++++++++++++++++++++++++-- interface-ip.h | 1 + interface.c | 6 ++ 3 files changed, 206 insertions(+), 8 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index 9d028c0..c02ae49 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -30,6 +30,8 @@ #include "ubus.h" #include "system.h" +#define MAX_SEARCH_DOMAINS 50 /* Search domain maximum number that can be stored under /etc/resolv.conf */ + enum { ROUTE_INTERFACE, ROUTE_TARGET, @@ -1500,7 +1502,6 @@ static void write_resolv_conf_entries(FILE *f, struct interface_ip_settings *ip, const char *dev) { struct dns_server *s; - struct dns_search_domain *d; const char *str; char buf[INET6_ADDRSTRLEN]; @@ -1514,10 +1515,6 @@ write_resolv_conf_entries(FILE *f, struct interface_ip_settings *ip, const char else fprintf(f, "nameserver %s\n", str); } - - vlist_simple_for_each_element(&ip->dns_search, d, node) { - fprintf(f, "search %s\n", d->name); - } } /* Sorting of interface resolver entries : */ @@ -1555,9 +1552,7 @@ __interface_write_dns_entries(FILE *f, const char *jail) if (jail && (!iface->jail || strcmp(jail, iface->jail))) continue; - if (vlist_simple_empty(&iface->proto_ip.dns_search) && - vlist_simple_empty(&iface->proto_ip.dns_servers) && - vlist_simple_empty(&iface->config_ip.dns_search) && + if (vlist_simple_empty(&iface->proto_ip.dns_servers) && vlist_simple_empty(&iface->config_ip.dns_servers)) continue; @@ -1636,6 +1631,200 @@ interface_write_resolv_conf(const char *jail) } } +static void +free_search_domains(char **search_domains, int count) +{ + for (int i = 0; i < count; i++) + free(search_domains[i]); +} + +static int +gather_interface_search_domains(struct interface_ip_settings *ip, char **search_domains, int *count) +{ + struct dns_search_domain *d; + + vlist_simple_for_each_element(&ip->dns_search, d, node) { + if (*count < MAX_SEARCH_DOMAINS) { + search_domains[*count] = strdup(d->name); + if (!search_domains[*count]) + return -1; + ++(*count); + } + } + + return 0; +} + +static void +remove_search_domain_entries(FILE *origin_f, FILE *tmp_f, char **search_domains, int count) +{ + char line[256]; + + rewind(origin_f); + while (fgets(line, sizeof(line), origin_f)) { + if (!strncmp(line, "search ", 7)) { + char *token = strtok(line + 7, " \n"); + char new_line[256] = {'\0'}; + + while (token != NULL) { + bool exist = false; + + for (int i = 0; i < count; i++) { + if (!strcmp(search_domains[i], token)) { + exist = true; + break; + } + } + + if (!exist) { + // new_line cannot become longer than line , strcat can't lead to overflows + strcat(new_line, " "); + strcat(new_line, token); + } + + token = strtok(NULL, " \n"); + } + + if (new_line[0] != '\0') + fprintf(tmp_f, "search%s\n", new_line); + + } else { + fprintf(tmp_f, "%s", line); + } + } +} + +static int +write_search_domain_entries(FILE *origin_f, FILE *tmp_f, char **search_domains, int *count) +{ + char line[256]; + + rewind(origin_f); + while (fgets(line, sizeof(line), origin_f)) { + if (!strncmp(line, "search ", 7)) { + char *token = strtok(line + 7, " \n"); + + while (token != NULL) { + bool exist = false; + + for (int i = 0; i < *count; i++) { + if (!strcmp(search_domains[i], token)) { + exist = true; + break; + } + } + + if (!exist && (*count < MAX_SEARCH_DOMAINS)) { + search_domains[*count] = strdup(token); + if (!search_domains[*count]) + return -1; + ++(*count); + } + + token = strtok(NULL, " \n"); + } + } + } + + if (*count > 0) { + fprintf(tmp_f, "search"); + for (int i = 0; i < *count; i++) { + fprintf(tmp_f, " %s", search_domains[i]); + } + fprintf(tmp_f, "\n"); + + rewind(origin_f); + while (fgets(line, sizeof(line), origin_f)) { + if (strncmp(line, "search ", 7) != 0) + fprintf(tmp_f, "%s", line); + } + } + return 0; +} + +static void +update_search_domain_entries(char **search_domains, int *count, bool add) +{ + const char *resolv_conf_path = "/tmp/resolv.conf"; + char *tmppath = alloca(strlen(resolv_conf_path) + 5); + FILE *f1, *f2; + uint32_t crcold, crcnew; + + f1 = fopen(resolv_conf_path, "r"); + if (!f1) { + D(INTERFACE, "Failed to open %s for reading\n", resolv_conf_path); + return; + } + + sprintf(tmppath, "%s.tmp", resolv_conf_path); + f2 = fopen(tmppath, "w+"); + if (!f2) { + D(INTERFACE, "Failed to open %s for writing\n", tmppath); + fclose(f1); + return; + } + + if (add) { + if (write_search_domain_entries(f1, f2, search_domains, count) < 0) { + D(INTERFACE, "Failed to allocate memory for dns search domain list\n"); + fclose(f1); + fclose(f2); + return; + } + } else { + remove_search_domain_entries(f1, f2, search_domains, *count); + } + + rewind(f1); + crcold = crc32_file(f1); + fclose(f1); + + fflush(f2); + rewind(f2); + crcnew = crc32_file(f2); + fclose(f2); + + if (crcold == crcnew) { + unlink(tmppath); + } else if (rename(tmppath, resolv_conf_path) < 0) { + D(INTERFACE, "Failed to replace %s\n", resolv_conf_path); + unlink(tmppath); + } +} + +void +interface_update_search_domain_conf(struct interface *iface, bool add) +{ + char *search_domains[MAX_SEARCH_DOMAINS]; + int count = 0; + + if (iface->state != IFS_UP) + return; + + if (vlist_simple_empty(&iface->proto_ip.dns_search) && + (iface->proto_ip.no_dns || vlist_simple_empty(&iface->config_ip.dns_search))) + return; + + if (gather_interface_search_domains(&iface->config_ip, search_domains, &count) < 0) { + D(INTERFACE, "Failed to allocate memory for dns search domain list\n"); + free_search_domains(search_domains, count); + return; + } + + if (!iface->proto_ip.no_dns) { + if (gather_interface_search_domains(&iface->proto_ip, search_domains, &count) < 0) { + D(INTERFACE, "Failed to allocate memory for dns search domain list\n"); + free_search_domains(search_domains, count); + return; + } + } + + if (count > 0) { + update_search_domain_entries(search_domains, &count, add); + free_search_domains(search_domains, count); + } +} + static void interface_ip_set_route_enabled(struct interface_ip_settings *ip, struct device_route *route, bool enabled) @@ -1792,6 +1981,7 @@ interface_ip_update_start(struct interface_ip_settings *ip) void interface_ip_update_complete(struct interface_ip_settings *ip) { + interface_update_search_domain_conf(ip->iface, false); vlist_simple_flush(&ip->dns_servers); vlist_simple_flush(&ip->dns_search); vlist_flush(&ip->route); @@ -1799,6 +1989,7 @@ interface_ip_update_complete(struct interface_ip_settings *ip) vlist_flush(&ip->prefix); vlist_flush(&ip->neighbor); interface_write_resolv_conf(ip->iface->jail); + interface_update_search_domain_conf(ip->iface, true); } void diff --git a/interface-ip.h b/interface-ip.h index 917c961..534cb1f 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -178,6 +178,7 @@ extern struct list_head prefixes; void interface_ip_init(struct interface *iface); void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list); void interface_write_resolv_conf(const char *jail); +void interface_update_search_domain_conf(struct interface *iface, bool add); void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6); void interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6); diff --git a/interface.c b/interface.c index 7951dc8..bdac1e0 100644 --- a/interface.c +++ b/interface.c @@ -785,6 +785,7 @@ interface_proto_event_cb(struct interface_proto_state *state, enum interface_pro iface->state = IFS_UP; iface->start_time = system_get_rtime(); interface_event(iface, IFEV_UP); + interface_update_search_domain_conf(iface, true); netifd_log_message(L_NOTICE, "Interface '%s' is now up\n", iface->name); break; case IFPEV_DOWN: @@ -792,6 +793,7 @@ interface_proto_event_cb(struct interface_proto_state *state, enum interface_pro return; netifd_log_message(L_NOTICE, "Interface '%s' is now down\n", iface->name); + interface_update_search_domain_conf(iface, false); mark_interface_down(iface); interface_write_resolv_conf(iface->jail); if (iface->main_dev.dev && !(iface->config_state == IFC_NORMAL && iface->autostart && iface->available)) @@ -805,6 +807,7 @@ interface_proto_event_cb(struct interface_proto_state *state, enum interface_pro return; netifd_log_message(L_NOTICE, "Interface '%s' has lost the connection\n", iface->name); + interface_update_search_domain_conf(iface, false); mark_interface_down(iface); iface->state = IFS_SETUP; break; @@ -1378,6 +1381,7 @@ interface_change_config(struct interface *if_old, struct interface *if_new) update_prefix_delegation = true; } + interface_update_search_domain_conf(if_old, false); if_old->proto_ip.no_dns = if_new->proto_ip.no_dns; interface_replace_dns(&if_old->config_ip, &if_new->config_ip); @@ -1411,6 +1415,8 @@ interface_change_config(struct interface *if_old, struct interface *if_new) interface_update_prefix_delegation(&if_old->proto_ip); interface_write_resolv_conf(if_old->jail); + interface_update_search_domain_conf(if_old, true); + if (if_old->main_dev.dev) interface_check_state(if_old); From a302888cf8a043ee4b29333ffb7708ab4812b75b Mon Sep 17 00:00:00 2001 From: kaies Chaouch Date: Sun, 3 Nov 2024 12:05:51 +0100 Subject: [PATCH 3/5] interface-ip: add search domain expiry time During the testing process for the IOL INTACT cetification for IPv6, we discovered some issues: 'Test v6LC.2.2.25 (F): Search List (DNSSL) Option Expired' failed because netifd doesn't support dns server expiry time (lifetime) Test Description: - TR1 to transmit Router Advertisement A. The RDNSS Option has a lifetime that lasts the entire test. The DNSSL Option has a lifetime of 60. - Configure the HUT to transmit an Echo Request with a destination of 'node1'. - Observe the HUT transmitting a DNS Query to DNS-Server. - Wait 65 seconds. - Configure the HUT to transmit an Echo Request with a destination of 'node1'. - Observe the HUT doesn't transmit a DNS Query to DNS-Server. Solution: - netifd will receive a list of DNS Search domain json objects instead of a list of DNS Search domain string. - The idea is to save expiration time for each server in new element "valid_until" in struct 'dns_search_domain'. - The function interface_ip_valid_until_handler is called each 1 second to remove expired element the dns_search_domain list. We collect expired dns search domain and remove them using update_search_domain_entries. Signed-off-by: kaies Chaouch --- interface-ip.c | 78 ++++++++++++++++++++++++++++++----------- interface-ip.h | 3 +- interface.c | 17 +++++++++ proto-shell.c | 21 ++++++++++- scripts/netifd-proto.sh | 17 +++++++-- 5 files changed, 111 insertions(+), 25 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index c02ae49..9e45361 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -103,6 +103,17 @@ static const struct blobmsg_policy dns_attr[__DNS_MAX]= { [DNS_LIFETIME]= { .name = "lifetime", .type = BLOBMSG_TYPE_INT32}, }; +enum { + DNS_SEARCH_DOMAIN, + DNS_SEARCH_LIFETIME, + __DNS_SEARCH_MAX +}; + +static const struct blobmsg_policy dns_search_attr[__DNS_SEARCH_MAX]= { + [DNS_SEARCH_DOMAIN]= { .name = "domain", .type = BLOBMSG_TYPE_STRING}, + [DNS_SEARCH_LIFETIME]= { .name = "lifetime", .type = BLOBMSG_TYPE_INT32}, +}; + struct list_head prefixes = LIST_HEAD_INIT(prefixes); static struct device_prefix *ula_prefix = NULL; static struct uloop_timeout valid_until_timeout; @@ -1466,36 +1477,39 @@ interface_add_dns_server(struct interface_ip_settings *ip, struct blob_attr *att vlist_simple_add(&ip->dns_servers, &s->node); } -static void -interface_add_dns_search_domain(struct interface_ip_settings *ip, const char *str) +void +interface_add_dns_search_domain(struct interface_ip_settings *ip, struct blob_attr *attr) { + struct blob_attr *tb[__DNS_SEARCH_MAX], *cur; struct dns_search_domain *s; - int len = strlen(str); + int len; + + blobmsg_parse(dns_search_attr, __DNS_SEARCH_MAX, tb, blobmsg_data(attr), blobmsg_data_len(attr)); + + cur = tb[DNS_SEARCH_DOMAIN]; + if (cur == NULL) + return; + len = strlen((char *)blobmsg_data(cur)); s = calloc(1, sizeof(*s) + len + 1); if (!s) return; - D(INTERFACE, "Add DNS search domain: %s", str); - memcpy(s->name, str, len); - vlist_simple_add(&ip->dns_search, &s->node); -} - -void -interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list) -{ - struct blob_attr *cur; - size_t rem; - - blobmsg_for_each_attr(cur, list, rem) { - if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) - continue; - - if (!blobmsg_check_attr(cur, false)) - continue; + memcpy(s->name, (char *)blobmsg_data(cur), len); + s->valid_until = 0; - interface_add_dns_search_domain(ip, blobmsg_data(cur)); + cur = tb[DNS_SEARCH_LIFETIME]; + if (cur != NULL) { + int64_t lifetime = blobmsg_get_u32(cur); + if (lifetime > 0) { + int64_t valid_until = lifetime + (int64_t)system_get_rtime(); + if (valid_until > 0) /* Catch overflow */ + s->valid_until = valid_until; + } } + + D(INTERFACE, "Add DNS search domain: %s Expiry Time: %lld\n", (char *)blobmsg_data(tb[DNS_SEARCH_DOMAIN]), s->valid_until); + vlist_simple_add(&ip->dns_search, &s->node); } static void @@ -2029,6 +2043,8 @@ interface_ip_init(struct interface *iface) static void interface_ip_valid_until_handler(struct uloop_timeout *t) { + char *search_domains[MAX_SEARCH_DOMAINS]; + int count = 0; time_t now = system_get_rtime(); struct interface *iface; vlist_for_each_element(&interfaces, iface, node) { @@ -2039,6 +2055,7 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) struct device_route *route, *routep; struct device_prefix *pref, *prefp; struct dns_server *srv, *tmpsrv; + struct dns_search_domain *domain, *tmpdomain; bool dns_expired = false; vlist_for_each_element_safe(&iface->proto_ip.addr, addr, node, addrp) @@ -2059,8 +2076,27 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) dns_expired = true; } + vlist_simple_for_each_element_safe(&iface->proto_ip.dns_search, domain, node, tmpdomain) + if (domain->valid_until && domain->valid_until < now) { + if (count < MAX_SEARCH_DOMAINS) { + search_domains[count] = strdup(domain->name); + if (!search_domains[count]) { + D(INTERFACE, "Failed to allocate memory to remove dns search domain\n"); + break; + } + ++count; + } + vlist_simple_delete(&iface->proto_ip.dns_search, &domain->node); + } + if (dns_expired) interface_write_resolv_conf(iface->jail); + + } + + if (count > 0) { + update_search_domain_entries(search_domains, &count, false); + free_search_domains(search_domains, count); } uloop_timeout_set(t, 1000); diff --git a/interface-ip.h b/interface-ip.h index 534cb1f..256ee81 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -168,6 +168,7 @@ struct dns_server { struct dns_search_domain { struct vlist_simple_node node; + time_t valid_until; char name[]; }; @@ -176,13 +177,13 @@ extern const struct uci_blob_param_list neighbor_attr_list; extern struct list_head prefixes; void interface_ip_init(struct interface *iface); -void interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list); void interface_write_resolv_conf(const char *jail); void interface_update_search_domain_conf(struct interface *iface, bool add); void interface_ip_add_route(struct interface *iface, struct blob_attr *attr, bool v6); void interface_ip_add_neighbor(struct interface *iface, struct blob_attr *attr, bool v6); void interface_add_dns_server(struct interface_ip_settings *ip, struct blob_attr *attr); +void interface_add_dns_search_domain(struct interface_ip_settings *ip, struct blob_attr *attr); void interface_ip_update_start(struct interface_ip_settings *ip); void interface_ip_update_complete(struct interface_ip_settings *ip); void interface_ip_flush(struct interface_ip_settings *ip); diff --git a/interface.c b/interface.c index bdac1e0..71ab37d 100644 --- a/interface.c +++ b/interface.c @@ -537,6 +537,23 @@ interface_add_dns_server_list(struct interface_ip_settings *ip, struct blob_attr } } +static void +interface_add_dns_search_list(struct interface_ip_settings *ip, struct blob_attr *list) +{ + struct blob_attr *cur; + size_t rem; + + blobmsg_for_each_attr(cur, list, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) + continue; + + if (!blobmsg_check_attr(cur, false)) + continue; + + interface_add_dns_search_domain(ip, cur); + } +} + static void interface_add_assignment_classes(struct interface *iface, struct blob_attr *list) { diff --git a/proto-shell.c b/proto-shell.c index 81045fb..83d978b 100644 --- a/proto-shell.c +++ b/proto-shell.c @@ -449,6 +449,25 @@ proto_shell_parse_dns_list(struct interface *iface, struct blob_attr *attr) } } +static void +proto_shell_parse_dns_search_list(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *cur; + size_t rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE) { + DPRINTF("Ignore wrong dns search type: %d\n", blobmsg_type(cur)); + continue; + } + + if (!blobmsg_check_attr(cur, false)) + continue; + + interface_add_dns_search_domain(&iface->proto_ip, cur); + } +} + static void proto_shell_parse_data(struct interface *iface, struct blob_attr *attr) { @@ -596,7 +615,7 @@ proto_shell_update_link(struct proto_shell_state *state, struct blob_attr *data, proto_shell_parse_dns_list(state->proto.iface, cur); if ((cur = tb[NOTIFY_DNS_SEARCH])) - interface_add_dns_search_list(&iface->proto_ip, cur); + proto_shell_parse_dns_search_list(state->proto.iface, cur); if ((cur = tb[NOTIFY_DATA])) proto_shell_parse_data(state->proto.iface, cur); diff --git a/scripts/netifd-proto.sh b/scripts/netifd-proto.sh index 63450ae..0e37d3d 100644 --- a/scripts/netifd-proto.sh +++ b/scripts/netifd-proto.sh @@ -113,8 +113,9 @@ proto_add_dns_server() { proto_add_dns_search() { local address="$1" + local lifetime="$2" - append PROTO_DNS_SEARCH "$address" + append PROTO_DNS_SEARCH "$address/$lifetime" } proto_add_ipv4_address() { @@ -315,6 +316,18 @@ _proto_push_dns() { json_close_object } +_proto_push_dns_search() { + local str="$1"; + local domain="${str%%/*}" + str="${str#*/}" + local lifetime="${str%%/*}" + + json_add_object "" + json_add_string domain "$domain" + [ -n "$lifetime" ] && json_add_int lifetime "$lifetime" + json_close_object +} + _proto_push_array() { local name="$1" local val="$2" @@ -346,7 +359,7 @@ proto_send_update() { _proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route _proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string _proto_push_array "dns" "$PROTO_DNS" _proto_push_dns - _proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string + _proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_dns_search _proto_push_array "neighbor" "$PROTO_NEIGHBOR" _proto_push_ipv4_neighbor _proto_push_array "neighbor6" "$PROTO_NEIGHBOR6" _proto_push_ipv6_neighbor _proto_notify "$interface" From 611420c00d51bea7f8da57e1c81a5565c67b26df Mon Sep 17 00:00:00 2001 From: kaies Chaouch Date: Tue, 5 Nov 2024 18:48:44 +0100 Subject: [PATCH 4/5] interface-ip: fix routes expiry time During the testing process for the IOL INTACT cetification for IPv6, we discovered some issues: 'Test v6LC.2.2.14 (A): Router Lifetime Updated with Same Lifetime' Failed because Routes are not removed at the correct expiry time. lifetime is set correctly but the condition to remove the route is wrong Description: - TR1 transmits the Router Advertisement (Router Lifetime: 20 seconds). - TN2 transmits a global Echo Request to the HUT every second for 19 seconds. - The HUT should respond to the Echo Requests from TN2 using TR1 as a first hop. - TR1 transmits the Router Advertisement (Router Lifetime: 20 seconds). - TN2 transmits a global Echo Request to the HUT every second for 21 seconds. - The HUT should respond to the Echo Requests from TN2 using TR1 as a first hop until the Router Lifetime expires. In response to the final Echo Request, the HUT Solution: - The function interface_ip_valid_until_handler is called each 1 second to remove expired element the route list. the condition to remove the route should be valid_until inferior or equal from time now. Delay of one Second cause the test to fail. - The precision of time is not efficient because we use only seconds from 'system_get_rtime' - change the timer of interface_ip_valid_until_handler to 500 ms instead of 1s. Signed-off-by: kaies Chaouch --- interface-ip.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/interface-ip.c b/interface-ip.c index 9e45361..4ae5787 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -2059,25 +2059,25 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) bool dns_expired = false; vlist_for_each_element_safe(&iface->proto_ip.addr, addr, node, addrp) - if (addr->valid_until && addr->valid_until < now) + if (addr->valid_until && addr->valid_until <= now) vlist_delete(&iface->proto_ip.addr, &addr->node); vlist_for_each_element_safe(&iface->proto_ip.route, route, node, routep) - if (route->valid_until && route->valid_until < now) + if (route->valid_until && route->valid_until <= now) vlist_delete(&iface->proto_ip.route, &route->node); vlist_for_each_element_safe(&iface->proto_ip.prefix, pref, node, prefp) - if (pref->valid_until && pref->valid_until < now) + if (pref->valid_until && pref->valid_until <= now) vlist_delete(&iface->proto_ip.prefix, &pref->node); vlist_simple_for_each_element_safe(&iface->proto_ip.dns_servers, srv, node, tmpsrv) - if (srv->valid_until && srv->valid_until < now) { + if (srv->valid_until && srv->valid_until <= now) { vlist_simple_delete(&iface->proto_ip.dns_servers, &srv->node); dns_expired = true; } vlist_simple_for_each_element_safe(&iface->proto_ip.dns_search, domain, node, tmpdomain) - if (domain->valid_until && domain->valid_until < now) { + if (domain->valid_until && domain->valid_until <= now) { if (count < MAX_SEARCH_DOMAINS) { search_domains[count] = strdup(domain->name); if (!search_domains[count]) { @@ -2099,12 +2099,12 @@ interface_ip_valid_until_handler(struct uloop_timeout *t) free_search_domains(search_domains, count); } - uloop_timeout_set(t, 1000); + uloop_timeout_set(t, 500); } static void __init interface_ip_init_worker(void) { valid_until_timeout.cb = interface_ip_valid_until_handler; - uloop_timeout_set(&valid_until_timeout, 1000); + uloop_timeout_set(&valid_until_timeout, 500); } From 9db07d163148b506edb52f9014838b6cace3e157 Mon Sep 17 00:00:00 2001 From: kaies Chaouch Date: Fri, 6 Dec 2024 17:22:08 +0100 Subject: [PATCH 5/5] interface-ip: remove ipv6 unreachable route when address is removed During the testing process for the IOL INTACT certification for IPv6, we discovered some issues: -When an ipv6 address is removed , the route unreachable associated with the address isn't removed because the variable v6 isn't filled with the right value. Solution: -We need just to fill v6 with correct value 'v6 = true' if a_old->flag is DEV_INET6 Signed-off-by: kaies Chaouch --- interface-ip.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface-ip.c b/interface-ip.c index 4ae5787..ef6e907 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -737,6 +737,9 @@ interface_update_proto_addr(struct vlist_tree *tree, interface_handle_subnet_route(iface, a_old, false); system_del_address(dev, a_old); + if ((a_old->flags & DEVADDR_FAMILY) == DEVADDR_INET6) + v6 = true; + if ((a_old->flags & DEVADDR_OFFLINK) && (a_old->mask < (v6 ? 128 : 32))) { struct device_route route;