使用 DNSTap 和 BGP 繞過 ILV 阻塞

使用 DNSTap 和 BGP 繞過 ILV 阻塞

我知道這個話題很糟糕。 例如,有一個很棒的 文章, 但只有黑名單的 IP 部分才被考慮在內。 我們還將添加域。

由於法院和 RKN 左右屏蔽所有內容,並且提供商正在努力避免受到 Revizorro 開出的罰款,因此屏蔽帶來的相關損失相當大。 在“合法”阻止的網站中,有很多有用的網站(你好,rutracker)

我住在 RKN 管轄範圍之外,但我的父母、親戚和朋友留在家裡。 因此決定為遠離 IT 的人想出一個簡單的方法來繞過封鎖,最好是完全沒有他們的參與。

在這篇筆記中,我不會按步驟描述基本的網絡事物,但我會描述如何實現這個方案的一般原則。 因此,必須了解網絡的一般工作原理,尤其是在 Linux 中的工作原理。

鎖的種類

首先,讓我們回顧一下被阻止的內容。

從 RKN 卸載的 XML 中有幾種類型的鎖:

  • IP
  • 域名
  • 網址

為簡單起見,我們將它們減少為兩個:IP 和域,我們將簡單地通過 URL 將域從阻止中拉出來(更準確地說,他們已經為我們做了這件事)。

好人來自 俄羅斯聯邦 實現了精彩 API,通過它我們可以得到我們需要的東西:

訪問被封鎖的網站

為此,我們需要一些小型的外國 VPS,最好是無限流量 - 有很多這樣的 VPS 只需 3-5 美元。 您需要在國外附近使用它,這樣 ping 就不會很大,但同樣要考慮到 Internet 和地理位置並不總是重合。 而且由於沒有 5 美元的 SLA,最好從不同的供應商處獲取 2+ 件以實現容錯。

接下來,我們需要建立一條從客戶端路由器到 VPS 的加密隧道。 我使用 Wireguard 作為最快和最容易設置的。 我也有基於 Linux 的客戶端路由器(APU2 或者 OpenWRT 中的東西)。 對於某些 Mikrotik / Cisco,您可以使用它們提供的協議,如 OpenVPN 和 GRE-over-IPSEC。

識別和重定向感興趣的流量

當然,您可以關閉所有通過國外的互聯網流量。 但是,最有可能的是,處理本地內容的速度會因此受到很大影響。 另外,VPS 的帶寬要求會更高。

因此,我們需要以某種方式將流量分配給被阻止的站點,並有選擇地將其定向到隧道。 即使有一些“額外”的交通到達那裡,也比讓所有東西都通過隧道要好得多。

為了管理流量,我們將使用 BGP 協議並宣布從我們的 VPS 到客戶端的必要網絡的路由。 讓我們將 BIRD 作為功能最強大、最方便的 BGP 守護進程之一。

IP

通過 IP 阻止,一切都很清楚:我們只需使用 VPS 公佈所有被阻止的 IP。 問題是 API 返回的列表中大約有 600 萬個子網,其中絕大多數是 /32 主機。 如此多的路由會使弱客戶端路由器感到困惑。

因此,在處理列表時,如果有 24 個或更多主機,則決定匯總到網絡 / 2。 因此,路線數量減少到約 100 萬條。 將遵循此腳本。

域名

它比較複雜,有幾種方法。 例如,您可以在每個客戶端路由器上安裝一個透明的 Squid,並在那裡進行 HTTP 攔截並窺視 TLS 握手,以便在第一種情況下獲取請求的 URL,在第二種情況下從 SNI 獲取域。

但是由於各種新奇的 TLS1.3 + eSNI,HTTPS 分析每天都變得越來越不真實。 是的,客戶端的基礎設施變得越來越複雜——你將不得不至少使用 OpenWRT。

