Category Archives: Technology

Securing DNS Traffic in China

Overview

DNS poisoning is one of the most common cause of nuisance when accessing websites
that are outside this 1.4 billion-people Oriental country. So far, the best way to protect yourself from this trouble is to route all your DNS traffic through an encrypted channel, and the method I am going to introduce is DNSCrypt. There is not yet a standard for encrypted DNS, DNSCrypt is a project done by OpenDNS. According my experience, DNSCrypt is very reliable and robust, the cryptography of the protocol is called DNSCurve, which is a public-key crypto that employes an extremely strong elliptic-curve cryptography called Curve25519.

If you have read my previous writing, you should know my setup is a Raspberry Pi, and so the rest of this article is based on that, running Raspbian. Dnsmasq will be used as the first DNS caching proxy to serve incoming DNS queries from machines on the network. If the queried domain name is a China one, the request will be served by a China DNS. This is necessary because for some domains, answers from DNS servers in China and global ones could be different. If the requested domain does not belong to any known China domains, the request will be forwarded to dnscrypt-proxy, which will ask a DNSCrypt server for an answer.

After DNSCrypt is used, your DNS traffic will look like this:

                                             +----------------------+
                                             |   China DNS server   |
                                      +----> |                      | 
                                      |      | e.g. 114.114.114.114 |
                                    China    +----------------------+
                                   domains
                                      |
                                +-----|---------------------------------+
                                | +-------+   Other   +----------------+|
       +------+                 | |dnsmasq|---------->| dnscrypt-proxy ||
       | Host |-- DNS query --> | +-------+  domains  +----------------+|
       +------+                 |                              |        |
                                |            Raspberry Pi      |        |
                                +------------------------------|--------+
                                                               V
                                                       +---------------+
                                                       |DNSCrypt server|
                                                       +---------------+

Setting up DNSCrypt

As illustrated in the above diagram, dnscrypt-proxy is the piece of software that handles DNSCrypt, but it is not available in Raspbian’s Wheezy and Jessie releases, only in testing (currently Stretch). You can either compile it yourself, or grab the debian package I built and install it. You can find the package here. It is based on the Raspbian package in testing repo, with some modification to debian packaging files, since the one in testing depends on systemd, which had not yet been adopted when Wheezy was released.

If you really want to build the package yourself, first install the libsodium packages. The package are also not available in Wheezy repo but the ones from testing, libsodium13_1.0.3-1_armhf.deb and libsodium-dev_1.0.3-1_armhf.deb, can be installed without any problem. Download and install them, then follow these steps to build your dnscrypt-proxy package:

$ sudo apt-get install autotools-dev debhelper pkg-config
$ git clone https://github.com/anthonywong/dnscrypt-proxy-raspbian-wheezy.git
$ cd dnscrypt-proxy-raspbian-wheezy.git
$ fakeroot debian/rules binary

After dnscrypt-proxy is installed, you have to update the port it uses. Change DNSCRYPT_PROXY_LOCAL_ADDRESS in /etc/default/dnscrypt-proxy to another port other than 53 (as it will be used by dnsmasq later), like this:

DNSCRYPT_PROXY_LOCAL_ADDRESS=127.0.0.1:5353

You can also change the remote DNSCrypt server, but since the default (cisco) works well for me, I left it unchanged.

Now test it to make sure it works as expected:

$ dig @localhost -p 5353 www.facebook.com

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @localhost -p 5353 www.facebook.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9735
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.facebook.com.      IN  A

;; ANSWER SECTION:
www.facebook.com.   2725    IN  CNAME   star-mini.c10r.facebook.com.
star-mini.c10r.facebook.com. 47 IN  A   31.13.77.36

;; Query time: 140 msec
;; SERVER: 127.0.0.1#5353(127.0.0.1)
;; WHEN: Mon Jan 18 00:39:36 2016
;; MSG SIZE  rcvd: 90

$ dig @localhost -p 5353 www.sina.com.cn

; <<>> DiG 9.8.4-rpz2+rl005.12-P1 <<>> @localhost -p 5353 www.sina.com.cn
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22371
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;www.sina.com.cn.       IN  A

