准备一个Linux系统,移植过程中需要拉取源码,下载各种软件,设置socks5代理可以代理一部分软件,但是大多数软件只能支持http代理,通过设置shadowsocks配合nftables,设置透明代理,国内IP直连,国外IP走代理是最优的解决方案。

最新的CN IP列表可以从这里获取,配合下面脚本生成一个nft配置文件。

<?php
// 可以从 https://ftp.apnic.net/stats/apnic/assigned-apnic-latest 获取最新版
if (count($argv) < 2) {
    die(sprintf("Usage %s file\n", $argv[0]));
}

$data = file_get_contents($argv[1]);
if (!$data) {
    die("error\n");
}
$local_port = 1081;
$server = "";

foreach (explode("\n", $data) as $line) {
    $line = trim($line);
    if ($line == "" || $line[0] == "#") {
        continue;
    }
    @list($registry, $cc, $type, $start, $value, $date, $status) = explode("|", $line);
    if ($cc != "CN" || $type != "ipv4" || $type == "asn") {
        continue;
    }
    $value = intval($value);
    $i = 0;
    while ($value > 1) {
        $value /= 2;
        $i++;
    }
    $address = sprintf("%s/%d", $start, 32 - $i);
    if (isset($NOSS_ADDRESSES)) {
        $NOSS_ADDRESSES .= ", " . $address;
    } else {
        $NOSS_ADDRESSES = $address;
    }
}
$template = <<<NFTABLES
flush ruleset
table ip shadowsocks {
    chain output {
        type nat hook output priority -100;
        ip daddr { 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 224.0.0.0/4, 240.0.0.0/4 } return 
        ip daddr { {$server} } return 
        udp dport 53 redirect to {$local_port}  
        ip daddr { {$NOSS_ADDRESSES} } return 
        tcp sport {32768-61000} redirect to {$local_port}
        ip protocol icmp dnat {$server} 
    }
    
    chain input {
        type nat hook input priority -100;
    }
}

NFTABLES;
file_put_contents("rule.nft", str_replace("\r\n", "\n", $template));

这里非常重要的一点是生成的nft配置文件最后需要添加一个空行,是的}后面再加一个空行,不然就会出错,我不知道这是不是一个bug,但是我因为这个问题浪费了好多时间。然后运行下面命令导入文件,开启流量转发net.ipv4.ip_forward设置为1,配合ss-redir使用,这时候没出错的话,非大陆IP的流量应该走SS通道,但是Google应该打不开的,因为有DNS污染。

sudo nft -f rule.nft

解决DNS污染使用dnsmasq配合ss-tunnel,让gfwlist名单上面的地址,走ss通道查询,具体操作也是用下面脚本生成一个dnsmasq的配置文件,最新的gfwlist可以从这里获取。

/**
 * Created by PhpStorm.
 * User: fairy08
 * Date: 18-11-6
 * Time: 上午11:48
 */

if (count($argv) < 2) {
    die(sprintf("Usage %s file\n", $argv[0]));
}

$data = file_get_contents($argv[1]);
if (!$data) {
    die("error\n");
}
$raw = "";
foreach (explode("\n", base64_decode($data)) as $line) {
    $line = trim($line);
    if ($line == "" ||
        $line[0] == '!' ||
        substr($line, 0, 2) == "@@" ||
        strpos($line, "/") !== false
        || strpos($line, ".") === false) {
        continue;
    }
    if (substr($line, 0, 2) == "||") {
        $line = substr($line, 2);
    }
    $raw .= sprintf("server=/%s/127.0.0.1#5353\n", $line);
}
file_put_contents("dnsmasq.ini", $raw);

我把dnsmasq安装在路由器上,同时用下面命令启动ss-tunel:

ss-tunnel -c /opt/etc/shadowsocks.json -u -l 5353 -L 8.8.8.8:53 -f /tmp/ss-tunnel.pid

对应的的服务文件/opt/etc/init.d/S23tunnel内容为

#!/bin/sh

ENABLED=yes
PROCS=ss-tunnel
ARGS="-c /opt/etc/shadowsocks.json -u -l 5353 -L 8.8.8.8:53 -f /tmp/ss-tunnel.pid"
PREARGS=""
DESC=$PROCS
PATH=/opt/sbin:/opt/bin:/opt/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[ -z "$(which $PROCS)" ] && exit 0

. /opt/etc/init.d/rc.func

如果正确的配置完之后,在系统设置里面取消代理,但是还是可以正常查看Google、YouTube等网站的话,就是完成了。顺带说一下,Ubuntu desktop修改DNS方法,Ubuntu弃用了/etc/resolv.conf这个文件,在这个文件修改会被覆盖,需要修改/etc/network/interfaces这个文件,添加dns-nameservers 192.168.1.1设置DNS。