Skip to content

Commit 354673f

Browse files
committed
feat: add usbip server and client
1 parent 3e26809 commit 354673f

10 files changed

+261
-0
lines changed

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.dockerignore
2+
Dockerfile.*
3+
*/docker-compose.yml

Dockerfile.alpine

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM alpine
2+
RUN apk update && apk add --no-cache linux-tools-usbip
3+
RUN if [ ! -e /usr/share/hwdata/usb.ids ]; then mkdir -p /usr/share/hwdata && ln -s /dev/null /usr/share/hwdata/usb.ids; fi
4+
ARG SERVICE
5+
COPY usbip-common.sh "${SERVICE}/init" /opt/usbip-${SERVICE}/
6+
CMD [ "/opt/usbip-${SERVICE}/init" ]

Dockerfile.bullseye

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
FROM debian:bullseye-slim
2+
RUN apt-get update && apt-get install -y usbip && apt-get clean
3+
ARG SERVICE
4+
COPY usbip-common.sh "${SERVICE}/init" "/opt/usbip-${SERVICE}/"
5+
CMD [ "/opt/usbip-${SERVICE}/init" ]

Dockerfile.buster

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
FROM debian:buster-slim
2+
RUN apt-get update && apt-get install -y usbip && apt-get clean
3+
ARG SERVICE
4+
COPY usbip-common.sh "${SERVICE}/init" "/opt/usbip-${SERVICE}/"
5+
# bash is preferred over sh as workaround for
6+
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=779416
7+
# affected package is dash:0.5.10.2-5, fixed in 0.5.11
8+
CMD [ "bash", "/opt/usbip-${SERVICE}/init" ]

build

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
set -eu
3+
dir="$(dirname "${0}")"
4+
find "${dir}" -maxdepth 1 -name 'Dockerfile.*' -printf '%f\n' \
5+
| while read file; do
6+
distro="${file##Dockerfile.}"
7+
find "${dir}" -maxdepth 1 -type d ! -iname ".*" -printf '%f\n' \
8+
| while read service; do
9+
docker build \
10+
-t "ckware/usbip-${service}:${distro}" \
11+
-f "${dir}/Dockerfile.${distro}" \
12+
--build-arg "SERVICE=${service}" \
13+
"${dir}"
14+
done
15+
done

client/docker-compose.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
services:
2+
usbip-client:
3+
#
4+
# host requirement: kernel module `vhci-hcd`
5+
# - command to load until next boot: `modprobe vhci-hcd`
6+
# - command to load after next boot: `echo vhci-hcd >>/etc/modules`
7+
#
8+
9+
image: ckware/usbip-client
10+
container_name: usbip-client
11+
init: true
12+
restart: unless-stopped
13+
environment:
14+
USBIP_SERVER: "server-host"
15+
# USBIP_DEVICE_IDS: "0000:0000,1111:1111"
16+
USBIP_BUS_IDS: "1-1.1,2-2.2"
17+
# USBIP_DEBUG: "true"
18+
19+
#
20+
# privileged mode is required to write to
21+
# `/sys/devices/platform/vhci_hcd.0`
22+
#
23+
# Related issue:
24+
# - [Docker Mount sysfs sub-directory RW without --privileged](https://github.com/moby/moby/issues/22825)
25+
#
26+
# Alternative:
27+
# - Replace runc with sysbox, see
28+
# [Unable to mount /sys inside the container with rw access in unprivileged mode](https://forums.docker.com/t/97043)
29+
#
30+
privileged: true

client/init

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
. "$(dirname "${0}")/usbip-common.sh"
5+
portid_file=/var/run/usbip-client.portids
6+
7+
to_log() {
8+
# jumping through hoops to fail on error in a POSIX shell:
9+
set -eu # shell options from above are not set because we are in a subshell and options do not get inherited
10+
local output # return code of subcommand would get lost when 'local' would be declared and assigned in one step
11+
output="$("${@}")" # return code of subcommand would get lost when piped to tee
12+
echo "${output}" | tee "${log_file}"
13+
}
14+
15+
detect_bus_ids_from_device_ids() {
16+
local device_ids="${1}"
17+
to_log usbip list -r "${USBIP_SERVER}" | sed -nE "s/^\s*([^:]+):.* \((${device_ids})\)$/\1/p"
18+
}
19+
20+
start() {
21+
log "Starting client..."
22+
23+
if [ -z "${USBIP_SERVER-}" ]; then
24+
error "Missing environment variable USBIP_SERVER"
25+
return 1
26+
fi
27+
local bus_ids
28+
bus_ids="$(detect_bus_ids detect_bus_ids_from_device_ids)"
29+
30+
log "Attaching ${bus_ids}"
31+
echo "${bus_ids}" | tr ',' '\n' | xargs -n 1 usbip ${USBIP_DEBUG:+--debug} attach -r "${USBIP_SERVER}" -b || {
32+
rc="$?"
33+
log "Attachment failed. Possible causes:"
34+
log "- Kernel module 'vhci-hcd' is not loaded (try 'modprobe vhci-hcd')"
35+
log "- No write access to /sys/devices/platform/ (try to run as root)"
36+
return "${rc}"
37+
}
38+
39+
sleep 1
40+
local ports
41+
ports="$(to_log usbip ${USBIP_DEBUG:+--debug} port)"
42+
echo "${ports}" | sed -nE 's/^Port ([^:]+):.*$/\1/p' >>"${portid_file}"
43+
log "Attached ports: $(xargs -r -a "${portid_file}")"
44+
45+
log "Attachment complete."
46+
log "Client is started."
47+
}
48+
49+
stop() {
50+
log "Stopping client..."
51+
log "Detaching all ports..."
52+
if [ -f "${portid_file}" ]; then
53+
xargs -a "${portid_file}" -n 1 usbip ${USBIP_DEBUG:+--debug} detach -p || true
54+
rm "${portid_file}"
55+
fi
56+
log "Detachment complete."
57+
log "Client is stopped."
58+
}
59+
60+
main

