Setting up a network scanner using SANE

Sharing a scanner over the network using SANE is fairly straightforward. Here's how I shared a scanner on a server (running Debian jessie) with a client (running Ubuntu trusty).

Install SANE

The packages you need on both the client and the server are:

You should check whether or your scanner is supported by the latest stable release or by the latest development version.

In my case, I needed to get a Canon LiDE 220 working so I had to grab the libsane 1.0.25+git20150528-1 package from Debian experimental.

Test the scanner locally

Once you have SANE installed, you can test it out locally to confirm that it detects your scanner:

scanimage -L

Note that you may need to be root for this to work. We'll fix that in the next section.

This should give you output similar to this:

device `genesys:libusb:001:006' is a Canon LiDE 220 flatbed scanner

If that doesn't work, make sure that the scanner is actually detected by the USB stack:

$ lsusb | grep Canon
Bus 001 Device 006: ID 04a9:190f Canon, Inc.

and that its USB ID shows up in the SANE backend it needs:

$ grep 190f /etc/sane.d/genesys.conf
usb 0x04a9 0x190f

To do a test scan, simply run:

scanimage > test.pnm

and then take a look at the (greyscale) image it produced (test.pnm).

Letting normal users access the scanner

In order for users to be able to see the scanner, they will need to be in the scanner group:

adduser francois scanner
adduser saned scanner

with the second one being for remote users.

Next, you'll need to put this in /etc/udev/rules.d/55-libsane.rules:

SUBSYSTEM=="usb", ATTRS{idVendor}=="04a9", MODE="0660", GROUP="scanner", ENV{libsane_matched}="yes"

and then restart udev:

systemctl restart udev.service

That 04a9 ID is the first part of what you saw in lsusb, but you can also see it in the output of sane-find-scanner.

Finally, test the scanner as your normal user:

scanimage > test.pnm

to confirm that everything is working.

Configure the server

With the scanner working locally, it's time to expose it to network clients by adding the client IP addresses to /etc/sane.d/saned.conf:

## Access list
192.168.1.3

and then opening the appropriate ports on your firewall (typically /etc/network/iptables in Debian):

-A INPUT -s 192.168.1.3 -p tcp --dport 6566 -j ACCEPT
-A INPUT -s 192.168.1.3 -p udp -j ACCEPT

Then you need to ensure that the SANE server is running by setting the following in /etc/default/saned:

RUN=yes

if you're using the sysv init system, or by running this command:

systemctl enable saned.socket

if using systemd.

I actually had to reboot to make saned visible to systemd, so if you still run into these errors:

$ service saned start
Failed to start saned.service: Unit saned.service is masked.

you're probably just one reboot away from getting it to work.

One more time you may want to do on the server is to comment out the pixma line in /etc/sane.d/dll.conf if you don't need that backend. When it's enabled (in use or not), it ends up sending broadcast packets on UDP ports 8612 and 8610 on the local network. If you want to avoid seeing this on your endpoint firewalls, simply disable that backend.

Configure the client

On the client, all you need to do is add the following to /etc/sane.d/net.conf:

connect_timeout = 60
myserver

where myserver is the hostname or IP address of the server running saned.

If you have a firewall runnning on the client, make sure you allow SANE traffic from the server:

-A INPUT -s 192.168.1.2 -p tcp --sport 6566 -j ACCEPT

Test the scanner remotely

With everything in place, you should be able to see the scanner from the client computer:

$ scanimage -L
device `net:myserver:genesys:libusb:001:006' is a Canon LiDE 220 flatbed scanner

and successfully perform a test scan using this command:

scanimage > test.pnm

Troubleshooting connection problems

If you see the following error in your logs (systemctl status saned.socket):

saned.socket: Too many incoming connections (1), dropping connection.

then you can work around this bug in the systemd unit by overriding the systemd unit that comes with the package:

cp /lib/systemd/system/saned.socket /etc/systemd/system/saned.socket

then replace:

[Socket]
MaxConnections=1

with:

[Socket]
MaxConnections=64

before finally restarting the service:

systemctl daemon-reload
systemctl restart saned.socket
Hooking into docking and undocking events to run scripts

In order to automatically update my monitor setup and activate/deactivate my external monitor when plugging my ThinkPad into its dock, I found a way to hook into the ACPI events and run arbitrary scripts.

This was tested on a T420 with a ThinkPad Dock Series 3 as well as a T440p and a T460p with a ThinkPad Ultra Dock.

The only requirement is the ThinkPad kernel module. On most ThinkPads it's the tp_smapi module (which you can find in the tp-smapi-dkms package in Debian) but on newer hardware, that interface is gone and you can simply use the thinkpad_acpi module built into the kernel. That's what generates the ibm/hotkey events we will listen for.

Hooking into the events

Create the following ACPI event scripts as suggested in this guide.

Firstly, /etc/acpi/events/thinkpad-dock:

event=ibm/hotkey LEN0068:00 00000080 00006030
action=su francois -c "/home/francois/bin/external-monitor dock"

Secondly, /etc/acpi/events/thinkpad-undock:

event=ibm/hotkey LEN0068:00 00000080 00004011
action=su francois -c "/home/francois/bin/external-monitor undock"

then restart acpid:

sudo systemctl restart acpid.service

Note that I'm not using the real "docking" event (ibm/hotkey LEN0068:00 00000080 00004010) because it seems to be triggered too early and the new displays aren't ready.

Finding the right events

To make sure the events are the right ones, lift them off of:

sudo acpi_listen

and ensure that your script is actually running by adding:

logger "ACPI event: $*"

at the begininng of it and then looking in /var/log/syslog for lines like:

logger: external-monitor undock
logger: external-monitor dock

If that doesn't work for some reason, try using an ACPI event script like this:

event=ibm/hotkey
action=logger %e

to see which event you should hook into.

Using xrandr inside an ACPI event script

Because the script will be running outside of your user session, the xrandr calls must explicitly set the display variable (-d). This is what I used:

#!/bin/sh
logger "ACPI event: $*"
xrandr -d :0.0 --output DP2 --auto
xrandr -d :0.0 --output eDP1 --auto
xrandr -d :0.0 --output DP2 --left-of eDP1