/*
* Printer discovery implementation for
* bjnp backend for the Common UNIX Printing System (CUPS).
* Copyright 2008-2014 by Louis Lagendijk
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 or later.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "config.h"
#include
#include
#include
#include
#ifdef HAVE_GETIFADDRS
#include
#endif
#include "bjnp.h"
#include "bjnp-protocol.h"
#include "bjnp-commands.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: cannot 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;
char family[BJNP_FAMILY_MAX];
int i;
bjnp_address_type_t host_type;
char host[BJNP_HOST_MAX];
host_type = get_printer_host(*printer_sa, host, &port, family);
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, cannot 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, family);
bjnp_debug(LOG_DEBUG, "Printer not yet in our list, added it: %s:%d(%s)\n",
host, port, family);
(*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);
}
#ifdef ENABLE_IPV6
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);
}
}
}
#endif
}
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;
char family[BJNP_FAMILY_MAX];
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_defaults_set_command_header(&cmd, CMD_UDP_DISCOVER, 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 /* ENABLE_IPV6 */
}
#endif /* HAVE_GETIFADDRS */
/* 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 {
/* process incoming packet */
get_address_info(&printer_sa, host, &port, family);
bjnp_debug(LOG_DEBUG, "Response received from %s port %d(%s)\n", host, port,
family);
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;
}
};
/* valid response, register printer */
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;
}