Openwrt x86安装教程

网上Openwrt x86虚拟机安装的教程一大堆,却很少见到非虚拟机的教程,现在正好有机会重新刷机,就顺便把步骤记录一下,免得再次踩坑。

镜像下载

第一步就是下载openwrt x86的镜像,这里两个镜像可以选择,带squashfs的刷机后会有一个只读的分区,带ext4的所有分区都是可读写的。本教程是以combined-ex4为例子的。

下载地址:openwrt-18.06.2-x86-64-combined-ext4.img.gz

combined-squashfs 和 combined-ex4 的区别:

combined-squashfs.img.gz This disk image uses the traditional OpenWrt layout, a squashfs read-only root filesystem and a read-write partition where settings and packages you install are stored. Due to how this image is assembled, you will have only 230-ish MB of space to store additional packages and configuration, and Extroot does not work.

combined-ext4.img.gz This disk image uses a single read-write ext4 partition with no read-only squashfs root filesystem, which allows to enlarge the partition. Features like Failsafe Mode or Factory Reset won’t be available as they need a read-only squashfs partition to function.

制作linux启动盘

我们需要两个U盘,其中一个就是用来做linux启动盘,这里以SystemRescueCd为例,制作USB启动盘的教程可一参考这里:Installing-SystemRescueCd-on-a-USB-stick/

USB启动盘制作好后,将下载下来的openwrt安装包拷贝到另一个U盘。

安装镜像

将两个U盘插入软路由,以linux启动盘启动,成功进入系统后执行以下命令将第二个U盘挂载到系统里:

1
2
mkdir /mnt/usb
mount /dev/sdx /mnt/usb

解压并将镜像刷到软路由内存里:

1
2
3
cd /mnt/usb
gunzip openwrt-18.06.2-x86-64-combined-ext4.img.gz
dd if=openwrt-18.06.2-x86-64-combined-ext4.img of=/dev/sdX

其中sdx就是你软路由的安装盘。

PS:官网里的步骤并没有提到解压这一步骤,实际操作中直接dd的话,openwrt是无法启动的,可能是linux系统的差异。

分区扩容(可选)

刷入openwrt后,执行fdisk -l,就会看到软路由的sdx盘被分成了两个分区,你会发现第二个分区只有256M,这里就可以调节第二个分区的大小,将硬盘的剩余容量全部划分到第二个分区:

  1. fdisk -l /dev/sdx将第二个分区的起始扇区号start的值记下来
  2. fdisk /dev/sdx 进入修改硬盘分区信息
  3. 输入d,然后回车
  4. 这里会提示你要删除哪个分区,输入2,然后回车
  5. 输入n,然后回车,选择分区类型primaryextended都可以。
  6. 选择分区后会让你输入分区开始扇区号,这里输入之前记下来的扇区号
  7. 输入w,这时候会提示你Partition #2 contains a ext4 signature. Do you want to remove the signature?这一步很重要,这里要选no
  8. 输入w,然后回车
  9. 分区完成后执行resize2fs /dev/sdx2,分区扩容就大功告成了。

系统配置

  • 重启进入Openwrt系统后,系统会开放一个lan口,将网线接入这个lan口后就可以通过192.168.1.1来访问路由器管理界面了。
  • 进入管理界面后需要设置管理员密码,同时你可以上传ssh key,免得每次ssh都需要输入密码。
  • interfaces页面,LAN标签页面,在Physical settings->Interface你可以修改路由的lan口,将另外几个没开放的lan口都勾选。
  • WAN标签页面,Protocol选择PPPoE,输入宽带密码,save and apply就可以上网了。

应用安装

我们会安装一些应用,但这里有个坑就是应用的安装源是http的,可能会被劫持,所以要修改成https的。先执行以下命令,安装必备的包:

1
2
3
4
opkg update
opkg install ca-certificates ca-bundle
opkg install libustream-mbedtls
opkg install git-http curl

修改源为https

1
2
3
4
5
//将http改为https
vim /etc/opkg/distfeeds.conf
opkg update
//安装一些调试用的软件
opkg install bind-dig

一些常见问题

  1. Samba添加用户与设置密码
1
2
vi /etc/passwd
smbpasswd -a samba
  1. dnsmasqdnsmasq-full的区别

    dnsmasq-full多了ipset功能

  2. dnsmqsq --no-resolv

