summary refs log tree commit diff
path: root/src/rdisc/nm-lndp-rdisc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/rdisc/nm-lndp-rdisc.c')
-rw-r--r--src/rdisc/nm-lndp-rdisc.c468
1 files changed, 50 insertions, 418 deletions
diff --git a/src/rdisc/nm-lndp-rdisc.c b/src/rdisc/nm-lndp-rdisc.c
index ff1bd33d..cadbfdfa 100644
--- a/src/rdisc/nm-lndp-rdisc.c
+++ b/src/rdisc/nm-lndp-rdisc.c
@@ -27,6 +27,7 @@
 #include <ndp.h>
 
 #include "nm-lndp-rdisc.h"
+#include "nm-rdisc-private.h"
 
 #include "NetworkManagerUtils.h"
 #include "nm-logging.h"
@@ -39,13 +40,9 @@
 typedef struct {
 	struct ndp *ndp;
 
-	guint send_rs_id;
 	GIOChannel *event_channel;
 	guint event_id;
-	guint timeout_id;   /* prefix/dns/etc lifetime timeout */
 	guint ra_timeout_id;  /* first RA timeout */
-
-	int solicitations_left;
 } NMLNDPRDiscPrivate;
 
 #define NM_LNDP_RDISC_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_LNDP_RDISC, NMLNDPRDiscPrivate))
@@ -54,185 +51,6 @@ G_DEFINE_TYPE (NMLNDPRDisc, nm_lndp_rdisc, NM_TYPE_RDISC)
 
 /******************************************************************/
 
