DN42全攻略-part4-DNS!

接下来我们要接触到网络的另外一个基础设施了,没错,就是DNS!(我是因为想起要记录一下这个才发现自己咕咕咕了这么久)

结果展示

我重构了我的DNS系统,现在选的是TechnitiumDNS服务器,支持网页操作,新版同时支持集群部署,自动同步各实例,对于打算上Anycast的我非常友好,实际部署下来也确实比较舒爽。

先来设计一下整体的架构:

我在3个节点上各部署一个实例,然后3个实例之间组成集群,在每个节点上各绑定多了一个veth作为loopback接口,然后在每个节点上写静态路由。这样,无论DNS请求在哪个节点上收到,就会直接在同一个节点上回复,显著地降低了延迟。

没错,就是anycast!

最后的结果如下:

哎~到处都是很低的延迟,GoodJob。然后根据延迟,猜测到了3个节点的位置。(不过我也只有3个啦)

部署过程

部署单个节点

# 创建bridge,准备给容器接入
/interface/bridge add name=containers.dn42
/ip/vrf set dn42 interfaces=<之前有的接口>,containers.dn42
/ip/address add address=172.23.183.153/29 interface=containers.dn42
/ipv6 address add address=fd48:fa31:502b:8200::/64 advertise=no eui-64=no interface=containers.dn42
# 创建容器接口,一个互联接口,一个loopback接口
/interface veth add address=fd48:fa31:502b:8200:172:23:183:158/64,172.23.183.158/29  gateway=172.23.183.153 gateway6=fd48:fa31:502b:8200:: name=container.dn42.ns1
/interface veth add address=172.23.183.136/32,fd48:fa31:502b:100:172:23:183:136/128 name=containers.dn42.anycast.dns
# 创建容器
/container envs add key=DNS_SERVER_DOMAIN list=ns value=ns1.ferrets.dn42
/container mounts add dst=/etc/dns list=ns src=/containers/technitium/config
/container add envlists=ns interface=container.dn42.ns1,containers.dn42.anycast.dns mountlists=ns name=ns1.ferrets.dn42 remote-image=technitium/dns-server restart-policy=on-failure root-dir=/technitiumdns start-on-boot=yes
# 手动写静态路由
/ip route add check-gateway=arp comment=ns.ferrets.space-Anycast dst-address=172.23.183.136/32 gateway=fd48:fa31:502b:8200:172:23:183:158@dn42 vrf-interface=dn42
/ipv6 route add check-gateway=arp comment=ns.ferrets.space-Anycast dst-address=fd48:fa31:502b:100:172:23:183:136 gateway= fd48:fa31:502b:8200:172:23:183:158@dn42 vrf-interface=dn42

如此一来,一个节点就整好了,在其他节点重复多2次,3个节点就部署好了。

部署集群

集群化之后,就可以一次修改,全局生效了。去Administration-Cluster点击右上角的Initialize,下拉。第一个节点是New cluster,后面的节点是Join cluster。

创建集群这个没啥好说的,重点是加入集群:

Quick Add可以下拉,会列出节点可用的ip地址
Primary Node URL可以直接从主节点的状态页面上复制
Primary Node IP Address需要手动填,直接填创建集群的节点的ip地址就行
Certificate Validation这个配置起来有点麻烦,我们先忽略证书告警,反正几个节点之间没有经过其他人的网络,问题不大。
Primary Node Username 主节点的管理员账号
Primary Node Password 主节点的管理员密码

创建集群之后,用户账号会同步,使用相同的账号和密码,与此同时,集群中随便一个节点都能管理整个集群。

创建集群之后,会多一个叫做cluster-catalog的zone。其他zone会经过这个特殊的zone来选择是否同步。

反向DNS zone

反向DNS zone这个比较特殊,因为我们拿到的都不是整个的/24的地址块,所以,以我手里的地址快为例:172.23.183.128/26,我们拿不到183.23.172.in-addr.arpa的授权,这个域的授权在dn42的delegation-server上。查询delegation-server的时候,将会返回一个CNAME记录。会通过CNAME指向128/26.183.23.172.in-addr.arpa

例如:查询172.23.183.128的PTR,delegation-server将会返回CNAME:128.128/26.183.23.172.in-addr.arpa。然后,递归查询服务器再查找128/26.183.23.172.in-addr.arpa的授权服务器,并向其查询128.128/26.183.23.172.in-addr.arpa

DN42的相关文档都会教你创建一个128/26.183.23.172.in-addr.arpa的zone,但是我在用Technitium的时候遇到了一个问题:Technitium严格遵守规范,认为“/”是非法字符。于是我们只能通过一个间接的方法来处理:

  1. 创建一个128-26.183.23.172.in-addr.arpa的zone
    在APPS里面,安装一个叫做“Zone Alias”的插件
    配置Zone Alias,通过映射,让服务器返回128/26.183.23.172.in-addr.arpa128-26.183.23.172.in-addr.arpa一样的结果

如此一来,虽然我们配置的是128-26.183.23.172.in-addr.arpa,但是服务器同样能够相应128/26.183.23.172.in-addr.arpa的查询请求。

递归服务器

我们已经部署好授权服务器了,下一步是部署递归服务器,不然我们自己没法解析.dn42的域名,这就很不好了。

同样以TechnitumDNS为例。这次我们就不做集群了,单点就行,反正就我们自己用,炸了就先修嘛。

容器部署过程参考上面,我们从安装后的配置开始,一共需要配置7个zone:

dn42
d.f.ip6.arpa
20.172.in-addr.arpa
21.172.in-addr.arpa
22.172.in-addr.arpa
23.172.in-addr.arpa
delegation-servers.dn42

其中dn42和其他几个反向zone的类型都是stub,意味着如果有这种类型的查询,就跳过从根域名开始的查询,直接从这个域开始递归查询。
delegation-servers.dn42的类型是secondary,可以直接从各delegation-servers直接进行zone transfer。