1
2
--no-resolv
Don't read /etc/resolv.conf. Get upstream servers only from the command line or the dnsmasq configuration file.
  1. ss-tunnel dns解析不了

一般原因有以下几点:

1. `ss-tunnel` 未加上`-u`参数,开启`udn relay`

2. `ss-server` 未加上`-u`参数,开启`udn relay`

3. 服务端未开启端口

调试用的命令:

1
2
3
4
5
6
7
//dnstracer
dnstracer -vo -s 127.0.0.1 google.com

//dns测试
dig +trace google.com
// dns请求也可以直接发送给ss-tunnel
dig google.com @127.0.0.1 -p 5353
  1. 开启ss-rules后域名无法解析

具体什么原因不是很清楚,可能是iptablesudp转发有点问题,ss-redir for UDP这项打勾去掉即可。

一些定时任务脚本

写了一些定时任务脚本,这里记录一下。

安全设置

Luci页面访问控制

Openwrt的管理页面默认是允许任何IP访问的,如果你的路由器有公网IP的话,那任何人都可访问到你家的路由器登录页面,通过下面设置只允许来自内网地址的访问:

修改配置文件:

1
vi /etc/config/uhttpd

192.168.1.1fd54:274c:96dd::1换成路由器的v4和v6地址,路由器的lan IP可以在Network-> Interfaces->LAN查看,openwrt默认情况下是192.168.1.1

1
2
3
4
5
6
7
# HTTP listen addresses, multiple allowed
list listen_http 192.168.1.1:80
list listen_http [fd54:274c:96dd::1]:80

# HTTPS listen addresses, multiple allowed
list listen_https 192.168.1.1:443
list listen_https [fd54:274c:96dd::1]:443

Note: 如果要访问通过IPv6访问界面,URL应该是这个样子的:http://[fd54:274c:96dd::1]

修改完成后重启服务:/etc/init.d/uhttpd restart

SSH 访问控制

登录openwrt管理页面 System -> Administration->Dropbear Instance

Interface 那一项选择lan,然后Save & Apply,这样你的路由器ssh只能通过内网访问了。

参考

更新日志

  • 2019-11-09 添加了安全设置

hexo-deploy-git提交历史丢失的解决方法

迫于笔记本太重再加上每天上下班要背两小时多,笔记本就一直丢在公司里了。家里的windows越用越不爽,装了ubuntu折腾了一阵子发现不是太好用,V站好多推荐manjaro linux,用下来很顺手而且非常稳定,以后家中主力操作系统就用manjaro了。

由于之前一直是在笔记本上写博客的,最近在manjaro上写博客提交后发现github pages的提交历史全丢了,一查下来是hexo-deployer-git这个插件搞的鬼,该插件第一次提交时会force push,覆盖仓库代码,插件作者似乎无意修复这个BUG,遂想弃用这插件。

网上查下来发现用travis ci来部署github pages是个不错的解决方法,而且这样的话不用每次都手动生成page和提交了。

实现方法也很简单,在项目里加上.travis.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
language: node_js

node_js:
- 11.10.0

cache: npm

script:
- ./node_modules/hexo/bin/hexo generate

deploy:
provider: pages
skip-cleanup: true
keep-history: true
github-token: $GITHUB_TOKEN
local-dir: public
repo: wancaibida/blog
target-branch: master
on:
branch: master

notifications:
email:
recipients:
- wancaibida@gmail.com
on_success: always

设置下github pages的仓库名称,在github上创建一个权限为public_repo 的token给travis用就好了。

记wr1200js刷机遇到的问题

前几天入手了一台wr1200js路由器,到手后成功刷上了openwrt。但重启后发现路由器的配置都丢失了,连管理密码都丢了,网上好多人刷了openwrt后也遇到这样的问题,有说法是路由器闪存读写次数过多坏了,但这刚买的因该不会是这问题,后来在重新刷了下sysupgrade.bin就没这问题了,可能是openwrt在某些条件下会触发这问题。

路由器相关的一些研究

家中房间里的5G信号不好,每次都得手动切换到2.4G信号,有点麻烦,所以想在房间里弄一台AP,来实现无线无缝漫游,期间做了不少研究,故在此记录一下。

路由器/AP

网上看了一圈AP的价格都不便宜,可能是家用AP需求少且大部分是商用的原因,关键还要上POE。论坛好多推荐ubnt的,看了下普通版都要700+rmb。

