2020年7月3日星期五

我的动森光速开荒日志

不包含剧透,给希望光速开荒的玩家一个参考。

Day 0 (Jun 11, 2020)

输入昵称、生日,捏脸
造 3 个帐篷(慎重选址,拆迁很贵)
输入岛屿名称(不能修改)

Day 1 (Jun 11)

清日常(签到、打招呼、摇树)
赚取 5000 哩数,还贷
捐赠 5 种虫或鱼给狸克(提示“咦?这是……”即是新物种)
造博物馆
保管想要明天捐赠的物种

Day 2 (Jun 12)

清日常(签到、打招呼、摇树、漂流瓶)
解锁联机和无人岛
解锁撑杆和铲子
捐赠 10 种虫、鱼、化石给傅达
保管想要大后天捐赠的物种
开矿,挖钱
收集除特产水果之外的 5 种水果及竹子
去无人岛领养 3 只动物(否则会随机分配)
收集木材×30、软木×30、硬木×30、铁矿×30
造商店

Day 3 (Jun 13)

清日常(签到、打招呼、摇树、开矿、挖钱、漂流瓶)
买大头菜(从本周日起)
保管想要明天捐赠的物种
造桥
造 3 个房子,解锁梯子
造 18 个家具

Day 4 (Jun 14)

欢迎第 3 位岛民
清日常(签到、打招呼、摇树、开矿、挖钱、炒菜、漂流瓶)
解锁巴猎岛
解锁季节活动(6 月新娘)
解锁家具改造
捐赠前两日收集的物种
向娟儿买够 $5000 服装

Day 5 (Jun 15)

欢迎第 4 位岛民
清日常,清季节活动
博物馆开始接受美术品

Day 6 (Jun 16)

欢迎第 5 位岛民
种下的果树开始结果
清日常,清季节活动
向狐利买艺术品并捐赠给博物馆
取钱,明天 ATM 将不能用
晚上傅珂出现,解锁流星许愿

Day 7 (Jun 17)

村委会和博物馆扩建

Day 8 (Jun 18)

欢迎西施惠
游戏背景音乐发生改变
解锁建造桥梁和斜坡、移动建筑物
解锁岛屿音乐、岛屿旗帜
解锁兑换四行背包容量
可以进入狐利的船上商店了
新的主线任务:邀请 K.K.
造露营地

Day 9 (Jun 19)

可以向骆岚购买地毯、壁纸、地板了
第六日节日活动,婚礼派对
解锁巴猎岛海报

Day 10 (Jun 20)

娟儿再次来岛
露营地来人了,强制收留
造新房子
解锁出售土地

Day 11 (Jun 21)

欢迎第 6 位岛民
解锁岛屿评价系统
解锁 amiibo
建服装店

Day 12 (Jun 22)

欢迎第 7 位岛民

Day 13 (Jun 23)

欢迎第 8 位岛民
服装店开业
岛评达到三星,成功邀请 K.K.
傅珂出现,有流星雨

Day 14 (Jun 24)

欢迎第 9 位岛民
K.K. 来岛上开演唱会
解锁岛屿创作家

主线剧情结束。


Day 15 (Jun 25)

欢迎第 10 位岛民

Day 17 (Jun 27)

龙克斯来岛
第一次捕虫大会

Day 19 (Jun 29)

俞司廷来岛
傅珂出现,有流星雨
算出天气种子 (714058723)

Day 20 (Jun 30)

薛革来岛
季节活动结束
露营地来客

Day 22 (Jul 2)

龙克斯来岛

Day 23 (Jul 3)

软件更新到版本 v1.3.0,开放潜水
然然来岛

Day 28 (Jul 8)

绵儿来岛

Day 29 (Jul 9)

「我的房子」扩建完成

2020年3月25日星期三

Reply UDP with correct source address on a multihomed Linux server

这篇文章有中文版:https://blog.swineson.me/________(还未发布)

Multihoming means connecting a machine to multiple computer networks. A multihomed server has multiple IP address on either single or multiple network interfaces.
One particular problem for Linux is, all outgoing UDP packets will use the primary IP address, even for requests sent to the secondary IP address.

Fix at userland, if possible

First, check whether your application supports multihoming. For example, OpenVPN supports a --multihome switch to enable multihome support.
Sometimes, you can bind the server to a specific IP address and run multiple instances on different IP addresses.

Otherwise, fix at kernel side

A: If all IP addresses are assigned to the same network interface

For example:
eth0:
    inet 192.0.2.2/24 brd 192.0.2.255 scope global eth0
        valid_lft forever preferred_lft forever
    inet 192.0.2.3/24 brd 192.0.2.255 scope global eth0
        valid_lft forever preferred_lft forever
