소스 검색

netbios_ns: add discover_start / discover_stop

Perform a NETBIOS discovery in a separate thread.
Thomas Guillem 10 년 전
부모
커밋
26d7ce63d4
2개의 변경된 파일199개의 추가작업 그리고 3개의 파일을 삭제
  1. 33 0
      include/bdsm/netbios_ns.h
  2. 166 3
      src/netbios_ns.c

+ 33 - 0
include/bdsm/netbios_ns.h

@@ -156,4 +156,37 @@ void                netbios_ns_clear(netbios_ns *ns);
  */
 void          netbios_ns_abort(netbios_ns *ns);
 
+typedef struct
+{
+    // Opaque pointer that will be passed to callbacks
+    void *p_opaque;
+    // Called when a new entry is added
+    void (*pf_on_entry_added) (void *p_opaque, netbios_ns_entry *entry);
+    // Called when an entry is removed
+    void (*pf_on_entry_removed)(void *p_opaque, netbios_ns_entry *entry);
+} netbios_ns_discover_callbacks;
+
+/** @brief Perform a NETBIOS discovery in a separate thread.
+ *
+ * @details This functions starts a new thread that will send a message to '*'
+ * Netbios name (broadcast NB query on 255.255.255.255). It'll wait for the
+ * machine on the LAN to answer. It'll then perform a reverse lookup on all the
+ * ip he received packet from. Once a name and an ip is found, this function
+ * will notify the caller by a callback.
+ *
+ * @param ns The name service object.  @param broadcast_timeout Do a broadcast
+ * every timeout seconds @param callbacks The callbacks previously setup by the
+ * caller
+ *
+ * @return a value > 0 if successfull, or 0 otherwise
+ */
+int netbios_ns_discover_start(netbios_ns *ns, unsigned int broadcast_timeout,
+                              netbios_ns_discover_callbacks *callbacks);
+
+/**
+ * @brief Stop the NETBIOS discovery.
+ * @param ns The name service object.
+ */
+int netbios_ns_discover_stop(netbios_ns *ns);
+
 #endif

+ 166 - 3
src/netbios_ns.c

@@ -20,6 +20,8 @@
 #include <stdio.h>
 #include <string.h>
 #include <stdbool.h>
+#include <pthread.h>
+#include <sys/time.h>
 #include <sys/queue.h>
 
 #include <unistd.h>
@@ -31,6 +33,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/select.h>
+#include <sys/queue.h>
 
 #include <bdsm/netbios_ns.h>
 
@@ -38,6 +41,8 @@
 #include "netbios_query.h"
 #include "netbios_utils.h"
 
+#define DISCOVER_REMOVE_TIMEOUT 60 // in sec
+
 enum name_query_type {
     NAME_QUERY_TYPE_INVALID,
     NAME_QUERY_TYPE_NB,
@@ -73,6 +78,10 @@ struct netbios_ns
     NS_ENTRY_QUEUE      entry_queue;
     uint8_t             buffer[RECV_BUFFER_SIZE];
     int                 abort_pipe[2];
+    unsigned int        discover_broadcast_timeout;
+    pthread_t           discover_thread;
+    bool                discover_started;
+    netbios_ns_discover_callbacks discover_callbacks;
 };
 
 typedef struct netbios_ns_name_query netbios_ns_name_query;