server/docker-compose.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
services:
2+
usbip-server:
3+
#
4+
# host requirement: kernel module `usbip-host`
5+
# - command to load until next boot: `modprobe usbip-host`
6+
# - command to load after next boot: `echo usbip-host >>/etc/modules`
7+
#
8+
9+
image: ckware/usbip-server
10+
container_name: usbip-server
11+
init: true
12+
restart: unless-stopped
13+
ports:
14+
- "3240:3240"
15+
environment:
16+
USBIP_DEVICE_IDS: "0000:0000,1111:1111"
17+
# USBIP_BUS_IDS: "1-1.1,2-2.2"
18+
# USBIP_DEBUG: "true"
19+
volumes:
20+
- "/sys/bus/usb/drivers/usb:/sys/bus/usb/drivers/usb"
21+
- "/sys/bus/usb/drivers/usbip-host:/sys/bus/usb/drivers/usbip-host"
22+
- "/sys/devices/platform:/sys/devices/platform"

server/init

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
. "$(dirname "${0}")/usbip-common.sh"
5+
busid_file=/var/run/usbip-server.busids
6+
7+
detect_bus_ids_from_device_ids() {
8+
local device_ids="${1}"
9+
usbip list -p -l | sed -nE "s/busid=(.+)#usbid=(${device_ids})#/\1/p"
10+
}
11+
12+
bind_device() {
13+
local bus_id="${1}"
14+
if grep "${bus_id}" "${busid_file}" >/dev/null; then
15+
log "Skipping binding of ${bus_id} because it is already bound"
16+
else
17+
log "Binding ${bus_id}"
18+
usbip ${USBIP_DEBUG:+--debug} bind -b "${bus_id}" || {
19+
rc="$?"
20+
log "Binding failed. Possible causes:"
21+
log "- Kernel module 'usbip-host' is not loaded (try 'modprobe usbip-host')"
22+
log "- No write access to /sys/bus/usb/drivers"
23+
return "${rc}"
24+
}
25+
echo "${bus_id}" >>"${busid_file}"
26+
fi
27+
}
28+
29+
30+
start() {
31+
log "Starting server..."
32+
33+
local bus_ids
34+
bus_ids="$(detect_bus_ids detect_bus_ids_from_device_ids)"
35+
36+
log "Starting network listener..."
37+
usbipd ${USBIP_DEBUG:+--debug} 2>&1 >"${log_file}" &
38+
log "Network listener is started."
39+
40+
usbip list -r localhost | sed -nE "s/^\s*([^: ]+):.*$/\1/p" >>"${busid_file}"
41+
[ ! -s "${busid_file}" ] || log "Picking up devices that are already bound: $(xargs -a ${busid_file})"
42+
echo "${bus_ids}" | while read busid; do bind_device "${busid}"; done
43+
44+
usbip list -r localhost >"${log_file}"
45+
log "Server is started."
46+
}
47+
48+
stop() {
49+
log "Stopping server..."
50+
log "Unbinding all devices..."
51+
if [ -f "${busid_file}" ]; then
52+
xargs -a "${busid_file}" -n 1 usbip ${USBIP_DEBUG:+--debug} unbind -b || true
53+
rm "${busid_file}"
54+
fi
55+
log "Unbinding complete."
56+
log "Server is stopped."
57+
}
58+
59+
main

usbip-common.sh

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
log_file=/dev/stderr
5+
6+
log() {
7+
echo >"${log_file}" "${@}"
8+
}
9+
10+
error() {
11+
log "Error: ${@}"
12+
}
13+
14+
detect_bus_ids() {
15+
local from_device_ids="${1}"
16+
17+
local bus_ids
18+
if [ -n "${USBIP_BUS_IDS-}" ]; then
19+
bus_ids="${USBIP_BUS_IDS}"
20+
[ -z "${USBIP_DEVICE_IDS-}" ] || log "Ignoring USBIP_DEVICE_IDS for USBIP_BUS_IDS"
21+
elif [ -n "${USBIP_DEVICE_IDS-}" ]; then
22+
local device_ids
23+
device_ids="$(echo "${USBIP_DEVICE_IDS}" | tr ',' '|')"
24+
bus_ids="$("${from_device_ids}" "${device_ids}")"
25+
[ -n "${bus_ids}" ] || error "No bus ids found for device ids ${USBIP_DEVICE_IDS}"
26+
else
27+
error "One of [USBIP_BUS_IDS, USBIP_DEVICE_IDS] must be set as environment variable."
28+
fi
29+
if [ -z "${bus_ids-}" ]; then
30+
error "No bus ids found."
31+
return 2
32+
fi
33+
34+
echo "${bus_ids}"
35+
}
36+
37+
reload() {
38+
log "Reloading..."
39+
stop
40+
load
41+
}
42+
43+
load() {
44+
start
45+
tail -f /dev/null &
46+
wait "${!}"
47+
}
48+
49+
main() {
50+
trap reload HUP
51+
trap stop TERM INT
52+
load
53+
}

0 commit comments

Comments
 (0)