The main routing table:
default via 192.0.2.1 dev eth0 onlink
We use iptables to perform a DNAT to the primary IP address for incoming UDP packets.
sudo iptables -t nat -A PREROUTING -i eth0 -m addrtype --dst-type LOCAL -p udp -j REDIRECT
sudo conntrack -F
If you prefer to use the newer nftables instead of the older iptables, here is the equivalant nft commands:
sudo nft add table nat
sudo nft add chain nat prerouting { type nat hook prerouting priority dstnat \; }
sudo nft add rule ip nat prerouting iifname "eth0" meta l4proto udp fib daddr type local counter redirect
sudo conntrack -F
That's it, we've done.

B: If IP addresses are assigned to different network interfaces

For example:
eth0:
    inet 192.0.2.2/24 brd 192.0.2.255 scope global eth0
        valid_lft forever preferred_lft forever
eth1:
    inet 198.51.100.2/24 brd 198.51.100.255 scope global eth0
        valid_lft forever preferred_lft forever
The main routing table:
default via 192.0.2.1 dev eth0 onlink
First, we disable reverse path filter on eth1:
sudo sysctl net.ipv4.conf.eth1.rp_filter=0
You may want to write the sysctl configuration to /etc/sysctl.d to execute it automatically on boot. If you are worried about whether disabling rp_filter causes security issues, you can use additional firewall rules to protect you.
Next, we set up routing policies:
sudo ip rule add fwmark 0x42 pref 42 table 42
sudo ip route add default table 42 via 198.51.100.1 dev eth1
Then we set up connection tracking and DNAT for the UDP packets:
sudo iptables -t mangle -A INPUT -i eth1 -j MARK --set-mark 0x42
sudo iptables -t mangle -A INPUT -i eth1 -j CONNMARK --save-mark
sudo iptables -t mangle -A OUTPUT -j CONNMARK --restore-mark
sudo iptables -t nat -A PREROUTING -i eth1 -m addrtype --dst-type LOCAL -p udp -j DNAT --to-destination 192.0.2.2
sudo conntrack -F
The nftables equivalents are:
sudo nft add table mangle
sudo nft add table nat

sudo nft add chain ip mangle input { type filter hook input priority mangle \; }
sudo nft add chain ip mangle output { type route hook output priority mangle \; }
sudo nft add chain nat prerouting { type nat hook prerouting priority dstnat \; }

sudo nft add rule ip mangle input iifname "eth1" counter meta mark set 0x42
sudo nft add rule ip mangle input counter ct mark set mark
sudo nft add rule ip mangle output counter meta mark set ct mark
sudo nft add rule ip nat prerouting iifname "eth1" meta l4proto udp fib daddr type local counter dnat 192.0.2.2

sudo conntrack -F

Why SNAT may not work

You might have attempted to use an SNAT on outgoing packets and failed. That is because SNAT causes the request packet and the reponse packet being considered as separate connections by conntrack.
For example, your server listens on 0.0.0.0:53, and a request sents from 203.0.113.1:1024 to 198.51.100.2:53. Conntrack tracks a connection from 203.0.113.1:1024 to 198.51.100.2:53.
The server accepts the request, but replies with 192.0.2.2:53. This time, conntrack adds another connection from 192.0.2.2:53 to 203.0.113.1:1024, not merging with the previous one.
When the packet travels through the firewall, SNAT only applies to the second connection. To get things worse, the outgoing port number may be changed because the kernel thinks port 53 is already occupied!

If we use the DNAT method, conntrack will instead track a connection from 203.0.113.1:1024 to 192.0.2.2:53, even though the packet is actually sent to 198.51.100.2:53.
Then, when the server replies with 192.0.2.2:53, the reply packet directly matches the conntrack record, causing the source address to be restored to 198.51.100.2:53.

How to match and filter NATed packets

Since the filter chain comes after mangle and nat chain, we are unable to determine the intended destination address using the iptables -d switch.
Instead, you should use -m conntrack --ctorigdst. You can also use -m conntrack --ctstatus DNAT to determine whether the packet is DNATed or not.
For nftables uesrs, ct original daddr and ct status have the same usage.

2018年7月29日星期日

用 strongSwan 配一个支持 AEAD 的远程访问 IKEv2 VPN

前言

