summaryrefslogtreecommitdiff
path: root/bjnp-discover.c
diff options
context:
space:
mode:
authorJoe Nahmias <joe@nahmias.net>2012-12-07 21:44:43 -0500
committerJoe Nahmias <joe@nahmias.net>2012-12-07 21:44:43 -0500
commit879e6db06338166657609930768f76d8d7e7afbb (patch)
tree3018a2ec0a6aca4150250084bdaf4451ec4e1c3b /bjnp-discover.c
Imported Upstream version 1.2
Diffstat (limited to 'bjnp-discover.c')
-rw-r--r--bjnp-discover.c522
1 files changed, 522 insertions, 0 deletions
diff --git a/bjnp-discover.c b/bjnp-discover.c
new file mode 100644
index 0000000..9f005aa
--- /dev/null
+++ b/bjnp-discover.c
@@ -0,0 +1,522 @@
+/*
+ * TCP/IP IO communication implementation for
+ * bjnp backend for the Common UNIX Printing System (CUPS).
+ * Copyright 2008 by Louis Lagendijk
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Louis Lagendijk and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * "LICENSE" which should have been included with this file. If this
+ * file is missing or damaged, see the license at "http://www.cups.org/".
+ *
+ * This file is subject to the Apple OS-Developed Software exception.
+ *
+ * Contents:
+ * <to be added>
+ */
+
+#include "bjnp.h"
+#include <errno.h>
+#include <sys/types.h>
+#include <net/if.h>
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#endif
+#include "bjnp-protocol.h"
+
+static int create_broadcast_socket( const http_addr_t * local_addr )
+{
+ int sockfd = -1;
+ int broadcast = 1;
+ int ipv6_v6only = 1;
+
+
+ if ((sockfd = socket (local_addr-> addr.sa_family, SOCK_DGRAM, 0)) == -1)
+ {
+ bjnp_debug
+ (LOG_CRIT, "create_broadcast_socket: can not open socket - %s",
+ strerror (errno));
+ return -1;
+ }
+
+ /* Set broadcast flag on socket */
+
+ if (setsockopt
+ (sockfd, SOL_SOCKET, SO_BROADCAST, (const char *) &broadcast,
+ sizeof (broadcast)) != 0)
+ {
+ bjnp_debug
+ (LOG_CRIT,
+ "create_broadcast_socket: setting socket option SO_BROADCAST failed - %s",
+ strerror (errno));
+ close (sockfd);
+ return -1;
+ };
+
+#ifdef ENABLE_IPV6
+ /* For an IPv6 socket, bind to v6 only so a V6 socket can co-exist with a v4 socket */
+ if ( (local_addr -> addr.sa_family == AF_INET6) && ( setsockopt
+ (sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (const char *) &ipv6_v6only,
+ sizeof (ipv6_v6only)) != 0) )
+ {
+ bjnp_debug
+ (LOG_CRIT,
+ "create_broadcast_socket: setting socket option IPV6_V6ONLY failed - %s",
+ strerror (errno));
+ close (sockfd);
+ return -1;
+ };
+#endif
+
+ if (bind
+ (sockfd, &(local_addr->addr),
+ (socklen_t) sa_size( local_addr)) != 0)
+ {
+ bjnp_debug
+ (LOG_CRIT,
+ "create_broadcast_socket: bind socket to local address failed - %s\n",
+ strerror (errno));
+ close (sockfd);
+ return -1;
+ }
+ return sockfd;
+}
+
+static int
+prepare_socket(const char *if_name, const http_addr_t *local_sa,
+ const http_addr_t *broadcast_sa, http_addr_t * dest_sa)
+{
+ /*
+ * Prepare a socket for broadcast or multicast
+ * Input:
+ * if_name: the name of the interface
+ * local_sa: local address to use
+ * broadcast_sa: broadcast address to use, if NULL we use all hosts
+ * dest_sa: (write) where to return destination address of broadcast
+ * retuns: open socket or -1
+ */
+
+ int socket = -1;
+ http_addr_t local_sa_copy;
+
+ if ( local_sa == NULL )
+ {
+ bjnp_debug (LOG_DEBUG,
+ "%s is not a valid IPv4 interface, skipping...\n",
+ if_name);
+ return -1;
+ }
+
+ memset( &local_sa_copy, 0, sizeof(local_sa_copy) );
+ memcpy( &local_sa_copy, local_sa, sa_size(local_sa) );
+
+ switch( local_sa_copy.addr.sa_family )
+ {
+ case AF_INET:
+ {
+ local_sa_copy.ipv4.sin_port = htons(BJNP_PORT_PRINT);
+
+ if (local_sa_copy.ipv4.sin_addr.s_addr == htonl (INADDR_LOOPBACK) )
+ {
+ /* not a valid interface */
+
+ bjnp_debug (LOG_DEBUG,
+ "%s is not a valid IPv4 interface, skipping...\n",
+ if_name);
+ return -1;
+ }
+
+
+ /* send broadcasts to the broadcast address of the interface */
+
+ memcpy(dest_sa, broadcast_sa, sa_size(dest_sa) );
+ dest_sa -> ipv4.sin_port = htons(BJNP_PORT_PRINT);
+ if ( (socket = create_broadcast_socket( &local_sa_copy) ) != -1)
+ {
+ bjnp_debug (LOG_DEBUG, "%s is IPv4 capable, sending broadcast, socket = %d\n",
+ if_name, socket);
+ }
+ else
+ {
+ bjnp_debug (LOG_DEBUG, "%s is IPv4 capable, but failed to create a socket.\n",
+ if_name);
+ return -1;
+ }
+ }
+ break;
+#ifdef ENABLE_IPV6
+ case AF_INET6:
+ {
+ local_sa_copy.ipv6.sin6_port = htons(BJNP_PORT_PRINT);
+
+ if (IN6_IS_ADDR_LOOPBACK( &(local_sa_copy.ipv6.sin6_addr) ) )
+ {
+ /* not a valid interface */
+
+ bjnp_debug (LOG_DEBUG,
+ "%s is not a valid IPv6 interface, skipping...\n",
+ if_name);
+ return -1;
+ }
+ else
+ {
+ dest_sa -> ipv6.sin6_family = AF_INET6;
+ dest_sa -> ipv6.sin6_port = htons(BJNP_PORT_PRINT);
+ inet_pton(AF_INET6, "ff02::1", dest_sa -> ipv6.sin6_addr.s6_addr);
+ if ( (socket = create_broadcast_socket( &local_sa_copy ) ) != -1)
+ {
+ bjnp_debug (LOG_DEBUG, "%s is IPv6 capable, sending broadcast, socket = %d\n",
+ if_name, socket);
+ }
+ else
+ {
+ bjnp_debug (LOG_DEBUG, "%s is IPv6 capable, but failed to create a socket.\n",
+ if_name);
+ return -1;
+ }
+ }
+ }
+ break;
+#endif
+
+ default:
+ socket = -1;
+ }
+ return socket;
+}
+
+static int
+bjnp_send_broadcast (int sockfd, const http_addr_t * broadcast_addr,
+ bjnp_command_t cmd, int size)
+{
+ int num_bytes;
+
+ /* set address to send packet to */
+ /* usebroadcast address of interface */
+
+ if ((num_bytes = sendto (sockfd, &cmd, size, 0,
+ &(broadcast_addr->addr),
+ sa_size( broadcast_addr)) ) != size)
+ {
+ bjnp_debug (LOG_DEBUG,
+ "bjnp_send_broadcast: Socket: %d: sent only %x = %d bytes of packet, error = %s\n",
+ sockfd, num_bytes, num_bytes, strerror (errno));
+ /* not allowed, skip this interface */
+
+ return -1;
+ }
+ return sockfd;
+}
+
+static void add_printer_address( http_addr_t *printer_sa, char *mac_address, int *no_printers, struct printer_list *printers )
+{
+ char ip_addr[BJNP_HOST_MAX];
+ int port;
+ int i;
+ bjnp_address_type_t host_type;
+ char host[BJNP_HOST_MAX];
+
+
+ host_type = get_printer_host( printer_sa, host, &port);
+
+ bjnp_debug (LOG_DEBUG, "add_printer_address(%s) %d\n", host, *no_printers );
+
+ /* Check if a device number is already allocated to any of the printer's addresses */
+ for (i = 0; i < *no_printers; i++)
+ {
+ /* we check for mac_addresses as */
+ /* an IPv6 host can have multiple adresses */
+
+ if ( strcmp( printers[i].mac_address, mac_address ) == 0 )
+ {
+ if (host_type > printers[i].host_type)
+ {
+ /* This is a better address as it resolves to a FQDN or */
+ /* it is a global address, so we do not rely on a scope */
+ /* which could change when the number of IP-interfaces changes */
+ memcpy(printers[i].addr, printer_sa, sa_size(printer_sa) );
+ strcpy( printers[i].hostname, host);
+ printers[i].host_type = host_type;
+ bjnp_debug (LOG_DEBUG, "Printer at %s found before, but found better address!\n",
+ host);
+ }
+ else
+ {
+ bjnp_debug (LOG_DEBUG, "Printer at %s found before, not added!\n",
+ host);
+ }
+ return;
+ }
+ }
+
+ /* create a new device structure for this address */
+
+ if (*no_printers == BJNP_PRINTERS_MAX)
+ {
+ bjnp_debug (LOG_CRIT,
+ "Too many devices, ran out of device structures, can not add %s\n",
+ host);
+ return;
+ }
+ printers[*no_printers].addr = malloc(sizeof(http_addr_t));
+ memset(printers[*no_printers].addr, 0, sizeof(http_addr_t) );
+ memcpy(printers[*no_printers].addr, printer_sa, sa_size(printer_sa));
+ printers[*no_printers].host_type = host_type;
+ printers[*no_printers].port = port;
+ strcpy(printers[*no_printers].hostname, host);
+ strcpy(printers[*no_printers].mac_address, mac_address);
+ if (get_printer_id (printer_sa, printers[*no_printers].model,
+ printers[*no_printers].IEEE1284_id ) != 0)
+ {
+ bjnp_debug (LOG_CRIT, "Cannot read printer make & model: %s\n", host);
+ free(printers[*no_printers].addr);
+ return;
+ }
+ get_address_info( printer_sa, ip_addr, &port);
+ bjnp_debug (LOG_DEBUG, "Printer not yet in our list, added it: %s:%d\n", host, port);
+ (*no_printers)++;
+}
+
+static void add_printer( http_addr_t *printer_sa, bjnp_response_t *resp,
+ int *no_printers, struct printer_list *printers )
+{
+ char mac_address_string[BJNP_SERIAL_MAX];
+ int i;
+
+ /* create serial/mac_address string */
+
+ u8tohex_string(resp -> udp_discover_response.mac_addr, mac_address_string,
+ sizeof(resp -> udp_discover_response.mac_addr) );
+
+ if (printer_sa->addr.sa_family == AF_INET)
+ {
+ add_printer_address( printer_sa, mac_address_string, no_printers, printers);
+ }
+ else
+ {
+ int no_addresses;
+ http_addr_t printer_addr;
+
+ /* IPV6, we also need to try all adresses received in the discover response */
+
+ add_printer_address( printer_sa, mac_address_string, no_printers, printers);
+ no_addresses = resp -> udp_discover_response.addr_len >> 4;
+ memset(&printer_addr, 0, sizeof(printer_addr) );
+ memcpy(&printer_addr, printer_sa, sa_size(printer_sa) );
+ for (i = 0; i < no_addresses; i++ )
+ {
+ memcpy(printer_addr.ipv6.sin6_addr.s6_addr, &(resp -> udp_discover_response.addresses.ipv6.ipv6_addr[i]), 16);
+
+ /* we only add link-local address if the response came from a link-local aadress (done above) */
+
+ if ( ! IN6_IS_ADDR_LINKLOCAL( &(printer_addr.ipv6.sin6_addr) ) )
+ {
+ /* address is already in network byte order */
+
+ add_printer_address(&printer_addr, mac_address_string, no_printers, printers );
+ }
+ }
+ }
+}
+
+int bjnp_discover_printers ( struct printer_list *printers )
+{
+ int numbytes = 0;
+ bjnp_command_t cmd;
+ bjnp_response_t disc_resp;
+ int socket_fd[BJNP_SOCK_MAX];
+ int no_sockets = 0;
+ int i;
+ int attempt;
+ int last_socketfd = 0;
+ fd_set fdset;
+ fd_set active_fdset;
+ struct timeval timeout;
+ int no_printers = 0;
+ http_addr_t broadcast_addr[BJNP_SOCK_MAX];
+ http_addr_t printer_sa;
+ socklen_t socklen;
+ char host[BJNP_HOST_MAX];
+ int port;
+
+ clear_cmd(&cmd);
+ memset( broadcast_addr, 0, sizeof( broadcast_addr) );
+ memset( &printer_sa, 0 ,sizeof( printer_sa ) );
+ bjnp_debug (LOG_DEBUG, "sanei_bjnp_find_devices:\n");
+
+ for (i=0; i < BJNP_SOCK_MAX; i++)
+ {
+ socket_fd[i] = -1;
+ }
+ bjnp_debug
+ (LOG_DEBUG,
+ "Do auto detection of printers...\n");
+
+ /*
+ * Send UDP DISCOVER to discover printers and return the list of printers found
+ */
+
+ FD_ZERO (&fdset);
+ bjnp_set_command_header (&cmd, CMD_UDP_DISCOVER, 0, sizeof(cmd.udp_discover) );
+
+ no_sockets = 0;
+#ifdef HAVE_GETIFADDRS
+ {
+ struct ifaddrs *interfaces = NULL;
+ struct ifaddrs *interface;
+ getifaddrs (&interfaces);
+
+ /* create a socket for each suitable interface */
+
+ interface = interfaces;
+ while ((no_sockets < BJNP_SOCK_MAX) && (interface != NULL))
+ {
+ if ( ! (interface -> ifa_flags & IFF_POINTOPOINT) &&
+ ( (socket_fd[no_sockets] =
+ prepare_socket( interface -> ifa_name,
+ (http_addr_t *) interface -> ifa_addr,
+ (http_addr_t *) interface -> ifa_broadaddr,
+ &broadcast_addr[no_sockets] ) ) != -1 ) )
+ {
+ /* track highest used socket for later use in select */
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ FD_SET (socket_fd[no_sockets], &fdset);
+ no_sockets++;
+ }
+ interface = interface->ifa_next;
+ }
+ freeifaddrs (interfaces);
+ }
+#else
+ /* we have no easy way to find interfaces with their broadcast addresses. */
+ /* use global broadcast and all-hosts instead */
+ {
+ http_addr_t local;
+ http_addr_t bc_addr;
+
+ memset( &local, 0, sizeof( local) );
+ local.ipv4.sin_family = AF_INET;
+ local.ipv4.sin_addr.s_addr = htonl (INADDR_ANY);
+
+ bc_addr.ipv4.sin_family = AF_INET;
+ bc_addr.ipv4.sin_port = htons(BJNP_PORT_PRINT);
+ bc_addr.ipv4.sin_addr.s_addr = htonl (INADDR_BROADCAST);
+
+ socket_fd[no_sockets] = prepare_socket( "any_interface",
+ &local,
+ &bc_addr,
+ &broadcast_addr[no_sockets] );
+ if (socket_fd[no_sockets] >= 0)
+ {
+ FD_SET (socket_fd[no_sockets], &fdset);
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ no_sockets++;
+ }
+#ifdef ENABLE_IPV6
+ local.ipv6.sin6_family = AF_INET6;
+ local.ipv6.sin6_addr = in6addr_any;
+
+ socket_fd[no_sockets] = prepare_socket( "any_interface",
+ &local,
+ NULL,
+ &broadcast_addr[no_sockets] );
+ if (socket_fd[no_sockets] >= 0)
+ {
+ FD_SET (socket_fd[no_sockets], &fdset);
+ if (socket_fd[no_sockets] > last_socketfd)
+ {
+ last_socketfd = socket_fd[no_sockets];
+ }
+ no_sockets++;
+ }
+#endif
+ }
+#endif
+
+ /* send BJNP_MAX_BROADCAST_ATTEMPTS broadcasts on each prepared socket */
+ for (attempt = 0; attempt < BJNP_MAX_BROADCAST_ATTEMPTS; attempt++)
+ {
+ for ( i=0; i < no_sockets; i++)
+ {
+ bjnp_send_broadcast ( socket_fd[i], &broadcast_addr[i], cmd, sizeof (cmd.udp_discover));
+ }
+ /* wait for some time between broadcast packets */
+ usleep (BJNP_BROADCAST_INTERVAL * USLEEP_MS);
+ }
+
+ /* wait for a UDP response */
+
+ timeout.tv_sec = 0;
+ timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * USLEEP_MS;
+
+
+ active_fdset = fdset;
+
+ while (select (last_socketfd + 1, &active_fdset, NULL, NULL, &timeout) > 0)
+ {
+ bjnp_debug (LOG_DEBUG, "Select returned, time left %d.%d....\n",
+ (int) timeout.tv_sec, (int) timeout.tv_usec);
+ for (i = 0; i < no_sockets; i++)
+ {
+ if (FD_ISSET (socket_fd[i], &active_fdset))
+ {
+ socklen = sizeof(printer_sa);
+ if ((numbytes =
+ recvfrom (socket_fd[i], &disc_resp, sizeof (disc_resp), 0,
+ &(printer_sa.addr), &socklen ) ) == -1)
+ {
+ bjnp_debug
+ (LOG_INFO, "find_devices: no data received, while socket is ready");
+ break;
+ }
+ else
+ {
+ get_address_info( &printer_sa, host, &port);
+ bjnp_debug( LOG_DEBUG, "Response received from %s port %d\n", host, port);
+ bjnp_hexdump (LOG_DEBUG2, "Discover response:\n", &disc_resp, numbytes);
+
+ /* check if something sensible is returned */
+ if ( (numbytes < bjnp_header_size ) ||
+ (strncmp ("BJNP", disc_resp.header.BJNP_id, 4) != 0))
+ {
+ /* not a valid response, assume not a printer */
+
+ char bjnp_id[5];
+ strncpy(bjnp_id, disc_resp.header.BJNP_id, 4);
+ bjnp_id[4] = '\0';
+ bjnp_debug (LOG_INFO,
+ "Invalid discover response! Length = %d, Id = %s\n",
+ numbytes, bjnp_id );
+ break;
+ }
+ if ( ! ((disc_resp.header.dev_type) & 0x80) )
+ {
+ /* not a response, a discover command from somebody else or */
+ /* a discover command that we generated */
+ break;
+ }
+ };
+
+
+ add_printer( &printer_sa, &disc_resp, &no_printers, printers);
+
+ }
+ }
+ active_fdset = fdset;
+ timeout.tv_sec = 0;
+ timeout.tv_usec = BJNP_BC_RESPONSE_TIMEOUT * USLEEP_MS;
+ }
+ bjnp_debug (LOG_DEBUG, "printer discovery finished...\n");
+
+ for (i = 0; i < no_sockets; i++)
+ close (socket_fd[i]);
+
+ return no_printers;
+}
+