Transmission 屏蔽迅雷等吸血客户端

2022-04-26

一直跟你索取数据,你有求必应地给它了,等你跟它请求的时候,不好意思,没有,不信你看我进度,我可是 0% 啊!

——这就是吸血。

然而 BT 协议非常宽松,大多数开源客户端并不屏蔽吸血客户端,有些客户端如 qee,可以通过 Peer/Client ID 识别它们并屏蔽。

个人使用的 Transmission 就只能屏蔽 IP,而且作者表示不会加入屏蔽客户端这种功能1。我只好写了个 Shell 脚本2,检测到特定客户端就屏蔽它的 IP。

transmission-remote 的 API 并不能获取 Peer ID,只有 Client ID,也就是根据客户端名称屏蔽的,通过 CLIENTS 来设置。

由于 IP 动态分配,也建议设置一下 TIMEOUT_SECONDS,这样每过一段时间就会清空黑名单。至于 NAT 共用 IP 什么的,那就不在考虑范围内了,误伤就误伤了吧。

另一个问题是加入黑名单后即使 reload 了也不会立即生效,这个我已经提了 issue3。目前暂时的解决方法是设置 RESTART_TORRENT=true 重启该任务。

trans-block.sh
#!/usr/bin/env sh

HOST="localhost:9091"

AUTH="username:password"

CLIENTS="xunlei thunder gt[[:digit:]]\{4\} xl0012 xf dandanplay dl3760 qq libtorrent"

LIST="$HOME/.config/transmission-daemon/blocklists/leechers.txt"
BIN="$LIST.bin"

# Clear blocklist
# 0=disable
# TIMEOUT_SECONDS=$((60 * 60 * 24)) # 24 hours
TIMEOUT_SECONDS=0

# Restart related torrents immediately if leechers detected
RESTART_TORRENT=true

error() { echo "$@" >&2; }

HASH_SHORT=

error_with_hash_tag() { error "[$HASH_SHORT]" "$@"; }

pattern=$(echo "$CLIENTS" | xargs -0 | sed 's/ /\\)\\|\\(/g')
pattern="\(\($pattern\)\)"

trans_reload() {
  error "Reloading"
  # reload: https://github.com/transmission/transmission/blob/main/docs/Editing-Configuration-Files.md#reload-settings
  killall -HUP transmission-daemon
}

block_leechers() {
  peers=$(transmission-remote "$HOST" --auth "$AUTH" --torrent "$1" --info-peers)
  leechers=$(echo "$peers" | grep -i "$pattern")
  result=1

  while IFS= read -r leecher; do
    [ -z "$leecher" ] && continue
    # https://en.wikipedia.org/wiki/PeerGuardian#P2P_plaintext_format
    client=$(echo "$leecher" | awk '{ print $6 }')
    client=$(echo "$leecher" | grep -o -- "$client.*$")
    ip=$(echo "$leecher" | awk '{ print $1 }')
    line="$client:$ip-$ip"
    error_with_hash_tag "Blocking leecher"
    if grep -qs -- "$line" "$LIST"; then
      error_with_hash_tag "Duplicate: $line"
    else
      echo "$line"
      echo "$line" >>"$LIST"
    fi
    result=0
  done <<EOF
$leechers
EOF

  return $result
}

trans_restart_torrent() {
  error_with_hash_tag "Restarting"
  retry_max=5
  for _ in $(seq 0 "$retry_max"); do
    if transmission-remote "$HOST" --auth "$AUTH" --torrent "$1" --stop | grep -qs success; then
      break
    fi
    sleep 1
  done
  stopped=false
  for _ in $(seq 0 "$retry_max"); do
    if transmission-remote "$HOST" --auth "$AUTH" --torrent "$1" --info | grep -qs 'State: Stopped'; then
      error_with_hash_tag "Stopped"
      stopped=true
      break
    fi
    sleep 1
  done
  if [ "$stopped" = false ]; then
    error_with_hash_tag "Unable to stop, skipping"
    return 1
  fi

  for _ in $(seq 0 "$retry_max"); do
    if transmission-remote "$HOST" --auth "$AUTH" --torrent "$1" --start | grep -qs success; then
      break
    fi
    sleep 1
  done
  for _ in $(seq 0 "$retry_max"); do
    if transmission-remote "$HOST" --auth "$AUTH" --torrent "$1" --info | grep -qs 'State: Stopped'; then
      sleep 1
      continue
    fi
    error_with_hash_tag "Started"
    stopped=false
    break
  done
  if [ "$stopped" = true ]; then
    error_with_hash_tag "Unable to start"
    error_with_hash_tag "You may have to start manually"
    return 1
  fi
}

start=$(date +%s)
while true; do
  diff=$(($(date +%s) - start))
  if [ $TIMEOUT_SECONDS -ne 0 ] && [ $diff -ge $TIMEOUT_SECONDS ]; then
    error "Clearing blocklist"
    rm -f "$LIST"
    rm -f "$BIN"
    start=$(date +%s)
    trans_reload
  fi

  hashes=$(transmission-remote "$HOST" --auth "$AUTH" --torrent all --info | grep Hash | awk '{ print $2 }')
  for h in $hashes; do
    HASH_SHORT="$(echo "$h" | cut -c -8)"
    if block_leechers "$h"; then
      trans_reload
      if [ $RESTART_TORRENT = true ]; then
        trans_restart_torrent "$h"
      fi
    fi
  done

  sleep 30
done

通过 Peer ID 或 Client ID 屏蔽只是权宜之计,且不说可以随意修改,迅雷的离线服务器也是用的 libtorrent4,而且普通用户吸血也是不能这样简单判断的。如果要根据其百分比、是否上传之类的判断,实现就会比较复杂,先凑合用吧。

还有一个已知问题,目前 Transmission 黑名单不支持 IPv65,甚至应用本身也没法禁用 IPv66。如果要禁就只能系统里禁了。

systemd 脚本

transmission-block.service
[Unit]
Description=Block Specified Clients for Transmission
Requires=network.target

[Service]
User=debian-transmission
ExecStart=/path/to/trans-block.sh
CPUSchedulingPolicy=idle
Nice=19

[Install]
WantedBy=multi-user.target

加戏时间

不吸血我怎么办?开会员?

更不能用。

一个号称离线下载的?

正经人谁用离线啊。

是啊。

你用离线吗?

我不用,你用吗?

玩 P2P 的能用离线下载?

用离线下载的能叫 P2P?

(异口同声)__!

LinuxShellBitTorrent

本作品根据 署名-非商业性使用-相同方式共享 4.0 国际许可 进行授权。

在 Android 上部署 Linux

Dnsmasq 去 DNS 污染和广告