From 8cefbcf1e028004efa70f2f1956fbc9d25c8b372 Mon Sep 17 00:00:00 2001 From: kraxel Date: Thu, 26 Jan 2006 16:27:01 +0000 Subject: - more mdns bits. --- mdns.c | 655 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 582 insertions(+), 73 deletions(-) (limited to 'mdns.c') diff --git a/mdns.c b/mdns.c index 9e27006..ec7d29f 100644 --- a/mdns.c +++ b/mdns.c @@ -3,8 +3,15 @@ #include #include +#include +#include +#include +#include /* FIXME: find more portable one */ + +#include +#include + #ifdef HAVE_AVAHI -# include # include # include # include @@ -18,6 +25,41 @@ #ifdef HAVE_AVAHI +static int debug = 0; + +/* ---------------------------------------------------------------------- */ + +enum { + /* browse */ + ST_COL_NAME = 0, + ST_COL_TYPE, + ST_COL_DOMAIN, + ST_COL_INTERFACE, + ST_COL_PROTOCOL, + + /* resolve */ + ST_COL_HOSTNAME, + ST_COL_ADDR, + ST_COL_PORT, + ST_COL_PATH, + + /* other */ + ST_COL_URL, + + ST_NUM_COLS +}; + +struct mdns_window { + GtkListStore *store; + GtkWidget *toplevel, *view, *status; + int standalone; + + const AvahiPoll *poll_api; + AvahiGLibPoll *glib_poll; + AvahiClient *client; + AvahiServiceBrowser *sb; +}; + /* ---------------------------------------------------------------------- */ static const char *revents[] = { @@ -32,27 +74,108 @@ static const char *bevents[] = { [ AVAHI_BROWSER_FAILURE ] = "FAILURE", }; -static const AvahiPoll *poll_api; -static AvahiGLibPoll *glib_poll; -static AvahiClient *client; - -struct mdns_entry { - const char *name; - const char *type; - const char *domain; - AvahiIfIndex interface; - AvahiProtocol protocol; - - const char *address; - const char *hostname; - uint16_t port; - - struct list_head next; -}; -static LIST_HEAD(mdns_entries); +/* ---------------------------------------------------------------------- */ + +static int find_entry(struct mdns_window *mdns, GtkTreeIter *iter, + const char *sname, const char *stype, const char *sdomain, + const char *sproto, const char *snif) +{ + GtkTreeModel *model = GTK_TREE_MODEL(mdns->store); + gboolean valid; + char *name, *type, *domain, *proto, *nif; + + for (valid = gtk_tree_model_get_iter_first(model, iter); + valid; + valid = gtk_tree_model_iter_next(model, iter)) + { + gtk_tree_model_get(model, iter, + ST_COL_NAME, &name, + ST_COL_TYPE, &type, + ST_COL_DOMAIN, &domain, + ST_COL_PROTOCOL, &proto, + ST_COL_INTERFACE, &nif, + -1); + if (0 == strcmp(name, sname) && + 0 == strcmp(type, stype) && + 0 == strcmp(domain, sdomain) && + 0 == strcmp(proto, sproto) && + 0 == strcmp(nif, snif)) + return 0; + } + return -1; +} + +static void get_entry(struct mdns_window *mdns, GtkTreeIter *iter, + const char *sname, const char *stype, const char *sdomain, + const char *sproto, const char *snif) +{ + if (0 == find_entry(mdns, iter, sname, stype, sdomain, sproto, snif)) + return; + gtk_list_store_append(mdns->store, iter); + gtk_list_store_set(mdns->store, iter, + ST_COL_NAME, sname, + ST_COL_TYPE, stype, + ST_COL_DOMAIN, sdomain, + ST_COL_PROTOCOL, sproto, + ST_COL_INTERFACE, snif, + -1); + if (debug) + fprintf(stderr, "add: %s: %s\n",stype, sname); +} + +static void del_entry(struct mdns_window *mdns, + const char *sname, const char *stype, const char *sdomain, + const char *sproto, const char *snif) +{ + GtkTreeIter iter; + + if (0 == find_entry(mdns, &iter, sname, stype, sdomain, sproto, snif)) + return; + gtk_list_store_remove(mdns->store, &iter); + if (debug) + fprintf(stderr, "del: %s: %s\n", stype, sname); +} + +static void del_entries(struct mdns_window *mdns) +{ + GtkTreeModel *model = GTK_TREE_MODEL(mdns->store); + GtkTreeIter iter; + + while (gtk_tree_model_get_iter_first(model, &iter)) + gtk_list_store_remove(mdns->store, &iter); +} /* ---------------------------------------------------------------------- */ +static void ifname(char *dst, int len, int nif) +{ +#ifdef SIOCGIFNAME + struct ifreq ifr; + int fd; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + goto fallback; + + ifr.ifr_ifindex = nif; + if (ioctl (fd, SIOCGIFNAME, &ifr) < 0) { + close (fd); + goto fallback; + } + snprintf(dst, len, "%s", ifr.ifr_name); + close (fd); + return; + + fallback: +#endif + + if (-1 == nif) { + snprintf(dst, len, "wide"); + return; + } + snprintf(dst, len, "if-%d", nif); +} + static void resolve_callback(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol, @@ -67,19 +190,66 @@ static void resolve_callback(AvahiServiceResolver *r, AvahiLookupResultFlags flags, void* userdata) { - struct mdns_entry *e = userdata; - char a[AVAHI_ADDRESS_STR_MAX]; + static const struct { + char *type; + char *proto; + int defport; + } protos[] = { + { "_http._tcp", "http", 80 }, + { "_ftp._tcp", "ftp", 21 }, + }; + struct mdns_window *mdns = userdata; + char a[AVAHI_ADDRESS_STR_MAX], p[32], url[256]; + unsigned char *path = NULL; + char *proto = NULL; + AvahiStringList *pathlist; + GtkTreeIter iter; + char nif[32]; + int defport, i; switch (event) { case AVAHI_RESOLVER_FOUND: + ifname(nif, sizeof(nif), interface); + if (0 != find_entry(mdns, &iter, name, type, domain, + avahi_proto_to_string(protocol), nif)) + break; avahi_address_snprint(a, sizeof(a), address); - e->address = strdup(a); - e->hostname = strdup(host_name); - e->port = port; - fprintf(stderr, "%s @ %s: %s | %s if #%d | %s:%d (%s)\n", - e->type, e->domain, e->name, - avahi_proto_to_string(e->protocol), e->interface, - e->address, e->port, e->hostname); + snprintf(p, sizeof(p), "%d", port); + gtk_list_store_set(mdns->store, &iter, + ST_COL_HOSTNAME, host_name, + ST_COL_ADDR, a, + ST_COL_PORT, p, + -1); + + /* path */ + pathlist = avahi_string_list_find(txt, "path"); + if (pathlist) + path = avahi_string_list_get_text(pathlist); + if (!path) + break; + gtk_list_store_set(mdns->store, &iter, + ST_COL_PATH, path+5, + -1); + + /* url */ + for (i = 0; i < sizeof(protos)/sizeof(protos[0]); i++) { + if (0 != strcmp(protos[i].type, type)) + continue; + proto = protos[i].proto; + defport = protos[i].defport; + break; + } + if (!proto) + break; + if (defport != port) + snprintf(url, sizeof(url), "%s://%s:%d%s", + proto, host_name, port, path+5); + else + snprintf(url, sizeof(url), "%s://%s%s", + proto, host_name, path+5); + gtk_list_store_set(mdns->store, &iter, + ST_COL_URL, url, + -1); break; default: fprintf(stderr, "%s: %s (#%d)\n", __FUNCTION__, @@ -99,29 +269,29 @@ static void browse_callback(AvahiServiceBrowser *b, AvahiLookupResultFlags flags, void* userdata) { - AvahiClient *c = userdata; - struct mdns_entry *e; + struct mdns_window *mdns = userdata; + GtkTreeIter iter; + char nif[32]; + + ifname(nif, sizeof(nif), interface); switch (event) { case AVAHI_BROWSER_NEW: - e = malloc(sizeof(*e)); - memset(e,0,sizeof(*e)); - e->name = strdup(name); - e->type = strdup(type); - e->domain = strdup(domain); - e->interface = interface; - e->protocol = protocol; - list_add_tail(&e->next, &mdns_entries); - avahi_service_resolver_new(c, interface, protocol, name, type, domain, - AVAHI_PROTO_UNSPEC, 0, resolve_callback, e); + get_entry(mdns, &iter, name, type, domain, + avahi_proto_to_string(protocol), nif); + avahi_service_resolver_new(mdns->client, + interface, protocol, name, type, domain, + AVAHI_PROTO_UNSPEC, 0, + resolve_callback, mdns); break; case AVAHI_BROWSER_REMOVE: - fprintf(stderr, "%s: DEL: service '%s' of type '%s' in domain '%s'\n", - __FUNCTION__, name, type, domain); + del_entry(mdns, name, type, domain, + avahi_proto_to_string(protocol), nif); break; default: - fprintf(stderr, "%s: %s (#%d)\n", __FUNCTION__, - bevents[event], event); + if (debug) + fprintf(stderr, "%s: %s (#%d)\n", __FUNCTION__, + bevents[event], event); break; } } @@ -129,80 +299,419 @@ static void browse_callback(AvahiServiceBrowser *b, static void client_callback(AvahiClient *client, AvahiClientState state, void *userdata) { +// struct mdns_window *mdns = userdata; + switch (state) { case AVAHI_CLIENT_FAILURE: fprintf(stderr, "%s: error: connection lost\n", __FUNCTION__); break; default: - fprintf(stderr, "%s: state %d\n", __FUNCTION__, state); + if (debug) + fprintf(stderr, "%s: state %d\n", __FUNCTION__, state); + break; + } +} + +/* ---------------------------------------------------------------------- */ + +static void service_type_browser_callback(AvahiServiceTypeBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata) +{ + char nif[32]; + + switch (event) { + case AVAHI_BROWSER_NEW: + ifname(nif, sizeof(nif), interface); + fprintf(stderr, " service: %s, %s, %s: %s\n", + nif, avahi_proto_to_string(protocol), domain, type); + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_FAILURE: + avahi_service_type_browser_free(b); + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + /* don't log */ + break; + default: + fprintf(stderr, "%s: %s (#%d)\n", __FUNCTION__, + bevents[event], event); + break; + } +} + +static void domain_browser_callback(AvahiDomainBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata) +{ + struct mdns_window *mdns = userdata; + char nif[32]; + + switch (event) { + case AVAHI_BROWSER_NEW: + ifname(nif, sizeof(nif), interface); + fprintf(stderr, " domain : %s, %s, %s\n", + nif, avahi_proto_to_string(protocol), domain); + avahi_service_type_browser_new(mdns->client, + interface, protocol, domain, + 0, service_type_browser_callback, mdns); + break; + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_FAILURE: + avahi_domain_browser_free(b); + break; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + /* don't log */ + break; + default: + fprintf(stderr, "%s: %s (#%d)\n", __FUNCTION__, + bevents[event], event); break; } } +static void dump_stuff(struct mdns_window *mdns) +{ + const char *domain; + + /* playground ... */ + domain = avahi_client_get_domain_name(mdns->client); + fprintf(stderr, "default domain is \"%s\"\n", domain); + avahi_service_type_browser_new(mdns->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, domain, + 0, service_type_browser_callback, mdns); + avahi_domain_browser_new(mdns->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, NULL, + AVAHI_DOMAIN_BROWSER_BROWSE, + 0, domain_browser_callback, mdns); +} + /* ---------------------------------------------------------------------- */ -int mdns_init(void) +static void mdns_fini(struct mdns_window *mdns) +{ + if (mdns->client) { + if (debug) + fprintf(stderr, "%s\n", __FUNCTION__); + avahi_client_free(mdns->client); + mdns->client = NULL; + } + if (mdns->glib_poll) { + avahi_glib_poll_free(mdns->glib_poll); + mdns->glib_poll = NULL; + } +} + +static int mdns_init(struct mdns_window *mdns) { int error; + if (mdns->client) + return 0; + if (debug) + fprintf(stderr, "%s\n", __FUNCTION__); + /* Create the GLIB Adaptor */ avahi_set_allocator(avahi_glib_allocator()); - glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); - poll_api = avahi_glib_poll_get(glib_poll); - client = avahi_client_new(poll_api, AVAHI_CLIENT_NO_FAIL, - client_callback, NULL /* user data */, - &error); - if (client == NULL) + mdns->glib_poll = avahi_glib_poll_new(NULL, G_PRIORITY_DEFAULT); + mdns->poll_api = avahi_glib_poll_get(mdns->glib_poll); + mdns->client = avahi_client_new(mdns->poll_api, AVAHI_CLIENT_NO_FAIL, + client_callback, mdns, + &error); + if (mdns->client == NULL) goto fail; + + if (0) + dump_stuff(mdns); return 0; fail: - mdns_fini(); + mdns_fini(mdns); return -1; } -int mdns_browse(const char *service, const char *domain) +int mdns_browse(struct mdns_window *mdns, + const char *service, const char *domain) { - AvahiServiceBrowser *sb = NULL; + char label[256]; + + if (mdns->sb) { + avahi_service_browser_free(mdns->sb); + mdns->sb = NULL; + del_entries(mdns); + gtk_label_set_text(GTK_LABEL(mdns->status), "idle"); + } if (NULL == domain) - domain = avahi_client_get_domain_name(client); - sb = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, - service, domain, 0, browse_callback, client); - if (NULL == sb) { + domain = avahi_client_get_domain_name(mdns->client); + mdns->sb = avahi_service_browser_new(mdns->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + service, domain, 0, + browse_callback, mdns); + if (NULL == mdns->sb) { fprintf(stderr, "%s: failed to create service browser: %s\n", - __FUNCTION__, avahi_strerror(avahi_client_errno(client))); + __FUNCTION__, avahi_strerror(avahi_client_errno(mdns->client))); return -1; } + snprintf(label, sizeof(label), + "service \"%s\" in domain \"%s\"", + service, domain); + gtk_label_set_text(GTK_LABEL(mdns->status), label); return 0; } -void mdns_fini(void) +/* ---------------------------------------------------------------------- */ + +static void menu_cb_close(GtkWidget *whatever, gpointer userdata) +{ + struct mdns_window *mdns = userdata; + + gtk_widget_destroy(mdns->toplevel); +} + +static void destroy(GtkWidget *widget, + gpointer data) +{ + struct mdns_window *mdns = data; + + mdns_fini(mdns); + g_object_unref(mdns->store); + if (mdns->standalone) + gtk_main_quit(); + free(mdns); +} + +/* ---------------------------------------------------------------------- */ + +static const GtkActionEntry entries[] = { + { + .name = "FileMenu", + .label = "_File", + },{ + .name = "Close", + .stock_id = GTK_STOCK_CLOSE, + .label = "_Close", + .accelerator = "Q", + .callback = G_CALLBACK(menu_cb_close), + }, +}; + +static char ui_xml[] = +"" +" " +" " +" " +" " +" " +" " +" " +" " +""; + +/* ------------------------------------------------------------------ */ + +static GtkWidget *mdns_create_view(struct mdns_window *mdns) +{ + GtkCellRenderer *renderer; + GtkWidget *view; + + view = gtk_tree_view_new(); + gtk_tree_view_set_model(GTK_TREE_VIEW(view), + GTK_TREE_MODEL(mdns->store)); + gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)), + GTK_SELECTION_SINGLE); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "name", renderer, + "text", ST_COL_NAME, + NULL); + +#if 0 + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "type", renderer, + "text", ST_COL_TYPE, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "domain", renderer, + "text", ST_COL_DOMAIN, + NULL); +#endif + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "if", renderer, + "text", ST_COL_INTERFACE, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "proto", renderer, + "text", ST_COL_PROTOCOL, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "hostname", renderer, + "text", ST_COL_HOSTNAME, + NULL); + + renderer = gtk_cell_renderer_text_new(); + g_object_set(renderer, + "xalign", 1.0, + NULL); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "address", renderer, + "text", ST_COL_ADDR, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "port", renderer, + "text", ST_COL_PORT, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "path", renderer, + "text", ST_COL_PATH, + NULL); + + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "url", renderer, + "text", ST_COL_URL, + NULL); + + /* fill remaining space */ + renderer = gtk_cell_renderer_text_new(); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW(view), -1, "", renderer, + NULL); + + return view; +} + +struct mdns_window *mdns_create_window(int standalone) { - if (client) { - avahi_client_free(client); - client = NULL; + struct mdns_window *mdns; + GtkWidget *vbox, *menubar, *toolbar, *scroll; + GtkAccelGroup *accel; + GtkActionGroup *ag; + GtkUIManager *ui; + GError *err; + + mdns = malloc(sizeof(*mdns)); + memset(mdns,0,sizeof(*mdns)); + if (-1 == mdns_init(mdns)) { + free(mdns); + return NULL; } - if (glib_poll) { - avahi_glib_poll_free(glib_poll); - glib_poll = NULL; + mdns->standalone = standalone; + + mdns->toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(mdns->toplevel), "mdns"); + gtk_widget_set_size_request(GTK_WIDGET(mdns->toplevel), 480, 320); + g_signal_connect(G_OBJECT(mdns->toplevel), "destroy", + G_CALLBACK(destroy), mdns); + + /* menu + toolbar */ + ui = gtk_ui_manager_new(); + ag = gtk_action_group_new("MenuActions"); + gtk_action_group_add_actions(ag, entries, G_N_ELEMENTS(entries), mdns); + gtk_ui_manager_insert_action_group(ui, ag, 0); + accel = gtk_ui_manager_get_accel_group(ui); + gtk_window_add_accel_group(GTK_WINDOW(mdns->toplevel), accel); + + err = NULL; + if (!gtk_ui_manager_add_ui_from_string(ui, ui_xml, -1, &err)) { + g_message("building menus failed: %s", err->message); + g_error_free(err); + exit(1); } + + /* list */ + mdns->store = gtk_list_store_new(ST_NUM_COLS, + G_TYPE_STRING, // ST_COL_NAME + G_TYPE_STRING, // ST_COL_TYPE + G_TYPE_STRING, // ST_COL_DOMAIN + G_TYPE_STRING, // ST_COL_INTERFACE + G_TYPE_STRING, // ST_COL_PROTOCOL + + G_TYPE_STRING, // ST_COL_HOSTNAME + G_TYPE_STRING, // ST_COL_ADDR + G_TYPE_STRING, // ST_COL_PORT + G_TYPE_STRING, // ST_COL_PATH + + G_TYPE_STRING); // ST_COL_URL + mdns->view = mdns_create_view(mdns); +#if 0 + g_signal_connect(mdns->view, "row-activated", G_CALLBACK(activate), mdns); +#endif + scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + + /* other widgets */ + mdns->status = gtk_widget_new(GTK_TYPE_LABEL, + "label", "status line", + "xalign", 0.0, + NULL); + + /* Make a vbox and put stuff in */ + vbox = gtk_vbox_new(FALSE, 1); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 1); + gtk_container_add(GTK_CONTAINER(mdns->toplevel), vbox); + menubar = gtk_ui_manager_get_widget(ui, "/MainMenu"); + gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0); + toolbar = gtk_ui_manager_get_widget(ui, "/ToolBar"); + gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); + gtk_container_add(GTK_CONTAINER(scroll), mdns->view); + gtk_box_pack_end(GTK_BOX(vbox), mdns->status, FALSE, TRUE, 0); + + return mdns; } -#else +void mdns_show_window(struct mdns_window *mdns) +{ + gtk_widget_show_all(mdns->toplevel); +} -int mdns_init(void) +void mdns_destroy_window(struct mdns_window *mdns) { - return -1; + gtk_widget_destroy(mdns->toplevel); } -int mdns_browse(const char *service, const char *domain) +/* ---------------------------------------------------------------------- */ + +#else /* ! HAVE_AVAHI */ + +struct mdns_window *mdns_create_window(void) { - return -1; + return NULL; } -void mdns_fini(void) +void mdns_show_window(struct mdns_window *mdns) {} +void mdns_destroy_window(struct mdns_window *mdns) {} + +int mdns_browse(struct mdns_window *mdns, + const char *service, const char *domain) { + return -1; } #endif -- cgit