智能家居已经走进了千家万户,越来越多的家居产品具备了联网功能。如果想要将这些智能家居产品接入家庭网络,就需要给他们提供家庭wifi的账号和密码。

但是给智能家具产品提供家庭wifi的账号密码,可能会存在以下3个问题:

  1. 各大平台基本上会把wifi账号密码保存在云端,如果发生数据泄露事件,别人就可以随便连接你的家庭wifi
  2. 部分智能家具产品存在扫描局域网拓扑的行为,将它们连入家庭网络可能会泄露隐私
  3. 如果某个智能家具产品存在安全漏洞,就可能会被黑客控制,通过SSRF等方式攻击家庭网络

因此,比较好的做法是将智能家具产品放在一个隔离的网络环境中,像华硕/梅林的系统天然提供了访客网络功能,连接访客网络的设备可以访问互联网,但是会被限制访问家庭网络,从而提升家庭网络的安全性。

alt text

使用这种方式可以解决前面说的问题2和问题3,但是会带来一个新问题,如果将访问内部网络设置成关闭,不止这个访客网络中的设备无法访问家庭网络,家庭网络中的设备也无法访问这个访客网络。在有些场景下,我们需要从家庭网络中访问某个接入访客网络的设备就会遇到问题。好在社区里有人为这个问题开发了一个插件YazFi。通过这个插件,可以实现从家庭网络到访问网络的单向或者双向访问,以及可以限制访客网络中的设备互相访问,进一步提高了访客网络的安全性。

alt text

为了解决前面说的问题1,即使在wifi账号和密码被泄露的情况下,也只有授权设备才能访问互联网,防止其他人白嫖家庭网络带宽。我们可以在访客网络中启用mac地址过滤,这样只有认证过mac地址的设备才能连入wifi。

alt text

比较可惜的是这种方式最多只能限制16个mac地址,而现在具备联网功能的智能家居产品越来越多,很快就会达到这个限制。并且这个限制是硬编码在无线网卡的驱动中,无法进行修改。

因此就需要寻找其他的方式来实现对连接设备mac地址的控制,snbforums论坛中有一个帖子提到了一种思路,使用iptables和ipset配合来实现对mac地址的限制,这种限制虽然不能阻止未认证mac地址的设备连接wifi,但是可以阻止那些设备连接网络,属于一种曲线救国的方案。

配置方法

  1. /jffs/addons/YazFi.d/userscripts.d/目录中创建一个以.sh为后缀的脚本,名称随意,内容如下:

    1
    2
    3
    #!/bin/sh
    ipset restore -! < /jffs/addons/YazFi.d/userscripts.d/iot.ipset
    iptables -I YazFiFORWARD -i wl0.2 -m set ! --match-set iot src -j logdrop

    其中wl0.2对应的是第二个2.4GHz访客网络,然后给这个脚本设置可执行权限:

    1
    chmod +x /jffs/addons/YazFi.d/userscripts.d/${script_name}.sh

    在设置完访客网络,或者访客网络发生变更时,这个脚本会被自动执行。

  2. iot.ipset内容

    1
    2
    3
    4
    create iot hash:mac hashsize 1024 maxelem 65536 comment
    add iot 88:B9:xx:xx:xx:xx comment "water heater"
    add iot 88:B9:xx:xx:xx:xx comment "water softener"
    add iot 88:B9:xx:xx:xx:xx comment "water purifier"

iptables

iptables之所以被称为iptables,就是因为它内部有多个表,比如管理本机进出的filter、管理封包转发的nat和管理特殊标记的mangle等。这些表中又有不同的处理链,如:

  • filter
    • INPUT: 管理封包进入本机的规则
    • OUTPUT:管理封包送出本机的规则
    • FORWARD:管理封包转发的规则
  • nat
    • PREROUTING: 在判断路由之前要执行的规则,如DNAT/REDIRECT
    • POSTROUTING: 在判断路由之后要执行的规则,如SNAT/MASQUERADE
    • OUTPUT:管理本机产生的包的送出规则,如修改目的地址

在一般使用场景下,不太会涉及mangle表中的链,一个封包从进入本机到离开本机的链路如下:

alt text