千兆路由器好一点的基本是200左右的价格,但这个价位的厂家路由系统比较挫,还不能刷机。在再加点钱都赶上京东618 ac66u b1 499的价格了。

那有没有又便宜要好的路由呢,V站好多人都推荐这几款:

  • 斐讯K2P
  • 联想Newifi系列
  • 友华wr1200js

斐讯K2P

这个路由器现在都涨到120+了,如果要买的话要买a1版本的。

优点是做工好,有千兆网口,信号貌似是最好的。

缺点是现在比较难买,刷机的话要先上恩山论坛网友开发的breed, breed 就是路由器上的bootloader,breed一开始是基于u-boot的,现在好像是作者自主开发的,但这东西不开源,存在安全风险。固件可以刷padavan,padavan最初是由这个固件https://bitbucket.org/padavan/rt-n56u修改过来的,比较有名的是荒野无灯的,但这个也没开源。github上也有基于padavan开源的,不放心的可以拿来自己编译。

联想Newifi

联想这边就看了newifi3,这款路由器配置比k2p还要好,价格比k2p还要便宜,某宝的价格才80+,但这款的bootloader被官方锁了,如果要刷第三方固件的话要先越狱,恩山有越狱文件。刷机也是breed+恩山上一大堆网友上传的固件,风险自负,还有一个缺点就是这款信号比较差。

友华wr1200js

友华wr1200js配置和k2p差不多,但做工、信号没有k2p好,最终选择了这款,某宝138入手。这款路由器最大的优点是bootloader没有锁,可以直接上传第三方固件!!还有一个是openwrt完美支持,固件下载地址

Openwrt 和 NAT

喜欢路由器刷机的都知道openwrt,路由器刷了openwrt基本上就告别了硬件nat(貌似MT7621是支持的),由路由器CPU来实现nat,由于路由器CPU的性能都比较弱如果再加上一些附带的功能,路由器CPU就有点力不从心了,这是就推荐openwrt x86了。

MESH网络

研究了下mesh网络,研究下来一些mesh设备是通过在开一个无线频道来实现组网的,貌似无线组网的效果都不怎么好,要走有线回程才靠谱。

一些实现方案:

无线漫游

比较省钱的无线漫游方式就是路由器设置成AP模式,要相同SSID,要隔开几个频道。

专业的AP都带管理功能的。

参考

MT7621 路由性能评测

300m,600m还是1200m——读懂无线路由器的速度标称

如何选购无线路由器?有几个坑一定要避免

路由器透明代理

作为程序员的都知道,现在比较流行的科学上网方式是$$。$$搭建步骤简单,速度快,相比其他的科学上网方式比较轻量。但这种方式也有一些缺点,比如需要在本机安装客户端,维护服务端信息等缺点,路由器透明代理就是为了解决这些问题而诞生的 。

传统科学上网实现方式

传统的科学上网方式是在本机安装$$客户端,不同平台的客户端如下:

  • IOS
    • ShadowRocket(国区已下架)
    • Surge(国区已下架)
  • Mac OS
    • Shadowsocks NG
    • Surge
    • ss-local
  • Linux
    • ss-local
  • Windows
    • Shadowsocks

代理模式

$$一般有两种代理模式,

  1. 自动切换模式

    • 黑名单模式,只代理被墙的网站,对于名单中匹配的网站进行代理,如果不匹配则直接连接,比较著名的 有GFW List
    • 白名单模式,默认代理,对于名单中匹配的网站进行直连。白名单模式在不同平台上的效果不同,在IOS可以实现白名单模式,在Mac OS 上只支持黑名单模式,如果要实现需要做一些hack操作,如:ShadowsocksX-NG实现白名单模式
  2. 全局模式

    所有流量都走代理,这个在不同平台上表现也不同,在Mac os上只能代理部分流量,像CLI的流量需要额外配置。

传统代理模式的缺点

传统的代理模式的缺点也很明显:

  1. 需要安装客户端,且需要在所有客户端配置服务器信息
  2. 无法代理系统级流量或需要额外设置
  3. 无法实现大陆白名单模式(除了大陆以外的网站都走代理)
  4. 被屏蔽的网站越来越多,所有客户端都需要更新名单

路由器透明代理