IKEv2/IPsec 是一套网络层数据加密方案,它可以配置为点到点、站到站、网状网(Cisco DMVPN)、远程访问。IKEv2/IPsec 远程访问 VPN 十分适合于远程办公的场景。
IKEv2/IPsec 有两个组成部分,IKEv2 负责对通信双方进行鉴权并协商临时密钥,IPsec 负责匹配给定的流量规则并加密数据。
IPsec 有两种模式,AH 和 ESP。AH 只进行真实性校验,不加密;ESP 既做真实性校验又加密。实际应用中,AH 因为不支持 NAT 穿隧而并没有什么用处。如果真的有不加密的需求,可以将 ESP 的加密模式设置成 null。
其中,ESP 也有两种模式,transport 和 tunnel。Transport 模式下,加密载荷里直接就是传输层段或者报文,因而只能加密两个端点之间的连接;tunnel 模式下,加密载荷里有第二层 IP 报头,不仅可以进行路由,还可以给 NAT 后的客户端分配虚拟 IP 地址。
综上所述,我们要配置的 IPsec 模式应为 ESP tunnel。

关于认证阶段

IKEv2 的认证分为两个阶段。
Phase 1:双方鉴权和协商 IKE_SA,使用 UDP 500 端口。对于远程访问 VPN 来说,客户端验证服务端的 PKI 证书,服务端验证客户端的用户名密码。
Phase 2:建立隧道和协商 CHILD_SA,使用 IP 50 协议或 UDP 4500 端口(若启用 NAT 穿隧)。双方根据配置好的规则来决定哪些流量需要加密。对于远程访问 VPN 来说,客户端会请求加密全部流量(0.0.0.0/0::/0)。
随后,双方配置操作系统的 IPsec Policy 来把流量加密转发到 IP 50 协议或 UDP 4500 端口上。
为了保证同一个临时密钥不使用太长时间,IKEv2 会每隔一段时间 rekey 一次。在本例,我们关闭服务端的 rekey,将 rekey 的任务交给客户端,否则会因为双方同时 rekey 而掉线。

软件环境

服务端:Debian testing (10.0pre),strongSwan 5.6.3,systemd 239,AppArmor 2.12。
客户端:Windows 10 Pro 1803、macOS High Sierra 10.13.6、iOS 11.4.1。
如果你在用旧版的 Debian 或者 Ubuntu,强烈建议你打开 updatesbackports 源。其中 updates 包含版本内功能更新,backports 包含跨版本功能更新。

步骤一:安装依赖

sudo apt update
sudo apt install certbot charon-systemd iptables-persistent libcharon-extra-plugins libstrongswan-extra-plugins ndppd
本文使用了新版 strongSwan 的部分新功能,所以请务必确认 strongSwan 的版本高于 5.7.1。

步骤二:申请 Let's Encrypt 证书

如果你已经有在开 HTTPS 业务,那么直接把 TLS 证书拿来用就好。
没有的话用 certbot 申请一个 Let's Encrypt 的证书:
sudo certbot certonly --standalone -d example.com
sudo ln -s /etc/letsencrypt/live/example.com/privkey.pem /etc/swanctl/private/example.com.key
一切顺利的话证书已经躺在 /etc/letsencrypt/live 里了。证书需要每隔 90 天续期一次,幸运的是 systemd 的 certbot.timer 会自动启动 certbot 来续期。不过续期之后 strongSwan 并不会自动重启,你可以在 /etc/letsencrypt/renewal-hooks 里写个脚本。

步骤三:配置 AppArmor

