2
0
Fork 0

Merge branch 'dev'

This commit is contained in:
soxfor 2023-03-23 23:01:55 +00:00
commit e6bf893c05
5 changed files with 152 additions and 43 deletions

View File

@ -17,6 +17,7 @@ on:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
TARGET_PLATFORMS: linux/amd64,linux/arm64
jobs:
build-and-push-image:
@ -29,6 +30,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
platforms: ${{ env.TARGET_PLATFORMS }}
- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
@ -49,3 +58,4 @@ jobs:
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: ${{ env.TARGET_PLATFORMS }}

View File

@ -15,24 +15,24 @@ FROM ubuntu:jammy
LABEL org.opencontainers.image.source="https://github.com/soxfor/qbittorrent-natmap"
LABEL org.opencontainers.image.base.name="ubuntu:jammy"
LABEL description="Map port via NAT-PMP and update qBittorrent configuration"
LABEL version="1.0.2"
LABEL version="1.0.5"
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update
RUN apt install --no-install-suggests --no-install-recommends -y natpmpc curl bc
RUN apt install --no-install-suggests --no-install-recommends -y natpmpc curl bc netcat-openbsd
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt/*
RUN apt clean
ENV QBITTORRENT_SERVER=''
ENV QBITTORRENT_PORT='8080'
ENV QBITTORRENT_USER='admin'
ENV QBITTORRENT_PASS='adminadmin'
ENV VPN_GATEWAY=''
ENV VPN_CT_NAME='gluetun'
ENV VPN_IF_NAME='tun0'
ENV CHECK_INTERVAL='300'
ENV NAT_LEASE_LIFETIME='300'
ENV QBITTORRENT_SERVER=
ENV QBITTORRENT_PORT=8080
ENV QBITTORRENT_USER=admin
ENV QBITTORRENT_PASS=adminadmin
ENV VPN_GATEWAY=
ENV VPN_CT_NAME=gluetun
ENV VPN_IF_NAME=tun0
ENV CHECK_INTERVAL=300
ENV NAT_LEASE_LIFETIME=300
COPY --from=docker-cli /usr/bin/docker /usr/bin/docker
COPY data/start.sh /start.sh

View File

@ -1,21 +1,56 @@
# qbittorrent-natmap
# qBittorrent-NatMap
Docker container to maintain and update the NAT-PMP/UPnP forwarded port to qBittorrent.
The objective of this container is to run a script that requests a port forward (via NAT-PMP) from the VPN provider and upon success changes the listening port of the qBittorrent client when running in Docker
Tested with Gluetun using ProtonVPN (Wireguard) and qBittorrent.
This solution is currently in use and tested with [Gluetun](https://github.com/qdm12/gluetun) and [qBittorrent](https://github.com/linuxserver/docker-qbittorrent) from Linuxserver.io and with VPN fron ProtonVPN using Wireguard.
This solution can probably need a few tweaks and error handling, also some work in opening only the needed port on the VPN container.
## What made me do this?
In the repo a sample docker-compose.yml can be found.
The need to improve the seeding/upload performance and not finding any work done for this scenario (qBittorrent using docker'ized VPN), but finding [this post on reddit](https://old.reddit.com/r/ProtonVPN/comments/10owypt/successful_port_forward_on_debian_wdietpi_using/) by u/TennesseeTater for Deluge made me try and do something similar. His post is also referenced in the [ProtonVPN Guide][1].
The following variables can be set:
## Why not modify the Gluetun image and include natpmpc there?
- QBITTORRENT_SERVER (Default: <not set>)*
- QBITTORRENT_PORT (Default: 8080)
- QBITTORRENT_USER (Default: admin)
- QBITTORRENT_PASS (Default: adminadmin)
- VPN_CT_NAME (Default: gluetun)
- VPN_IF_NAME (Default: tun0)
- VPN_GATEWAY* (Default: <not set>)*
- CHECK_INTERVAL (Default: 300s)
- NAT_LEASE_TIME (Default: 300s)
Well, as far as I could find, Alpine Linux doesn't have natively the binary for **natpmpc**, the NAT-PMP client used to request the *port forward* as per the instructions for [manual mapping][1] on ProtonVPN. Gluetun is using alpine as it's base image.
On AlpineLinux package info: [natpmpc binary not found][2] and [here][3] a request still in open state.
If I had the binary needed on the Gluetun container a script running on the host system instead of the container would probably suffice, allowing the following action: doing "docker exec <container> natpmpc <args>"
## What does the script do/modify?
So far:
* Evaluates the required variables for execution (for now, if they're set) and if **docker.sock** was mapped from the host into the container
* If the above succeeds:
* Get the VPN public IP
* Grab the SessionID cookie from qBittorrent
* Grab the current listen port from qBittorrent
* After the configured checks pass, a function to request and verify the port mapping starts
* Using *natpmpc* a port mapping request is made to the address defined in `VPN_GATEWAY` for udp and tcp
* Comparison is made between the current configured port in qBittorrent and the currently active mapped port from the VPN
* If a condition of different port is found:
* The new port is configured in qBittorrent, along with that it's also disabled the random port setting and UPnP mapping from the torrent client
* A couple of commands are executed to add/remove iptables rules regarding the previous active and new active mapped port on the VPN container
These actions are performed continuously (in a loop, every 5 minutes (default, can be lowered/increased)), most likely an option to set a failure count will be added in the future.
## Configurable variables:
* QBITTORRENT_SERVER (Defaults to empty, **needs to be set**)
* If setting here an address not related to the `VPN_IF_NAME` (default: tun0) a few users have [reported](https://old.reddit.com/r/ProtonVPN/comments/11ubgvi/port_forward_with_qbittorrent_and_protonvpn_on/jcxirts/) needing to set `FIREWALL_OUTBOUND_SUBNETS` for the Gluetun/VPN container
* For ProtonVPN using Wireguard and qBittorrent container using `VPN_CT_NAME` as network_mode this would be set to **10.2.0.2**
* QBITTORRENT_PORT (Defaults to **8080**)
* QBITTORRENT_USER (Defaults to **admin**)
* QBITTORRENT_PASS (Defaults to **adminadmin**)
* VPN_GATEWAY (Defaults to empty, **needs to be set**)
* The value for this variable will be the `VPN_IF_NAME` (default: tun0) gateway address, not the `VPN_ENDPOINT_IP` from the Gluetun/VPN Container when using Wireguard, [more info here](https://github.com/qdm12/gluetun/wiki/Custom-provider#wireguard-only).
* For ProtonVPN using Wireguard this would be set to **10.2.0.1**
* VPN_CT_NAME (Defaults to **gluetun**)
* VPN_IF_NAME (Defaults to **tun0**)
* CHECK_INTERVAL (Defaults to **300s**)
* NAT_LEASE_LIFETIME (Defaults to **300s**)
* Ideally both `CHECK_INTERVAL` and `NAT_LEASE_LIFETIME` should be set equal or the check interval lower than the lease lifetime, but never above.
[1]: https://protonvpn.com/support/port-forwarding-manual-setup/
[2]: https://pkgs.alpinelinux.org/contents?file=natpmpc&path=&name=&branch=edge
[3]: https://gitlab.alpinelinux.org/alpine/awall/-/issues/2220

View File

@ -13,6 +13,7 @@ findconfiguredport() {
}
findactiveport() {
natpmpc -g ${VPN_GATEWAY} -a 0 0 udp ${NAT_LEASE_LIFETIME} >/dev/null 2>&1
natpmpc -g ${VPN_GATEWAY} -a 0 0 tcp ${NAT_LEASE_LIFETIME} | grep -oP '(?<=Mapped public port.).*(?=.protocol.*)'
}
@ -25,18 +26,44 @@ qbt_changeport(){
return $?
}
public_ip=$(getpublicip)
qbt_sid=$(qbt_login)
configured_port=$(findconfiguredport ${qbt_sid})
active_port=''
qbt_checksid(){
if echo $(curl -s --header "Referer: http://${QBITTORRENT_SERVER}:${QBITTORRENT_PORT}" --cookie "${qbt_sid}" http://${QBITTORRENT_SERVER}:${QBITTORRENT_PORT}/api/v2/app/version) | grep -qi forbidden; then
return 1
else
return 0
fi
}
qbt_isreachable(){
nc -4 -vw 5 ${QBITTORRENT_SERVER} ${QBITTORRENT_PORT} 2>&1 &>/dev/null
}
fw_delrule(){
if (docker exec ${VPN_CT_NAME} /sbin/iptables -L INPUT -n | grep -qP "^ACCEPT.*${configured_port}.*"); then
docker exec ${VPN_CT_NAME} /sbin/iptables -D INPUT -i ${VPN_IF_NAME} -p tcp --dport ${configured_port} -j ACCEPT
docker exec ${VPN_CT_NAME} /sbin/iptables -D INPUT -i ${VPN_IF_NAME} -p udp --dport ${configured_port} -j ACCEPT
fi
}
fw_addrule(){
if ! (docker exec ${VPN_CT_NAME} /sbin/iptables -L INPUT -n | grep -qP "^ACCEPT.*${active_port}.*"); then
docker exec ${VPN_CT_NAME} /sbin/iptables -A INPUT -i ${VPN_IF_NAME} -p tcp --dport ${active_port} -j ACCEPT
docker exec ${VPN_CT_NAME} /sbin/iptables -A INPUT -i ${VPN_IF_NAME} -p udp --dport ${active_port} -j ACCEPT
return 0
else
return 1
fi
}
get_portmap() {
res=0
public_ip=$(getpublicip)
if echo $(curl -s --header "Referer: http://${QBITTORRENT_SERVER}:${QBITTORRENT_PORT}" --cookie "${qbt_sid}" http://${QBITTORRENT_SERVER}:${QBITTORRENT_PORT}/api/v2/app/version) | grep -qi forbidden; then
if ! qbt_checksid; then
echo "$(timestamp) | qBittorrent Cookie invalid, getting new SessionID"
qbt_sid=$(qbt_login)
else
echo "$(timestamp) | qBittorrent SessionID Ok!"
fi
configured_port=$(findconfiguredport ${qbt_sid})
@ -48,12 +75,9 @@ get_portmap() {
if [ ${configured_port} != ${active_port} ]; then
if qbt_changeport ${qbt_sid} ${active_port}; then
docker exec ${VPN_CT_NAME} /sbin/iptables -A INPUT -i ${VPN_IF_NAME} -p tcp --dport ${active_port} -j ACCEPT
docker exec ${VPN_CT_NAME} /sbin/iptables -D INPUT -i ${VPN_IF_NAME} -p tcp --dport ${configured_port} -j ACCEPT
if docker exec ${VPN_CT_NAME} /sbin/iptables -L INPUT -n | grep -qP "^ACCEPT.*${active_port}.*"; then
echo "$(timestamp) | IPTables rule added for port ${active_port} on ${VPN_CT_NAME} container"
if fw_delrule; then
echo "$(timestamp) | IPTables rule deleted for port ${configured_port} on ${VPN_CT_NAME} container"
fi
sleep 3
echo "$(timestamp) | Port Changed to: $(findconfiguredport ${qbt_sid})"
else
echo "$(timestamp) | Port Change failed."
@ -63,9 +87,14 @@ get_portmap() {
echo "$(timestamp) | Port OK (Act: ${active_port} Cfg: ${configured_port})"
fi
if fw_addrule; then
echo "$(timestamp) | IPTables rule added for port ${active_port} on ${VPN_CT_NAME} container"
fi
return $res
}
pre_reqs() {
while read var; do
[ -z "${!var}" ] && { echo "$(timestamp) | ${var} is empty or not set."; exit 1; }
done << EOF
@ -82,6 +111,27 @@ EOF
[ ! -S /var/run/docker.sock ] && { echo "$(timestamp) | Docker socket doesn't exist or is inaccessible"; exit 2; }
return 0
}
load_vals(){
public_ip=$(getpublicip)
if qbt_isreachable; then
qbt_sid=$(qbt_login)
configured_port=$(findconfiguredport ${qbt_sid})
else
echo "$(timestamp) | Unable to reach qBittorrent at ${QBITTORRENT_SERVER}:${QBITTORRENT_PORT}"
exit 6
fi
active_port=''
}
if pre_reqs; then load_vals; fi
[ -z ${public_ip} ] && { echo "$(timestamp) | Unable to grab VPN Public IP. Please check configuration"; exit 3; }
[ -z ${configured_port} ] && { echo "$(timestamp) | qBittorrent configured port value is empty(?). Please check configuration"; exit 4; }
[ -z ${qbt_sid} ] && { echo "$(timestamp) | Unable to grab qBittorrent SessionID. Please check configuration"; exit 5; }
while true;
do
if get_portmap; then

View File

@ -8,12 +8,26 @@ services:
environment:
- TZ=Etc/UTC
- QBITTORRENT_SERVER=ip.a.dd.r
# - QBITTORRENT_PORT= # Defaults to 8080
# - QBITTORRENT_USER= # Defaults to admin
# - QBITTORRENT_PASS= # Defaults to adminadmin
# - VPN_CT_NAME= # Defaults to gluetun
# - QBITTORRENT_PORT=
# Defaults to 8080
# - QBITTORRENT_USER=
# Defaults to admin
# - QBITTORRENT_PASS=
# Defaults to adminadmin
# - VPN_CT_NAME=
# Defaults to gluetun
- VPN_GATEWAY=ip.a.dd.r
# - VPN_IF_NAME= # Defaults to tun0
# - CHECK_INTERVAL= # Defaults to 300sec
# - NAT_LEASE_LIFETIME= # Defaults to 300sec
# - VPN_IF_NAME=
# Defaults to tun0
# - CHECK_INTERVAL=
# Defaults to 300sec
# - NAT_LEASE_LIFETIME=
# Defaults to 300sec
depends_on:
# VPN Container Name
- gluetun
# qBittorrent Container Name
- qbittorrent
network_mode: "container:gluetun" # Specify the VPN container name here
# or
# network_mode: "service:gluetun" # if defined on the same docker-compose file