操作

  • 查看iptables的规则:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    $ iptables-save

    # Generated by iptables-save v1.4.7 on Fri Jul 22 15:51:52 2011
    *filter <==星号开头的指的是表格,这里为 filter
    :INPUT ACCEPT [0:0] <==冒号开头的指的是链,三条内建的链
    :FORWARD ACCEPT [0:0] <==三条内建链的政策都是 ACCEPT 啰!
    :OUTPUT ACCEPT [680:100461]
    -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT <==针对 INPUT 的规则
    -A INPUT -p icmp -j ACCEPT
    -A INPUT -i lo -j ACCEPT <==这条很重要!针对本机内部接口开放!
    -A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
    -A INPUT -j REJECT --reject-with icmp-host-prohibited
    -A FORWARD -j REJECT --reject-with icmp-host-prohibited <==针对 FORWARD 的规则
    COMMIT
    # Completed on Fri Jul 22 15:51:52 2011

    或者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    iptables -L -n --line-number

    Chain INPUT (policy ACCEPT) <==针对 INPUT 链,且预设政策为可接受
    num target prot opt source destination <==说明栏
    1 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED <==第 1 条规则
    2 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 <==第 2 条规则
    3 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 <==第 3 条规则
    4 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 state NEW tcp dpt:22 <==以下类推
    5 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited

    Chain FORWARD (policy ACCEPT) <==针对 FORWARD 链,且预设政策为可接受
    num target prot opt source destination
    1 REJECT all -- 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited

    Chain OUTPUT (policy ACCEPT) <==针对 OUTPUT 链,且预设政策为可接受
    num target prot opt source destination

    两种方式相比来说,第一种方式的结果更能体现iptables中完整的信息,不像第二种方式中INPUT链的第3条规则,看起来像是允许所有的封包,但是实际上在输出结果中省略了网络接口,容易产生误解。个人认为第二种方式唯一有价值的地方在于能够显示每一条规则的序号,便于对指定位置的规则进行操作。

  • 新增规则

    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
    iptables [-AI 链名] [-io 网络接口] [-p 协议] [-s 来源IP/网段] [-d 目标IP/网段] [--sport 来源端口号] [--dport 目标端口号] [-m 扩展匹配] -j [ACCEPT|DROP|REJECT|LOG]

    -AI 链名
    -A 在链的最后位置插入规则
    -I 可指定插入位置,不指定则在链的最开始位置插入规则

    -io 网络接口
    -i input,封包进入的网络接口
    -o output,封包送出的网络接口

    -p 协议
    tcp, udp, icmp, all

    -s 来源IP/网段
    -d 目标IP/网段
    IP:192.168.0.100 或者
    网段:192.168.0.0/24

    --sport 来源端口号
    --dport 目标端口号
    指定端口号

    -m 扩展匹配
    state:匹配封包状态,如--state NEW,RELATED,ESTABLISHED
    set:匹配ipset规则,如--match-set iot src
    tcp|udp|icmp:匹配协议扩展规则,如--tcp-flags FIN,SYN,RST,ACK SYN


    -j 动作,默认有ACCEPT, DROP, REJECT, LOG等,另外还支持自定义动作

    示例:

    1
    iptables -A INPUT -i eth1 -s 192.168.100.10 -p tcp --dport 3306 -j ACCEPT

    这条规则的意思是eth1网络接口接收到的源地址为192.168.100.10的tcp协议封包,且目标端口是本机的3306端口则允许通过。

    DROP vs REJECT: DROPREJECT的区别在于DROP不会响应用户的请求,而REJECT会回复一个拒绝连接的封包。

  • 删除规则

    1
    iptables -D INPUT 3  # 删除INPUT的第3条规则

ipset

ipset是一个管理IP的集合,可以配合iptables使用,实现针对一个IP集合设置白名单或黑名单的功能。

数据类型

  • list:set
  • hash:mac
  • hash:ip,mac
  • hash:net,iface
  • hash:net
  • hash:ip
  • bitmap:port
  • bitmap:ip,mac
  • bitmap:ip

上述的类型主要分为3类,hashbitmaplisthash使用hash的方式存储数据,bitmap使用位图的方式存储数据,list使用list的方式存储ipset中的set名称,数据类型的完整说明可以参考manpage

操作

  • 创建一个set

    1
    2
    3
    4
    5
    6
    ipset create SETNAME TYPENAME [OPTIONS]

    SETNAME set名称
    TYPENAME 前一个小节中的数据类型
    OPTIONS 参数
    comment 是否使用备注
  • 增加set中的数据

    1
    2
    3
    4
    ipset add SETNAME ENTRY [OPTIONS]

    OPTIONS
    comment 如果在创建的时候指定了comment参数,则增加数据的时候可以指定备注信息
  • 删除set中的数据

    1
    ipset del SETNAME ENTRY [OPTIONS]
  • 清空一个或所有set

    1
    ipset flush [SETNAME]
  • 删除一个或所有set

    1
    ipset destroy [SETNAME]
  • 查询一个或所有set中的数据

    1
    ipset list [SETNAME]
  • 测试一个set中的数据

    1
    ipset test SETNAME ENTRY
  • 导出一个或所有set数据

    1
    2
    3
    ipset save [SETNAME]

    默认存储到标准输出,可以使用-f指定输出文件
  • 恢复数据

    1
    2
    3
    4
    ipset restore

    默认从标准输入读取,可以使用-f指定输入文件
    可以使用-!忽略创建已存在set、新增已存在数据、删除不存在数据时的错误

配合iptables使用

1
2
3
4
5
iptables -I INPUT -s 192.168.100.36 -m set --match-set set_name dst -j DROP

--match-set set_name src|dst
src|dst 用于指定使用来源地址还是目标地址进行匹配
如果需要排除set_name中的地址,则可以使用! --match-set的方式

reference