Debian 会默认给 strongSwan 打开 AppArmor。这样的话 strongSwan 就读取不到我们的证书文件了。在 /etc/apparmor.d/local/usr.sbin.swanctl 里加入:
/etc/letsencrypt/       r,
/etc/letsencrypt/**     r,

步骤四:配置日志

在遇到问题的时候有日志看是很重要的,因此我们要先打开必要的日志。在 /etc/strongswan.d/charon-systemd.conf 里写上:
charon-systemd {

    # Section to configure native systemd journal logger, very similar to the
    # syslog logger as described in LOGGER CONFIGURATION in strongswan.conf(5).
    journal {

        # Loglevel for a specific subsystem.
        # <subsystem> = <default>

        # Default loglevel.
        # default = 1

        cfg = 2
        ike = 2

    }

}

步骤五:配置 swanctl

strongSwan 有两套配置系统:旧的一套是 ipsec.conf,使用 stroke 插件来启动;新的一套是 swanctl.conf,使用 vici 插件来启动。我们将使用新的 swanctl.conf 系统。虽然网络上关于 swantl.conf 的文档和资料还比较少,但是配置起来更加灵活。
/etc/swanctl/swanctl.conf 里加入:
authorities {
    lets-encrypt {
        file = /etc/letsencrypt/live/example.com/chain.pem
    }
}

connections {
    ikev2 {
        version = 2
        local_addrs = %any
        remote_addrs = %any
        # 这是 Windows 10 和 macOS High Sierra 兼容的最优 Phase 1 加密套件。如果你的客户端支持更好的加密套件,可以在这里写上。
        proposals = chacha20poly1305-aes256gcm16-prfsha384-curve25519-ecp384-modp3072-ecp256,aes256-sha384-curve25519-ecp384-modp3072-ecp256,aes256gcm16-prfsha384-modp1024,aes256-sha256-ecp256
        dpd_delay = 30s
        send_certreq = no
        send_cert = always
        rekey_time = 0s
        pools = pool-ipv4,pool-ipv6
        local {
            certs = /etc/letsencrypt/live/example.com/cert.pem
            id = example.com
        }
        remote {
            eap_id = %any
            auth = eap-mschapv2
        }
        children {
            ikev2 {
                # 这是 Windows 10 和 macOS High Sierra 兼容的最优 Phase 2 加密套件。如果你的客户端支持更好的加密套件,可以在这里写上。
                esp_proposals = chacha20poly1305-aes256gcm16-prfsha384-curve25519-ecp384-modp3072-ecp256-modpnone-esn-noesn,aes256-sha384-curve25519-ecp384-modp3072-ecp256-modpnone-esn-noesn,aes256-sha256-sha1
                local_ts = 0.0.0.0/0,::/0
                rekey_time = 0s
                hw_offload = auto
            }
        }
    }
}

secrets {
    eap-1 {
        # 两个客户端不可以共用同一个用户名,否则会发生 IP 地址冲突。如果有多个客户端,请分配多个用户名。
        secret = 用户1和用户2共用的密码
        id-user1 = 用户名1
        id-user2 = 用户名2
    }
    eap-2 {
        secret = 用户3的密码
        id-user3 = 用户名3
    }
    private-1 {
        file = example.com.key
    }
}

pools {
    pool-ipv4 {
        # 这里也可以写 100.100.100.129-100.100.100.254 的格式,或者换成你想要的 IPv4 地址段。
        addrs = 100.100.100.128/25
        dns = 8.8.8.8,8.8.4.4
    }
    pool-ipv6 {
        # 改成上游 ISP 分配的 IPv6 子网的一小段。
        addrs = 2001:db8::/97
        dns = 2001:4860:4860::8888,2001:4860:4860::8844
    }
}

步骤六:配置 IPv4 NAT

/etc/iptables/rules.v4 加入:
*nat
:PREROUTING ACCEPT [31:5739]
:INPUT ACCEPT [31:5739]
:OUTPUT ACCEPT [28:3794]
:POSTROUTING ACCEPT [11:1486]
-A POSTROUTING -m policy --pol ipsec --dir out -j ACCEPT
-A POSTROUTING -s 100.100.100.128/25 -o eth0 -j MASQUERADE
COMMIT

步骤七:配置 IPv6 NDP Proxy

/etc/ndppd.conf 加入:
# 改成你的出站设备名
proxy eth0 {
    # 改成上游 ISP 分配的 IPv6 子网的一小段。
    rule 2001:db8::/97 {
        static
    }
}

步骤八:启用 IP 转发

/etc/sysctl.d/50-ip-forwarding.conf 里写上:
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
注意当开启 IPv6 转发的时候,SLAAC 会被默认关闭。如果你通过 SLAAC 从上游 ISP 收取 IPv6 地址,那么需要将其手动打开:(可选)
net.ipv6.conf.eth0.accept_ra = 2
或者,如果你使用 /etc/network/interfaces、NetworkManager、systemd-networkd 来配置网络,只需要打开 SLAAC 开关即可。

步骤九:启动服务

sudo systemctl restart apparmor
# 禁用旧版 ipsec.conf 配置服务
sudo systemctl disable --now strongswan
sudo systemctl restart netfilter-persistent ndppd strongswan-swanctl systemd-sysctl
sudo systemctl enable certbot.timer netfilter-persistent ndppd strongswan-swanctl

步骤十:连接客户端

在开始之前,我强烈建议在一台空闲的电脑上连接 VPN 网关,输入:
sudo journalctl -u strongswan-swanctl -f
来查看实时滚动日志。

Windows

在 Windows 设置里,进入 Network & Internet → VPN → Add a VPN connection。
VPN provider:Windows (built-in)
Connection name:随意
Server name or address:服务器的域名
VPN type:IKEv2
User name:用户名
Password:密码
如果提示 Error 13801: IKE authentication credentials are unacceptable,那么请用 certmgr.msc 导入 Let's Encrypt Authority X3 中间证书。
如果还是不行,就导入这条注册表项:
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RasMan\Parameters]
"DisableIKENameEkuCheck"=dword:00000001

macOS

在 macOS 系统偏好设置里,进入 Network,点击左边栏底部的「+」。
Interface:VPN
VPN Type:IKEv2
Service Name:随意
Server Address:服务器的域名或 IP 地址(如果填域名连接超时就填 IP 地址)
Remote ID:服务器的域名,不能填 IP 地址
Local ID:留空
Authentication Settings:Username
Username:用户名
Password:密码

iOS

在 iOS 的设置里,进入 VPN → Add VPN Configuration。
Type:IKEv2
Description:随意
Server:服务器的域名或 IP 地址(如果填域名连接超时就填 IP 地址)
Remote ID:服务器的域名,不能填 IP 地址
Local ID:留空
Uesr Authentication:Username
Username:用户名
Password:密码

Linux 和 Android

Linux 用户只需要将 swanctl.conf 的内容反过来填就成了客户端配置。
对于 Android 用户,可以去 Google Play 下载 strongSwan 客户端

如何配置默认网关

Windows 默认不把所有流量都发送到 VPN,macOS 和 iOS 则相反。如果要改变这个设置,可以根据以下步骤来操作。

Windows

IPv4:在 Windows 设置里,进入 Network & Internet → Status → Network and Sharing Center → Change adapter settings → VPN (IKEv2) → 右键属性 → Networking → TCP/IPv4 → Properties → Advanced → IP Settings → Use default gateway on remote network。
IPv6:虽然 TCP/IPv6 里也有这个名字的选项,但是实际上无效。要想设置 IPv6 的默认网关,请打开 PowerShell,输入:
Add-VpnConnectionRoute "连接名" ::/1
Add-VpnConnectionRoute "连接名" 8000::/1

macOS

VPN 已断开的状态下,在 macOS 系统偏好设置里,进入 Network,选择 VPN (IKEv2),点击 Advanced,在 Options 选项卡内,勾选 Send all traffic over VPN connection。

如何打开 AEAD(仅 Windows 10 支持)

AEAD 可以在同一个算法内实现加密和校验,能够提高连接的安全性。
请打开 PowerShell,输入:
Set-VpnConnectionIPsecConfiguration -ConnectionName "连接名" -AuthenticationTransformConstants GCMAES256 -CipherTransformConstants GCMAES256 -EncryptionMethod GCMAES256 -IntegrityCheckMethod SHA384 -PfsGroup ECP384 -DHGroup ECP384 -Force

备用:旧版 ipsec.conf 配置

这是过时的 ipsec.conf 配置文件,仅供参考,不要使用。
config setup
    charondebug = "cfg 2, ike 2"

ca lets-encrypt
    cacert = /etc/letsencrypt/live/example.com/chain.pem
    auto = add

conn ikev2
    left = %any
    leftauth = pubkey
    leftcert = /etc/letsencrypt/live/example.com/cert.pem
    leftid = @example.com
    leftsendcert = always
    leftsubnet = 0.0.0.0/0,::/0
    right = %any
    rightauth = eap-mschapv2
    rightdns = 8.8.8.8,8.8.4.4,2001:4860:4860::8888,2001:4860:4860::8844
    rightsendcert = never
    rightsourceip = 100.100.100.128/25,2001:db8::/97
    auto = add
    dpdaction = clear
    eap_identity = %any
    esp = chacha20poly1305-aes256gcm16-prfsha384-curve25519-ecp384-modp3072-ecp256-modpnone-esn-noesn,aes256-sha384-curve25519-ecp384-modp3072-ecp256-modpnone-esn-noesn,aes256-sha256-sha1!
    ike = chacha20poly1305-aes256gcm16-prfsha384-curve25519-ecp384-modp3072-ecp256,aes256-sha384-curve25519-ecp384-modp3072-ecp256,aes256gcm16-prfsha384-modp1024,aes256-sha256-ecp256!
    keyexchange = ikev2
    rekey = no
/etc/ipsec.secrets
# ln -s /etc/letsencrypt/live/example.com/privkey.pem /etc/ipsec.d/private/example.com.key
: RSA "example.com.key"

用户名1 : EAP "用户1的密码"
用户名2 : EAP "用户2的密码"
用户名3 : EAP "用户3的密码"

2016年7月26日星期二

用 tun2socks 配置全局 SOCKS5 代理转发

笔者要在安装了 ArchLinuxARM 的 Raspberry Pi 上配置 SOCKS5 代理转发,并把局域网内其它计算机的网关、DNS 服务器设置成 Raspberry Pi,做到全局 SOCKS5 代理转发。
ArchLinuxARM 的系统控制服务是 Systemd。如果你不用 Systemd,可能需要调整某些步骤。
在开始之前,安装这些软件包:
badvpn-tun2socks iproute2 iptables pdnsd
首先配置 pdnsd 的 DNS 转发:
/etc/pdnsd.conf
 
global {
    max_ttl=300;
    min_ttl=0;
    neg_domain_pol=off;
    query_method=tcp_only;
    run_as=pdnsd;
    timeout=10;
}
 
server {
    ip=8.8.8.8,8.8.4.4;
}
然后写启动和终止脚本:
/usr/local/bin/socksfwd
 
#!/bin/bash
 
SOCKS_SERVER=xxx.xxx.xxx.xxx # SOCKS 服务器的 IP 地址
SOCKS_PORT=1080 # SOCKS 服务器的端口
GATEWAY_IP=192.168.1.1 # 家用网关(路由器)的 IP 地址
BYPASS_IPS=(yyy.yyy.yyy.yyy zzz.zzz.zzz.zzz) # 不需要代理的 IP 地址
TUN_NETWORK_DEV=tun0 # 选一个不冲突的 tun 设备号
TUN_NETWORK_PREFIX=10.210.0 # 选一个不冲突的内网 IP 段的前缀
 
start_fwd() {
    ip tuntap add dev "$TUN_NETWORK_DEV" mode tun
    ip addr change "$TUN_NETWORK_PREFIX.1/30" dev "$TUN_NETWORK_DEV"
    ip link set "$TUN_NETWORK_DEV" up
    ip route del default via "$GATEWAY_IP"
    ip route add default via "$GATEWAY_IP" metric 600
    for i in "${BYPASS_IPS[@]}"
    do
        ip route add "$i" via "$GATEWAY_IP" metric 5
    done
    ip route add default via "$TUN_NETWORK_PREFIX.2" metric 6
    badvpn-tun2socks --tundev "$TUN_NETWORK_DEV" --netif-ipaddr "$TUN_NETWORK_PREFIX.2" --netif-netmask 255.255.255.252 --socks-server-addr "$SOCKS_SERVER:$SOCKS_PORT" &
    TUN2SOCKS_PID="$!"
}
 
stop_fwd() {
    ip route del default via "$TUN_NETWORK_PREFIX.2" metric 6
    for i in "${BYPASS_IPS[@]}"
    do
        ip route del "$i" via "$GATEWAY_IP" metric 5
    done
    ip tuntap del dev "$TUN_NETWORK_DEV" mode tun
}
 
start_fwd
trap stop_fwd INT TERM
wait "$TUN2SOCKS_PID"
写 Systemd 服务:
/etc/systemd/system/socksfwd.service
 
[Unit]
Description=Transparent SOCKS5 forwarding
After=network-online.target
 
[Service]
Type=simple
ExecStart=/usr/local/bin/socksfwd
LimitNOFILE=1048576
 
[Install]
WantedBy=multi-user.target
打开流量转发:
/etc/sysctl.d/sysctl.conf
 
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.ipv4.tcp_congestion_control=westwood
net.ipv4.tcp_syn_retries = 5
net.ipv4.tcp_synack_retries = 5
/etc/iptables/iptables.rules
 
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [1:1500]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING ! -s 127.0.0.0/8 -j MASQUERADE
COMMIT
最后设置 DNS 为本机 pdnsd。如果用 systemd-network,可以这么写,其中 eth0 改成对外的网卡名称:
/etc/systemd/network/eth0.network
 
[Match]
Name=eth0
 
[Network]
DHCP=yes
DNS=127.0.0.1
IPForward=yes
 
[DHCP]
UseDNS=no
 
[Route]
Metric=100
如果不用 systemd-network,也可以改 /etc/resolv.conf
/etc/resolv.conf
 
nameserver 127.0.0.1
最后启动服务并重启:
sudo systemctl enable socksfwd iptables
systemctl reboot

P.S. 这个方案也可以在桌面 Linux 或者 ChromeOS 上运行,只需要安装依赖并设法运行 socksfwd 脚本即可,其它步骤可以不做。
可以不使用 pdnsd 来转发 DNS,但是 DNS 服务器就需要加到 BYPASS_IPS 中。
在 ChromeOS 平台,创建的 tun 设备会被 shill 服务自动删掉,需要在执行 socksfwd 之前执行:
stop shill
start shill BLACKLISTED_DEVICES=tun0

2015年1月18日星期日

MinGW-w64 的 libwinpthread-1.dll 依赖问题

用 MinGW-w64 工具链编译程序默认是动态链接 libgcclibstdc++。这样客户机上必须有这两个 DLL 才能运行目标程序。
可以用 -static-libgcc -static-libstdc++ 来设置成静态链接。
但是 libstdc++ 有一个依赖是 libwinpthread-1.dll,它无法用 -static-* 选项来关掉。
虽然很多人建议用 -static,但是它有副作用。
编过研究,我找到了解决方案:
-static-libgcc -static-libstdc++ -Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic
将它加在 g++ 命令行的最末尾即可。注意 -lstdc++-lpthread 之前。

去 StackOverflow 为我的这个答案投票:[问题 1] [问题 2]

2014年12月14日星期日

GCC LTO 连接时优化的几点注意

GCC 4.7 开始支持 Link Time Optimization (LTO),即连接时优化,可以使跨不同源文件的函数之间的关系得到进一步的优化。
它的做法是先把源代码编译成 GIMPLE 中间码,再在连接的时候生成真正的机器码。
在 Linux 上编译本机程序时一直正常工作,但是在使用 MingW-w64 工具链的时候,发现有时连接静态库会报错提示找不到符号。
在 Google 和 StackOverflow 一段时间之后,发现需要注意以下几点:
  1. arnmranlib 换成 gcc-argcc-nmgcc-ranlib
    因为原生的 arnmranlib 无法处理含有 GIMPLE 中间码的目标文件。
  2. 使用 gcc -fuse-linker-plugin 参数。
    它开启连接器插件功能,GCC 可以在连接时做更多的优化。
  3. 如果各种方法都不行,使用 gcc -ffat-lto-objects
    这会让 GCC 生成一个“胖的(fat)”目标文件,它同时含有非 LTO 的机器码和 GIMPLE 中间码。工具链中如果有不支持 LTO 的程序如(ar),会使用非 LTO 的机器码副本。
总结一下,编译的时候加上以下的 CFLAGS
gcc -flto -fuse-linker-plugin
如果 Makefile 是别人写好的,可以使用我的 LTOWrapper 来注入 -flto 参数。

注:在 Linux 下使用 LLVM/Clang 的全程序优化遇到困难的时候可以试试 http://nochair.net/posts/2011/07-01-whole-program-llvm-bitcode.html
Linux 下,Clang 需要使用 Gold 连接器。配置好之后 -flto 就可以使用。

2014年10月24日星期五

用命令关闭 Gnome Shell 的 Looking Glass

Gnome 3 的 Gnome Shell 有一个很方便的调试器,叫 Looking Glass。
启动方式是 Alt-F2 输入 lg 回车,退出方式是按 Esc 键。
但是我笔记本电脑的 Esc 键坏了,所以找到了一个用命令关闭 Looking Glass 的方法。
输入 Main.createLookingGlass().toggle() 即可。

P.S. 用 global.get_window_actors().forEach(function (w) { w.set_scale(0.8, 0.8) }) 可以把所有窗口缩放到 80% 大小。

2014年8月22日星期五

宣布 JKSN,又一个 JSON 兼容二进制压缩格式

本来只是想可不可以用二进制来储存 JSON 并且做一些优化之类的,结果造出来 JKSN 这货。
比较了一下文件大小,发现没有经过压缩的 JKSN 数据比 gzip -9 JSON 还要小。于是把 JKSN 的目标定为让 JSON 序列更小。

JKSN 适用于任何网络带宽比处理时间更重要的场合,如移动即时通讯。

项目地址:https://github.com/m13253/JKSN。Specification 和参考实现均以 BSD 协议发布。

为什么不用 GZIP 呢?
JKSN 和 GZIP 不冲突。JKSN 编码后的数据可以被 GZIP 压缩得更小。
由于 GZIP 的 32 KiB 滑动窗口的设计,相似的数据只有互相间隔不超过 32 KiB 才能得到有效压缩。JKSN 会将数据重新组织,使相似的数据尽可能靠近排列,更有利于 GZIP 的压缩。

下面,用项目主页上举的例子,做个比较,:
格式文件大小
JSON150 字节
JSON gzip -9112 字节
JKSN109 字节
JKSN gzip -9107 字节
再来比较同类二进制 JSON 实现:
格式文件大小
BSON172 字节
Universal Binary Json150 字节
MessagePack120 字节
JSONH116 字节
BJSON(内置自适应哈夫曼压缩)115 字节
JKSN109 字节

如果各位读者对此项目感兴趣,且有空余时间,不妨让 JKSN 变得更好:
目前 JKSN 已经有 Python 3、Python 2、浏览器 JavaScript、Node.JS、C 五种环境的实现了。如果你愿意,请帮助我为 JKSN 补上 PHP 5、Java 的实现。
追求模范性和稳定性的实现可以直接 fork JKSN repository 并发给我 Pull Request;追求速度的实现可以另开新 repository 并通知我。
如果你知道怎么重新组织 JSON 结构让其更易于压缩,请联系我。我希望和你探讨问题。

2014年8月16日星期六

中文普通话参考音素表

Rocaloid 语音合成引擎正在 Sleepwalking 的领导下紧张地开发中,制作中文音源需要哪些音素呢?
Rocaloid 现在版本是以单音节(monophone)为合成单位,所以录音数量大大降低了,有条件录制某些特殊情况下的发音变形。
以此我整理了一份参考音素表,以我个人的普通话口音为基准,只做参考,如有遗漏或错误请留言通知我。
标有 (p) 的是弱读和快读时的发音变化,相对的是 (f) 标记。

辅音 Consonants


汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
bpp
pp_h
mmm
fff
dtt
tt_h
nnn
lll
gkk
kk_h
hxx
jts\
qtɕʰts\_h
xɕs\
zhʈʂts`
chʈʂʰts`_h
shʂs`
rʐz`
ztsts
ctsʰts_h
sss
szzz用来拼写外来语、少数民族语言和方言
vvv
y (p)jj快读时 i u 变为 j w
w (p)ww
0ʔ?声门塞音

单元音 Mono-vowels

汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
aaa
o (p)oo如“咯”
e (p)ɤ7如“了”
ehɛE汉语拼音方案记作“ê”,注音符号记作“ㄝ”,如“诶”
iii
ihɯ / ɨM / 1注音符号记作“ㄭ”,如“私”
uuu
üyy有时使用字母“v”代替
nnn这里把鼻音视作元音处理
ngŋN
mmm

双元音 Di-vowels

汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
aiaI
aoaU
ananan
angɑŋAN
erɑɹAr\如“二”
ouɤʊ7U有些人偏好读作 oʊ
enən@n
engɤŋ7N
eieI
iaiaia
ioioio如“哟”
ieiE
ininin
uauaua
o (f)uouo与 p pʰ m f 拼读,如“播”
uouO如“我”
ongʊŋUN
üeyE如“月”
e (f)ɯʌMV如“饿”

三元音 Tri-vowels

汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
iaoiaʊiaU
iangiɑŋiAN
iuiɤʊi7U有些人偏好读作 ioʊ
iong (f)iʊŋiUN如“穷”慢读
ianiɛniEn
ingiɯŋiMN南方口音更偏好读作 iŋ
uaiuaɪuaI
uanuanuan
uanguɑŋuAN
uiueɪueI
unuənu@n
uenguɤŋu7N
iong (p)yʊŋyUN如“永”快读
üanyɛnyEn如“远”,北方口音更偏好读作 yan
ünyinyin如“云”

儿化音 R-endings

汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
oror\
e-erɤɹ7r\不是 ɑɹ,ɑɹ 已在前面列出
ehrer\
ihrɯɹMr\
urur\
üryr\

唇化鼻音 M-endings (语速快的场合)

汉语拼音参考IPA 音标参考X-SAMPA 音标参考备注
amamam
amgɑmAm
omgomom
eməm@m
emgɤm7m
imimim
üm (p)ymym
iomgiʊmiUm
iamgiɑmiAm
iamiɛmiEm
uamuamuam
uamguɑmuAm
umuəmu@m
uemguɤmu7m
üm (f)yimyim
当快读时,i u 变为 j w,儿化音与前面一个元音连读,p pʰ m 之前的鼻音变为唇化鼻音,鼻音前的元音变为鼻音化元音。

可以使用我的 Pinyin2XSampa 程序将拼音转换成 X-SAMPA。

论交互设计师的英文素养

很多 Web App 都会提供第三方调用的 API,在 API 的提示信息中如果有语法错误就贻笑大方了。
你不信?我来举几个例子:
{"error": "Hit sexy spam"} ——来自百度
{"error": "Wrong verify code"} ——来自腾讯
{"error": "Apps is not exists"} ——来自哔哩哔哩
交互设计师的英文素养很重要啊。
下面给出我的参考翻译
百度那例:Hit porn filter
腾讯那例:Wrong Captcha
哔哩哔哩那例:App does not exist