路由器透明代理,说直白点就是在路由器实现科学上网功能。路由器透明代理的优点很明显,无需在本机安装客户端,只需在路由器维护服务器信息及黑白名单,且能代理系统级流量。

数据流逻辑图

如下图所示, 路由器透明代理主要是两个流程,DNS解析流程与流量代理流程。
数据逻辑图

实现原理

路由器透明代理主要依赖如下工具:

  1. iptables
  2. ipset
  3. ss-redir
  4. ss-tunnel
  5. dnsmasq

dnsmasq+ss-tunnel

dnsmasqss-tunnel主要是用来解决域名污染的问题,dnsmasq会将国外域名dns解析请求通过ss-tunnel转发到shadowsocks服务器 dnsmasq会将除了国内以外的DNS解析请求通过ss-tunnel转发到shadowsocks服务器

dnsmasq配置文件:

https://cokebar.github.io/gfwlist2dnsmasq/dnsmasq_gfwlist.conf

1
2
3
4
5
server=/google.com/127.0.0.1#5353
server=/google.co.ma/127.0.0.1#5353
server=/google.com.af/127.0.0.1#5353
server=/google.com.ag/127.0.0.1#5353
server=/google.com.ai/127.0.0.1#5353

如上面的代码所示,遇到如需要解析google.com的域名时,dnsmasq会将解析请求转发到ss-tunnel监听的5353端口,ss-tunnel会将解析请求转发到远程服务器,通过远程服务器来解析域名。

不建议用上面的方法了,DNS污染太多太频繁,直接上DNS白名单模式:

https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf

1
2
3
4
5
server=/baidu.cc/114.114.114.114
server=/baidu.cm/114.114.114.114
server=/baidu.com/114.114.114.114
server=/baidu.hk/114.114.114.114
server=/baidu.jp/114.114.114.114

dnsmasq.confss-tunnel设置为上游服务器

1
2
3
no-resolv
server=127.0.0.1#5353
conf-dir=/root/dnsmasq.d

iptables+ipset

iptablesipset主要用来区分国内流量和转发代理流量。

ipset

ipset.conf

1
2
3
4
5
1.0.1.0/24
1.0.2.0/23
1.0.8.0/21
1.0.32.0/19
...

shell:

1
2
ipset create chnroute hash:net
ipset -R < ipset.conf

上面代码创建了一个名为chnroute的IP集合。

iptables sample:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
iptables -t nat -N SHADOWSOCKS
iptables -t mangle -N SHADOWSOCKS

# 直连服务器 IP
iptables -t nat -A SHADOWSOCKS -d [SS-SERVER-IP]/24 -j RETURN

# 允许连接保留地址
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# 直连中国 IP
iptables -t nat -A SHADOWSOCKS -p tcp -m set --match-set chnroute dst -j RETURN
iptables -t nat -A SHADOWSOCKS -p icmp -m set --match-set chnroute dst -j RETURN

# 重定向到 ss-redir 端口
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 1080
iptables -t nat -A SHADOWSOCKS -p udp -j REDIRECT --to-ports 1080
iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS

# Apply the rules
iptables -t nat -A PREROUTING -p tcp -j SHADOWSOCKS
iptables -t mangle -A PREROUTING -j SHADOWSOCKS

上面代码的主要含义是:

  • 先判断是不是请求远程$$服务流量,如果是则直接放行
  • 如果是请求内网的流量也会直接放行
  • 如果是请求大陆网站的流量,则直接放行
  • 如果上面的条件都不匹配,则会将流量转发到本机ss-local监听的1080端口

ss-redir

ss-redir会监听端口,并将代理请求转发到$$服务器

路由器透明代理实现方式

一般高端一点的路由器都可以通过刷机来实现,以华硕路由器为例:

梅林固件

梅林固件华硕路由器第三方固件,基于华硕官方固件。这种方式你需要自己搭建整套工具,虽然网上也有一些一键安装的脚本,但与我们上面提到的有一些出入。

官网: https://asuswrt.lostrealm.ca/

优点:开源,基于华硕官方固件,比较稳定。

缺点:

  • 需要熟悉linux
  • 需要自己搭建且坑比较多
  • 定制差
  • 除华硕路由器外,只支持部分非华硕路由器

梅林小宝

梅林小宝是基于梅林的固件,这个实现方式比较傻瓜式,刷机完成后可通过在软件中心安装shadowsocks,配置一下服务器信息就可以实现路由器透明代理。