;; ANSWER SECTION:
www.sina.com.cn.    2733    IN  CNAME   jupiter.sina.com.cn.
jupiter.sina.com.cn.    3580    IN  CNAME   region.sina.csglb.txcdn.cn.
region.sina.csglb.txcdn.cn. 1261 IN CNAME   n2wous.panthercdn.com.
n2wous.panthercdn.com.  20  IN  A   103.4.200.227
n2wous.panthercdn.com.  20  IN  A   103.4.200.235

;; Query time: 125 msec
;; SERVER: 127.0.0.1#5353(127.0.0.1)
;; WHEN: Mon Jan 18 00:38:25 2016
;; MSG SIZE  rcvd: 171

Setting up dnsmasq

Dnsmasq is very common and is available in Raspbian, installing it is easy:

$ sudo apt-get install dnsmasq

Now we have to do some configuration in /etc/dnsmasq.conf. These are my recommended settings. Please note that the interface option is the network interface that dnsmasq will serve, and in my case that is wlan0. You have to change it to the one that applies to your case.

domain-needed
bogus-priv
no-resolv
interface=wlan0

Now comes the interesting part. We are going to tell dnsmasq to use a China DNS server (114.114.114.114 in my example) for China domains and DNSCrypt server for all others. This is done by using the server option in /etc/dnsmasq.conf. Here is an example:

# Add other name servers here, with domain specs if they are for
# non-public domains.
server=/baidu.com/114.114.114.114
server=/sina.com/114.114.114.114
server=/ifeng.com/114.114.114.114
server=/phoenixtv.com/114.114.114.114
...
server=/deppon.com/114.114.114.114
server=127.0.0.1#5353

This is pretty straightforward. The last line tells dnsmasq to use your dnscrypt proxy if the domain you query does not match any China domains. In my config file there are 12238 lines for China domains so I'm not going to post them all here, you can get the snippet of my dnsmasq.conf here, and put it into your own dnsmasq.conf. The problem is to maintain the list for all China hosts. I am now using the list from the fqrouter project, it has been serving me well, since most common domains are already there. What's worrying is due to the abandon of the project by it's author, the list is now unmaintained. If you know a more updated list, please let me know!

Obfuscated SSH tunnel

VPN providers were cracked down, open source anti-censorship tools were eliminated. This is what’s happening in China and has become even more severe than ever. Shadowsocks alone is no longer reliable due to more powerful deep packet inspection implemented at the GFW.

I am now replacing shadowsocks on my gateway with obfuscated SSH tunnel, based on Tor‘s obfsproxy. To the impatient ones, I will first give a concise summary of the necessary steps of my set up. You can follow it without drilling down the details. I will explain in more details later. But please note that you have to follow the other instructions in this blog post to complete the whole set up.

Quick Set up

On your server

Assume your server runs Debian 8 (jessie) or Ubuntu, and its IP is 1.2.3.4, run these commands:

$ sudo apt-get install obfsproxy
$ mkdir /tmp/obfsproxy-temp
$ obfsproxy --data-dir=/tmp/obfsproxy-temp scramblesuit --password=QWLY42YHB75J3B57XOKYNNFJPOQ7APHL --dest=127.0.0.1:22 server 0.0.0.0:7700
On Raspberry Pi gateway
$ sudo apt-get install python-dev
$ sudo pip install obfsproxy

Edit ~/.ssh/config:

Host 1.2.3.4
  ProxyCommand nc -X 5 -x 127.0.0.1:3333 %h 7700

Run these commands:

$ obfsproxy --log-file=obfsproxy.log --log-min-severity=info scramblesuit --password=QWLY42YHB75J3B57XOKYNNFJPOQ7APHL socks 127.0.0.1:3333
$ ssh user@1.2.3.4 -N -D 1080

Boost WIFI Speed by Forcing 40 MHz Channels in hostapd on RPi

802.11n can double the channel bandwidth of 802.11g from 20 MHz to 40 MHz, but this operation mode is not recommended in areas that the spectrums are congested and likely interfere with existing WIFI and bluetooth devices. As a result, hostapd will not enable 40 MHz when it finds other channels are being used, like what is seen from the hostapd log below:

nl80211: New scan results available
nl80211: Received scan results (16 BSSes)
40 MHz affected channel range: [2397,2447] MHz
Neighboring BSS: 1c:fa:68:8e:a6:e0 freq=2412 pri=1 sec=5
Neighboring BSS: e0:05:c5:4c:e2:b6 freq=2427 pri=0 sec=0
Neighboring BSS: b0:48:7a:6a:9d:32 freq=2437 pri=6 sec=10
40 MHz pri/sec mismatch with BSS b0:48:7a:6a:9d:32 <2437,2457> (chan=6+) vs. <2412,2432>
20/40 MHz operation not permitted on channel pri=1 sec=5 based on overlapping BSSes

