netconfig: Subscribe for IPv6 address changes
PATCH v4 1/5 netconfig: Subscribe for IPv6 address changes
The IPv6 addresses changes are maintained in ifaddr_list.
--- src/netconfig.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
src/netconfig.c +92 -0
@@ -224,6 +224,23 @@ static char **netconfig_ipv4_get_dns(struct netconfig *netconfig, uint8_t proto)return NULL ; } +static bool netconfig_ifaddr_match (const void *a,const void *b) +{ +const struct netconfig_ifaddr *entry = a; +const struct netconfig_ifaddr *query = b; + +if (entry->family != query->family) +return false; + +if (entry->prefix_len != query->prefix_len) +return false; + +if (strcmp (entry->ip, query->ip)) +return false; + +return true; +} + static struct netconfig_ifaddr *netconfig_ifaddr_find( const struct netconfig *netconfig, uint8_t family, uint8_t prefix_len,
@@ -335,6 +352,73 @@ static void netconfig_ifaddr_cmd_cb(int error, uint16_t type,netconfig_ifaddr_notify (type, data, len, user_data); } +static void netconfig_ifaddr_ipv6_added (struct netconfig *netconfig, +const struct ifaddrmsg *ifa, +uint32_t len) +{ +struct netconfig_ifaddr *ifaddr; + + ifaddr = l_new(struct netconfig_ifaddr, 1); + ifaddr->family = ifa->ifa_family; + ifaddr->prefix_len = ifa->ifa_prefixlen; + +rtnl_ifaddr_ipv6_extract (ifa, len, &ifaddr->ip); + +l_debug ("ifindex %u: ifaddr %s/%u" , netconfig->ifindex, ifaddr->ip, + ifaddr->prefix_len); + +l_queue_push_tail (netconfig->ifaddr_list, ifaddr); +} + +static void netconfig_ifaddr_ipv6_deleted (struct netconfig *netconfig, +const struct ifaddrmsg *ifa, +uint32_t len) +{ +struct netconfig_ifaddr *ifaddr; +struct netconfig_ifaddr query; + +rtnl_ifaddr_ipv6_extract (ifa, len, &query.ip); + + query.family = ifa->ifa_family; + query.prefix_len = ifa->ifa_prefixlen; + + ifaddr =l_queue_remove_if (netconfig->ifaddr_list, + netconfig_ifaddr_match, &query); + +l_free (query.ip); + +if (!ifaddr) +return ; + +l_debug ("ifaddr %s/%u" , ifaddr->ip, ifaddr->prefix_len); + +netconfig_ifaddr_destroy (ifaddr); +} + +static void netconfig_ifaddr_ipv6_notify (uint16_t type,const void *data, +uint32_t len,void *user_data) +{ +const struct ifaddrmsg *ifa = data; +struct netconfig *netconfig; +uint32_t bytes; + + netconfig =netconfig_find (ifa->ifa_index); +if (!netconfig) +/* Ignore the interfaces which aren't managed by iwd. */ +return ; + + bytes = len -NLMSG_ALIGN (sizeof (struct ifaddrmsg )); + +switch (type) { +case RTM_NEWADDR : +netconfig_ifaddr_ipv6_added (netconfig, ifa, bytes); +break ; +case RTM_DELADDR : +netconfig_ifaddr_ipv6_deleted (netconfig, ifa, bytes); +break ; + } +} +static void netconfig_route_cmd_cb (int error,uint16_t type,const void *data,uint32_t len,void *user_data)
@@ -740,6 +824,14 @@ static int netconfig_init(void) goto error; } + r =l_netlink_register (rtnl,RTNLGRP_IPV6_IFADDR , + netconfig_ifaddr_ipv6_notify,NULL ,NULL ); +if (!r) { +l_error ("netconfig: Failed to register for RTNL link IPv6 " +"address notifications." ); + goto error; + } +if (!l_settings_get_uint (iwd_get_config (),"General" ,"route_priority_offset" , &ROUTE_PRIORITY_OFFSET ))
Signature · 1 line
2.13.6
PATCH v4 2/5 netconfig: Request all known IPv6 addresses
--- src/netconfig.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
src/netconfig.c +24 -0
@@ -419,6 +419,22 @@ static void netconfig_ifaddr_ipv6_notify(uint16_t type, const void *data, } } +static void netconfig_ifaddr_ipv6_cmd_cb (int error,uint16_t type, +const void *data,uint32_t len, +void *user_data) +{ +if (error) { +l_error ("netconfig: ifaddr IPv6 command failure. " +"Error %d: %s" , error,strerror (-error)); +return ; + } + +if (type !=RTM_NEWADDR ) +return ; + +netconfig_ifaddr_ipv6_notify (type, data, len, user_data); +} +static void netconfig_route_cmd_cb (int error,uint16_t type,const void *data,uint32_t len,void *user_data)
@@ -832,6 +848,14 @@ static int netconfig_init(void) goto error; } + r =rtnl_ifaddr_ipv6_get (rtnl, netconfig_ifaddr_ipv6_cmd_cb,NULL , +NULL ); +if (!r) { +l_error ("netconfig: Failed to get IPv6 addresses from RTNL" +" link." ); + goto error; + } +if (!l_settings_get_uint (iwd_get_config (),"General" ,"route_priority_offset" , &ROUTE_PRIORITY_OFFSET ))
Signature · 1 line
2.13.6
PATCH v4 3/5 netconfig: Add IPv6 static address installation/removal
In reply to Tim KourtThe network configuration options for IPv6 are grouped under [IPv6] and include the following:
ip= ADDRESS/PREFIX
gateway=ADDRESS
dns=ADDRESS
The placeholders for DHCPv6 are placed along the way and marked as TODO items.
--- src/netconfig.c | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 152 insertions(+), 8 deletions(-)
src/netconfig.c +152 -8
@@ -48,6 +48,7 @@ struct netconfig {struct l_dhcp_client *dhcp_client;struct l_queue *ifaddr_list;uint8_t rtm_protocol; +uint8_t rtm_v6_protocol;const struct l_settings *active_settings; };
@@ -224,6 +225,70 @@ static char **netconfig_ipv4_get_dns(struct netconfig *netconfig, uint8_t proto)return NULL ; } +static struct netconfig_ifaddr *netconfig_ipv6_get_ifaddr ( +struct netconfig *netconfig, +uint8_t proto) +{ +struct in6_addr in6_addr; +struct netconfig_ifaddr *ifaddr; +char *ip; +char *p; + +switch (proto) { +case RTPROT_STATIC : +if (!netconfig->active_settings) +return NULL ; + + ip =l_settings_get_string (netconfig->active_settings,"IPv6" , +"ip" ); +if (!ip) +return NULL ; + + ifaddr = l_new(struct netconfig_ifaddr, 1); + ifaddr->ip = ip; + + p =strrchr (ifaddr->ip,'/' ); +if (!p) + goto no_prefix_len; + + *p ='\0' ; + +if (inet_pton (AF_INET6 , ifaddr->ip, &in6_addr) <1 ) { +l_error ("netconfig: Invalid IPv6 address %s is " +"provided in network configuration file." , + ifaddr->ip); + +netconfig_ifaddr_destroy (ifaddr); + +return NULL ; + } + +if (*++p =='\0' ) + goto no_prefix_len; + + ifaddr->prefix_len =strtoul (p,NULL ,10 ); + +if (!unlikely (errno ==EINVAL || errno ==ERANGE || + !ifaddr->prefix_len || + ifaddr->prefix_len >128 )) + goto proceed; + +no_prefix_len: + ifaddr->prefix_len =128 ; +proceed: + ifaddr->family =AF_INET6 ; + +return ifaddr; + +case RTPROT_DHCP : +/* TODO */ + +return NULL ; + } + +return NULL ; +} +static bool netconfig_ifaddr_match (const void *a,const void *b) {const struct netconfig_ifaddr *entry = a;
@@ -541,6 +606,19 @@ done:netconfig_ifaddr_destroy (ifaddr); } +static void netconfig_ipv6_ifaddr_add_cmd_cb (int error,uint16_t type, +const void *data,uint32_t len, +void *user_data) +{ +if (error && error != -EEXIST ) { +l_error ("netconfig: Failed to add IPv6 address. " +"Error %d: %s" , error,strerror (-error)); +return ; + } + +/* TODO Install routes and DNS */ +} +static void netconfig_install_address (struct netconfig *netconfig,struct netconfig_ifaddr *ifaddr) {
@@ -560,6 +638,16 @@ static void netconfig_install_address(struct netconfig *netconfig,l_error ("netconfig: Failed to set IP %s/%u." , ifaddr->ip, ifaddr->prefix_len);break ; +case AF_INET6 : +if (rtnl_ifaddr_ipv6_add (rtnl, netconfig->ifindex, + ifaddr->prefix_len, ifaddr->ip, + netconfig_ipv6_ifaddr_add_cmd_cb, + netconfig,NULL )) +return ; + +l_error ("netconfig: Failed to set IPv6 address %s/%u." , + ifaddr->ip, ifaddr->prefix_len); +break ;default :l_error ("netconfig: Unsupported address family: %u" , ifaddr->family);
@@ -606,6 +694,16 @@ static void netconfig_uninstall_address(struct netconfig *netconfig,l_error ("netconfig: Failed to delete IP %s/%u." , ifaddr->ip, ifaddr->prefix_len);break ; +case AF_INET6 : +if (rtnl_ifaddr_ipv6_delete (rtnl, netconfig->ifindex, + ifaddr->prefix_len, ifaddr->ip, + netconfig_ifaddr_del_cmd_cb, netconfig, +NULL )) +return ; + +l_error ("netconfig: Failed to delete IPv6 address %s/%u." , + ifaddr->ip, ifaddr->prefix_len); +break ;default :l_error ("netconfig: Unsupported address family: %u" , ifaddr->family);
@@ -717,6 +815,49 @@ static void netconfig_ipv4_select_and_uninstall(struct netconfig *netconfig)l_dhcp_client_stop (netconfig->dhcp_client); } +static void netconfig_ipv6_select_and_install (struct netconfig *netconfig) +{ +struct netconfig_ifaddr *ifaddr; + + ifaddr =netconfig_ipv6_get_ifaddr (netconfig,RTPROT_STATIC ); +if (ifaddr) { + netconfig->rtm_v6_protocol =RTPROT_STATIC ; +netconfig_install_address (netconfig, ifaddr); +netconfig_ifaddr_destroy (ifaddr); + +return ; + } + +/* +* TODO +* +* netconfig->rtm_v6_protocol = RTPROT_DHCP; +* +* if (l_dhcp_v6_client_start(netconfig->l_dhcp_v6_client)) +* return; +* +* l_error("netconfig: Failed to start DHCPv6 client for " +* "interface %u", netconfig->ifindex); +*/ +} + +static void netconfig_ipv6_select_and_uninstall (struct netconfig *netconfig) +{ +struct netconfig_ifaddr *ifaddr; + + ifaddr =netconfig_ipv6_get_ifaddr (netconfig, + netconfig->rtm_v6_protocol); +if (ifaddr) { +netconfig_uninstall_address (netconfig, ifaddr); +netconfig_ifaddr_destroy (ifaddr); + } + +/* +* TODO +* l_dhcp_v6_client_stop(netconfig->l_dhcp_v6_client); +*/ +} +bool netconfig_configure (struct netconfig *netconfig,const struct l_settings *active_settings,const uint8_t *mac_address)
@@ -728,7 +869,7 @@ bool netconfig_configure(struct netconfig *netconfig,netconfig_ipv4_select_and_install (netconfig); -/* TODO: IPv6 addressing */ +netconfig_ipv6_select_and_install (netconfig);return true; }
@@ -739,7 +880,9 @@ bool netconfig_reconfigure(struct netconfig *netconfig)/* TODO l_dhcp_client sending a DHCP inform request */ } -/* TODO: IPv6 addressing */ +if (netconfig->rtm_v6_protocol ==RTPROT_DHCP ) { +/* TODO l_dhcp_v6_client sending a DHCP inform request */ + }return true; }
@@ -747,13 +890,13 @@ bool netconfig_reconfigure(struct netconfig *netconfig)bool netconfig_reset (struct netconfig *netconfig) {netconfig_ipv4_select_and_uninstall (netconfig); + netconfig->rtm_protocol =0 ; -/* TODO: IPv6 addressing */ +netconfig_ipv6_select_and_uninstall (netconfig); + netconfig->rtm_v6_protocol =0 ;resolve_remove (netconfig->ifindex); - netconfig->rtm_protocol =0 ; -return true; }
@@ -790,13 +933,14 @@ void netconfig_destroy(struct netconfig *netconfig)l_queue_remove (netconfig_list, netconfig); -if (netconfig->rtm_protocol) { +if (netconfig->rtm_protocol)netconfig_ipv4_select_and_uninstall (netconfig); -/* TODO Uninstall IPv6 addresses. */ +if (netconfig->rtm_v6_protocol) +netconfig_ipv6_select_and_uninstall (netconfig); +if (netconfig->rtm_protocol || netconfig->rtm_v6_protocol)resolve_remove (netconfig->ifindex); - }netconfig_free (netconfig); }
Signature · 1 line
2.13.6
Re: [PATCH v4 3/5] netconfig: Add IPv6 static address installation/removal
Hi Tim,
Quoted 9 lines · Tim Kourt
The network configuration options for IPv6 are grouped under [IPv6] and include the following: ip= ADDRESS/PREFIX gateway=ADDRESS dns=ADDRESS
The placeholders for DHCPv6 are placed along the way and marked as TODO items. ---
src/netconfig.c| 160 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 152 insertions(+), 8 deletions(-)
<snip>
Quoted 13 lines
(quoted diff) +1
-0@@ -224,6 +225,70 @@ static char **netconfig_ipv4_get_dns(struct netconfig *netconfig, uint8_t proto) return NULL; } +static struct netconfig_ifaddr *netconfig_ipv6_get_ifaddr( +struct netconfig *netconfig, +
uint8_tproto) +{ + structin6_addrin6_addr; + structnetconfig_ifaddr*ifaddr; + char *ip; +
char *p; + + switch (proto) { + caseRTPROT_STATIC: + if
(!netconfig->active_settings) + return NULL; + + ip =
l_settings_get_string(netconfig->active_settings, "IPv6", +
"ip"); + if (!ip) + return NULL; + + ifaddr = l_new(structnetconfig_ifaddr, 1); +ifaddr->ip= ip; + + p =strrchr(ifaddr->ip, '/'); + if (!p) + gotono_prefix_len; + + *p
= '\0'; + + if (inet_pton(AF_INET6, ifaddr->ip, &in6_addr)< 1) { +
Quoted 8 lines
l_error("netconfig: Invalid IPv6 address %s is " + "provided in
network configuration file.", +ifaddr->ip); + +netconfig_ifaddr_destroy(ifaddr); + + return NULL; + } + + if
(*++p == '\0') + gotono_prefix_len; + +ifaddr->prefix_len=strtoul(p, NULL, 10); + + if (!unlikely(errno == EINVAL || errno ==
ERANGE || + !ifaddr->prefix_len || +ifaddr->prefix_len>
128)) + goto proceed; + +no_prefix_len: +ifaddr->prefix_len=
128; +proceed: +ifaddr->family=AF_INET6; +
Didn't we agree to perform the validation in netconfig_configure?
Quoted 4 lines
(quoted diff) +1
-0+ return ifaddr; + + case RTPROT_DHCP: + /* TODO */ + + returnNULL; + } + + return NULL; +} + static bool
netconfig_ifaddr_match(const void *a, const void *b){ const structnetconfig_ifaddr*entry = a;
<snip>
Quoted 26 lines
(quoted diff) +0
-0@@ -747,13 +890,13 @@ bool netconfig_reconfigure(struct netconfig *netconfig) bool netconfig_reset(struct netconfig *netconfig) {
netconfig_ipv4_select_and_uninstall(netconfig); +netconfig->rtm_protocol= 0;(quoted diff) +0
-1- /* TODO: IPv6 addressing */ +
netconfig_ipv6_select_and_uninstall(netconfig); +netconfig->rtm_v6_protocol= 0;
resolve_remove(netconfig->ifindex);src/netconfig.c +0
-3- netconfig->rtm_protocol = 0; - return true; }@@ -790,13 +933,14 @@ void netconfig_destroy(struct netconfig *netconfig)l_queue_remove (netconfig_list, netconfig); - if (netconfig->rtm_protocol) { + if (netconfig->rtm_protocol)netconfig_ipv4_select_and_uninstall (netconfig); -/* TODO Uninstall IPv6 addresses. */ + if(
netconfig->rtm_v6_protocol) +netconfig_ipv6_select_and_uninstall(netconfig);src/netconfig.c +1
-0+if (netconfig->rtm_protocol || netconfig->rtm_v6_protocol)
Quoted 1 line
resolve_remove(netconfig->ifindex); - }
Shouldn't you implement netconfig_destroy using netconfig_reset? Since they perform nearly identical set of operations?
Quoted 3 lines
netconfig_free(netconfig); }
Anyway, I'll just take this patch, but please fix these.
Regards,
-Denis
PATCH v4 4/5 netconfig: Install IPv6 default route
In reply to Tim Kourt--- src/netconfig.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-)
src/netconfig.c +70 -1
@@ -289,6 +289,40 @@ proceed:return NULL ; } +static char *netconfig_ipv6_get_gateway (struct netconfig *netconfig) +{ +struct in6_addr in6_addr; +char *gateway; + +switch (netconfig->rtm_v6_protocol) { +case RTPROT_STATIC : +if (!netconfig->active_settings) +return NULL ; + + gateway =l_settings_get_string (netconfig->active_settings, +"IPv6" ,"gateway" ); + +if (inet_pton (AF_INET6 , gateway, &in6_addr) <1 ) { +l_error ("netconfig: Invalid IPv6 gateway address %s is " +"provided in network configuration file." , + gateway); + +l_free (gateway); + +return NULL ; + } + +return gateway; + +case RTPROT_DHCP : +/* TODO */ + +return NULL ; + } + +return NULL ; +} +static bool netconfig_ifaddr_match (const void *a,const void *b) {const struct netconfig_ifaddr *entry = a;
@@ -606,17 +640,52 @@ done:netconfig_ifaddr_destroy (ifaddr); } +static bool netconfig_ipv6_routes_install (struct netconfig *netconfig) +{ + L_AUTO_FREE_VAR(char *, gateway) = NULL; + + gateway =netconfig_ipv6_get_gateway (netconfig); +if (!gateway) { +l_error ("netconfig: Failed to obtain gateway from %s." , + netconfig->rtm_v6_protocol ==RTPROT_STATIC ? +"settings file" :"DHCPv6 lease" ); + +return false; + } + +if (!rtnl_route_ipv6_add_gateway (rtnl, netconfig->ifindex, gateway, +ROUTE_PRIORITY_OFFSET , + netconfig->rtm_v6_protocol, + netconfig_route_cmd_cb, +NULL ,NULL )) { +l_error ("netconfig: Failed to add route for: %s gateway." , + gateway); + +return false; + } + +return true; +} +static void netconfig_ipv6_ifaddr_add_cmd_cb (int error,uint16_t type,const void *data,uint32_t len,void *user_data) { +struct netconfig *netconfig = user_data; +if (error && error != -EEXIST ) {l_error ("netconfig: Failed to add IPv6 address. " "Error %d: %s" , error,strerror (-error));return ; } -/* TODO Install routes and DNS */ +if (!netconfig_ipv6_routes_install (netconfig)) { +l_error ("netconfig: Failed to install IPv6 routes." ); + +return ; + } + +/* TODO Install DNS */ } static void netconfig_install_address(struct netconfig *netconfig,
Signature · 1 line
2.13.6
Denis Kenzior
AppliedRe: [PATCH v4 4/5] netconfig: Install IPv6 default route
Hi Tim,
Quoted 12 lines · Tim Kourt
--- src/netconfig.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-)src/netconfig.c +0
-0@@ -289,6 +289,40 @@ proceed:return NULL ; }
src/netconfig.c proceed: · line 301
Quoted 10 lines
src/netconfig.c +10
-0+static char *netconfig_ipv6_get_gateway (struct netconfig *netconfig) +{ +struct in6_addr in6_addr; +char *gateway; + +switch (netconfig->rtm_v6_protocol) { +case RTPROT_STATIC : +if (!netconfig->active_settings) +return NULL ; +
I took out this check. active_settings should never be NULL, so thee principle of crash early applies. Also, since we're moving validation to _configure eventually, the need for this check is even less justifiable.
Applied, thanks.
Regards,
-Denis
PATCH v4 5/5 netconfig: Install IPv6 DNS
In reply to Tim Kourt--- src/netconfig.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-)
src/netconfig.c +55 -1
@@ -323,6 +323,50 @@ static char *netconfig_ipv6_get_gateway(struct netconfig *netconfig)return NULL ; } +static char **netconfig_ipv6_get_dns (struct netconfig *netconfig,uint8_t proto) +{ +struct in6_addr in6_addr; +char **dns_list; +char **p; + +switch (proto) { +case RTPROT_STATIC : +if (!netconfig->active_settings) +return NULL ; + + p = dns_list = +l_settings_get_string_list (netconfig->active_settings, +"IPv6" ,"dns" ,' ' ); + +if (!dns_list || !*dns_list) { +l_strv_free (dns_list); + +return NULL ; + } + +for (; *p; p++) { +if (inet_pton (AF_INET6 , *p, &in6_addr) ==1 ) +continue ; + +l_error ("netconfig: Invalid IPv6 DNS address %s is " +"provided in network configuration file." , *p); + +l_strv_free (dns_list); + +return NULL ; + } + +return dns_list; + +case RTPROT_DHCP : +/* TODO */ + +return NULL ; + } + +return NULL ; +} +static bool netconfig_ifaddr_match (const void *a,const void *b) {const struct netconfig_ifaddr *entry = a;
@@ -672,6 +716,7 @@ static void netconfig_ipv6_ifaddr_add_cmd_cb(int error, uint16_t type, void *user_data) {struct netconfig *netconfig = user_data; +char **dns;if (error && error != -EEXIST ) { l_error("netconfig: Failed to add IPv6 address. "
@@ -685,7 +730,16 @@ static void netconfig_ipv6_ifaddr_add_cmd_cb(int error, uint16_t type,return ; } -/* TODO Install DNS */ + dns =netconfig_ipv6_get_dns (netconfig, netconfig->rtm_v6_protocol); +if (!dns) { +l_error ("netconfig: Failed to obtain the DNS addresses from " +"%s." , netconfig->rtm_v6_protocol ==RTPROT_STATIC ? +"setting file" :"DHCPv6 lease" ); +return ; + } + +resolve_add_dns (netconfig->ifindex,AF_INET6 , dns); +l_strv_free (dns); } static void netconfig_install_address(struct netconfig *netconfig,
Signature · 1 line
2.13.6
Denis Kenzior
AppliedRe: [PATCH v4 5/5] netconfig: Install IPv6 DNS
Hi Tim,
Quoted 13 lines · Tim Kourt
--- src/netconfig.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-)src/netconfig.c +1
-0@@ -323,6 +323,50 @@ static char *netconfig_ipv6_get_gateway(struct netconfig *netconfig)return NULL ; } +static char **netconfig_ipv6_get_dns (struct netconfig *netconfig,uint8_t proto)
src/netconfig.c static char *netconfig_ipv6_get_gateway(struct netconfig *netconfig) · line 336
Quoted 10 lines
src/netconfig.c +10
-0+{ +struct in6_addr in6_addr; +char **dns_list; +char **p; + +switch (proto) { +case RTPROT_STATIC : +if (!netconfig->active_settings) +return NULL ; +
Took out this check, same reasoning as the previous commit.
Applied, thanks.
Regards,
-Denis
Hi Tim,
Quoted 5 lines · Tim Kourt
The IPv6 addresses changes are maintained in
ifaddr_list.--- src/netconfig.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+)
Patch 1 & 2 applied, thanks.
Regards,
-Denis