官网:http://koolshare.cn/forum-96-1.html

优点:

  • 无需自己搭建
  • 有图形化管理界面

缺点:

  • 闭源,存在风险
  • 除华硕路由器外,只支持部分非华硕路由器

OpenWrt

OpenWrt是开源的路由器固件,他能提供一整套linux操作环境,插件众多且大多数插件都提供图形管理界面,大大降低而搭建难度。
当然在刷机之前你需要在Table of Hardware 里查一下你的路由器是否支持openwrt.

官网:https://openwrt.org/

优点:

  • 开源,维护者众多
  • 插件众多,很多定制功能:广告过滤,文件共享等。
  • 图形化管理界面
  • OpenWrt x86 支持X86平台,可实现软路由。

缺点:

  • 需要一点linux知识

路由器透明代理的不足

当然路由器透明代理也有其不足,路由器一般都是低功耗平台。以ac66u为例,大陆白名单模式下只能跑到30M的带宽,CPU占用已经是100%。

软路由

软路由顾名思义,就是在软件层面实现路由功能,基于x86平台,性能强大,解决硬路由性能不足的问题,这里就不多说了。

参考

利用shadowsocks打造局域网翻墙透明网关

dnsmasq gfwlist

China IP List

https://www.v2ex.com/t/503057

Groovy JSON处理的一些细节

最近遇到一项目,需要在手机端存储用户数据,实现离线访问。其中用户数据处理的逻辑如下图:

User data flow

  1. 服务端从亚马逊S3上下载用户JSON文本数据库

  2. 反序列化用户数据

  3. 更新用户数据

  4. 将用户数据序列化为JSON文本

  5. 保存到亚马逊S3上

由于项目设计缺陷,用户所有的数据都存储一个Map对象里,导致Map对象过大,在项目运行过程中出现了内存不足的异常。为了解决内存不足问题,服务端采用了JacksonStreamingApi优化了JSON序列及反序列化步骤,避免将整个用户文件载入到内存中,至此内存不足的异常就再也没有发生。

其实这里有个问题,如果是用户数据过大,内存不足异常会在步骤3结束后就会发生,为什么偏偏在步骤4序列化为JSON时抛出呢?这里就要说到 Groovy的LazyMap了。

LazyMap代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
public class LazyMap extends AbstractMap<String, Object> {

static final String JDK_MAP_ALTHASHING_SYSPROP = System.getProperty("jdk.map.althashing.threshold");

/* Holds the actual map that will be lazily created. */
private Map<String, Object> map;
/* The size of the map. */
private int size;
/* The keys stored in the map. */
private String[] keys;
/* The values stored in the map. */
private Object[] values;

public LazyMap() {
keys = new String[5];
values = new Object[5];
}
...

public Object put(String key, Object value) {
if (map == null) {
for (int i = 0; i < size; i++) {
String curKey = keys[i];
if ((key == null && curKey == null)
|| (key != null && key.equals(curKey))) {
Object val = values[i];
keys[i] = key;
values[i] = value;
return val;
}
}
keys[size] = key;
values[size] = value;
size++;
if (size == keys.length) {
keys = grow(keys);
values = grow(values);
}
return null;
} else {
return map.put(key, value);
}
}

public Object get(Object key) {
buildIfNeeded();
return map.get(key);
}

private void buildIfNeeded() {
if (map == null) {
// added to avoid hash collision attack
if (Sys.is1_8OrLater() || (Sys.is1_7() && JDK_MAP_ALTHASHING_SYSPROP != null)) {
map = new LinkedHashMap<String, Object>(size, 0.01f);
} else {
map = new TreeMap<String, Object>();
}

for (int index = 0; index < size; index++) {
map.put(keys[index], values[index]);
}
this.keys = null;
this.values = null;
}
}
}

从代码中可以看出:对于未进行过读操作(get,containsKey等)的LazyMap对象,keys和values分别存在了两个数组中,一旦调用了读取方法,LazyMap会将数组转化成Map对象,就是这一步操作引起了内存占用变化。

拿大小约为35MB的用户文件测试,反序列化后,内存中LazyMap对象为73MB(line 1),一旦对对象进行toJson操作(line 2),内存占用上升到了559MB(line 3)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static void main(String[] args) {
File jsonFile = new File('data-format.json')
Object obj = new JsonSlurper().parse(jsonFile) // LazyMap here
showSize(obj) // line 1
JsonOutput.toJson(obj) // line 2
showSize(obj) // line 3
}