However, this is unrealistic in modern cities. You should be grateful the primary channel you choose has not been used already, let alone the additional one for 40 MHz. What can we do? We have to force hostapd to turn on 40 MHz anyway. But since hostapd does not have any configuration options for that, we have to recompile hostapd from source. Actually, this has been done in other distros. For example, OpenWRT and Arch has patched their hostapd with the noscan patch, which adds an option to force 40MHz mode regardless of the environment.

Before turning on 40 MHz, the iwconfig on my laptop is like this, notice the bit rate is only 54 Mb/s:

wlan0     IEEE 802.11abgn  ESSID:"bibiworld"  
          Mode:Managed  Frequency:2.437 GHz  Access Point: C8:3A:35:C9:17:4A   
          Bit Rate=54 Mb/s   Tx-Power=15 dBm   
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:off
          Link Quality=70/70  Signal level=-32 dBm  
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:30  Invalid misc:683   Missed beacon:0

Now let us start. First you need to add a deb-src source to apt so that we can download source packages. Note that I am still using Raspbian Wheezy. If you use Jessie, change the deb-src line according to your original deb line.

pi@raspberrypi $ echo "deb-src http://mirrordirector.raspbian.org/raspbian/ wheezy main contrib non-free rpi" \
 | sudo tee /etc/apt/sources.list
pi@raspberrypi $ sudo apt-get update
pi@raspberrypi $ apt-get source hostapd            # download source of hostapd
pi@raspberrypi $ apt-get build-dep hostapd    # download all build dependencies
pi@raspberrypi $ cd wpa-1.0

Save the following patch into a file:

--- wpa-1.0.orig/src/ap/hw_features.c      2012-05-10 05:56:09.000000000 +0800
+++ wpa-1.0/src/ap/hw_features.c        2015-07-18 17:22:37.788215009 +0800
@@ -430,6 +430,7 @@
                oper40 = ieee80211n_check_40mhz_2g4(iface, scan_res);
        wpa_scan_results_free(scan_res);
 