-static inline gint32
-ipv6_sysctl_get (const char *ifname, const char *property, gint32 defval)
-{
-	return nm_platform_sysctl_get_int32 (nm_utils_ip6_property_path (ifname, property), defval);
-}
-
-NMRDisc *
-nm_lndp_rdisc_new (int ifindex, const char *ifname)
-{
-	NMRDisc *rdisc;
-	NMLNDPRDiscPrivate *priv;
-	int error;
-
-	rdisc = g_object_new (NM_TYPE_LNDP_RDISC, NULL);
-
-	rdisc->ifindex = ifindex;
-	rdisc->ifname = g_strdup (ifname);
-
-	rdisc->max_addresses = ipv6_sysctl_get (ifname, "max_addresses",
-	                                        NM_RDISC_MAX_ADDRESSES_DEFAULT);
-	rdisc->rtr_solicitations = ipv6_sysctl_get (ifname, "router_solicitations",
-	                                            NM_RDISC_RTR_SOLICITATIONS_DEFAULT);
-	rdisc->rtr_solicitation_interval = ipv6_sysctl_get (ifname, "router_solicitation_interval",
-	                                                    NM_RDISC_RTR_SOLICITATION_INTERVAL_DEFAULT);
-
-	priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-	error = ndp_open (&priv->ndp);
-	if (error != 0) {
-		g_object_unref (rdisc);
-		debug ("(%s): error creating socket for NDP; errno=%d", ifname, -error);
-		return NULL;
-	}
-	return rdisc;
-}
-
-static gboolean
-add_gateway (NMRDisc *rdisc, const NMRDiscGateway *new)
-{
-	int i;
-
-	for (i = 0; i < rdisc->gateways->len; i++) {
-		NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i);
-
-		if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
-			if (item->preference != new->preference) {
-				g_array_remove_index (rdisc->gateways, i--);
-				continue;
-			}
-			memcpy (item, new, sizeof (*new));
-			return FALSE;
-		}
-
-		/* Put before less preferable gateways. */
-		if (item->preference < new->preference)
-			break;
-	}
-
-	g_array_insert_val (rdisc->gateways, i, *new);
-	return TRUE;
-}
-
-static gboolean
-add_address (NMRDisc *rdisc, const NMRDiscAddress *new)
-{
-	int i;
-
-	for (i = 0; i < rdisc->addresses->len; i++) {
-		NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i);
-
-		if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
-			gboolean changed = item->timestamp + item->lifetime  != new->timestamp + new->lifetime ||
-			                   item->timestamp + item->preferred != new->timestamp + new->preferred;
-
-			*item = *new;
-			return changed;
-		}
-	}
-
-	/* we create at most max_addresses autoconf addresses. This is different from
-	 * what the kernel does, because it considers *all* addresses (including
-	 * static and other temporary addresses).
-	 **/
-	if (rdisc->max_addresses && rdisc->addresses->len >= rdisc->max_addresses)
-		return FALSE;
-
-	g_array_insert_val (rdisc->addresses, i, *new);
-	return TRUE;
-}
-
-static gboolean
-add_route (NMRDisc *rdisc, const NMRDiscRoute *new)
-{
-	int i;
-
-	for (i = 0; i < rdisc->routes->len; i++) {
-		NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i);
-
-		if (IN6_ARE_ADDR_EQUAL (&item->network, &new->network) && item->plen == new->plen) {
-			if (item->preference != new->preference) {
-				g_array_remove_index (rdisc->routes, i--);
-				continue;
-			}
-			memcpy (item, new, sizeof (*new));
-			return FALSE;
-		}
-
-		/* Put before less preferable routes. */
-		if (item->preference < new->preference)
-			break;
-	}
-
-	g_array_insert_val (rdisc->routes, i, *new);
-	return TRUE;
-}
-
-static gboolean
-add_dns_server (NMRDisc *rdisc, const NMRDiscDNSServer *new)
-{
-	int i;
-
-	for (i = 0; i < rdisc->dns_servers->len; i++) {
-		NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i);
-
-		if (IN6_ARE_ADDR_EQUAL (&item->address, &new->address)) {
-			gboolean changed;
-
-			if (new->lifetime == 0) {
-				g_array_remove_index (rdisc->dns_servers, i);
-				return TRUE;
-			}
-
-			changed = (item->timestamp != new->timestamp ||
-			           item->lifetime != new->lifetime);
-			if (changed) {
-				item->timestamp = new->timestamp;
-				item->lifetime = new->lifetime;
-			}
-			return changed;
-		}
-	}
-
-	g_array_insert_val (rdisc->dns_servers, i, *new);
-	return TRUE;
-}
-
-/* Copies new->domain if 'new' is added to the dns_domains list */
-static gboolean
-add_dns_domain (NMRDisc *rdisc, const NMRDiscDNSDomain *new)
-{
-	NMRDiscDNSDomain *item;
-	int i;
-
-	for (i = 0; i < rdisc->dns_domains->len; i++) {
-		item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i);
-
-		if (!g_strcmp0 (item->domain, new->domain)) {
-			gboolean changed;
-
-			if (new->lifetime == 0) {
-				g_array_remove_index (rdisc->dns_domains, i);
-				return TRUE;
-			}
-
-			changed = (item->timestamp != new->timestamp ||
-			           item->lifetime != new->lifetime);
-			if (changed) {
-				item->timestamp = new->timestamp;
-				item->lifetime = new->lifetime;
-			}
-			return changed;
-		}
-	}
-
-	g_array_insert_val (rdisc->dns_domains, i, *new);
-	item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i);
-	item->domain = g_strdup (new->domain);
-	return TRUE;
-}
-
 static gboolean
 send_rs (NMRDisc *rdisc)
 {
@@ -244,185 +62,12 @@ send_rs (NMRDisc *rdisc)
 	g_assert (!error);
 	ndp_msg_ifindex_set (msg, rdisc->ifindex);
 
-	debug ("(%s): sending router solicitation", rdisc->ifname);
-
 	error = ndp_msg_send (priv->ndp, msg);
-	if (error)
-		error ("(%s): cannot send router solicitation: %d.", rdisc->ifname, error);
-	else
-		priv->solicitations_left--;
-
 	ndp_msg_destroy (msg);
-
-	if (priv->solicitations_left > 0) {
-		debug ("(%s): scheduling router solicitation retry in %d seconds.",
-		       rdisc->ifname, rdisc->rtr_solicitation_interval);
-		priv->send_rs_id = g_timeout_add_seconds (rdisc->rtr_solicitation_interval,
-		                                          (GSourceFunc) send_rs, rdisc);
-	} else {
-		debug ("(%s): did not receive a router advertisement after %d solicitations.",
-		       rdisc->ifname, rdisc->rtr_solicitations);
-		priv->send_rs_id = 0;
-	}
-
-	return G_SOURCE_REMOVE;
-}
-
-static void
-solicit (NMRDisc *rdisc)
-{
-	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-
-	if (!priv->send_rs_id) {
-		debug ("(%s): scheduling router solicitation.", rdisc->ifname);
-		priv->send_rs_id = g_idle_add ((GSourceFunc) send_rs, rdisc);
-		priv->solicitations_left = rdisc->rtr_solicitations;
-	}
-}
-
-static void
-clean_gateways (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent)
-{
-	int i;
-
-	for (i = 0; i < rdisc->gateways->len; i++) {
-		NMRDiscGateway *item = &g_array_index (rdisc->gateways, NMRDiscGateway, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-
-		if (item->lifetime == G_MAXUINT32)
-			continue;
-
-		if (now >= expiry) {
-			g_array_remove_index (rdisc->gateways, i--);
-			*changed |= NM_RDISC_CONFIG_GATEWAYS;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
-	}
-}
-
-static void
-clean_addresses (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent)
-{
-	int i;
-
-	for (i = 0; i < rdisc->addresses->len; i++) {
-		NMRDiscAddress *item = &g_array_index (rdisc->addresses, NMRDiscAddress, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-
-		if (item->lifetime == G_MAXUINT32)
-			continue;
-
-		if (now >= expiry) {
-			g_array_remove_index (rdisc->addresses, i--);
-			*changed |= NM_RDISC_CONFIG_ADDRESSES;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
-	}
-}
-
-static void
-clean_routes (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent)
-{
-	int i;
-
-	for (i = 0; i < rdisc->routes->len; i++) {
-		NMRDiscRoute *item = &g_array_index (rdisc->routes, NMRDiscRoute, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-
-		if (item->lifetime == G_MAXUINT32)
-			continue;
-
-		if (now >= expiry) {
-			g_array_remove_index (rdisc->routes, i--);
-			*changed |= NM_RDISC_CONFIG_ROUTES;
-		} else if (*nextevent > expiry)
-			*nextevent = expiry;
-	}
-}
-
-static void
-clean_dns_servers (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent)
-{
-	int i;
-
-	for (i = 0; i < rdisc->dns_servers->len; i++) {
-		NMRDiscDNSServer *item = &g_array_index (rdisc->dns_servers, NMRDiscDNSServer, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-		guint64 refresh = (guint64) item->timestamp + item->lifetime / 2;
-
-		if (item->lifetime == G_MAXUINT32)
-			continue;
-
-		if (now >= expiry) {
-			g_array_remove_index (rdisc->dns_servers, i--);
-			*changed |= NM_RDISC_CONFIG_DNS_SERVERS;
-		} else if (now >= refresh)
-			solicit (rdisc);
-		else if (*nextevent > refresh)
-			*nextevent = refresh;
-	}
-}
-
-static void
-clean_dns_domains (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap *changed, guint32 *nextevent)
-{
-	int i;
-
-	for (i = 0; i < rdisc->dns_domains->len; i++) {
-		NMRDiscDNSDomain *item = &g_array_index (rdisc->dns_domains, NMRDiscDNSDomain, i);
-		guint64 expiry = (guint64) item->timestamp + item->lifetime;
-		guint64 refresh = (guint64) item->timestamp + item->lifetime / 2;
-
-		if (item->lifetime == G_MAXUINT32)
-			continue;
-
-		if (now >= expiry) {
-			g_free (item->domain);
-			g_array_remove_index (rdisc->dns_domains, i--);
-			*changed |= NM_RDISC_CONFIG_DNS_DOMAINS;
-		} else if (now >= refresh)
-			solicit (rdisc);
-		else if (*nextevent > refresh)
-			*nextevent = refresh;
-	}
-}
-
-static gboolean timeout_cb (gpointer user_data);
-
-static void
-check_timestamps (NMRDisc *rdisc, guint32 now, NMRDiscConfigMap changed)
-{
-	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-	/* Use a magic date in the distant future (~68 years) */
-	guint32 never = G_MAXINT32;
-	guint32 nextevent = never;
-
-	if (priv->timeout_id) {
-		g_source_remove (priv->timeout_id);
-		priv->timeout_id = 0;
-	}
-
-	clean_gateways (rdisc, now, &changed, &nextevent);
-	clean_addresses (rdisc, now, &changed, &nextevent);
-	clean_routes (rdisc, now, &changed, &nextevent);
-	clean_dns_servers (rdisc, now, &changed, &nextevent);
-	clean_dns_domains (rdisc, now, &changed, &nextevent);
-
-	if (changed)
-		g_signal_emit_by_name (rdisc, NM_RDISC_CONFIG_CHANGED, changed);
-
-	if (nextevent != never) {
-		g_return_if_fail (nextevent > now);
-		debug ("(%s): scheduling next now/lifetime check: %u seconds",
-		       rdisc->ifname, nextevent - now);
-		priv->timeout_id = g_timeout_add_seconds (nextevent - now, timeout_cb, rdisc);
+	if (error) {
+		error ("(%s): cannot send router solicitation: %d.", rdisc->ifname, error);
+		return FALSE;
 	}
-}
-
-static gboolean
-timeout_cb (gpointer user_data)
-{
-	check_timestamps (user_data, nm_utils_get_monotonic_timestamp_s (), 0);
 
 	return TRUE;
 }
@@ -442,28 +87,6 @@ translate_preference (enum ndp_route_preference preference)
 	}
 }
 
-static void
-clear_rs_timeout (NMLNDPRDisc *rdisc)
-{
-	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-
-	if (priv->send_rs_id) {
-		g_source_remove (priv->send_rs_id);
-		priv->send_rs_id = 0;
-	}
-}
-
-static void
-clear_ra_timeout (NMLNDPRDisc *rdisc)
-{
-	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-
-	if (priv->ra_timeout_id) {
-		g_source_remove (priv->ra_timeout_id);
-		priv->ra_timeout_id = 0;
-	}
-}
-
 static int
 receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 {
@@ -488,9 +111,6 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	 */
 	debug ("(%s): received router advertisement at %u", rdisc->ifname, now);
 
-	clear_ra_timeout (NM_LNDP_RDISC (rdisc));
-	clear_rs_timeout (NM_LNDP_RDISC (rdisc));
-
 	/* DHCP level:
 	 *
 	 * The problem with DHCP level is what to do if subsequent
@@ -524,7 +144,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 	gateway.timestamp = now;
 	gateway.lifetime = ndp_msgra_router_lifetime (msgra);
 	gateway.preference = translate_preference (ndp_msgra_route_preference (msgra));
-	if (add_gateway (rdisc, &gateway))
+	if (nm_rdisc_add_gateway (rdisc, &gateway))
 		changed |= NM_RDISC_CONFIG_GATEWAYS;
 
 	/* Addresses & Routes */
@@ -539,7 +159,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 		route.timestamp = now;
 		if (ndp_msg_opt_prefix_flag_on_link (msg, offset)) {
 			route.lifetime = ndp_msg_opt_prefix_valid_time (msg, offset);
-			if (add_route (rdisc, &route))
+			if (nm_rdisc_add_route (rdisc, &route))
 				changed |= NM_RDISC_CONFIG_ROUTES;
 		}
 
@@ -557,7 +177,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 				/* Add the Interface Identifier to the lower 64 bits */
 				nm_utils_ipv6_addr_set_interface_identfier (&address.address, rdisc->iid);
 
-				if (add_address (rdisc, &address))
+				if (nm_rdisc_add_address (rdisc, &address))
 					changed |= NM_RDISC_CONFIG_ADDRESSES;
 			}
 		}
@@ -573,7 +193,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 		route.timestamp = now;
 		route.lifetime = ndp_msg_opt_route_lifetime (msg, offset);
 		route.preference = translate_preference (ndp_msg_opt_route_preference (msg, offset));
-		if (add_route (rdisc, &route))
+		if (nm_rdisc_add_route (rdisc, &route))
 			changed |= NM_RDISC_CONFIG_ROUTES;
 	}
 
@@ -596,7 +216,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			 */
 			if (dns_server.lifetime && dns_server.lifetime < 7200)
 				dns_server.lifetime = 7200;
-			if (add_dns_server (rdisc, &dns_server))
+			if (nm_rdisc_add_dns_server (rdisc, &dns_server))
 				changed |= NM_RDISC_CONFIG_DNS_SERVERS;
 		}
 	}
@@ -618,7 +238,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 			 */
 			if (dns_domain.lifetime && dns_domain.lifetime < 7200)
 				dns_domain.lifetime = 7200;
-			if (add_dns_domain (rdisc, &dns_domain))
+			if (nm_rdisc_add_dns_domain (rdisc, &dns_domain))
 				changed |= NM_RDISC_CONFIG_DNS_DOMAINS;
 		}
 	}
