*
* Copyright (C) 2019 and up by Alexander Pevzner (pzz@apevzner.com)
* See LICENSE for license terms and conditions
*
* Web Services Dynamic Discovery (WS-Discovery)
*/
#define _GNU_SOURCE
#include "airscan.h"
#include <errno.h>
#include <fnmatch.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <net/if.h>
#ifdef BSD
# include <net/if_dl.h>
#endif
#include <sys/socket.h>
*/
#define WSDD_RETRANSMIT_MIN 100
#define WSDD_RETRANSMIT_MAX 250
#define WSDD_DISCOVERY_TIME 2500
#define WSDD_DISCOVERY_TIME_EX 5000
*/
#define WSDD_STABLE_ENDPOINT \
"/StableWSDiscoveryEndpoint/schemas-xmlsoap-org_ws_2005_04_discovery"
*/
typedef struct {
int fd;
int ifindex;
netif_name ifname;
bool ipv6;
eloop_fdpoll *fdpoll;
eloop_timer *timer;
uint32_t time_limit;
uint32_t time_elapsed;
ip_straddr str_ifaddr;
ip_straddr str_sockaddr;
bool initscan;
} wsdd_resolver;
* device discovery
*/
typedef struct {
zeroconf_finding finding;
const char *address;
ll_head xaddrs;
ll_head xaddrs_unresolved;
zeroconf_endpoint *endpoints_unresolved;
mdns_resolver *mdns_resolver;
http_client *http_client;
ll_node list_node;
eloop_timer *publish_timer;
bool is_printer;
bool is_scanner;
bool published;
} wsdd_finding;
*/
typedef struct {
http_uri *uri;
ll_node list_node;
} wsdd_xaddr;
*/
typedef enum {
WSDD_ACTION_UNKNOWN,
WSDD_ACTION_HELLO,
WSDD_ACTION_BYE,
WSDD_ACTION_PROBEMATCHES
} WSDD_ACTION;
*/
typedef struct {
WSDD_ACTION action;
const char *address;
ll_head xaddrs;
bool is_scanner;
bool is_printer;
} wsdd_message;
*/
static void
wsdd_finding_add_xaddr (wsdd_finding *wsdd, wsdd_xaddr *xaddr);
static void
wsdd_finding_get_metadata (wsdd_finding *wsdd, int ifindex, wsdd_xaddr *xaddr);
static void
wsdd_message_free(wsdd_message *msg);
static void
wsdd_resolver_send_probe (wsdd_resolver *resolver);
static wsdd_resolver*
wsdd_netif_resolver_by_ifindex (int ifindex);
*/
static log_ctx *wsdd_log;
static netif_notifier *wsdd_netif_notifier;
static netif_addr *wsdd_netif_addr_list;
static int wsdd_mcsock_ipv4 = -1;
static int wsdd_mcsock_ipv6 = -1;
static eloop_fdpoll *wsdd_fdpoll_ipv4;
static eloop_fdpoll *wsdd_fdpoll_ipv6;
static char wsdd_buf[65536];
static struct sockaddr_in wsdd_mcast_ipv4;
static struct sockaddr_in6 wsdd_mcast_ipv6;
static ll_head wsdd_finding_list;
static int wsdd_initscan_count;
static http_client *wsdd_http_client;
static ip_addrset *wsdd_addrs_probing;
*/
static const char *wsdd_probe_template =
"<?xml version=\"1.0\"?>"
"<soap:Envelope xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:wsdp=\"http://schemas.xmlsoap.org/ws/2006/02/devprof\">"
"<soap:Header>"
"<wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>"
"<wsa:MessageID>%s</wsa:MessageID>"
"<wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>"
"</soap:Header>"
"<soap:Body>"
"<wsd:Probe>"
"<wsd:Types>wsdp:Device</wsd:Types>"
"</wsd:Probe>"
"</soap:Body>"
"</soap:Envelope>";
*/
static const char *wsdd_get_metadata_template =
"<?xml version=\"1.0\"?>"
"<soap:Envelope xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\">"
"<soap:Header>"
"<wsa:Action>http://schemas.xmlsoap.org/ws/2004/09/transfer/Get</wsa:Action>"
"<wsa:MessageID>%s</wsa:MessageID>"
"<wsa:To>%s</wsa:To>"
"<wsa:ReplyTo>"
"<wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>"
"</wsa:ReplyTo>"
"</soap:Header>"
"<soap:Body/>"
"</soap:Envelope>";
*/
static const xml_ns wsdd_ns_rules[] = {
{"s", "http*://schemas.xmlsoap.org/soap/envelope"},
{"s", "http*://www.w3.org/2003/05/soap-envelope"},
{"d", "http*://schemas.xmlsoap.org/ws/2005/04/discovery"},
{"a", "http*://schemas.xmlsoap.org/ws/2004/08/addressing"},
{"devprof", "http*://schemas.xmlsoap.org/ws/2006/02/devprof"},
{"mex", "http*://schemas.xmlsoap.org/ws/2004/09/mex"},
{"pnpx", "http*://schemas.microsoft.com/windows/pnpx/2005/10"},
{NULL, NULL}
};
*/
static wsdd_xaddr*
wsdd_xaddr_new (http_uri *uri)
{
wsdd_xaddr *xaddr = mem_new(wsdd_xaddr, 1);
xaddr->uri = uri;
return xaddr;
}
*/
static void
wsdd_xaddr_free (wsdd_xaddr *xaddr)
{
http_uri_free(xaddr->uri);
mem_free(xaddr);
}
* Takes ownership on URI.
*/
static void
wsdd_xaddr_list_add (ll_head *list, http_uri *uri)
{
wsdd_xaddr *xaddr;
ll_node *node;
for (LL_FOR_EACH(node, list)) {
xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
if (http_uri_equal(xaddr->uri, uri)) {
http_uri_free(uri);
return;
}
}
xaddr = wsdd_xaddr_new(uri);
ll_push_end(list, &xaddr->list_node);
}
*/
static void
wsdd_xaddr_list_purge (ll_head *list)
{
ll_node *node;
while ((node = ll_pop_beg(list)) != NULL) {
wsdd_xaddr *xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
wsdd_xaddr_free(xaddr);
}
}
*/
static void
wsdd_initscan_count_inc (void)
{
wsdd_initscan_count ++;
}
*/
static void
wsdd_initscan_count_dec (void)
{
log_assert(wsdd_log, wsdd_initscan_count > 0);
wsdd_initscan_count --;
if (wsdd_initscan_count == 0) {
zeroconf_finding_done(ZEROCONF_WSD);
}
}
*/
static wsdd_finding*
wsdd_finding_new (int ifindex, const char *address)
{
wsdd_finding *wsdd = mem_new(wsdd_finding, 1);
wsdd->finding.method = ZEROCONF_WSD;
wsdd->finding.uuid = uuid_parse(address);
if (!uuid_valid(wsdd->finding.uuid)) {
wsdd->finding.uuid = uuid_hash(address);
}
wsdd->finding.addrs = ip_addrset_new();
wsdd->finding.ifindex = ifindex;
wsdd->address = str_dup(address);
ll_init(&wsdd->xaddrs);
ll_init(&wsdd->xaddrs_unresolved);
wsdd->mdns_resolver = mdns_resolver_new(ifindex);
wsdd->http_client = http_client_new(wsdd_log, wsdd);
return wsdd;
}
*/
static void
wsdd_finding_free (wsdd_finding *wsdd)
{
if (wsdd->published) {
zeroconf_finding_withdraw(&wsdd->finding);
}
mdns_resolver_cancel(wsdd->mdns_resolver);
mdns_resolver_free(wsdd->mdns_resolver);
http_client_cancel(wsdd->http_client);
http_client_free(wsdd->http_client);
if (wsdd->publish_timer != NULL) {
eloop_timer_cancel(wsdd->publish_timer);
}
zeroconf_endpoint_list_free(wsdd->finding.endpoints);
mem_free((char*) wsdd->address);
wsdd_xaddr_list_purge(&wsdd->xaddrs);
wsdd_xaddr_list_purge(&wsdd->xaddrs_unresolved);
zeroconf_endpoint_list_free(wsdd->endpoints_unresolved);
ip_addrset_free(wsdd->finding.addrs);
mem_free((char*) wsdd->finding.model);
mem_free((char*) wsdd->finding.name);
mem_free(wsdd);
}
*/
static void
wsdd_finding_publish (wsdd_finding *wsdd)
{
if (wsdd->published) {
return;
}
wsdd->published = true;
wsdd->finding.endpoints = zeroconf_endpoint_list_sort_dedup(
wsdd->finding.endpoints);
if (wsdd->publish_timer != NULL) {
log_debug(wsdd_log, "\"%s\": publish-delay timer canceled",
wsdd->finding.model);
eloop_timer_cancel(wsdd->publish_timer);
wsdd->publish_timer = NULL;
}
zeroconf_finding_publish(&wsdd->finding);
}
* has pending MDNS or HTTP queries
*/
static bool
wsdd_finding_has_pending_queries (wsdd_finding *wsdd)
{
return mdns_resolver_has_pending(wsdd->mdns_resolver) ||
http_client_has_pending(wsdd->http_client);
}
*/
static void
wsdd_finding_publish_delay_timer_callback (void *data)
{
wsdd_finding *wsdd = data;
wsdd->publish_timer = NULL;
log_debug(wsdd_log, "\"%s\": publish-delay timer expired",
wsdd->finding.model);
wsdd_finding_publish(wsdd);
}
*/
static void
wsdd_finding_publish_delay (wsdd_finding *wsdd)
{
bool delay = false;
if (wsdd->published) {
return;
}
* and we have not yet discovered address of the same address
* family of device
*
* Some devices doesn't return their IPv4 endpoints, if
* metadata is queried via IPv6, and visa versa. This is
* possible that we will finish discovery of the particular
* address family, before we'll ever know that the device
* may have address of another address family, so part
* of addresses will be never discovered (see #44 for details).
*
* To prevent this situation, we continue discovery with
* some reasonable delay, if network interface has IPv4/IPv6
* address, but device is not yet.
*/
if (netif_has_non_link_local_addr(AF_INET, wsdd->finding.ifindex) &&
!zeroconf_endpoint_list_has_non_link_local_addr(AF_INET,
wsdd->finding.endpoints)) {
log_debug(wsdd_log,
"\"%s\": IPv4 address expected but not yet discovered",
wsdd->finding.model);
delay = true;
}
if (netif_has_non_link_local_addr(AF_INET6, wsdd->finding.ifindex) &&
!zeroconf_endpoint_list_has_non_link_local_addr(AF_INET6,
wsdd->finding.endpoints)) {
log_debug(wsdd_log,
"\"%s\": IPv6 address expected but not yet discovered",
wsdd->finding.model);
delay = true;
}
if (delay) {
if (wsdd->publish_timer == NULL) {
wsdd->publish_timer = eloop_timer_new(ZEROCONF_PUBLISH_DELAY,
wsdd_finding_publish_delay_timer_callback, wsdd);
}
return;
}
wsdd_finding_publish(wsdd);
}
*/
static wsdd_finding*
wsdd_finding_get (int ifindex, const char *address)
{
ll_node *node;
wsdd_finding *wsdd;
for (LL_FOR_EACH(node, &wsdd_finding_list)) {
wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
if (wsdd->finding.ifindex == ifindex &&
!strcmp(wsdd->address, address)) {
return wsdd;
}
}
wsdd = wsdd_finding_new(ifindex, address);
ll_push_end(&wsdd_finding_list, &wsdd->list_node);
return wsdd;
}
*/
static wsdd_finding*
wsdd_finding_by_address (ip_addr addr)
{
ll_node *node, *node2;
wsdd_finding *wsdd;
wsdd_xaddr *xaddr;
const struct sockaddr *sockaddr;
for (LL_FOR_EACH(node, &wsdd_finding_list)) {
wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
for (LL_FOR_EACH(node2, &wsdd->xaddrs)) {
xaddr = OUTER_STRUCT(node2, wsdd_xaddr, list_node);
sockaddr = http_uri_addr(xaddr->uri);
if (sockaddr != NULL) {
ip_addr addr2 = ip_addr_from_sockaddr(sockaddr);
if (ip_addr_equal(addr, addr2)) {
return wsdd;
}
}
}
}
return NULL;
}
*/
static bool
wsdd_finding_has_xaddr (wsdd_finding *wsdd, const wsdd_xaddr *xaddr)
{
ll_node *node;
wsdd_xaddr *xaddr2;
for (LL_FOR_EACH(node, &wsdd->xaddrs)) {
xaddr2 = OUTER_STRUCT(node, wsdd_xaddr, list_node);
if (http_uri_equal(xaddr->uri, xaddr2->uri)) {
return true;
}
}
for (LL_FOR_EACH(node, &wsdd->xaddrs_unresolved)) {
xaddr2 = OUTER_STRUCT(node, wsdd_xaddr, list_node);
if (http_uri_equal(xaddr->uri, xaddr2->uri)) {
return true;
}
}
return false;
}
* hostnames in xaddrs
*/
static void
wsdd_finding_mdns_resolver_xaddr_callback (const mdns_query *query)
{
wsdd_finding *wsdd = mdns_query_get_ptr(query);
const char *host = mdns_query_get_name(query);
const ip_addrset *answer = mdns_query_get_answer(query);
size_t count;
const ip_addr *addrs = ip_addrset_addresses(answer, &count);
ll_node *node;
for (LL_FOR_EACH(node, &wsdd->xaddrs_unresolved)) {
wsdd_xaddr *xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
if (http_uri_host_is(xaddr->uri, host)) {
size_t i;
for (i = 0; i < count; i ++) {
ip_addr addr = addrs[i];
http_uri *uri = http_uri_clone(xaddr->uri);
wsdd_xaddr *xaddr_resolved;
http_uri_set_host_addr(uri, addr);
xaddr_resolved = wsdd_xaddr_new(uri);
wsdd_finding_add_xaddr(wsdd, xaddr_resolved);
}
}
}
}
* hostnames in endpoints
*/
static void
wsdd_finding_mdns_resolver_endpoint_callback (const mdns_query *query)
{
wsdd_finding *wsdd = mdns_query_get_ptr(query);
const char *host = mdns_query_get_name(query);
const ip_addrset *answer = mdns_query_get_answer(query);
size_t count;
const ip_addr *addrs = ip_addrset_addresses(answer, &count);
zeroconf_endpoint *ep;
for (ep = wsdd->endpoints_unresolved; ep != NULL; ep = ep->next) {
if (http_uri_host_is(ep->uri, host)) {
size_t i;
for (i = 0; i < count; i ++) {
ip_addr addr = addrs[i];
http_uri *uri = http_uri_clone(ep->uri);
zeroconf_endpoint *ep_resolved;
http_uri_set_host_addr(uri, addr);
ip_addrset_add(wsdd->finding.addrs, addr);
ep_resolved = zeroconf_endpoint_new(ID_PROTO_WSD, uri);
ep_resolved->next = wsdd->finding.endpoints;
wsdd->finding.endpoints = ep_resolved;
}
}
}
}
* This function takes ownership over xaddr
*/
static void
wsdd_finding_add_xaddr (wsdd_finding *wsdd, wsdd_xaddr *xaddr)
{
if (wsdd_finding_has_xaddr(wsdd, xaddr)) {
wsdd_xaddr_free(xaddr);
return;
}
if (http_uri_is_literal(xaddr->uri)) {
ll_push_end(&wsdd->xaddrs, &xaddr->list_node);
if (wsdd->is_scanner) {
wsdd_finding_get_metadata(wsdd, wsdd->finding.ifindex, xaddr);
}
} else {
ll_push_end(&wsdd->xaddrs_unresolved, &xaddr->list_node);
mdns_query_submit(
wsdd->mdns_resolver,
http_uri_get_host(xaddr->uri),
wsdd_finding_mdns_resolver_xaddr_callback,
wsdd
);
}
}
*/
static void
wsdd_finding_del (const char *address)
{
ll_node *node;
for (LL_FOR_EACH(node, &wsdd_finding_list)) {
wsdd_finding *wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
if (!strcmp(wsdd->address, address)) {
ll_del(&wsdd->list_node);
wsdd_finding_free(wsdd);
return;
}
}
}
*/
static void
wsdd_finding_list_purge (void)
{
ll_node *node;
while ((node = ll_first(&wsdd_finding_list)) != NULL) {
wsdd_finding *wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
ll_del(&wsdd->list_node);
wsdd_finding_free(wsdd);
}
}
* device metadata:
* <devprof:Hosted>
* <wsa:EndpointReference>
* <wsa:Address>http://192.168.1.102:5358/WSDScanner</wsa:Address>
* </addressing:EndpointReference>
* <devprof:Types>scan:ScannerServiceType</devprof:Types>
* <devprof:ServiceId>uri:4509a320-00a0-008f-00b6-002507510eca/WSDScanner</devprof:ServiceId>
* <pnpx:CompatibleId>http://schemas.microsoft.com/windows/2006/08/wdp/scan/ScannerServiceType</pnpx:CompatibleId>
* <pnpx:HardwareId>VEN_0103&DEV_069D</pnpx:HardwareId>
* </devprof:Hosted>
*
* It ignores all endpoints except ScannerServiceType, extracts endpoint
* URLs and prepends them to the wsdd->finding.endpoints
*/
static void
wsdd_finding_parse_endpoints (wsdd_finding *wsdd, xml_rd *xml)
{
unsigned int level = xml_rd_depth(xml);
size_t prefixlen = strlen(xml_rd_node_path(xml));
bool is_scanner = false;
zeroconf_endpoint *endpoints = NULL;
while (!xml_rd_end(xml)) {
const char *path = xml_rd_node_path(xml) + prefixlen;
const char *val;
if (!strcmp(path, "/devprof:Types")) {
val = xml_rd_node_value(xml);
if (strstr(val, "ScannerServiceType") != NULL) {
is_scanner = true;
}
} else if (!strcmp(path, "/a:EndpointReference/a:Address")) {
http_uri *uri;
zeroconf_endpoint *ep;
val = xml_rd_node_value(xml);
uri = http_uri_new(val, true);
if (uri != NULL) {
http_uri_fix_ipv6_zone(uri, wsdd->finding.ifindex);
ep = zeroconf_endpoint_new(ID_PROTO_WSD, uri);
ep->next = endpoints;
endpoints = ep;
}
}
xml_rd_deep_next(xml, level);
}
if (!is_scanner) {
zeroconf_endpoint_list_free(endpoints);
return;
}
while (endpoints != NULL) {
zeroconf_endpoint *ep = endpoints;
const struct sockaddr *addr = http_uri_addr(ep->uri);
endpoints = endpoints->next;
if (addr != NULL) {
ip_addrset_add(wsdd->finding.addrs, ip_addr_from_sockaddr(addr));
ep->next = wsdd->finding.endpoints;
wsdd->finding.endpoints = ep;
} else if (!zeroconf_endpoint_list_contains(wsdd->endpoints_unresolved,
ep)) {
ep->next = wsdd->endpoints_unresolved;
wsdd->endpoints_unresolved = ep;
mdns_query_submit(
wsdd->mdns_resolver,
http_uri_get_host(ep->uri),
wsdd_finding_mdns_resolver_endpoint_callback,
wsdd
);
} else {
zeroconf_endpoint_free_single(ep);
}
}
}
*/
static void
wsdd_finding_get_metadata_callback (void *ptr, http_query *q)
{
error err;
xml_rd *xml = NULL;
http_data *data;
wsdd_finding *wsdd = ptr;
char *model = NULL, *manufacturer = NULL;
err = http_query_error(q);
if (err != NULL) {
log_trace(wsdd_log, "metadata query: %s", ESTRING(err));
goto DONE;
}
data = http_query_get_response_data(q);
if (data->size == 0) {
log_trace(wsdd_log, "metadata query: no data");
goto DONE;
}
err = xml_rd_begin(&xml, data->bytes, data->size, wsdd_ns_rules);
if (err != NULL) {
log_trace(wsdd_log, "metadata query: %s", ESTRING(err));
goto DONE;
}
while (!xml_rd_end(xml)) {
const char *path = xml_rd_node_path(xml);
if (!strcmp(path, "s:Envelope/s:Body/mex:Metadata/mex:MetadataSection"
"/devprof:Relationship/devprof:Hosted")) {
wsdd_finding_parse_endpoints(wsdd, xml);
} else if (!strcmp(path, "s:Envelope/s:Body/mex:Metadata/mex:MetadataSection"
"/devprof:ThisModel/devprof:Manufacturer")) {
if (manufacturer == NULL) {
manufacturer = str_dup(xml_rd_node_value(xml));
}
} else if (!strcmp(path, "s:Envelope/s:Body/mex:Metadata/mex:MetadataSection"
"/devprof:ThisModel/devprof:ModelName")) {
if (model == NULL) {
model = str_dup(xml_rd_node_value(xml));
}
}
xml_rd_deep_next(xml, 0);
}
if (wsdd->finding.model == NULL) {
if (model != NULL && manufacturer != NULL &&
str_has_prefix(model, manufacturer)) {
mem_free(manufacturer);
manufacturer = NULL;
}
if (model != NULL && manufacturer != NULL) {
wsdd->finding.model = str_printf("%s %s", manufacturer, model);
} else if (model != NULL) {
wsdd->finding.model = model;
model = NULL;
} else if (manufacturer != NULL) {
wsdd->finding.model = manufacturer;
manufacturer = NULL;
} else {
wsdd->finding.model = str_dup(wsdd->address);
}
}
*
* Depending on the device, metadata request may either return all
* endpoints, or metadata request sent to IPv4 address may return
* only IPv4 endpoints and visa versa
*
* But if we have both IPv4/IPv6 endpoints, seems waiting for completion
* of more metadata requests is not unnecessary. So lets cancel them,
* if any.
*/
if (ip_addrset_has_af(wsdd->finding.addrs, AF_INET) &&
ip_addrset_has_af(wsdd->finding.addrs, AF_INET6)) {
http_client_cancel(wsdd->http_client);
}
DONE:
xml_rd_finish(&xml);
mem_free(model);
mem_free(manufacturer);
if (!wsdd_finding_has_pending_queries(wsdd)) {
wsdd_finding_publish_delay(wsdd);
}
}
*/
static void
wsdd_finding_get_metadata (wsdd_finding *wsdd, int ifindex, wsdd_xaddr *xaddr)
{
uuid u = uuid_rand();
http_query *q;
log_trace(wsdd_log, "querying metadata from %s", http_uri_str(xaddr->uri));
sprintf(wsdd_buf, wsdd_get_metadata_template, u.text, wsdd->address);
q = http_query_new(wsdd->http_client, http_uri_clone(xaddr->uri),
"POST", str_dup(wsdd_buf), "application/soap+xml; charset=utf-8");
http_query_set_uintptr(q, ifindex);
http_query_submit(q, wsdd_finding_get_metadata_callback);
}
* for Hello/Bye/ProbeMatch message
*/
static void
wsdd_message_parse_endpoint (wsdd_message *msg, xml_rd *xml)
{
unsigned int level = xml_rd_depth(xml);
char *xaddrs_text = NULL;
size_t prefixlen = strlen(xml_rd_node_path(xml));
while (!xml_rd_end(xml)) {
const char *path = xml_rd_node_path(xml) + prefixlen;
const char *val;
if (!strcmp(path, "/d:Types")) {
val = xml_rd_node_value(xml);
if (strstr(val, "ScanDeviceType")) {
msg->is_scanner = true;
}
if (strstr(val, "PrintDeviceType") != NULL) {
msg->is_printer = true;
}
} else if (!strcmp(path, "/d:XAddrs")) {
mem_free(xaddrs_text);
xaddrs_text = str_dup(xml_rd_node_value(xml));
} else if (!strcmp(path, "/a:EndpointReference/a:Address")) {
mem_free((char*) msg->address);
msg->address = str_dup(xml_rd_node_value(xml));
}
xml_rd_deep_next(xml, level);
}
if (xaddrs_text != NULL) {
char *tok, *saveptr;
static const char *delim = "\t\n\v\f\r \x85\xA0";
for (tok = strtok_r(xaddrs_text, delim, &saveptr); tok != NULL;
tok = strtok_r(NULL, delim, &saveptr)) {
http_uri *uri = http_uri_new(tok, true);
if (uri != NULL) {
wsdd_xaddr_list_add(&msg->xaddrs, uri);
}
}
}
mem_free(xaddrs_text);
}
*/
static wsdd_message*
wsdd_message_parse (const char *xml_text, size_t xml_len)
{
wsdd_message *msg = mem_new(wsdd_message, 1);
xml_rd *xml;
error err;
ll_init(&msg->xaddrs);
err = xml_rd_begin(&xml, xml_text, xml_len, wsdd_ns_rules);
if (err != NULL) {
goto DONE;
}
while (!xml_rd_end(xml)) {
const char *path = xml_rd_node_path(xml);
const char *val;
if (!strcmp(path, "s:Envelope/s:Header/a:Action")) {
val = xml_rd_node_value(xml);
if (strstr(val, "Hello")) {
msg->action = WSDD_ACTION_HELLO;
} else if (strstr(val, "Bye")) {
msg->action = WSDD_ACTION_BYE;
} else if (strstr(val, "ProbeMatches")) {
msg->action = WSDD_ACTION_PROBEMATCHES;
}
} else if (!strcmp(path, "s:Envelope/s:Body/d:Hello") ||
!strcmp(path, "s:Envelope/s:Body/d:Bye") ||
!strcmp(path, "s:Envelope/s:Body/d:ProbeMatches/d:ProbeMatch")) {
wsdd_message_parse_endpoint(msg, xml);
}
xml_rd_deep_next(xml, 0);
}
DONE:
xml_rd_finish(&xml);
if (err != NULL ||
msg->action == WSDD_ACTION_UNKNOWN ||
msg->address == NULL ||
(msg->action == WSDD_ACTION_HELLO && ll_empty(&msg->xaddrs)) ||
(msg->action == WSDD_ACTION_PROBEMATCHES && ll_empty(&msg->xaddrs))) {
wsdd_message_free(msg);
msg = NULL;
}
return msg;
}
*/
static void
wsdd_message_free (wsdd_message *msg)
{
if (msg != NULL) {
mem_free((char*) msg->address);
wsdd_xaddr_list_purge(&msg->xaddrs);
mem_free(msg);
}
}
*/
static const char*
wsdd_message_action_name (const wsdd_message *msg)
{
switch (msg->action) {
case WSDD_ACTION_UNKNOWN:
break;
case WSDD_ACTION_HELLO: return "Hello";
case WSDD_ACTION_BYE: return "Bye";
case WSDD_ACTION_PROBEMATCHES: return "ProbeMatches";
}
return "UNKNOWN";
}
*/
static int
wsdd_sock_enable_pktinfo_ip6 (int fd)
{
static int yes = 1;
int rc;
rc = setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &yes, sizeof(yes));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET6, IPV6_RECVPKTINFO): %s",
strerror(errno));
}
return rc;
}
*/
static int
wsdd_sock_enable_pktinfo_ip4 (int fd)
{
static int yes = 1;
int rc;
#ifdef IP_PKTINFO
rc = setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &yes, sizeof(yes));
#elif defined(IP_RECVIF)
rc = setsockopt(fd, IPPROTO_IP, IP_RECVIF, &yes, sizeof(yes));
#else
# error FIX ME
#endif
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET,IP_PKTINFO/IP_RECVIF): %s",
strerror(errno));
}
return rc;
}
*/
static void
wsdd_resolver_message_dispatch (wsdd_resolver *resolver,
wsdd_message *msg, const char *from)
{
wsdd_finding *wsdd;
wsdd_xaddr *xaddr;
ll_node *node;
log_trace(wsdd_log, "%s message received from %s:",
wsdd_message_action_name(msg), from);
log_trace(wsdd_log, " address: %s", msg->address);
log_trace(wsdd_log, " is_scanner: %s", msg->is_scanner ? "yes" : "no");
log_trace(wsdd_log, " is_printer: %s", msg->is_printer ? "yes" : "no");
for (LL_FOR_EACH(node, &msg->xaddrs)) {
xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
log_trace(wsdd_log, " xaddr: %s", http_uri_str(xaddr->uri));
}
if (msg->action != WSDD_ACTION_BYE) {
if (!(msg->is_scanner || msg->is_printer)) {
log_trace(wsdd_log,
"skipped: device is neither scanner not printer");
goto DONE;
}
if (ll_empty(&msg->xaddrs)) {
log_trace(wsdd_log,
"skipped: no xaddrs in the message");
goto DONE;
}
}
for (LL_FOR_EACH(node, &msg->xaddrs)) {
xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
http_uri_fix_ipv6_zone(xaddr->uri, resolver->ifindex);
}
switch (msg->action) {
case WSDD_ACTION_HELLO:
case WSDD_ACTION_PROBEMATCHES:
wsdd = wsdd_finding_get(resolver->ifindex, msg->address);
wsdd->is_printer = wsdd->is_printer || msg->is_printer;
wsdd->is_scanner = wsdd->is_scanner || msg->is_scanner;
* query
*/
while ((node = ll_pop_beg(&msg->xaddrs)) != NULL) {
xaddr = OUTER_STRUCT(node, wsdd_xaddr, list_node);
wsdd_finding_add_xaddr(wsdd, xaddr);
}
* one of the following:
* 1) device is not scanner, metadata won't be requested
* 2) there is no xaddrs (which is very unlikely, but
* just in case...)
* 3) device already known and all metadata queries
* already finished
*
* At this case we can publish device now
*/
if (!wsdd_finding_has_pending_queries(wsdd)) {
if (wsdd->is_scanner) {
wsdd_finding_publish_delay(wsdd);
} else {
wsdd_finding_publish(wsdd);
}
}
break;
case WSDD_ACTION_BYE:
wsdd_finding_del(msg->address);
break;
default:
break;
}
DONE:
wsdd_message_free(msg);
log_trace(wsdd_log, "");
}
*/
static void
wsdd_resolver_read_callback (int fd, void *data, ELOOP_FDPOLL_MASK mask)
{
struct sockaddr_storage from, to;
socklen_t tolen = sizeof(to);
ip_straddr str_from, str_to;
int rc;
wsdd_message *msg;
struct iovec vec = {wsdd_buf, sizeof(wsdd_buf)};
uint8_t aux[8192];
struct cmsghdr *cmsg;
int ifindex = 0;
wsdd_resolver *resolver;
struct msghdr msghdr = {
.msg_name = &from,
.msg_namelen = sizeof(from),
.msg_iov = &vec,
.msg_iovlen = 1,
.msg_control = aux,
.msg_controllen = sizeof(aux)
};
(void) mask;
(void) data;
rc = recvmsg(fd, &msghdr, 0);
if (rc <= 0) {
return;
}
for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6 &&
cmsg->cmsg_type == IPV6_PKTINFO) {
struct in6_pktinfo *pkt = (struct in6_pktinfo*) CMSG_DATA(cmsg);
ifindex = pkt->ipi6_ifindex;
}
#ifdef IP_PKTINFO
else if (cmsg->cmsg_level == IPPROTO_IP &&
cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pkt = (struct in_pktinfo*) CMSG_DATA(cmsg);
ifindex = pkt->ipi_ifindex;
}
#elif defined(IP_RECVIF)
else if (cmsg->cmsg_level == IPPROTO_IP &&
cmsg->cmsg_type == IP_RECVIF) {
struct sockaddr_dl *pkt = (struct sockaddr_dl *) CMSG_DATA(cmsg);
ifindex = pkt->sdl_index;
}
#else
# error FIX ME
#endif
}
str_from = ip_straddr_from_sockaddr((struct sockaddr*) &from, true);
(void) getsockname(fd, (struct sockaddr*) &to, &tolen);
str_to = ip_straddr_from_sockaddr((struct sockaddr*) &to, true);
log_trace(wsdd_log, "%d bytes received: %s->%s", rc,
str_from.text, str_to.text);
log_trace_data(wsdd_log, "application/xml", wsdd_buf, rc);
resolver = wsdd_netif_resolver_by_ifindex(ifindex);
if (resolver == NULL) {
return;
}
msg = wsdd_message_parse(wsdd_buf, rc);
if (msg != NULL) {
wsdd_resolver_message_dispatch(resolver, msg, "UDP");
}
}
*
* Pattern is the glob-style expression, applied to the model name
* of discovered devices.
*/
static unsigned int
wsdd_resolver_count_devices_by_model (wsdd_resolver *resolver, const char *pattern)
{
unsigned int answer = 0;
ll_node *node;
wsdd_finding *wsdd;
for (LL_FOR_EACH(node, &wsdd_finding_list)) {
wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
if (wsdd->finding.ifindex == resolver->ifindex &&
wsdd->finding.model != NULL &&
wsdd->published &&
fnmatch(pattern, wsdd->finding.model, 0) == 0) {
answer ++;
}
}
return answer;
}
*/
static void
wsdd_resolver_timer_callback (void *data)
{
wsdd_resolver *resolver = data;
const char pattern[] = "Pantum*";
resolver->timer = NULL;
* Should we use extended discovery time?
*/
if (resolver->time_elapsed >= resolver->time_limit &&
resolver->time_limit < WSDD_DISCOVERY_TIME_EX) {
unsigned int cnt_mdns, cnt_wsdd;
cnt_mdns = mdns_device_count_by_model(resolver->ifindex, pattern);
cnt_wsdd = wsdd_resolver_count_devices_by_model(resolver, pattern);
if (cnt_wsdd < cnt_mdns) {
const char *proto = resolver->ipv6 ? "ipv6" : "ipv4";
log_debug(wsdd_log, "%s@%s: \"%s\": MDNS/WSDD count: %d/%d",
proto, resolver->ifname.text,
pattern, cnt_mdns, cnt_wsdd);
log_debug(wsdd_log, "%s@%s: extending discovery time (%d->%d ms)",
proto, resolver->ifname.text,
resolver->time_limit, WSDD_DISCOVERY_TIME_EX);
resolver->time_limit = WSDD_DISCOVERY_TIME_EX;
}
}
if (resolver->time_elapsed < resolver->time_limit) {
wsdd_resolver_send_probe(resolver);
return;
}
eloop_fdpoll_free(resolver->fdpoll);
close(resolver->fd);
resolver->fdpoll = NULL;
resolver->fd = -1;
log_debug(wsdd_log, "%s: done discovery", resolver->str_ifaddr.text);
if (resolver->initscan) {
resolver->initscan = false;
wsdd_initscan_count_dec();
}
}
*/
static void
wsdd_resolver_timer_set (wsdd_resolver *resolver)
{
uint32_t t;
log_assert(wsdd_log, resolver->timer == NULL);
if (resolver->time_elapsed >= resolver->time_limit) {
t = WSDD_RETRANSMIT_MIN;
} else {
* and WSDD_RETRANSMIT_MAX, but don't exceed
* available limit, if possible (i.e., if it
* is not less that WSDD_RETRANSMIT_MIN)
*/
t = resolver->time_limit - resolver->time_elapsed;
if (t < WSDD_RETRANSMIT_MIN) {
t = WSDD_RETRANSMIT_MIN;
} else {
if (t > WSDD_RETRANSMIT_MAX) {
t = WSDD_RETRANSMIT_MAX;
}
t = math_rand_range(WSDD_RETRANSMIT_MIN, t);
}
}
resolver->time_elapsed += t;
resolver->timer = eloop_timer_new(t,
wsdd_resolver_timer_callback, resolver);
}
*/
static void
wsdd_resolver_send_probe (wsdd_resolver *resolver)
{
uuid u = uuid_rand();
int n = sprintf(wsdd_buf, wsdd_probe_template, u.text);
int rc;
struct sockaddr *addr;
socklen_t addrlen;
ip_straddr straddr;
if (resolver->ipv6) {
addr = (struct sockaddr*) &wsdd_mcast_ipv6;
addrlen = sizeof(wsdd_mcast_ipv6);
} else {
addr = (struct sockaddr*) &wsdd_mcast_ipv4;
addrlen = sizeof(wsdd_mcast_ipv4);
}
straddr = ip_straddr_from_sockaddr(addr, true);
log_trace(wsdd_log, "probe sent: %s->%s",
resolver->str_sockaddr.text, straddr.text);
log_trace_data(wsdd_log, "application/xml", wsdd_buf, n);
rc = sendto(resolver->fd, wsdd_buf, n, 0, addr, addrlen);
if (rc < 0) {
log_debug(wsdd_log, "send_probe: %s", strerror(errno));
}
wsdd_resolver_timer_set(resolver);
}
*/
static wsdd_resolver*
wsdd_resolver_new (const netif_addr *addr, bool initscan)
{
wsdd_resolver *resolver = mem_new(wsdd_resolver, 1);
int af = addr->ipv6 ? AF_INET6 : AF_INET;
const char *af_name = addr->ipv6 ? "AF_INET6" : "AF_INET";
int rc;
uint16_t port;
resolver->ifindex = addr->ifindex;
resolver->ifname = addr->ifname;
resolver->time_limit = WSDD_DISCOVERY_TIME;
resolver->ipv6 = addr->ipv6;
resolver->fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (resolver->fd < 0) {
log_debug(wsdd_log, "socket(%s): %s", af_name, strerror(errno));
goto FAIL;
}
if (addr->ipv6) {
rc = setsockopt(resolver->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF,
&addr->ifindex, sizeof(addr->ifindex));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET6,IPV6_MULTICAST_IF): %s",
strerror(errno));
goto FAIL;
}
rc = wsdd_sock_enable_pktinfo_ip6(resolver->fd);
if (rc < 0) {
goto FAIL;
}
} else {
rc = setsockopt(resolver->fd, IPPROTO_IP, IP_MULTICAST_IF,
&addr->ip.v4, sizeof(addr->ip.v4));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET,IP_MULTICAST_IF): %s",
strerror(errno));
goto FAIL;
}
rc = wsdd_sock_enable_pktinfo_ip4(resolver->fd);
if (rc < 0) {
goto FAIL;
}
}
if (addr->ipv6) {
struct sockaddr_in6 a;
socklen_t alen = sizeof(a);
memset(&a, 0, sizeof(a));
a.sin6_family = AF_INET6;
a.sin6_addr = addr->ip.v6;
a.sin6_scope_id = addr->ifindex;
resolver->str_ifaddr = ip_straddr_from_ip(AF_INET6, &addr->ip);
rc = bind(resolver->fd, (struct sockaddr*) &a, sizeof(a));
(void) getsockname(resolver->fd, (struct sockaddr*) &a, &alen);
port = a.sin6_port;
resolver->str_sockaddr = ip_straddr_from_sockaddr(
(struct sockaddr*) &a, true);
} else {
struct sockaddr_in a;
socklen_t alen = sizeof(a);
memset(&a, 0, sizeof(a));
a.sin_family = AF_INET;
a.sin_addr = addr->ip.v4;
resolver->str_ifaddr = ip_straddr_from_ip(AF_INET, &addr->ip);
resolver->str_sockaddr = ip_straddr_from_sockaddr(
(struct sockaddr*) &a, true);
rc = bind(resolver->fd, (struct sockaddr*) &a, sizeof(a));
(void) getsockname(resolver->fd, (struct sockaddr*) &a, &alen);
port = a.sin_port;
resolver->str_sockaddr = ip_straddr_from_sockaddr(
(struct sockaddr*) &a, true);
}
log_debug(wsdd_log, "%s: started discovery, UDP port=%d",
resolver->str_ifaddr.text, ntohs(port));
if (rc < 0) {
log_debug(wsdd_log, "bind(%s): %s", resolver->str_sockaddr.text,
strerror(errno));
goto FAIL;
}
resolver->fdpoll = eloop_fdpoll_new(resolver->fd,
wsdd_resolver_read_callback, NULL);
eloop_fdpoll_set_mask(resolver->fdpoll, ELOOP_FDPOLL_READ);
wsdd_resolver_send_probe(resolver);
resolver->initscan = initscan;
if (resolver->initscan) {
wsdd_initscan_count_inc();
}
return resolver;
FAIL:
if (resolver->fd >= 0) {
close(resolver->fd);
resolver->fd = -1;
}
return resolver;
}
*/
static void
wsdd_resolver_free (wsdd_resolver *resolver)
{
if (resolver->initscan) {
wsdd_initscan_count_dec();
}
if (resolver->fdpoll != NULL) {
eloop_fdpoll_free(resolver->fdpoll);
close(resolver->fd);
}
if (resolver->timer != NULL) {
eloop_timer_cancel(resolver->timer);
}
mem_free(resolver);
}
*/
void
wsdd_initscan_timer_expired (void)
{
ll_node *node;
wsdd_finding *wsdd;
*
* Without it, if metadata query takes too long time (for example,
* device has 2 IP addresses, one is unreachable from PC) it
* effectively blocks the device from being discovered
*/
for (LL_FOR_EACH(node, &wsdd_finding_list)) {
wsdd = OUTER_STRUCT(node, wsdd_finding, list_node);
if (!wsdd->published && wsdd->finding.endpoints != NULL) {
http_client_cancel(wsdd->http_client);
wsdd_finding_publish(wsdd);
}
}
}
*/
static void
wsdd_send_directed_probe_callback (void *ptr, http_query *q)
{
error err;
const struct sockaddr *sockaddr = http_uri_addr(http_query_uri(q));
int ifindex;
http_data *data;
wsdd_resolver *resolver;
wsdd_message *msg;
(void) ptr;
if (sockaddr != NULL) {
ip_addrset_del(wsdd_addrs_probing, ip_addr_from_sockaddr(sockaddr));
}
err = http_query_error(q);
if (err != NULL) {
log_debug(wsdd_log, "directed probe: HTTP %s", ESTRING(err));
return;
}
ifindex = (int) http_query_get_uintptr(q);
resolver = wsdd_netif_resolver_by_ifindex(ifindex);
if (resolver == NULL) {
log_debug(wsdd_log,
"directed probe: resolver not found for interface %d", ifindex);
return;
}
data = http_query_get_response_data(q);
msg = wsdd_message_parse(data->bytes, data->size);
if (msg != NULL) {
wsdd_resolver_message_dispatch(resolver, msg, "HTTP");
}
}
*
* WS-Discovery defines two mechanisms for sending Probes:
* * probes can be send using UDP milticasts
* * probes can be send directly via HTTP POST to the following URL:
* http://addr//StableWSDiscoveryEndpoint/schemas-xmlsoap-org_ws_2005_04_discovery
*
* The second mechanism is called "Directed discovery Probe message", and
* information about it is exceptionally hard to discover.
*
* BTW, this is why this protocol is called Web Services Discovery: you
* need to browse the entire web to discover a bit of useful information
*
* This function is called from DNS-SD module when new device is found,
* and sends directed probe to its HTTP stable discovery endpoint
*
* To avoid device overload with discovery requests, this function
* sends only one request a time per address and doesn't send
* requests to already known devices.
*/
void
wsdd_send_directed_probe (int ifindex, int af, const void *addr)
{
char ifname[IF_NAMESIZE] = "?";
ip_straddr straddr = ip_straddr_from_ip(af, addr);
char uri_buf[1024];
http_uri *uri;
uuid u;
http_query *q;
ip_addr ipa = ip_addr_make(ifindex, af, addr);
if (!conf.discovery || conf.wsdd_mode == WSDD_OFF) {
return;
}
if_indextoname(ifindex, ifname);
log_debug(wsdd_log, "directed probe: trying if=%s, addr=%s",
ifname, straddr.text);
if (ip_is_loopback(af, addr)) {
log_debug(wsdd_log, "directed probe: skipping loopback address");
return;
}
if (ip_addrset_lookup(wsdd_addrs_probing, ipa)) {
log_debug(wsdd_log, "directed probe: already in progress, skipping");
return;
}
if (wsdd_finding_by_address(ipa) != NULL) {
log_debug(wsdd_log, "directed probe: device already contacted, skipping");
return;
}
ip_addrset_add_unsafe(wsdd_addrs_probing, ipa);
if (af == AF_INET) {
sprintf(uri_buf, "http://%s", straddr.text);
} else if (!ip_is_linklocal(af, addr)) {
sprintf(uri_buf, "http://[%s]", straddr.text);
} else {
* needs to be properly escaped, so it becomes %25
* See RFC6874 for details
*/
sprintf(uri_buf, "http://[%s%%25%d]", straddr.text, ifindex);
}
strcat(uri_buf, WSDD_STABLE_ENDPOINT);
uri = http_uri_new(uri_buf, true);
log_assert(wsdd_log, uri != NULL);
u = uuid_rand();
sprintf(wsdd_buf, wsdd_probe_template, u.text);
q = http_query_new(wsdd_http_client, uri,
"POST", str_dup(wsdd_buf), "application/soap+xml; charset=utf-8");
http_query_set_uintptr(q, ifindex);
http_query_submit(q, wsdd_send_directed_probe_callback);
}
*/
static int
wsdd_mcsock_open (bool ipv6)
{
int af = ipv6 ? AF_INET6 : AF_INET;
int fd, rc;
const char *af_name = ipv6 ? "AF_INET6" : "AF_INET";
static int yes = 1;
ip_straddr straddr;
fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
if (fd < 0) {
log_debug(wsdd_log, "socket(%s): %s", af_name, strerror(errno));
return fd;
}
rc = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(%s, SO_REUSEADDR): %s",
af_name, strerror(errno));
goto FAIL;
}
if (ipv6) {
rc = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(%s, IPV6_V6ONLY): %s",
af_name, strerror(errno));
goto FAIL;
}
rc = wsdd_sock_enable_pktinfo_ip6(fd);
if (rc < 0) {
goto FAIL;
}
} else {
rc = wsdd_sock_enable_pktinfo_ip4(fd);
if (rc < 0) {
goto FAIL;
}
}
* will be added later on per-interface-address basis
*/
if (ipv6) {
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_port = wsdd_mcast_ipv6.sin6_port;
straddr = ip_straddr_from_sockaddr((struct sockaddr*) &addr, true);
rc = bind(fd, (struct sockaddr*) &addr, sizeof(addr));
} else {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = wsdd_mcast_ipv4.sin_port;
straddr = ip_straddr_from_sockaddr((struct sockaddr*) &addr, true);
rc = bind(fd, (struct sockaddr*) &addr, sizeof(addr));
}
if (rc < 0) {
log_debug(wsdd_log,
"bind(%s): %s", straddr.text, strerror(errno));
goto FAIL;
}
return fd;
FAIL:
rc = errno;
close(fd);
errno = rc;
return -1;
}
* per-interface-address basis
*/
static void
wsdd_mcast_update_membership (int fd, netif_addr *addr, bool add)
{
int rc, opt;
if (addr->ipv6) {
struct ipv6_mreq mreq6;
memset(&mreq6, 0, sizeof(mreq6));
mreq6.ipv6mr_multiaddr = wsdd_mcast_ipv6.sin6_addr;
mreq6.ipv6mr_interface = addr->ifindex;
opt = add ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP;
rc = setsockopt(fd, IPPROTO_IPV6, opt, &mreq6, sizeof(mreq6));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET6,%s): %s",
add ? "IPV6_ADD_MEMBERSHIP" : "IPV6_DROP_MEMBERSHIP",
strerror(errno));
}
} else {
#ifdef OS_HAVE_IP_MREQN
struct ip_mreqn mreq4;
#else
struct ip_mreq mreq4;
#endif
memset(&mreq4, 0, sizeof(mreq4));
mreq4.imr_multiaddr = wsdd_mcast_ipv4.sin_addr;
#ifdef OS_HAVE_IP_MREQN
mreq4.imr_address = addr->ip.v4;
mreq4.imr_ifindex = addr->ifindex;
#else
mreq4.imr_interface = addr->ip.v4;
#endif
opt = add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
rc = setsockopt(fd, IPPROTO_IP, opt, &mreq4, sizeof(mreq4));
if (rc < 0) {
log_debug(wsdd_log, "setsockopt(AF_INET,%s): %s",
add ? "IP_ADD_MEMBERSHIP" : "IP_DROP_MEMBERSHIP",
strerror(errno));
}
}
}
*/
static void
wsdd_netif_dump_addresses (const char *prefix, netif_addr *list)
{
while (list != NULL) {
log_debug(wsdd_log, "%s%s@%s, ifindex=%d", prefix, list->straddr,
list->ifname.text, list->ifindex);
list = list->next;
}
}
*/
static wsdd_resolver*
wsdd_netif_resolver_by_ifindex (int ifindex)
{
netif_addr *addr;
for (addr = wsdd_netif_addr_list; addr != NULL; addr = addr->next) {
if (addr->ifindex == ifindex) {
return addr->data;
}
}
return NULL;
}
*/
static void
wsdd_netif_update_addresses (bool initscan)
{
netif_addr *addr_list = netif_addr_list_get();
netif_addr *addr;
netif_diff diff = netif_diff_compute(wsdd_netif_addr_list, addr_list);
log_debug(wsdd_log, "netif addresses update:");
wsdd_netif_dump_addresses(" + ", diff.added);
wsdd_netif_dump_addresses(" - ", diff.removed);
* per-interface-address resolvers */
for (addr = diff.removed; addr != NULL; addr = addr->next) {
int fd = addr->ipv6 ? wsdd_mcsock_ipv6 : wsdd_mcsock_ipv4;
wsdd_mcast_update_membership(fd, addr, false);
wsdd_resolver_free(addr->data);
}
for (addr = diff.added; addr != NULL; addr = addr->next) {
int fd = addr->ipv6 ? wsdd_mcsock_ipv6 : wsdd_mcsock_ipv4;
wsdd_mcast_update_membership(fd, addr, true);
addr->data = wsdd_resolver_new(addr, initscan);
}
wsdd_netif_addr_list = netif_addr_list_merge(diff.preserved, diff.added);
netif_addr_list_free(diff.removed);
}
*/
static void
wsdd_netif_notifier_callback (void *data)
{
(void) data;
log_debug(wsdd_log, "netif event");
wsdd_netif_update_addresses(false);
}
*/
static void
wsdd_start_stop_callback (bool start)
{
if (start) {
wsdd_addrs_probing = ip_addrset_new();
wsdd_http_client = http_client_new(wsdd_log, NULL);
if (wsdd_mcsock_ipv4 >= 0) {
wsdd_fdpoll_ipv4 = eloop_fdpoll_new(wsdd_mcsock_ipv4,
wsdd_resolver_read_callback, NULL);
eloop_fdpoll_set_mask(wsdd_fdpoll_ipv4, ELOOP_FDPOLL_READ);
}
if (wsdd_mcsock_ipv6 >= 0) {
wsdd_fdpoll_ipv6 = eloop_fdpoll_new(wsdd_mcsock_ipv6,
wsdd_resolver_read_callback, NULL);
eloop_fdpoll_set_mask(wsdd_fdpoll_ipv6, ELOOP_FDPOLL_READ);
}
*
* decremented to ensure that initial scan completion notification
* will be raised even if there are no network interfaces.
*/
wsdd_initscan_count_inc();
wsdd_netif_update_addresses(true);
wsdd_initscan_count_dec();
} else {
ip_addrset_free(wsdd_addrs_probing);
http_client_cancel(wsdd_http_client);
http_client_free(wsdd_http_client);
wsdd_addrs_probing = NULL;
wsdd_http_client = NULL;
if (wsdd_fdpoll_ipv4 != NULL) {
eloop_fdpoll_free(wsdd_fdpoll_ipv4);
wsdd_fdpoll_ipv4 = NULL;
}
if (wsdd_fdpoll_ipv6 != NULL) {
eloop_fdpoll_free(wsdd_fdpoll_ipv6);
wsdd_fdpoll_ipv6 = NULL;
}
wsdd_finding_list_purge();
}
}
*/
SANE_Status
wsdd_init (void)
{
wsdd_log = log_ctx_new("WSDD", zeroconf_log);
ll_init(&wsdd_finding_list);
if (!conf.discovery || conf.wsdd_mode == WSDD_OFF) {
log_debug(wsdd_log, "devices discovery disabled");
zeroconf_finding_done(ZEROCONF_WSD);
return SANE_STATUS_GOOD;
}
wsdd_mcast_ipv4.sin_family = AF_INET;
inet_pton(AF_INET, "239.255.255.250", &wsdd_mcast_ipv4.sin_addr);
wsdd_mcast_ipv4.sin_port = htons(3702);
wsdd_mcast_ipv6.sin6_family = AF_INET6;
inet_pton(AF_INET6, "ff02::c", &wsdd_mcast_ipv6.sin6_addr);
wsdd_mcast_ipv6.sin6_port = htons(3702);
wsdd_mcsock_ipv4 = wsdd_mcsock_open(false);
if (wsdd_mcsock_ipv4 < 0) {
goto FAIL;
}
wsdd_mcsock_ipv6 = wsdd_mcsock_open(true);
if (wsdd_mcsock_ipv6 < 0 && errno != EAFNOSUPPORT) {
goto FAIL;
}
wsdd_netif_notifier = netif_notifier_create(
wsdd_netif_notifier_callback, NULL);
if (wsdd_netif_notifier == NULL) {
goto FAIL;
}
eloop_add_start_stop_callback(wsdd_start_stop_callback);
return SANE_STATUS_GOOD;
FAIL:
wsdd_cleanup();
return SANE_STATUS_IO_ERROR;
}
*/
void
wsdd_cleanup (void)
{
netif_addr *addr;
if (wsdd_log == NULL) {
return;
}
if (wsdd_netif_notifier != NULL) {
netif_notifier_free(wsdd_netif_notifier);
wsdd_netif_notifier = NULL;
}
for (addr = wsdd_netif_addr_list; addr != NULL; addr = addr->next) {
wsdd_resolver_free(addr->data);
}
netif_addr_list_free(wsdd_netif_addr_list);
wsdd_netif_addr_list = NULL;
if (wsdd_mcsock_ipv4 >= 0) {
close(wsdd_mcsock_ipv4);
wsdd_mcsock_ipv4 = -1;
}
if (wsdd_mcsock_ipv6 >= 0) {
close(wsdd_mcsock_ipv6);
wsdd_mcsock_ipv6 = -1;
}
log_assert(wsdd_log, ll_empty(&wsdd_finding_list));
log_ctx_free(wsdd_log);
wsdd_log = NULL;
}
*/