因此,我決定採用攔截 DNS 請求響應的方式。 在這裡,任何 DNS-over-TLS / HTTPS 也開始懸停在你的頭上,但我們(現在)可以在客戶端控制這部分 - 要么禁用它,要么使用你自己的服務器進行 DoT / DoH。

如何攔截DNS?

這裡也可能有幾種方法。

  • 通過 PCAP 或 NFLOG 攔截 DNS 流量
    這兩種攔截方法都在實用程序中實現 西達. 但是支持時間不長,功能也很原始,所以還是需要自己寫一個harness。
  • 解析DNS服務器日誌
    不幸的是,我所知道的遞歸器無法記錄響應,而只能記錄請求。 原則上,這是合乎邏輯的,因為與請求不同,答案具有復雜的結構並且很難以文本形式編寫。
  • DNSTap
    幸運的是,他們中的許多人已經為此目的支持 DNSTap。

什麼是 DNSTap?

使用 DNSTap 和 BGP 繞過 ILV 阻塞

它是一種基於協議緩衝區和幀流的客戶端-服務器協議,用於從 DNS 服務器傳輸到結構化 DNS 查詢和響應的收集器。 本質上,DNS 服務器傳輸查詢和響應元數據(消息類型、客戶端/服務器 IP 等)以及(二進制)形式的完整 DNS 消息,並通過網絡與它們一起工作。

了解在 DNSTap 範例中,DNS 服務器充當客戶端,收集器充當服務器,這一點很重要。 也就是說,DNS 服務器連接到收集器,而不是相反。

今天,所有流行的 DNS 服務器都支持 DNSTap。 但是,例如,許多發行版(如 Ubuntu LTS)中的 BIND 通常出於某種原因在沒有其支持的情況下構建。 所以我們不要為重新組裝而煩惱,而是採用更輕更快的遞歸 - Unbound。

如何捕捉 DNSTap?

一些 號碼 用於處理 DNSTap 事件流的 CLI 實用程序,但它們不適合解決我們的問題。 因此,我決定發明自己的自行車,它可以完成所有必要的事情: dnstap-bgp

工作算法:

  • 啟動時,它會從文本文件加載域列表,將它們反轉 (habr.com -> com.habr),排除斷線、重複項和子域(即,如果列表包含 habr.com 和 www.habr.com,它只會加載第一個)並構建一個前綴樹以快速搜索此列表
  • 作為 DNSTap 服務器,它等待來自 DNS 服務器的連接。 原則上它同時支持UNIX和TCP套接字,但我所知道的DNS服務器只能使用UNIX套接字
  • 傳入的 DNSTap 數據包首先被反序列化為 Protobuf 結構,然後位於 Protobuf 字段之一的二進制 DNS 消息本身被解析為 DNS RR 記錄級別
  • 檢查請求的主機(或其父域)是否在加載列表中,如果不在,則忽略響應
  • 僅從響應中選擇A/AAAA/CNAME RR,並從中提取相應的IPv4/IPv6地址
  • IP 地址使用可配置的 TTL 緩存並通告給所有已配置的 BGP 對等體
  • 當收到指向已緩存 IP 的響應時,更新其 TTL
  • TTL 過期後,條目將從緩存和 BGP 公告中刪除

附加功能:

  • 通過 SIGHUP 重讀域列表
  • 保持緩存與其他實例同步 dnstap-bgp 通過 HTTP/JSON
  • 複製磁盤上的緩存(在 BoltDB 數據庫中)以在重啟後恢復其內容
  • 支持切換到不同的網絡命名空間(為什麼需要這個將在下面描述)
  • 支持 IPv6

限制:

  • 尚不支持 IDN 域
  • 很少的 BGP 設置

我收集了 RPM 和 DEB 易於安裝的軟件包。 應該可以在所有帶有 systemd 的相對較新的操作系統上工作。 他們沒有任何依賴關係。

駕駛

那麼,讓我們開始將所有組件組裝在一起。 結果,我們應該得到類似這樣的網絡拓撲:
使用 DNSTap 和 BGP 繞過 ILV 阻塞