为了保证能从delegation-servers拿到结果,避免鸡生蛋蛋生鸡的问题,需要写上Primary Name Server Addresses:

172.20.129.1
172.20.1.254
172.20.14.34
172.22.108.54
172.23.91.1
fd42:4242:2601:ac53::1
fd42:5d71:219:0:216:3eff:fe1e:22d6
fdcf:8538:9ad5:1111::2
fd86:bad:11b7:53::1
fd42:4242:2189::1


点击添加之后,可以直接从他们那边同步数据,然后得到结果。

接下来,你就可以用这个自建的递归服务器查询DN42网络上的域名啦~

另外,需要额外注意一下settings里面的ipv6 support,如果禁用了ipv6的话,会导致相当一部分域名无法解析。

DN42全攻略-part3-建立第一个peer

书接上回,在你得到了批准,拿到AS号和ip地址资源之后,就可以找其他人玩了。先去wiki那里找支持自动部署peer服务的玩家。这里先推荐技术实力最强的kioubit和iEdon。

Kioubit

https://map.iedon.net/上也可以看到,几乎大半个网络都有和kioubit有直接的peer,可以说是当之无愧的No.1,而且他提供了oAuth服务,挺多其他提供自动peer服务的玩家,都支持直接通过kioubit的oAuth来登录了。

使用自动peer系统创建配置

我们来访问https://dn42.g-load.eu/

直接点automatic peering,然后输入AS号,然后认证。可以通过邮箱、或者Part1里面的公钥进行签名认证。首次登陆成功之后还会给你一个密码,之后可以用密码来登录。

然后你就来到他们的门户,会列出所有已建立的peer及其状态:

这里着重说明一下绝大多数情况下,或者说当前网络里面的主流的互联方式是什么:

VPN:使用wireguard
BGP:使用ipv6的link-local地址建立BGP会话,并启用multiprotocol-bgp和extend-nexthop

以kioubit的表单为例,最低要求是只要填上你的wireguard public key,然后设定一下你打算在接口上配置的link-local地址,然后打开两个BGP扩展,就足够完成设定了。

配置自己这边的系统

我用的是Mikrotik的RouterOS,我就用脚本来介绍一下我的设置过程吧:

# 创建一个bridge作为loopback口
/interface/bridge/create name=lo.dn42
# 创建一个新的interface list,记录后续与外部互联的接口,方便写防火墙策略
/interface/list/add name=dn42-peerings
# 创建一个新的vrf,然后将刚才的bridge加到vrf里面
/interface/vrf add name=dn42 interfaces=lo.dn42,dn42-peerings
# 创建一个wireguard接口作为模板
/interface/wireguard add name=dn42.template listen-port=12345
# 创建路由过滤器,后续根据实际情况调整
/routing/filter/rule add chain=block disabled=no rule="reject;"
/routing/filter/rule add chain=accept disabled=no rule="accept;"
# 创建bgp实例
/routing bgp instance add as=4242423077 disabled=no name=dn42 router-id=172.20.183.132
# 创建bgp链接模板,路由过滤策略先是拦截全部,我们下一步再慢慢来调整该放开哪些
/routing bgp template add as=4242423077 disabled=no input.filter=block keepalive-time=10s name=dn42 nexthop-choice=force-self output.filter-chain=block .no-client-to-client-reflection=no .redistribute=connected,static routing-table=dn42

如此一来,基础就准备好了,剩下来的就是为每个链接建立增加对应的peer设置,这里以下图的信息为例,假设wireguard的模板接口对应的公钥就是图中的那个。

# 创建wireguard隧道接口
/interface/wireguard add copy-from=dn42.template listen-port=23914 name=dn42.kioubit
# 将隧道接口加入到dn42-peerings接口列表里面,因为vrf绑定了接口列表,所以这个接口会自动进入dn42的vrf
/interface/list/member add interface=dn42.kioubit list=dn42-peerings
# 创建wireguard的peer
/interface/wireguard/peers add allowed-address=172.20.0.0/14,fd00::/8,fe80::/64 endpoint-address=de2.g-load.eu endpoint-port=20114 interface=dn42.kioubit name=kioubit public-key=\
    "B1xSG/XTJRLd+GrWDsB06BqnIq8Xud93YVh/LYYYtUY="
# 手动设置接口的ipv6地址
/ipv6/address add address=fe80::3077/64 advertise=no auto-link-local=no disabled=yes eui-64=no from-pool="" interface=dn42.kioubit no-dad=no
# 创建bgp会话,引用dn42的模板
/routing/bgp/connection add afi=ip,ipv6 connect=yes instance=dn42 keepalive-time=10s local.address=fe80::3077%dn42.kioubit .role=ebgp multihop=no name=dn42.kioubit.v6 remote.address=fe80::ade0%dn42.kioubit .as=4242423914 templates=dn42

完了之后,通过/routing/bgp/session print可以看到状态。同时,可以去/routing/route print看收到的路由表了。

这时候,你就可以试试用本地的ip地址去ping出去测试网络通不通了。也可以使用kioubit的工具箱来测试一下外面能不能ping进来。

后续调整

调整路由过滤器

bgp会话能够成功建立,能收到对端的路由表之后,就可以开始调整路由过滤器了。我们需要创建2个过滤列表,分别处理入方向和出方向的路由

# 接收的路由过滤器,默认接受所有
/routing/filter/rule add chain=dn42-kioubit-in rule="accept;"
# 发送的路由过滤器,默认只通告自己的路由
/routing/filter/rule add chain=dn42-kioubit-out rule="if ( dst == 172.23.183.128/26 || dst == dst in fd48:fa31:502b::/48 ) { accept; } else { reject; }"
# 更新bgp connection的过滤器设置
/routing/bgp/connection set name=dn42.kioubit.v6 input.filter=dn42-kioubit-in output.filter-chain=dn42-kioubit-out

调整之后,你再去/routing/route print就能看到部分路由前面的F标记消失了,意味着这条路由没有被过滤,能参与到后续的算路中了。