static void showSize(obj) {
def size = ObjectSizeCalculator.getObjectSize(obj) / 1024 / 1024
println("Object memory size is $size MB")
}

// output logs:
Object memory size is 73.51363372802734375 MB
Object memory size is 559.32244873046875 MB

由于用户数据文件较大且嵌套了多层Map,加之JsonOutput.toJson方法会遍历LazyMap对象所有节点,相当于对所有节点进行了读操作,导致节点中的数组转换成Map对象,最终引起内存不足异常。

ASUS 66u 搭建透明代理

安装软件

1
2
3
4
5
6
7
8
9
10
/usr/sbin/entware-setup.sh

opkg install bind-dig
opkg install shadowsocks-libev-ss-redir
opkg install shadowsocks-libev-ss-tunnel
opkg install libc libssp libev libmbedtls libpcre libpthread libsodium haveged zlib libopenssl
opkg install ipset4

//ac66u only
opkg install iptables

配置ss-redir服务

修改配置文件

  1. vim /opt/etc/init.d/S22shadowsocks
  2. ss-local改为ss-redir
  3. 添加启动参数-b 0.0.0.0
  4. ENABLED值改为 yes,变成开机启动

修改shadowsocks.json

修改服务器IP,端口,加密码方式。

配置dnsmasq

创建配置文件

  • vim /jffs/configs/dnsmasq.conf.add
  • 添加conf-dir=/opt/etc/dnsmasq.d

创建目录

  • mkdir /opt/etc/dnsmasq.d/
  • 下载规则文件
1
2
cd /opt/etc/dnsmasq.d/
curl -O https://cokebar.github.io/gfwlist2dnsmasq/dnsmasq_gfwlist.conf

配置ss-tunnel服务

ss-tunnel主要用于解决dns污染的问题,dnsmasq会将解析请求转发到ss-tunnel

创建开机启动脚本

  • 转到目录/jffs/scripts/
  • 创建名为比如start-ss-tunnel.sh文件,内容如下:
1
2
3
#!/bin/sh

/opt/bin/ss-tunnel -c /tmp/mnt/onmp/entware/etc/shadowsocks.json -l 5353 -L 8.8.8.8:53 -u > /dev/null 2>&1 &

创建service-start文件

1
chmod +x service-start

/jffs/scripts/start-ss-tunnel.sh 加到service-start

至此ss-tunnel就能开机启动了

配置iptables

下载chnroute.txt

1
2
wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /jffs/configs/chnroute.txt

生成ipset集合

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh

wget -O- 'http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest' | awk -F\| '/CN\|ipv4/ { printf("%s/%d\n", $4, 32-log($5)/log(2)) }' > /jffs/configs/chnroute.txt

ipset -N chnroute nethash

for ip in $(cat '/jffs/configs/chnroute.txt'); do
ipset -A chnroute $ip
done


ipset --save > /jffs/configs/ipset.conf

因为chnroute.txt文件比较大,生成会比较慢,故将生成的ipset保存到ipset.conf中,每次启动时从ipset.conf中导入IP.

启动脚本

SS启动脚本ss-up.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/sh

alias iptables='/opt/sbin/iptables'

ipset -R < /jffs/configs/ipset.conf

# ipset -N chnroute iphash

# for ip in $(cat '/jffs/scripts/chnroute.txt'); do
# ipset -A chnroute $ip
# done

iptables -t nat -N SHADOWSOCKS
iptables -t mangle -N SHADOWSOCKS

# 直连服务器 IP
iptables -t nat -A SHADOWSOCKS -d [SS-SERVER-IP]/24 -j RETURN

# 允许连接保留地址
iptables -t nat -A SHADOWSOCKS -d 0.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 10.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 169.254.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 172.16.0.0/12 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 192.168.0.0/16 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 224.0.0.0/4 -j RETURN
iptables -t nat -A SHADOWSOCKS -d 240.0.0.0/4 -j RETURN

# 直连中国 IP
iptables -t nat -A SHADOWSOCKS -p tcp -m set --match-set chnroute dst -j RETURN
iptables -t nat -A SHADOWSOCKS -p icmp -m set --match-set chnroute dst -j RETURN