@@ -644,8 +264,7 @@ receive_ra (struct ndp *ndp, struct ndp_msg *msg, gpointer user_data)
 		}
 	}
 
-	check_timestamps (rdisc, now, changed);
-
+	nm_rdisc_ra_received (rdisc, now, changed);
 	return 0;
 }
 
@@ -659,41 +278,58 @@ event_ready (GIOChannel *source, GIOCondition condition, NMRDisc *rdisc)
 	return G_SOURCE_CONTINUE;
 }
 
-static gboolean
-rdisc_ra_timeout_cb (gpointer user_data)
-{
-	NMLNDPRDisc *rdisc = NM_LNDP_RDISC (user_data);
-	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
-
-	priv->ra_timeout_id = 0;
-	g_signal_emit_by_name (rdisc, NM_RDISC_RA_TIMEOUT);
-	return G_SOURCE_REMOVE;
-}
-
 static void
 start (NMRDisc *rdisc)
 {
 	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
 	int fd = ndp_get_eventfd (priv->ndp);
-	guint ra_wait_secs;
 
 	priv->event_channel = g_io_channel_unix_new (fd);
 	priv->event_id = g_io_add_watch (priv->event_channel, G_IO_IN, (GIOFunc) event_ready, rdisc);
 
-	clear_ra_timeout (NM_LNDP_RDISC (rdisc));
-	ra_wait_secs = CLAMP (rdisc->rtr_solicitations * rdisc->rtr_solicitation_interval, 30, 120);
-	priv->ra_timeout_id = g_timeout_add_seconds (ra_wait_secs, rdisc_ra_timeout_cb, rdisc);
-	debug ("(%s): scheduling RA timeout in %d seconds", rdisc->ifname, ra_wait_secs);
-
 	/* Flush any pending messages to avoid using obsolete information */
 	event_ready (priv->event_channel, 0, rdisc);
 
 	ndp_msgrcv_handler_register (priv->ndp, receive_ra, NDP_MSG_RA, rdisc->ifindex, rdisc);
-	solicit (rdisc);
 }
 
 /******************************************************************/
 