工作的邏輯,我認為,從圖中很清楚:

  • 客戶端將我們的服務器配置為 DNS,DNS 查詢也必須通過 VPN。 這是必要的,這樣提供商就無法使用 DNS 攔截來阻止。
  • 打開站點時,客戶端會發送一個 DNS 查詢,例如“xxx.org 的 IP 是什麼”
  • 不作承諾 解析 xxx.org(或從緩存中獲取)並向客戶端發送響應“xxx.org 具有某某 IP”,通過 DNSTap 並行複制它
  • dnstap-bgp 公佈這些地址 如果域在阻止列表中,則通過 BGP
  • 通告到這些 IP 的路由 next-hop self 客戶端路由器
  • 從客戶端到這些 IP 的後續數據包通過隧道

在服務器上,對於到被阻止站點的路由,我在 BIRD 中使用了一個單獨的表,它不會以任何方式與操作系統相交。

該方案有一個缺點:來自客戶端的第一個 SYN 數據包很可能有時間通過國內提供商離開。 路線不會立即公佈。 這裡的選項可能取決於提供商如何進行阻止。 如果他只是降低流量,那麼就沒有問題。 如果他將它重定向到某個 DPI,那麼(理論上)特效是可能的。

也有可能客戶端不尊重 DNS TTL 奇蹟,這可能導致客戶端使用其腐爛緩存中的一些陳舊條目而不是詢問 Unbound。

在實踐中,第一個和第二個都沒有給我帶來問題,但你的里程可能會有所不同。

服務器調整

為了便於滾動,我寫了 Ansible 的角色. 它可以配置基於 Linux 的服務器和客戶端(專為基於 deb 的發行版而設計)。 所有設置都非常明顯,並設置在 庫存.yml. 這個角色是從我的大劇本中刪減的,所以它可能包含錯誤 - 拉請求 歡迎🙂

讓我們來看看主要的組成部分。

BGP

在同一台主機上運行兩個 BGP 守護進程有一個根本問題:BIRD 不想與本地主機(或任何本地接口)建立 BGP 對等。 從字皆。 谷歌搜索和閱讀郵件列表沒有幫助,他們聲稱這是設計使然。 也許有某種方法,但我沒有找到。

你可以嘗試另一個 BGP 守護進程,但我喜歡 BIRD,我到處都用它,我不想生產實體。

因此,我將 dnstap-bgp 隱藏在網絡命名空間中,它通過 veth 接口連接到根:它就像一個管道,其末端伸出在不同的命名空間中。 在每一端,我們都掛有不超出主機範圍的私有 p2p IP 地址,因此它們可以是任何東西。 這與用於訪問內部進程的機制相同 受到所有人的喜愛 Docker 和其他容器。

為此,它被寫成 腳本 並且上面已經描述的將你自己拖到另一個命名空間的功能被添加到 dnstap-bgp。 因此,它必須以 root 身份運行或通過 setcap 命令發佈到 CAP_SYS_ADMIN 二進製文件。

創建命名空間的示例腳本

#!/bin/bash

NS="dtap"

IP="/sbin/ip"
IPNS="$IP netns exec $NS $IP"

IF_R="veth-$NS-r"
IF_NS="veth-$NS-ns"

IP_R="192.168.149.1"
IP_NS="192.168.149.2"

/bin/systemctl stop dnstap-bgp || true

$IP netns del $NS > /dev/null 2>&1
$IP netns add $NS

$IP link add $IF_R type veth peer name $IF_NS
$IP link set $IF_NS netns $NS

$IP addr add $IP_R remote $IP_NS dev $IF_R
$IP link set $IF_R up

$IPNS addr add $IP_NS remote $IP_R dev $IF_NS
$IPNS link set $IF_NS up

/bin/systemctl start dnstap-bgp

dnstap-bgp.conf