+#if 0
        if (!oper40) {
                wpa_printf(MSG_INFO, "20/40 MHz operation not permitted on "
                           "channel pri=%d sec=%d based on overlapping BSSes",
@@ -439,6 +440,12 @@
                iface->conf->secondary_channel = 0;
                iface->conf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
        }
+#endif
+       wpa_printf(MSG_INFO, "Force 20/40 MHz operation on "
+                  "channel pri=%d sec=%d even there are overlapping BSSes",
+                          iface->conf->channel,
+                          iface->conf->channel +
+                          iface->conf->secondary_channel * 4);
 
        res = ieee80211n_allowed_ht40_channel_pair(iface);
        hostapd_setup_interface_complete(iface, !res);
pi@raspberrypi $ patch -p1 < patch.diff

Because we are going to create a new hostapd package, it'd be a good practice to increment the version number by adding a new section at the top of debian/changelog, like this:

wpa (1.0-3+deb7u3) wheezy; urgency=low

  * Force 20/40 MHz.

 -- Anthony Wong   Sat, 18 Jul 2015 16:30:43 +0800

wpa (1.0-3+deb7u2) wheezy-security; urgency=high
...

Now we are ready to compile the packages:

pi@raspberrypi $ fakeroot debian/rules binary

If it succeeds, install the new package with dpkg -i.

Restart hostapd, it should now force 40 MHz mode:

nl80211: New scan results available
nl80211: Received scan results (23 BSSes)
40 MHz affected channel range: [2397,2447] MHz   
Neighboring BSS: e0:05:c5:4c:e2:b6 freq=2427 pri=0 sec=0
Neighboring BSS: 00:d0:41:c4:25:31 freq=2437 pri=6 sec=2
40 MHz pri/sec mismatch with BSS 00:d0:41:c4:25:31 <2437,2417> (chan=6-) vs. <2412,2432>
Force 20/40 MHz operation on channel pri=1 sec=5 even there are overlapping BSSes
HT40: control channel: 1  secondary channel: 5
Completing interface initialization

And my iwconfig output now shows Bit Rate is 150 Mb/s, yay!

wlan0     IEEE 802.11abgn  ESSID:"bibiworld"  
          Mode:Managed  Frequency:2.412 GHz  Access Point: C8:3A:35:C9:17:4A   
          Bit Rate=150 Mb/s   Tx-Power=15 dBm   
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:off
          Link Quality=70/70  Signal level=-28 dBm  
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:31  Invalid misc:330   Missed beacon:0

解決 WordPress 中 Google Web Font 被 GFW 屏蔽問題

自從本 blog 的 wordpress 升級並更換 theme 後,偶爾發現 page load 得很慢,用 chrome 自帶的 developer tool 查了一下才發現原來 theme 用了 google web font,因爲 google service 被牆了所以在大陸瀏覽就像卡住了一樣。由於我絕大部份時間都是科學上網,所以一開始沒有發現問題。

Google font is blocked in China

既然 google web font 不能用,最簡單就是乾脆不用,把相關代碼拿掉就行,不過這樣做的後果是網站會極其難看。上策還是把 google web font 放到 wordpress server,再將 CSS 配置好。Google 一下發現有個很棒的 shell script 能幫個大忙:https://neverpanic.de/blog/2014/03/19/downloading-google-web-fonts-for-local-hosting/,它能幫你把需要的字體下載下來並產生一個立馬能用的 CSS 文檔。

WordPress 最少有兩個地方會加載 google web font,第一是 wordpress 本身用到的 Open Sans,另外是個別 theme 可能會用到的字體。視乎你的 theme 有沒有用 google font,有可能兩個地方都要處理。

目前我用的 theme 基於 twentyfourteen(我把原來左邊的 navigation 拿掉,header 加了 facebook, twitter 等幾個 icon),best practice 是在 wordpress 的 themes 目錄開一個叫 twentyfourteen-child 的新目錄,裏面只放用來 override parent theme 的東西。

解決問題的具體步驟如下:

  1. 下載剛才介紹過的網站的 shell script,我把它命名爲 downloadgooglefont.sh
  2. 先下載 wordpress 會下載的所有 Open Sans 字體(包括不同粗幼和斜體),在 downloadgooglefont.sh 裏要指定它們:
    families+=('Open Sans:300')
    families+=('Open Sans:400')
    families+=('Open Sans:600')
    families+=('Open Sans:300italic')
    families+=('Open Sans:400italic')
    families+=('Open Sans:600italic')
  3. 針對你使用的 theme,需要把 theme 用到的 google 字體寫進去。你要查一下你的 theme 會用到什麼字體,例如 twentyfourteen 會用到的字體在
    themes/twentyfourteen/functions.php

    的 215 行有提到:

                            
         'family' => urlencode( 'Lato:300,400,700,900,300italic,400italic,700italic' ),
    

    總共有7個字體,所以 script 需要加上:

    families+=('Lato:300')
    families+=('Lato:400')
    families+=('Lato:700')
    families+=('Lato:900')
    families+=('Lato:300italic')
    families+=('Lato:400italic')
    families+=('Lato:700italic')
    
  4. 執行該 script:
    $ bash ./downloadgooglefont.sh
    Downloading Open Sans:300... eot woff ttf svg 
    Downloading Open Sans:400... eot woff ttf svg 
    Downloading Open Sans:600... eot woff ttf svg 
    Downloading Open Sans:300italic... eot woff ttf svg 
    Downloading Open Sans:400italic... eot woff ttf svg 
    Downloading Open Sans:600italic... eot woff ttf svg 
    Downloading Lato:300... eot woff ttf svg 
    Downloading Lato:400... eot woff ttf svg 
    Downloading Lato:700... eot woff ttf svg 
    Downloading Lato:900... eot woff ttf svg 
    Downloading Lato:300italic... eot woff ttf svg 
    Downloading Lato:400italic... eot woff ttf svg 
    Downloading Lato:700italic... eot woff ttf svg

    這些就是下載完的所有文檔,包括適用於 chrome/chromium, firefox, safari 會用到的字體,還有一個 CSS file:

    Lato_300italic.svg
    Lato_300italic.ttf
    Lato_300italic.woff
    Lato_300.svg
    Lato_300.woff
    Lato_400italic.svg
    Lato_400italic.ttf
    Lato_400italic.woff
    Lato_400.svg
    Lato_400.ttf
    Lato_400.woff
    Lato_700italic.svg
    Lato_700italic.ttf
    Lato_700italic.woff
    Lato_700.svg
    Lato_700.ttf
    Lato_700.woff
    Lato_900.svg
    Lato_900.ttf
    Lato_900.woff
    LNM3t2QINm
    Open_Sans_300italic.svg
    Open_Sans_300italic.ttf
    Open_Sans_300italic.woff
    Open_Sans_300.svg
    Open_Sans_300.ttf
    Open_Sans_300.woff
    Open_Sans_400italic.svg
    Open_Sans_400italic.ttf
    Open_Sans_400italic.woff
    Open_Sans_400.svg
    Open_Sans_400.ttf
    Open_Sans_400.woff
    Open_Sans_600italic.svg
    Open_Sans_600italic.ttf
    Open_Sans_600italic.woff
    Open_Sans_600.svg
    Open_Sans_600.ttf
    Open_Sans_600.woff
    font.css
  5. 把上面所有的字體上傳到 wordpress server 的 wp-content/themes/twentyfourteen-child/ 目錄。
  6. 把 font.css 複製到 wp-content/themes/twentyfourteen-chile/style.css。
  7. 由於 twentyfourteen 在 functions.php 通過 wp_enqueue_style 加上 fonts.googleapis.com 的 link 來加載 Lato 字體,要想辦法把它拿掉:
            // Add Lato font, used in the main stylesheet.
            wp_enqueue_style( 'twentyfourteen-lato', twentyfourteen_font_url(), array(), null );
    

    不過我不喜歡直接修改 parent theme,所以我選擇在 twentyfourteen-child 把它 dequeue 掉。注意 add_action 的 priority 必須低於 10,dequeue 才會成功。theme_enqueue_styles 的前兩行是用來禁止 Open Sans 字體從 google server 加載,第3行才是禁止 Lato。
    _twentyfourteen-child/functions.php:

Compile kernel module on Linode Debian VPS

Connection to linode hosts from within China can be poor from time to time. Choosing a suitable TCP congestion algorithm may alleviate that from insignificant to a great deal, your mileage may vary. Trying it out is always the best way to find out. Here I will show you how to do that step by step.

  1. Login to your VPS and find out the version of the running kernel

    [code gutter=”false”]
    # uname -a
    Linux jhelom 3.12.6-x86_64-linode36 #2 SMP Mon Jan 13 18:54:10 EST 2014 x86_64 GNU/Linux
    [/code]
    That is 3.12.6.

  2. Go get the 3.12.6 from kernel.org at https://www.kernel.org/pub/linux/kernel/v3.x/
  3. Extract it and copy the kernel config over from the current one.
    [code gutter=”false”]
    # tar xf linux-3.12.6.tar.xz
    # zcat /proc/config.gz > .config
    # make oldconfig
    [/code]
  4. Now, edit .config with an editor such as vi, look for the line CONFIG_TCP_CONG_ADVANCED, remove the line, and add the following lines there:
    [code gutter=”false”]
    CONFIG_TCP_CONG_ADVANCED=y
    CONFIG_TCP_CONG_BIC=m
    CONFIG_TCP_CONG_WESTWOOD=m
    CONFIG_TCP_CONG_HTCP=m
    CONFIG_TCP_CONG_HSTCP=m
    CONFIG_TCP_CONG_HYBLA=m
    CONFIG_TCP_CONG_VEGAS=m
    CONFIG_TCP_CONG_SCALABLE=m
    CONFIG_TCP_CONG_LP=m
    CONFIG_TCP_CONG_VENO=m
    CONFIG_TCP_CONG_YEAH=m
    CONFIG_TCP_CONG_ILLINOIS=m
    CONFIG_DEFAULT_CUBIC=y
    # CONFIG_DEFAULT_RENO is not set
    [/code]
  5. Now compile the kernel and the modules.
    [code gutter=”false”]
    # make -j4
    # make modules SUBDIRS=net/ipv4
    [/code]
  6. If everything goes well, you will be able to load the module.
    [code gutter=”false”]
    # sudo insmod net/ipv4/tcp_hybla.ko
    [/code]

    Check with lsmod and make sure the kernel module is loaded successfully. If not, check dmesg for any errors.

That’s it!