# 重定向到 ss-redir 端口
iptables -t nat -A SHADOWSOCKS -p tcp -j REDIRECT --to-ports 1080
iptables -t nat -A SHADOWSOCKS -p udp -j REDIRECT --to-ports 1080
iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS

# Apply the rules
iptables -t nat -A PREROUTING -p tcp -j SHADOWSOCKS
iptables -t mangle -A PREROUTING -j SHADOWSOCKS

将其中的SS-SERVER-IP修改成你的服务器地址

创建post-mount文件

1
chmod +x post-mount

ss-up.sh加入

1
2
#!/bin/sh
/jffs/scripts/ss-up.sh

经测试只有将启动脚本加入到post-mount中对能开机启动,可能的原因是ss-up.sh中设及到挂载分区的访问。

停止脚本

1
2
3
4
5
6
#!/bin/sh
iptables -t nat -D OUTPUT -p icmp -j SHADOWSOCKS
iptables -t nat -D OUTPUT -p tcp -j SHADOWSOCKS
iptables -t nat -F SHADOWSOCKS
iptables -t nat -X SHADOWSOCKS
ipset -X chnroute

参考

ShadowsocksX-NG实现白名单模式

ShadowsocksX-NG Proxy Auto Configure Mode使用的是黑名单模式,默认直连,符合规则的走代理。要实现白名单模式,默认代理,符合规则的直连,有一种hack方法:

  • 找到程序文件夹下的abp.js文件,路径一般为/Applications/ShadowsocksX-NG.app/Contents/Resources/abp.js

  • 修改FindProxyForURL方法,将direct,proxy对调,改成下面代码中的样子

    1
    2
    3
    4
    5
    6
    function FindProxyForURL(url, host) {
    if (defaultMatcher.matchesAny(url, host) instanceof BlockingFilter) {
    return direct;
    }
    return proxy;
    }
  • 重启程序

  • 修改程序preferences里的GFW List url,改成白名单规则

  • 然后点击Update PAC from GFW List更新pac文件即可

Groovy+Grails迁移到Kotlin+Spring Boot的一些研究

Kotlin是静态语言,可以运行在JVM上且与java百分百可互操作。自从Google将Kotlin正式做为Android开发的语言后,Kotlin的一下子就火了起来。Kotlin不仅可以做为Android的首选开发语言,而且它也非常适合服务端的开发。

为什么选择Kotlin

与Java百分百可互操作groovy-grails-to-kotlin-springbootgroovy-grails-to-kotlin-springboot

Kotlin可以使用JVM平台上的任何库与框架,Kotlin编写的代码也可以供Java调用

简洁

Kotlin的代码非常简洁:

变量

变量声明:

1
2
3
4
val a: Int = 1  // immediate assignment
val b = 2 // `Int` type is inferred
val c: Int // Type required when no initializer is provided
c = 3 // deferred assignment

可变变量:

1
2
var x = 5 // `Int` type is inferred
x += 1

可空值与不可空值:

1
2
val s: String? = null //May be null
val s2: String = "" //May not be null

方法

1
2
3
4
5
6
fun sum(a: Int, b: Int): Int {
return a + b
}

//Function with an expression body and inferred return type:
fun sum(a: Int, b: Int) = a + b

扩展函数

1
2
3
4
fun String.lastChar(): Char = this.get(this.length - 1)

>>> println("Kotlin".lastChar())
n

infix函数

1
2
3
infix fun Any.to(other: Any) = Pair(this, other)

val (number, name) = 1 to "one"

1
2
3
4
5
6
7
8
9
10
11
class Greeter(val name: String) {
fun greet() {
println("Hello, $name")
}
}

fun main(args: Array<String>) {
val greeter = Greeter("John")
greeter.greet()
println(greeter.name)
}

数据类(data class)

自带equals hashcode tostring copy等方法

1
data class User(val name: String, val age: Int)

DSL(Domain-specific language)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
open class Tag(val name: String) {
private val children = mutableListOf<Tag>()

protected fun <T : Tag> doInit(child: T, init: T.() -> Unit) {
child.init()
children.add(child)
}

override fun toString() = "<$name>${children.joinToString("")}</$name>"
}

class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) = doInit(TR(), init)
}

class TR : Tag("tr") {
fun td(init: TD.() -> Unit) = doInit(TD(), init)
}

class TD : Tag("td")