namespace = "dtap"
domains = "/var/cache/rkn_domains.txt"
ttl = "168h"

[dnstap]
listen = "/tmp/dnstap.sock"
perm = "0666"

[bgp]
as = 65000
routerid = "192.168.149.2"

peers = [
    "192.168.149.1",
]

鳥.conf

router id 192.168.1.1;

table rkn;

# Clients
protocol bgp bgp_client1 {
    table rkn;
    local as 65000;
    neighbor 192.168.1.2 as 65000;
    direct;
    bfd on;
    next hop self;
    graceful restart;
    graceful restart time 60;
    export all;
    import none;
}

# DNSTap-BGP
protocol bgp bgp_dnstap {
    table rkn;
    local as 65000;
    neighbor 192.168.149.2 as 65000;
    direct;
    passive on;
    rr client;
    import all;
    export none;
}

# Static routes list
protocol static static_rkn {
    table rkn;
    include "rkn_routes.list";
    import all;
    export none;
}

rkn_routes.list

route 3.226.79.85/32 via "ens3";
route 18.236.189.0/24 via "ens3";
route 3.224.21.0/24 via "ens3";
...

DNS

默認情況下,在 Ubuntu 中,Unbound 二進製文件被 AppArmor 配置文件限制,禁止它連接到各種 DNSTap 套接字。 您可以刪除或禁用此配置文件:

# cd /etc/apparmor.d/disable && ln -s ../usr.sbin.unbound .
# apparmor_parser -R /etc/apparmor.d/usr.sbin.unbound

這可能應該添加到劇本中。 當然,更正配置文件並頒發必要的權限是理想的,但我太懶了。

未綁定.conf

server:
    chroot: ""
    port: 53
    interface: 0.0.0.0
    root-hints: "/var/lib/unbound/named.root"
    auto-trust-anchor-file: "/var/lib/unbound/root.key"
    access-control: 192.168.0.0/16 allow

remote-control:
    control-enable: yes
    control-use-cert: no

dnstap:
    dnstap-enable: yes
    dnstap-socket-path: "/tmp/dnstap.sock"
    dnstap-send-identity: no
    dnstap-send-version: no

    dnstap-log-client-response-messages: yes

下載和處理列表

用於下載和處理 IP 地址列表的腳本
它下載列表,匯總到前綴 效果圖。 在 不要添加 и 不要總結 您可以告訴 IP 和網絡跳過或不匯總。 我需要它。 我的 VPS 的子網在黑名單中 🙂

有趣的是,RosKomSvoboda API 會阻止使用默認 Python 用戶代理的請求。 看起來腳本小子明白了。 因此,我們將其更改為 Ognelis。

到目前為止,它只適用於 IPv4。 IPv6 的份額很小,但很容易修復。 除非你也必須使用 bird6。

rkn.py

#!/usr/bin/python3

import json, urllib.request, ipaddress as ipa

url = 'https://api.reserve-rbl.ru/api/v2/ips/json'
pfx = '24'

dont_summarize = {
    # ipa.IPv4Network('1.1.1.0/24'),
}

dont_add = {
    # ipa.IPv4Address('1.1.1.1'),
}

req = urllib.request.Request(
    url,
    data=None, 
    headers={
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'
    }
)

f = urllib.request.urlopen(req)
ips = json.loads(f.read().decode('utf-8'))

prefix32 = ipa.IPv4Address('255.255.255.255')

r = {}
for i in ips:
    ip = ipa.ip_network(i)
    if not isinstance(ip, ipa.IPv4Network):
        continue

    addr = ip.network_address

    if addr in dont_add:
        continue

    m = ip.netmask
    if m != prefix32:
        r[m] = [addr, 1]
        continue

    sn = ipa.IPv4Network(str(addr) + '/' + pfx, strict=False)

    if sn in dont_summarize:
        tgt = addr
    else:
        tgt = sn

    if not sn in r:
        r[tgt] = [addr, 1]
    else:
        r[tgt][1] += 1