与此同时,可以用/routing/bgp/advertisements print 来查看自己向外发布的路由。

ROA校验和flapalertd

首先,我们需要做一些准备,因为这部分需要容器配合。

# 给系统打开容器功能
/system/device-mode set container=yes
# 手动断电重启(在vps的控制面板上重启)
# 安装container的包,重启之后,就能用了
/system/package/update check-for-updates
/system/package enable container
/system/package apply-changes
# 创建一个bridge,然后将容器接到这里来
/interface/bridge add name=containers
/ip/address add interface=containers address=192.168.22.1/24
/interface/veth add name=container.dn42-roa address=192.168.22.2/24 gateway=192.168.22.1
/interface/veth add name=container.dn42-flapping address=192.168.22.3/24 gateway=192.168.22.1
/interface/bridge/port add bridge=containers interface=container.dn42-roa
/interface/bridge/port add bridge=containers interface=container.dn42-flapping
# 打通容器到互联网的ipv4 nat
/ip/firewall/nat add action=masquerade chain=srcnat out-interface=ether1
# 创建2个容器,分别用来处理ROA记录和flapalertd的记录,我这里用的是burble的ROA,和strexp家的flapalertd数据
/container add cmd=" -cache https://dn42.burble.com/roa/dn42_roa_46.json" interface=container.dn42-roa name=dn42-roa remote-image=rpki/stayrtr:latest restart-policy=on-failure root-dir=/containers_root/dn42-roa start-on-boot=yes
/container add cmd="--cache,https://flap42-data.strexp.net/roa.json\?rate=0&vote=7" interface=dn42-flapping name=dn42-flapping remote-image=rpki/stayrtr:latest restart-policy=on-failure root-dir=/containers_root/dn42-flapping start-on-boot=yes
# 检查两个容器的日志,确认能够正常拉到数据
/container/log print where container=dn42-roa
/container/log print where container=dn42-flapping
# 配置rpki
/routing rpki add address=192.168.20.2 group=dn42-roa port=8282
/routing rpki add address=192.168.20.3 group=dn42-flapping port=8282
# 检查一下rpki能否正常连接
/routing/rpki/session print
# 如果两个rpki状态都是sync,那么可以开始调整入方向的路由过滤器
/routing/filter/rule set chain=dn42-kioubit-in rule="rpki-verify dn42-flapping;\
    \nif ( rpki invalid ) { set comment flapping; reject; }\
    \nrpki-verify dn42-roa;\
    \nif ( rpki invalid ) { set comment roa_invalid; reject; }\
    \naccept;"
# 调整过之后,收到路由会进行2次rpki检查,首先检查一下有没有在flapalertd的列表里面。如果在,就给路由写个备注“flapping”,然后过滤掉。如果不在flapalertd的列表里面,再进行roa检查,如果roa验证失败,就给路由写个备注“roa_invalid”,然后过滤掉。
# 如果两次rpki检查都不是invalid,那么就接受这条路由,参与后续路由计算。

这样,你就能对收到的路由进行ROA校验了,同时,也能过滤掉那些正在不停抖动的路由。如果后续对外发布路由表,发transit的话,过滤掉抖动路由可以显著地减少对方的CPU资源的消耗。又或者你的网络有多个节点的情况下,可以明显的减少对内部网络的资源消耗。

DN42全攻略-part2-提交注册信息

接上文DN42全攻略-part1-注册gitea。(刚发现这篇文我咕咕咕了3年……)

另外,可以阅览gitea上的指引(英文)

需要注意的是,因为包含需要执行脚本的步骤,这篇文章里面的内容最好在Linux环境下实施。

创建fork

在gitea的网页上,点击右上角的“派生”(fork),你就可以创建一份属于你的分支。

然后,在终端里面操作,将整个库下载下来到本地。

git clone git@git.dn42.dev:<gitea用户名>/registry.git

克隆到本地之后,就可以进入registry文件夹进行修改文件了。

注册身份

可以阅读英文原文,我在这里简要的用中文进行简单介绍。必要的项目一共有3个

维护人

维护人项目位于data/mntner/,名称格式是<名称>-MNT,参考内容如下:

mntner:             FOO-MNT
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
auth:               pgp-fingerprint 0123456789ABCDEF0123456789ABCDEF01234567
source:             DN42

这里需要特别注意的是auth上一篇文章里面提到过需要创建一个pgp密钥对(什么?已经是3年前了?!咕咕咕),这是用来对以后进行任何变化操作,或者是向其他AS的管理员证明你是你用的。(比如说各种自动peer系统)

FOO-DN42是指某个联系人项目,下一步我们就要创建这个项目。

这个文件有更多的参数,具体可以看看其他人的文档,或者上网搜搜,这东西写法是完全照搬互联网上的。

联系人

联系人项目位于data/person/,名称格式是<名称>-DN42,参考内容如下:

person:             John Doe
e-mail:             john.doe@example.com
nic-hdl:            FOO-DN42
mnt-by:             FOO-MNT
source:             DN42

nic-hdl的内容就是文件名,根据社区要求,必须以-DN42结尾。可以加其他的联系方式,用contact:字段标注就行。也可以独立地加签名的公钥。

组织(可选项)

如果你打算以个人身份玩的话,就不需要创建这个项目。项目位于data/organisation/,名称的格式是ORG-<名称>。参考内容如下:

organisation:       ORG-FOO
org-name:           Foo Organisation
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
source:             DN42

大概看懂了吧?里面的字段可以索引到另外一个项目。一切就连起来了。

申请资源

你可以向社区申请进入网络的资源,包括AS号、ipv4地址块、ipv6地址块、域名等,一共4类。申请规则先阅读资源申请政策,需要特别注意的只有一条,在首次申请资源的时候,只能申请1个ipv4地址块(一般是/27,最多/26)和1个ipv6地址块(只能是/48)。