fun table(init: TABLE.() -> Unit) = TABLE().apply(init)

fun createTable() = table {
tr {
td {

}
}
}

>>> println(createTable())
<table><tr><td></td></tr></table>

空安全

空安全(null safe)可以说是kotlin杀手级的特性了,它能排除一切空指针的可能,让你写出更安全的代码。

如下面这段代码会直接编译不通过:

1
2
var output: String
output = null // Compilation error
1
2
val name: String? = null    // Nullable type
println(name.length()) // Compilation error

自动类型转换:

1
2
3
4
fun calculateTotal(obj: Any) {
if (obj is Invoice)
obj.calculateTotal()
}

静态类型(Statically Typed)

动态语言是一把双刃剑,groovy的代码非常简洁,弥补了java语言太啰嗦的缺点,同时groovy的闭包也非常好,正因为如此我们选择了groovy。但时做为动态语言,许多行为是在代码运行时才会确定,编译错误在写代码时无法检测到,除非加上@TypeChecked注解。

Kotlin的代码非常简洁,更重要的是它是静态类型语言,能检测到编译错误,减少了代码中潜在的问题。

为什么选择Spring Boot

  • 从2015年初开始,Sring Boot的热度就超过了Grails,可参考:Grails && Spring Boot Stack Overflow Trends
  • Grails 2.x版本有上千个插件,但升级到Grails 3.x之后,这些插件就不再被支持,更糟糕的是没有人将这些插件迁移到Grails 3.x,Grails 3.x现在只有244个插件。
  • 尽管Grails 3是基于Spring Boot的,但其自身也带来了些奇怪的bug, 有时候解决框架bug的时间甚至超过了使用框架节省的时间。

Kotlin+Spring Boot的一些不足

没有Gorm查询

Grails框架的最大亮点是Gorm:动态方法和DSL查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
//will be translated to hql automatically
def person = Person.findByFirstName('xxx')
equals:
"FROM Person WHERE firstname = ?"

Person.withCretia {
or {
ilike('name','%j%')
gte('age',22)
}
}
equals:
"FROM person WHERE name ilike ? OR age >= ?"

在Spring Boot中你只能自己写查询语句或者使用类似spring data jpa query dsl插件:

1
2
3
4
5
6
7
internal interface TodoRepository : Repository<Todo, Long> {

fun findFirst3ByTitleOrderByTitleAsc(title: String): List<Todo>

fun findTop3ByTitleOrderByTitleAsc(title: String): List<Todo>
}

数据库迁移(Database Migration)

Grails 的database migration在数据库表结构和数据迁移两方面都非常强,它可以对比项目中的实体和数据库表来自动生成表结构修改语句,同时你可以写sql来完成数据迁移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
changeSet(author: "xxx (generated)", id: "1521527665533-1") {
addColumn(tableName: "staff") {
column(name: "date_created", type: "TIMESTAMP WITHOUT TIME ZONE") {
constraints(nullable: "true")
}
}
}

changeSet(author: "xxxx", id: "1521528248") {
grailsChange {
change {
sql.execute('''
UPDATE staff
SET date_created = now()
''')
}
}
}

而在Spring Boot方面,数据库迁移插件有flyway和liquibase,两者各有千秋,flyway在数据迁移上比较强,但缺少自动生成表结构修改语句功能。liquibase正好相反,它有自动生成表结构修改语句功能,但在写数据迁移sql方面比较弱。

表单校验

在自定义校验方法上Grails非常方便,只需要一个闭句就可以了:

1
2
3
4
5
6
7
8
9
10
11
12
13
class User {
String name
static constraints = {
name unique: true, validator: { val, obj ->

if (!val.matches('[A-Z0-9-]+')) {
return 'acronym.format.error'
}

true
}
}
}

而在Spring Boot方面比如用hiberate validator你需要定义类,定义注解,写很多代码来实现自定义校验。

总结

在项目开发速度上,Groovy+Grails上占优,如果时间比较紧的话,Groovy+Grails会是一个不错的选择,它能在较短时间内做出项目。如果你需要一个bug少,更稳定的项目或者你的项目用不到Gorm的话,你可以选择Kotlin+Spring Boot.

简而言之,100分做为满分的话,Groovy+Grails能在最短的时间内达到80分,而要达到90甚至95分的话,选择Kotlin+Spring Boot无疑。