o = []
for n, v in r.items():
    if v[1] == 1:
        o.append(str(v[0]) + '/32')
    else:
        o.append(n)

for k in o:
    print(k)

要更新的腳本
我每天在表冠上運行一次,也許值得每 4 小時拉動一次。 在我看來,這是 RKN 要求供應商提供的續訂期限。 另外,他們還有其他一些超級緊急的封鎖,可能會更快到達。

執行以下操作:

  • 運行第一個腳本並更新路由列表(rkn_routes.list) 為鳥
  • 重新加載鳥
  • 更新並清理 dnstap-bgp 的域列表
  • 重新加載 dnstap-bgp

rkn_update.sh

#!/bin/bash

ROUTES="/etc/bird/rkn_routes.list"
DOMAINS="/var/cache/rkn_domains.txt"

# Get & summarize routes
/opt/rkn.py | sed 's/(.*)/route 1 via "ens3";/' > $ROUTES.new

if [ $? -ne 0 ]; then
    rm -f $ROUTES.new
    echo "Unable to download RKN routes"
    exit 1
fi

if [ -e $ROUTES ]; then
    mv $ROUTES $ROUTES.old
fi

mv $ROUTES.new $ROUTES

/bin/systemctl try-reload-or-restart bird

# Get domains
curl -s https://api.reserve-rbl.ru/api/v2/domains/json -o - | jq -r '.[]' | sed 's/^*.//' | sort | uniq > $DOMAINS.new

if [ $? -ne 0 ]; then
    rm -f $DOMAINS.new
    echo "Unable to download RKN domains"
    exit 1
fi

if [ -e $DOMAINS ]; then
    mv $DOMAINS $DOMAINS.old
fi

mv $DOMAINS.new $DOMAINS

/bin/systemctl try-reload-or-restart dnstap-bgp

它們是在沒有經過深思熟慮的情況下編寫的,所以如果您看到可以改進的地方 - 那就去做吧。

客戶端設置

在這裡,我將給出 Linux 路由器的示例,但在 Mikrotik / Cisco 的情況下,它應該更容易。

首先,我們設置 BIRD:

鳥.conf

router id 192.168.1.2;
table rkn;

protocol device {
    scan time 10;
};

# Servers
protocol bgp bgp_server1 {
    table rkn;
    local as 65000;
    neighbor 192.168.1.1 as 65000;
    direct;
    bfd on;
    next hop self;
    graceful restart;
    graceful restart time 60;
    rr client;
    export none;
    import all;
}

protocol kernel {
    table rkn;
    kernel table 222;
    scan time 10;
    export all;
    import none;
}

因此,我們將從 BGP 接收的路由與內核路由表編號 222 同步。

之後,要求內核在查看默認板之前先查看此板就足夠了:

# ip rule add from all pref 256 lookup 222
# ip rule
0:  from all lookup local
256:    from all lookup 222
32766:  from all lookup main
32767:  from all lookup default

一切,剩下的就是在路由器上配置DHCP,將服務器的隧道IP地址分配為DNS,方案就準備好了。

限制

使用當前生成和處理域列表的算法,它包括,除其他外, youtube.com 及其 CDN。

這導致所有視頻都將通過 VPN,這會阻塞整個頻道。 也許值得編制一份流行域排除列表,暫時阻止 RKN,膽量很小。 並在解析時跳過它們。

結論

所描述的方法允許您繞過提供商當前實施的幾乎所有阻止。

原則上, dnstap-bgp 可用於任何其他需要基於域名進行某種級別的流量控制的目的。 請記住,在我們這個時代,一千個站點可以掛在同一個 IP 地址上(例如,在某些 Cloudflare 後面),因此這種方法的準確性相當低。

但是對於繞過鎖的需求,這已經足夠了。

添加、編輯、拉取請求 - 歡迎!

來源: www.habr.com

添加評論