burble提供了一个简单的服务,可以列出当前空闲的资源,很好使。

AS号

AS号项目位于data/aut-num/,名称格式是AS<AS号>,参考内容如下:

aut-num:            AS4242423999
as-name:            AS-FOO-DN42
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
source:             DN42

aut-num的内容和项目名称一样,as-name可以随意写,不一定要以-DN42结尾。想要加其他内容的时候,可以看看其他人写的酷炫内容。

ipv4地址块

ipv4地址的申请被标注为legacy,不过从目前来看还是没法完全丢掉的,所以还是强烈建议申请一个。在刚才提到的简单的服务里面挑一个/27,然后创建指定对应的项目:

ipv4地址块项目位于data/inetnum/,名称格式是<子网地址>_<掩码长度>(就是cidr里面的斜杠换成下划线),参考内容如下:

inetnum:            172.20.150.0 - 172.20.150.31
cidr:               172.20.150.0/27
netname:            FOO-NETWORK
descr:              Network of FOO
country:            XD
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
status:             ASSIGNED
source:             DN42

ipv4路由目标

在注册ipv4地址块的时候,我们需要同步创建路由目标。这个东西是用来生成ROA记录的,ROA记录可以让其他人通过rpki快速校验一条路由是不是由正确的AS发布。现在各大AS通常会拒绝rpki校验无效的路由,接受有效或者未知的路由,不过也有的只接受rpki校验有效,拒绝校验无效或者未知的路由(比如我)。

ipv4的路由目标项目位于data/route/,名称格式参考ipv4地址块,参考内容如下:

route:              172.20.150.0/27
origin:             AS4242423999
max-length:         27
mnt-by:             FOO-MNT
source:             DN42

注意这里的示例申请了一个172.20.150.0/27的地址块,然后创建了172.20.150.0/27的路由目标,路由目标限定了只通告172.20.150.0/27的路由,如果是更长的长度,可以修改max-length。不过现在才刚开始,先按部就班地写和地址快一样的长度就好。

ipv6地址块

接下来就是ipv6地址快了,在刚才提到的简单的服务里面挑一个/48,然后创建指定对应的项目:

ipv4地址块项目位于data/inet6num/,名称格式是<子网地址>_<掩码长度>(就是cidr里面的斜杠换成下划线),参考内容如下:

inet6num:           fd35:4992:6a6d:0000:0000:0000:0000:0000 - fd35:4992:6a6d:ffff:ffff:ffff:ffff:ffff
cidr:               fd35:4992:6a6d::/48
netname:            FOO-NETWORK
descr:              Network of FOO
country:            XD
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
status:             ASSIGNED
source:             DN42

ipv6路由目标

ipv6同样需要路由目标,项目位于data/route6/,名称格式参考ipv6地址块,参考内容如下:

route6:             fd35:4992:6a6d::/48
origin:             AS4242423999
max-length:         48
mnt-by:             FOO-MNT
source:             DN42

注意这里的示例申请了一个fd35:4992:6a6d::/48的地址块,然后创建了fd35:4992:6a6d::/48的路由目标,路由目标限定了只通告fd35:4992:6a6d::/48的路由。

域名

下一步是域名,这也是一个网络的基础设施之一,因为大家都不想记住一串没什么特点的字符串,特别是ipv6的长度已经来到32个字符,所以大家都想要个好记的域名。

域名项目位于data/dns/,必须以dn42结尾,名称需要符合域名规范,参考内容如下:

domain:             foo.dn42
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
nserver:            ns1.foo.dn42 172.20.150.1
nserver:            ns1.foo.dn42 fd35:4992:6a6d:53::1
nserver:            ns2.foo.dn42 172.20.150.2
nserver:            ns2.foo.dn42 fd35:4992:6a6d:53::2
source:             DN42

然后,你就留意到了,里面有一个参数是nserver,意味着你需要搭建一个dns授权服务器来处理其他dns递归服务器的查询请求,参考里面写了2个授权服务器,这通常是为了冗余设计。晚点我们就来做这个,现在先把茅坑占着。

RDNS

或许你知道,或许你没接触过,有了DNS服务器之后,我们就可以做反向地址解析(PTR),可以将ip地址解析成域名。于是……我们就来修改一下之前的inetnuminet6num项目。

inetnum:            172.20.150.0 - 172.20.150.31
cidr:               172.20.150.0/27
netname:            FOO-NETWORK
descr:              Network of FOO
country:            XD
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
nserver:            ns1.foo.dn42 172.20.150.1
nserver:            ns1.foo.dn42 fd35:4992:6a6d:53::1
nserver:            ns2.foo.dn42 172.20.150.2
nserver:            ns2.foo.dn42 fd35:4992:6a6d:53::2
status:             ASSIGNED
source:             DN42
inet6num:           fd35:4992:6a6d:0000:0000:0000:0000:0000 - fd35:4992:6a6d:ffff:ffff:ffff:ffff:ffff
cidr:               fd35:4992:6a6d::/48
netname:            FOO-NETWORK
descr:              Network of FOO
country:            XD
admin-c:            FOO-DN42
tech-c:             FOO-DN42
mnt-by:             FOO-MNT
nserver:            ns1.foo.dn42 172.20.150.1
nserver:            ns1.foo.dn42 fd35:4992:6a6d:53::1
nserver:            ns2.foo.dn42 172.20.150.2
nserver:            ns2.foo.dn42 fd35:4992:6a6d:53::2
status:             ASSIGNED
source:             DN42

加入了nserver的记录。

提交

在执行提交前,执行一下仓库里面的脚本:

  • fmt-my-stuff <FOO>-MNT: 检查和修复一些格式错误
  • check-my-stuff <FOO>-MNT: 检查你的内容是否符合表单数据
  • check-pol origin/master <FOO>-MNT: 检查是否违反了规范
  • squash-my-commits: 将之前的所有commit合并成一份来提交,让仓库历史变得整洁
  • sign-my-commit: 对你的commit进行签名(pgp或者ssh密钥)

