From 879e6db06338166657609930768f76d8d7e7afbb Mon Sep 17 00:00:00 2001 From: Joe Nahmias Date: Fri, 7 Dec 2012 21:44:43 -0500 Subject: Imported Upstream version 1.2 --- bjnp-discover.c | 522 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 522 insertions(+) create mode 100644 bjnp-discover.c (limited to 'bjnp-discover.c') 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: + * + */ + +#include "bjnp.h" +#include +#include +#include +#ifdef HAVE_GETIFADDRS +#include +#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; +} + -- cgit v1.2.3