+static inline gint32
+ipv6_sysctl_get (const char *ifname, const char *property, gint32 defval)
+{
+	return nm_platform_sysctl_get_int32 (nm_utils_ip6_property_path (ifname, property), defval);
+}
+
+NMRDisc *
+nm_lndp_rdisc_new (int ifindex, const char *ifname)
+{
+	NMRDisc *rdisc;
+	NMLNDPRDiscPrivate *priv;
+	int error;
+
+	rdisc = g_object_new (NM_TYPE_LNDP_RDISC, NULL);
+
+	rdisc->ifindex = ifindex;
+	rdisc->ifname = g_strdup (ifname);
+
+	rdisc->max_addresses = ipv6_sysctl_get (ifname, "max_addresses",
+	                                        NM_RDISC_MAX_ADDRESSES_DEFAULT);
+	rdisc->rtr_solicitations = ipv6_sysctl_get (ifname, "router_solicitations",
+	                                            NM_RDISC_RTR_SOLICITATIONS_DEFAULT);
+	rdisc->rtr_solicitation_interval = ipv6_sysctl_get (ifname, "router_solicitation_interval",
+	                                                    NM_RDISC_RTR_SOLICITATION_INTERVAL_DEFAULT);
+
+	priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
+	error = ndp_open (&priv->ndp);
+	if (error != 0) {
+		g_object_unref (rdisc);
+		debug ("(%s): error creating socket for NDP; errno=%d", ifname, -error);
+		return NULL;
+	}
+	return rdisc;
+}
+
 static void
 nm_lndp_rdisc_init (NMLNDPRDisc *lndp_rdisc)
 {
@@ -705,14 +341,6 @@ dispose (GObject *object)
 	NMLNDPRDisc *rdisc = NM_LNDP_RDISC (object);
 	NMLNDPRDiscPrivate *priv = NM_LNDP_RDISC_GET_PRIVATE (rdisc);
 
-	clear_rs_timeout (rdisc);
-	clear_ra_timeout (rdisc);
-
-	if (priv->timeout_id) {
-		g_source_remove (priv->timeout_id);
-		priv->timeout_id = 0;
-	}
-
 	if (priv->event_id) {
 		g_source_remove (priv->event_id);
 		priv->event_id = 0;
@@ -720,9 +348,12 @@ dispose (GObject *object)
 	g_clear_pointer (&priv->event_channel, g_io_channel_unref);
 
 	if (priv->ndp) {
+		ndp_msgrcv_handler_unregister (priv->ndp, receive_ra, NDP_MSG_RA, NM_RDISC (rdisc)->ifindex, rdisc);
 		ndp_close (priv->ndp);
 		priv->ndp = NULL;
 	}
+
+	G_OBJECT_CLASS (nm_lndp_rdisc_parent_class)->dispose (object);
 }
 
 static void
@@ -735,4 +366,5 @@ nm_lndp_rdisc_class_init (NMLNDPRDiscClass *klass)
 
 	object_class->dispose = dispose;
 	rdisc_class->start = start;
+	rdisc_class->send_rs = send_rs;
 }