全部都跑完了之后,执行git commitgit push。之后你就能在网页上操作向registry提交pr请求了。如果有些特殊情况,burble会询问你一些问题,照常回答就行,不要慌,只要你有正当的、合适的、充分的理由,一般都会批准的。

下一步

在等待审批的时候,我们先准备好加入DN42网络的节点,浏览一下wiki,准备找其他人进行peer。

MacOS 磁盘清理

最近发现手里的MacbookAir的磁盘空间告警了,于是打算稍微清理一下,然后就发现一个很奇怪的情况。之前听说过Mac的软件卸载非常轻松简单,只要将软件往垃圾桶一丢,这就卸载完成了。我用上mac之后,也是这样操作的,然而。

这是个大坑啊,根本不是这样的,一个软件在安装之后,会将文件存得到处都是,简单的删掉Application里面的app文件夹根本无法清理。经过一番搜索(命令行中使用df来统计+互联网搜索)。我找到几个目录需要额外注意的:

/Users/<用户名>/
/Users/<用户名>/.cache
/Users/<用户名>/Library/Application Support
/Users/<用户名>/Library/Caches
/Users/<用户名>/Library/Containers
/Users/<用户名>/Library/Logs

网上很多清理工具都要收费,要不然就疯狂要求权限,吓死个人,还是内置的命令行比较实在……

这里面特别注意的是/Users/<用户名>/.cache,之前玩lm-studio的时候,在这里面残留了20+G的玩意,没仔细看是不是模型的缓存就直接删了,因为最近不打算玩这个。

记录一次奇怪的Linux无转发报文的故障

首先说明一下背景,在阿里云上开了台机器,替换掉这篇文章中需要绕行香港节点,以提高访问速度,降低延迟(虽然带宽大砍,哭)。但是遇到了奇怪的系统不转发报文的奇怪问题。

节点运行的是Debian 13,安装了podman(<–就是这玩意出的问题)。

检查0,抓包

毫无疑问,tcpdump -n -i any host <ip>可以看到包是否转发,最方便快捷的确认方式。

这次就是看到报文接收了但是没发出去,肯定是哪里的配置有问题了。

检查1,路由表

可以用以下的命令来测试包的转发路径是否正确:

ip route get 10.7.0.10 from 10.8.0.5 iif if1

上面的命令是查询从if1进来的,源为10.8.0.5,目的为10.7.0.10的报文,会匹配什么路由,从什么接口发出去,就像这样:

ip route get 10.7.0.10 from 10.8.0.5 iif if1
10.7.0.10 from 10.8.0.5 via 10.4.10.10 dev if2
cache iif if1

检查2,路由规则

这里不多叙述,一般没用,至于都懂得自己配路由规则了,就应该懂得怎么查了。

检查3,防火墙

看iptables的FORWARD链,默认ACCEPT,没拦截。这里有个大坑,具体后面再说。

顺便检查conntrack,因为是不对称的路由,所以conntrack没列出来,看起来也是正常现象。

检查4,内核转发

检查内核的ipv4 forward,是1。没啥问题。如果不是1,修改/etc/sysctl.conf,然后用sysctl -p应用。

检查5,rp_filter

rp_filter还是挺重要的安全设置,可以检查包是否从正确的源地址过来。但是因为这个节点被配置成路由器,有不对称路由,所以需要关闭。可以用以下命令查看接口的rp_filter设置:0是禁用,1是宽松,2是严格。

sysctl -a | grep \\.rp_filter

如果有需要,可以在sysctl.conf添加配置,禁用掉某个端口的rp_filter。

检查6,防火墙2

最后检查nftables,检查出问题了。