@@ -529,7 +538,7 @@ int      netbios_ns_resolve(netbios_ns *ns, const char *name, char type, uint32_
     ssize_t             recv;
     netbios_ns_name_query name_query;
 
-    assert(ns != NULL);
+    assert(ns != NULL && !ns->discover_started);
 
     if ((cached = netbios_ns_entry_find(ns, name, 0)) != NULL)
     {
@@ -612,6 +621,20 @@ int           netbios_ns_discover(netbios_ns *ns)
     return (1);
 }
 
+static int    netbios_ns_is_aborted(netbios_ns *ns)
+{
+    fd_set        read_fds;
+    int           res;
+    struct timeval timeout = {0, 0};
+
+    FD_ZERO(&read_fds);
+    FD_SET(ns->abort_pipe[0], &read_fds);
+
+    res = select(ns->abort_pipe[0] + 1, &read_fds, NULL, NULL, &timeout);
+
+    return (res < 0 || FD_ISSET(ns->abort_pipe[0], &read_fds)) ? 1 : 0;
+}
+
 void          netbios_ns_abort(netbios_ns *ns)
 {
     uint8_t buf = '\0';
@@ -665,7 +688,7 @@ error:
 
 const char *netbios_ns_inverse(netbios_ns *ns, uint32_t ip)
 {
-    assert(ns != NULL && ip != 0);
+    assert(ns != NULL && ip != 0 && !ns->discover_started);
     netbios_ns_entry *entry = netbios_ns_inverse_internal(ns, ip);
     return entry ? entry->name : NULL;
 }
@@ -690,7 +713,7 @@ char netbios_ns_entry_type(netbios_ns_entry *entry)
     return entry ? entry->type : -1;
 }
 
-void                netbios_ns_clear(netbios_ns *ns)
+void netbios_ns_clear(netbios_ns *ns)
 {
     netbios_ns_entry  *entry, *entry_next;
 
@@ -737,3 +760,143 @@ netbios_ns_entry  *netbios_ns_entry_at(netbios_ns *ns, int pos)
 
     return (iter);
 }
+
+static void *netbios_ns_discover_thread(void *opaque)
+{
+    netbios_ns *ns = (netbios_ns *) opaque;
+    while (true)
+    {
+        struct timespec tp;
+        netbios_ns_entry  *entry, *entry_next;
+
+        if (netbios_ns_is_aborted(ns))
+            return NULL;
+
+        // check if cached entries timeout
+        clock_gettime(CLOCK_REALTIME, &tp);
+        for (entry = TAILQ_FIRST(&ns->entry_queue);
+             entry != NULL; entry = entry_next)
+        {
+            entry_next = TAILQ_NEXT(entry, next);
+            if (tp.tv_sec - entry->last_time_seen > DISCOVER_REMOVE_TIMEOUT)
+            {
+                BDSM_dbg("Discover: on_entry_removed: %s\n", entry->name);
+                ns->discover_callbacks.pf_on_entry_removed(
+                        ns->discover_callbacks.p_opaque, entry);
+                TAILQ_REMOVE(&ns->entry_queue, entry, next);
+                free(entry);
+            }
+        }
+
+        // send broadbast
+        if (netbios_ns_send_name_query(ns, 0, NAME_QUERY_TYPE_NB,
+                                       name_query_broadcast, 0) == -1)
+            return NULL;
+
+        while (true)
+        {
+            struct timeval      timeout;
+            struct sockaddr_in  recv_addr;
+            int                 res;
+            netbios_ns_name_query name_query;
+
+            timeout.tv_sec = ns->discover_broadcast_timeout;
+            timeout.tv_usec = 0;
+
+            // receive NB or NBSTAT answers
+            res = netbios_ns_recv(ns, ns->discover_broadcast_timeout == 0 ?
+                                  NULL : &timeout,
+                                  &recv_addr,
+                                  false, 0, &name_query);
+            // error or abort
+            if (res == -1)
+                return NULL;
+
+            // timeout reached, broadcast again
+            if (res == 0)
+                break;
+
+            clock_gettime(CLOCK_REALTIME, &tp);
+
+            if (name_query.type == NAME_QUERY_TYPE_NB)
+            {
+                uint32_t ip = name_query.u.nb.ip;
+                entry = netbios_ns_entry_find(ns, NULL, ip);
+
+                if (!entry)
+                {
+                    entry = netbios_ns_entry_add(ns, ip);
+                    if (!entry)
+                        return NULL;
+                }
+                entry->last_time_seen = tp.tv_sec;
+
+                // if entry is already valid, don't send NBSTAT query
+                if (entry->flag & NS_ENTRY_FLAG_VALID_NAME)
+                    continue;
+
+                // send NBSTAT query
+                if (netbios_ns_send_name_query(ns, ip, NAME_QUERY_TYPE_NBSTAT,
+                                               name_query_broadcast, 0) == -1)
+                    return NULL;
+
+            }
+            else if (name_query.type == NAME_QUERY_TYPE_NBSTAT)
+            {
+                bool send_callback;
+
+                entry = netbios_ns_entry_find(ns, NULL,
+                                              recv_addr.sin_addr.s_addr);
+
+                // ignore NBSTAT answers that didn't answered to NB query first.
+                if (!entry)
+                    continue;
+
+                entry->last_time_seen = tp.tv_sec;
+
+                send_callback = !(entry->flag & NS_ENTRY_FLAG_VALID_NAME);
+
+                netbios_ns_entry_set_name(entry, name_query.u.nbstat.name,
+                                          name_query.u.nbstat.group,
+                                          name_query.u.nbstat.type);
+                if (send_callback)
+                    ns->discover_callbacks.pf_on_entry_added(
+                            ns->discover_callbacks.p_opaque, entry);
+            }
+        }
+        if (ns->discover_broadcast_timeout == 0)
+            break;
+    }
+    return NULL;
+}
+
+int netbios_ns_discover_start(netbios_ns *ns,
+                              unsigned int broadcast_timeout,
+                              netbios_ns_discover_callbacks *callbacks)
+{
+    if (ns->discover_started || !callbacks)
+        return (0);
+
+    ns->discover_callbacks = *callbacks;
+    ns->discover_broadcast_timeout = broadcast_timeout;
+    if (pthread_create(&ns->discover_thread, NULL,
+                       netbios_ns_discover_thread, ns) != 0)
+        return (0);
+    ns->discover_started = true;
+
+    return (1);
+}
+
+int netbios_ns_discover_stop(netbios_ns *ns)
+{
+    if (ns->discover_started)
+    {
+        netbios_ns_abort(ns);
+        pthread_join(ns->discover_thread, NULL);
+        ns->discover_started = false;
+
+        return (1);
+    }
+    else
+        return (0);
+}