table inet netavark {
.....
    chain FORWARD {
            type filter hook forward priority filter; policy accept;
            ct state invalid drop
            jump NETAVARK-ISOLATION-1
            .....
    }
.....

如果conntrack的状态是invalid的话,就丢掉报文,结合之前conntrack没有追踪到会话,那问题估计就在这里了。

nftables拦截但是iptables放通的话,包依然是被拦截,但是iptables又看不到nftables的拦截策略……要不是AI助手提醒我都没想到这个。

根据AI助手的介绍,netavark是podman用来隔离流量的安全设计,和处理流量转发。如果用的是docker的话,应该用的是iptables而不是nftables就没这坑了吧(望天)。

最终关键处理

首先,用以下命令检查FORWARD链

nft -a list chain inet netavark FORWARD

会返回以下样子的结果:

table inet netavark {
chain FORWARD { # handle 2
type filter hook forward priority filter; policy accept;
ct state invalid drop # handle 15
jump NETAVARK-ISOLATION-1 # handle 16
ip daddr 172.22.222.0/24 ct state established,related accept # handle 25
ip saddr 172.22.222.0/24 accept # handle 26
ip daddr 10.89.0.0/24 ct state established,related accept # handle 82
ip saddr 10.89.0.0/24 accept # handle 83
}
}

然后,可以用以下命令去在丢弃报文的规则前插入放通的规则:

nft insert rule inet netavark FORWARD index 0 iifname "if1" oifname "if2" accept

上面的命令是放通了从接口if1到接口if2的流量转发。如果图省事,可以去掉源或者目的。插入完之后再检查的话,就会变成:

table inet netavark {
chain FORWARD { # handle 2
type filter hook forward priority filter; policy accept;
iifname "if1" accept # handle 96
iifname "if2" accept # handle 95
iifname "if3" accept # handle 94
ct state invalid drop # handle 15
jump NETAVARK-ISOLATION-1 # handle 16
ip daddr 172.22.222.0/24 ct state established,related accept # handle 25
ip saddr 172.22.222.0/24 accept # handle 26
ip daddr 10.89.0.0/24 ct state established,related accept # handle 82
ip saddr 10.89.0.0/24 accept # handle 83
}
}

这时候,转发就正常了。

希望这次的记录能够帮忙其他可能会遇到同样问题的人。

关于广州移动于广州电信之间的丢包分析

我现在手上有一条广州移动的宽带与广州电信的宽带,两边做了site-to-site VPN,但是发现互访比较慢,ping了以下有丢包的情况,于是顺手建了一个ping探测的监控。

然后……然后就发现问题了。

这是其中一天的ping丢包率,而且经过观察,基本上每天的情况都差不多,只有深夜到凌晨一小段时间是完全没丢包的。

于是我准备调整OSPF配置(对,我上了OSPF!),使流量绕行VPS,看看会不会顺畅一点。但是在修改的过程中,我突发奇想,只修改了移动接入一侧的bird配置中接口的OSPF的cost值,探究一下当两边配置的cost不一样的时候,会有什么效果,我又该如何使用这些特性。

然后,然后就发现了奇怪的情况了。两边监控点出现了结果不一样的ping丢包率,经过仔细探查,发现两边报文的走向是不一样的,结果如下:

监控点1的ping探测丢包率还是之前的样子,但是监控点2的ping探测丢包率与延迟都变了。延迟从稳定在20ms增长到稳定在100ms,丢包率就完全变样了:

偶尔有100%的丢包,但是没有持续的高达30%~50%的丢包。这是个很重要的线索。

对比两边的路由,我们可以很轻松的得出结论:丢包是发生在广州移动向广州电信发送的时候

原因的话大概就几个:
1.移动的网管技术比电信的糟糕,不能充分利用两个AS之间互联线路的带宽。
2.很多电信用户从移动那边下载东西,考虑到有CDN和DNS的负载均衡。这个应该不太可能。
3.很多移动用户跑PCDN,电信用户被调度过去从移动PCDN玩家那里获取资源。这个可能性比较大。

做了一个用来生成中国大陆路由的容器

背景

因为在研究BGP,也有中国大陆与其他地区进行路由选路的需求,所以在琢磨着要不在互联网上整一个bgp speaker,然后这个bgp speaker向所有连过来的对端通告全部的中国大陆路由,这样路由器就不需要配置大量的静态路由,减少配置量,而且更新的话也能够应用到所有出口路由器。

实时全球路由表?

某天,根据这篇文章试了一下,确实可行,但是吧……这效果强虽强,但是资源消耗也有点离谱。

这个容器实时从RIPE读取全球路由表的变更,一整天下来平均带宽有1.78Mbit/s,相当于一天就跑了18G的流量(单向)。要是放在vps上的话,一个月估计的流量500G。

我是放在家里出口的RouterOS上以容器的方式跑的,算上容器本身和加的路由表,需要吃掉大约400M的内存。我就想想,有没有一些更加轻量级的方案呢?

记得之前是见到过一个chnroute的东西,可以生成中国的路由表。然后灵机一动,要不我也用脚本写个差不多的玩意吧,看起来也不是很难。bgp软件话似乎可以用bird,可以直接重载,也不会闪断。

脑袋一拍,开干,结果就弄了个这样的玩意出来。

设计思路

github上的README也已经写了设计思路的,非常简单,以bird软件作为核心然后主要的配置有3个:

一个是手动写的需要配置的静态路由,比如说苹果的7.0.0.0/8,实测如果走VPN的话会遇到各种各样奇奇怪怪的问题,可能和我用的是自建的DNS递归服务器,然后我又用国区账号,部分苹果业务由云上贵州来负责的原因。反正就是会有这样的需求,先预留着。

一个是通过脚本生成中国的路由表,从网上搜一搜就找到类似的了,核心是从APNIC下载分发的资源表,过滤出属于中国的部分,然后通过文本处理格式化成bird能够读取的配置文件。然后定期重新加载就行了。

最后一个是协议的配置,我现在选择的是用bgp来输出,有其他需要的话可以考虑ospf或者isis之类的,不过bgp作为各ISP之间互联用的协议,又是我的学习目标,果断选择BGP来输出了。这块的配置我放在了custom.conf,有需要可以灵活修改。

最后,为了方便部署,选择了容器化,用docker打个包,就可以到处用了。现在是托管在dockerhub,未来可能放在其他地方。

如何使用

这里强烈推荐一下claw cloud的白嫖,只要有一个注册时间超过180天的github账号就可以使用他们的免费等级的服务,可以有5刀的credit,不绑信用卡也可以用。他们目前有新加坡,日本,美西岸,美东岸,德国几个区域。每个区域最高能放4个vcpu,8G内存,和10G的磁盘。(当然,如果拉满的话,价格是绝对会超5刀每月的),限制了每个月10G的流量。也限制了每个机器只能有1个端口(tcp或者udp)。

限制是挺多的,特别是流量,不过对于我这个拍脑袋整出来的东西,已经可以算得上是奢华了。从APNIC下载delegation的文件大概3.8M,每天一次,然后算上bgp的连接,一天估计跑不了10M。

于是,噔噔蹬蹬。

膨胀了啊,在k8s上直接replica 2!然而内存使用量还是太少了,都识别不出来。

下面贴一下过程:

先去注册,然后切换到需要部署的区域,然后点击App Launchpad。

在右上角点击create App进入部署的界面。

Application Name随便填,image选择public,然后填入ferrets/cn_route-bird。没错我已经预编译了一份并且上传到了dockerhub。

Usage里面CPU和RAM都拉到最低(为啥RAM不能选16M?)。Replica建议大于1,反正价格你也看到了,每天0.02刀,30天就是……0.6刀(手动狗头)。

Network里面Container Port写179,这是bgp的默认端口。打开互联网访问,完了之后会分配一个。

Advanced Configuration里面有一个需要填的,就是Configmaps。点击Add。

File Name写/etc/bird.d/custom.conf

File content就复制粘贴github上的内容就行,填完了之后confirm。

最后右上角Deploy Application就行。过一阵子,你就有了能够直接用bgp订阅的,能够自动更新的中国大陆路由表了。

考虑到高可用,和剩余的credit……找另外一个区域重复上述过程,避免一个区炸了之后就直接丢了路由。

配置订阅

下一步就是用出口路由器去连了这个bird。首先去App Launchpad,找到分配了啥网址和端口

然后,手动解析一下这个域名。(或者你的路由器可以直接填域名而不是ip地址来建立bgp会话,那就填域名)

从里面挑几个出来建立bgp会话,参考的mikrotik的配置脚本如下:

记得先写到中国大陆的静态路由。根据配置,bgp吐出来的ipv4的路由下一跳是114.114.114.114,ipv6的路由下一跳是240c::6666。必须先写静态路由以保证递归之后,从bgp会话收到的路由是从你想要的出口出口,避免路由震荡。或者你可以先在connection里面写上input.filter,先把路由全部过滤。搞好之后再取消过滤,应用到实际环境里面。

/ip route
add dst=114.114.114.114 gateway=wan
/ipv6 route
add dst=240c::6666 gateway=wan
/routing bgp template
add address-families=ip,ipv6 as=65000 cluster-id=192.168.0.0 disabled=no multihop=yes name=feed output.filter-chain=block .no-client-to-client-reflection=yes routing-table=main
/routing bgp connection
add template=feed connect=yes local.role=ibgp name=cn_route_feed-1 remote.address=[刚才解析出来的ip1] .as=65000 .port=[分配的端口]
add template=feed connect=yes local.role=ibgp name=cn_route_feed-1 remote.address=[刚才解析出来的ip2] .as=65000 .port=[分配的端口]
add template=feed connect=yes local.role=ibgp name=cn_route_feed-1 remote.address=[刚才解析出来的ip3] .as=65000 .port=[分配的端口]
add template=feed connect=yes local.role=ibgp name=cn_route_feed-1 remote.address=[刚才解析出来的ip4] .as=65000 .port=[分配的端口]

然后……为啥RouterOS不能去重路由……明明我都在bird上配置了cluster id。囧

如此一来,就有了相对稳定的,免费的,可以自动更新的,资源消耗相对低的中国路由表订阅了。

虽然订阅了8份之后,RouterOS里面的路由表数量高达85k+,都比实时全球路由表的数量(45k+)高一倍了。但……但这有高贵的ipv6啊!

MacOS上iscsi的替代方案

有时候,MacOS想要扩容,而且不是单纯的存放文件,而是要存放应用软件之类的,这时候,samba就不好使了,文件读写权限什么的,非常难搞。所以就需要考虑一下从基于文件的网络共享切换到基于块的网络共享了。

但是众所周知,MacOS不像windows那样,内置iscsi-initiator,而支持iscsi-initiator的软件,我在网上搜索一番之后,并没有找到什么开源(可以白嫖)的方案。唯一有的一两个看起来也是安装使用非常麻烦的那种,其他的商用方案,价格都比较离谱。唯一价格比较亲民点的DAEMON tools(没错!就是很久之前装机必备的虚拟光驱!),价格不贵,如果单纯只要iscsi-initiator一个功能的话,最低可以只买1个高级版,价格只要2.99刀,价格完全可以接受。不过网上有评论说DAEMON-tools的iscsi功能性能存在问题,千兆以太网只能跑到50MB/s左右,这就有点难受了。

在继续溜达的时候,偶尔看到一篇帖子,里面提到一种解决方案,就是不考虑iscsi,而是使用一种MacOS特有的磁盘格式——稀疏捆绑磁盘映像。

这种稀疏捆绑磁盘映像,就是Time Machine目前在使用的磁盘格式,可以存放在任意MacOS支持的文件系统上,这就包括了cifs(也就是samba啦)。所以,四舍五入一下,也算是另类的块共享了。

使用方法很简单,先从“启动台-其他”文件夹里面启动“磁盘工具”,然后新建一个空白映像。

然后,就是设置磁盘映像的具体属性:

  • 格式选择“稀疏捆绑磁盘映像”
  • 存储为这里是磁盘映像在文件系统上的名字,后续默认是.sparsebundle
  • 大小是这个磁盘映像的大小,稀疏捆绑磁盘映像实际上是一个文件夹,里面是一堆小的文件块,是按实际使用增长的,不是分配了就立马占用的。
  • 格式建议选择MacOS扩展(区分大小写,日志式)
  • 是否需要加密就看你自己需要
  • 分区随便选择
  • 最后位置选择在你的smb共享上

点击存储确认。

在访达可以看到这个磁盘映像的属性。

双击可以在磁盘工具打开。可以在访达看到已经挂载,在磁盘工具也能看到这个磁盘映像的属性。

如果需要调整大小,可以先卸载磁盘映像,然后在磁盘工具的菜单“映像-调整大小”,然后选择对应的磁盘映像,再输入变更后的大小,就可以了。

修复TimeMachine备份错误

今天在timemachine备份过程中,因为某些意外中断了,导致备份失败,无法继续备份,只能删掉备份链接然后重新添加。但是在重新添加的过程中,发现怎么添加都添加不上,历经一番辛苦,终于修复,这里记录一下遇到的几种故障情况与修复方法。

备份损坏

可以先尝试在“访达”程序里面找到对应的备份文件夹,是以.sparsebundle结尾的,可以用默认的DiskimageMounter打开,稍等个20秒,再打开“磁盘工具”就可以在磁盘工具中看到这个备份的镜像,然后右键选择这个备份的镜像,选择“急救”,可以进行尝试校验并修复这些备份文件。

如果修复失败……我没有继续研究修复方法,因为我是备份到TrueNAS上的,遇到这样的问题我就直接回滚了快照,回滚到了备份前的状态。(快照救我!)

对于没有快照回滚或者其他原因没法修复的童鞋,那就只能删掉旧备份了。

OSStatus 错误80

这个错误卡了我足足一天时间,翻来复去都没找到什么解决方法。有少量网页提到过,需要在keychain.app里面删掉对应的密码就能修复了,但是我用的是MacOS 15 sequioa,直接在启动台里面搜索是找不到keychain.app的,我以为已经没有这个东西了。

但是我灵机一动,搜索了一下,MacOS 15依旧是有这个app的,只是没有被显示出来。

你需要打开访达,然后从菜单栏里面选择“前往”-“前往文件夹”,或者使用快捷键shift+command+g,然后手动输入路径“/System/Library/CoreServices/Applications/”

然后你就能看到这个钥匙串访问了

打开会提示建议你不要使用钥匙串访问,试试他们新的密码app吧,忽略,就要打开钥匙串访问,输入密码,进去了。

搜索“.spar“,会列出timemachine备份的加密密码,手动删掉。OSstatus 错误 80就没啦~

磁盘镜像被占用

这个最简单,命令行登陆到TrueNAS,进入对应的.sparsebundle文件夹,删掉里面的lock文件就可以了。

于是timemachine备份就恢复正常啦~

在TrueNAS中给虚拟机直通磁盘

参考:https://www.truenas.com/community/threads/using-cli-to-passthrough-hdd-to-a-vm.98549/post-679949

刚给NAS加了几块硬盘,在导数据的时候不知道为啥,重启了好几次之后,旧磁盘的整个池都没法导入了,一导入就整个机器卡死重启,重启之后卡在ix-etc服务,没法进入系统。

不得已,只能在TrueNAS里面开个虚拟机,把磁盘直通给虚拟机,重新导入池然后scrub一次试试捞数据。但是TrueNAS的虚拟机功能,只能说有,和好用是一点边都沾不上。

幸好,在truenas的论坛上有人po出了操作方法,这里做个翻译和记录。

首先,需要使用root登录到Truenas。

然后,使用这个命令来查找所有磁盘:

find /dev/disk/by-id/ -type l|xargs -I{} ls -l {}|grep -v -E '[0-9]$' |sort -k11|cut -d' ' -f9,10,11,12

以下是输出结果的例子

root@datacenter[~]# find /dev/disk/by-id/ -type l|xargs -I{} ls -l {}|grep -v -E '[0-9]$' |sort -k11|cut -d' ' -f9,10,11,12 | sort
/dev/disk/by-id/ata-KINGSTON_SA400M8120G_50026B7685357785 -> ../../sdn
/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2KB41G -> ../../sdb
/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2KG36H -> ../../sdd
/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2KG8BD -> ../../sda
/dev/disk/by-id/ata-ST16000NM001G-2KK103_ZL2KGSRT -> ../../sdc
/dev/disk/by-id/ata-Samsung_SSD_850_EVO_500GB_S21JNSAG158509B -> ../../sdj
/dev/disk/by-id/ata-Samsung_SSD_850_EVO_500GB_S2RBNX0H937583L -> ../../sdk
/dev/disk/by-id/ata-WDC_WD10JFCX-68N6GN0_WD-WX11A15ET12E -> ../../sdm
/dev/disk/by-id/ata-WDC_WD10JFCX-68N6GN0_WD-WX11A15ETRZ9 -> ../../sdl
/dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K1DFDE54 -> ../../sdi
/dev/disk/by-id/ata-WDC_WD40EFRX-68WT0N0_WD-WCC4E0135118 -> ../../sde
/dev/disk/by-id/ata-WDC_WD40EFRX-68WT0N0_WD-WCC4E0185240 -> ../../sdh
/dev/disk/by-id/ata-WDC_WD40EFRX-68WT0N0_WD-WCC4E0243702 -> ../../sdg
/dev/disk/by-id/ata-WDC_WD40EFRX-68WT0N0_WD-WCC4ENSDRE27 -> ../../sdf
/dev/disk/by-id/wwn-0x5000c500dbd815d2 -> ../../sdb
/dev/disk/by-id/wwn-0x5000c500dbe1b5b7 -> ../../sdd
/dev/disk/by-id/wwn-0x5000c500dbe40981 -> ../../sdc
/dev/disk/by-id/wwn-0x5000c500dbe46e89 -> ../../sda
/dev/disk/by-id/wwn-0x50014ee209112b98 -> ../../sde
/dev/disk/by-id/wwn-0x50014ee20b01a0fc -> ../../sdf
/dev/disk/by-id/wwn-0x50014ee210a8b4bc -> ../../sdi
/dev/disk/by-id/wwn-0x50014ee25e74959c -> ../../sdg
/dev/disk/by-id/wwn-0x50014ee2b3bbed86 -> ../../sdh
/dev/disk/by-id/wwn-0x50014ee6b01bee55 -> ../../sdl
/dev/disk/by-id/wwn-0x50014ee6b01bfbaa -> ../../sdm
/dev/disk/by-id/wwn-0x5002538d414b3774 -> ../../sdk
/dev/disk/by-id/wwn-0x5002538da015f254 -> ../../sdj
/dev/disk/by-id/wwn-0x50026b7685357785 -> ../../sdn

其中/dev/disk/by-id/wwn-0x5002538da015f254就是一个磁盘目标(sdj)。

然后,创建一个alias命令,方便virsh连接到TrueNAS的qemu。

alias virsh='virsh -c "qemu+unix:///system?socket=/run/truenas_libvirt/libvirt-sock" $1'

然后用virsh list --all来列出所有vm,注意TrueNAS的在命令行里面的虚拟机名称会有一个编号在前头。

root@datacenter[~]# virsh list --all
 Id   Name      State
-------------------------
 7    1_mover   running

知道虚拟机名称之后,就可以将设备直通给虚拟机了,命令如下

virsh attach-disk <VM Name> <Disk ID> <Target Name>

举个例子

virsh attach-disk 1_mover /dev/disk/by-id/wwn-0x5002538da015f254 vdj

然后就能在虚拟机里面看到这个直通的磁盘了。

要注意的是,这个直通是临时的,关闭虚拟机会失去这个直通,需要重新配置。重启则不需要重新配置。