RouterOS DNS 和网关热备

环境:

  • Mikrotik RB5009 作为PPPOE和DHCP主路由,IP: 10.0.1.1
  • Linux 小主机同时作为主网关和 mosdns 服务器,IP: 10.0.1.21 (后边可能会拆开)

DNS 热备(劫持)

DNS 热备方案是通过劫持发往 ROS 主路由 53 端口的流量到 dns 服务器实现的。

劫持发往 10.0.1.1:53 的流量到 10.0.1.21:53

1
2
3
4
5
6
7
8
9
/ip firewall nat
add action=dst-nat chain=dstnat comment=hijack-dns dst-address=10.0.1.1 dst-port=53 in-interface-list=LAN protocol=udp \
src-address=!10.0.1.21 to-addresses=10.0.1.21 to-ports=53
add action=masquerade chain=srcnat comment=masquerade-dns dst-address=10.0.1.21 dst-port=53 protocol=udp src-address=\
10.0.1.0/24
add action=dst-nat chain=dstnat comment=hijack-dns dst-address=10.0.1.1 dst-port=53 in-interface-list=LAN protocol=tcp \
src-address=!10.0.1.21 to-addresses=10.0.1.21 to-ports=53
add action=masquerade chain=srcnat comment=masquerade-dns dst-address=10.0.1.21 dst-port=53 protocol=tcp src-address=\
10.0.1.0/24

此时可以测试 10.0.1.1 和 10.0.1.21 和其他 dns 的结果: 我的测试方案是在 mosdns 丢弃了 ipv6 结果,然后查询 cloudflare/github 域名的 A 记录

所谓热备,就是在 mosdns 机器挂掉的时候,让 ros 自己的 dns 上线

配置 ros dns 为阿里公共dns, 并且做 dns 服务器时需要开启 allow-remote-requests

1
2
/ip dns
set allow-remote-requests=yes servers=223.5.5.5

使用 script 和 netwatch 来监控 dns 服务器是否在线

1
2
3
4
5
6
/system script
add dont-require-permissions=yes name=dns-hijack-up owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source="/ip/firewall/nat/enable [find comment=hijack-dns]\r\
\n/ip/firewall/nat/enable [find comment=masquerade-dns]"
add dont-require-permissions=yes name=dns-hijack-down owner=admin policy=ftp,reboot,read,write,policy,test,password,sniff,sensitive,romon source=\
"/ip/firewall/nat/disable [find comment=hijack-dns]\r\
\n/ip/firewall/nat/disable [find comment=masquerade-dns]"
1
2
/tool netwatch
add disabled=no down-script=dns-hijack-down host=10.0.1.21 http-codes="" test-script="" type=simple up-script=dns-hijack-up

此时可以测试 10.0.1.21 拔掉网线时,10.0.1.1 是否切换到了 ros 内置 dns 来解析域名,可以用 router.lan 来测试

设置 dhcp server 的默认 dns 为 10.0.1.1

1
2
/ip dhcp-server network
add address=10.0.1.0/24 dns-server=10.0.1.1 gateway=10.0.1.1

网关热备(VRRP)

我的主网关是 linux 主机,安装 keepalived; 备网关是 ros,使用 vrrp

在 linux 网关下线时,需要下级设备无感自动切换到备用 ros 网关

ROS 新建 vrrp 接口

1
2
/interface vrrp
add interface=bridge interval=500ms name=vrrp1 priority=100 vrid=51

绑定一个 ip (10.0.1.22) 到 vrrp 接口,

1
2
/ip address
add address=10.0.1.22 interface=vrrp1 network=10.0.1.22

linux 主机安装 keepalived, 编辑 /etc/keepalived/keepalived.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
! Configuration File for keepalived

global_defs {
router_id LVS_DEVEL
vrrp_version 3
}

vrrp_instance VI_1 {
state MASTER
interface eno1
virtual_router_id 51
priority 130
advert_int 0.5
virtual_ipaddress {
10.0.1.22
}
}

state 为 MASTER, interface 为 lan 接口, virtual_router_id 需要和 vrrp 一致, priority 大于 vrrp, virtual_ipaddress 填写 vrrp ip 地址

此时路由已经默认走 10.0.1.22 出了,可以拔掉 linux 主机的网线来测试路由是否自动切换到 ros

dhcp server 里设置此 ip 为网关

1
2
/ip dhcp-server network
add address=10.0.1.0/24 dns-server=10.0.1.1 gateway=10.0.1.22

rv64 gcc 的 atomic 编译问题

atomic 分为 8-bit/16-bit/32-bit/64-bit 几种不同精度。涉及到 8-bit/16-bit 原子操作的指令,在 riscv64 上边是无法用 gcc 编译通过的 (使用 clang 则是没有任何问题)。

我们可以尝试编译 atomic-test.cpp (llvm-project_CheckAtomic.cmake at llvmorg-15.0.5):

1
2
3
4
5
6
7
8
9
10
#include <atomic>
std::atomic<int> x; // 64 bit
std::atomic<short> y; // 16 bit
std::atomic<char> z; // 8 bit
int main() {
// 此处是几个简单的 ++ 运算,但是可以观察到当前架构支持哪些精度的原子计算
++z;
++y;
return ++x;
}

如果没有 riscv64 的环境,可以在 gentoo 使用 crossdev 部署交叉编译工具链来复现:

1
2
3
sudo emerge --ask sys-devel/crossdev
sudo crossdev --target riscv64-unknown-linux-gnu
riscv64-unknown-linux-gnu-g++ /tmp/atomic-test.cpp

会看到以下报错

1
2
3
4
/usr/libexec/gcc/riscv64-unknown-linux-gnu/ld: /tmp/ccuxf3jX.o: in function `.L0 ':
atomic-test.cpp:(.text._ZNSt13__atomic_baseIcEppEv[_ZNSt13__atomic_baseIcEppEv]+0x16): undefined reference to `__atomic_fetch_add_1'
/usr/libexec/gcc/riscv64-unknown-linux-gnu/ld: atomic-test.cpp:(.text._ZNSt13__atomic_baseIsEppEv[_ZNSt13__atomic_baseIsEppEv]+0x16): undefined reference to `__atomic_fetch_add_2'
collect2: error: ld returned 1 exit status

报错找不到 __atomic_fetch_add_1 ? 肯定是因为没有链接 libatomic ,我们来试一下

1
riscv64-unknown-linux-gnu-g++ -latomic /tmp/atomic-test.cpp

这里编译通过了。

但是稍微修改代码,在只保留 64 位的时候,不加 -latomic 也是可以编译通过的,并不需要手动链接,这里比较让人迷惑。

cmake

GCC 在文档里也推荐加 -latomic,他们也是这么做的,比如 gcc/config/riscv/linux.h

1
2
3
4
5
6
7
8
9
/* Because RISC-V only has word-sized atomics, it requries libatomic where
others do not. So link libatomic by default, as needed. */
#undef LIB_SPEC
#ifdef LD_AS_NEEDED_OPTION
#define LIB_SPEC GNU_USER_TARGET_LIB_SPEC \
" %{pthread:" LD_AS_NEEDED_OPTION " -latomic " LD_NO_AS_NEEDED_OPTION "}"
#else
#define LIB_SPEC GNU_USER_TARGET_LIB_SPEC " -latomic "
#endif

这里我们发现了什么?对!在 --as-needed 的情况下,-latomic 只在有 -pthread (不是 -lpthread ) 的前提下才会自动加上,gcc 把他们绑定在一起了。

我们可以尝试一下 -pthread 能否让我们编译通过

1
$ riscv64-unknown-linux-gnu-g++ -pthread /tmp/atomic-test.cpp

可以编译通过。

在 CMake 项目中可以通过添加 set(THREADS_PREFER_PTHREAD_FLAG ON) 来将 -lpthread 的 flags 改为 -pthread,此时 -latomic 会被自动带进去。

cmake with glibc>=2.34

但是这在 glibc 2.34 以后就行不通了,glibc 2.34 自己实现了 pthread,不再需要 libpthread。

此时需要新方法: 在 CMake 项目中引用 CheckAtomic.cmake:

以下引用的代码来自 https://www.bilibili.com/video/BV1HZ4y1U7M2/ , 我没测试过

1
2
3
4
5
include(DetermineGCCCompatible.cmake)
include(CheckAtomic.cmake)
if (NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
link_libraries(atomic)
endif()

也可以参考 [cmake] link atomic library for certain CPU architectures by dlan17 · Pull Request #21743 · xbmc_xbmc, FindAtomic.cmake 可以看作是 llvm 里 CheckAtomic.cmake 的一种简写

据说,在其他架构上比如 ARMEL/MIPSEL/PPC 没有实现 64 位原子操作,也会出现类似的问题,但是我没有证实。

conclusion

总结一下,问题应该怎么修呢:

  1. 对于 Makefile 来说,需要添加在 Makefile 里边加上 -latomic
  2. 对于 CMake 项目来说,需要引用 CheckAtomic.cmake
  3. Gentoo 上可以用 append-libs "-latomic" 或者 append-atomic-flags

我觉得 append-atomic-flags 那段测试代码不如 6856e417 commit message 里边的覆盖全, 但是覆盖到了 risv64 现在的错误场景, 也够用了)

还有一些不那么优雅,可能有后遗症,甚至不一定能用的解法馊主意:

  1. 把 gcc 里边的 pthread 检查去掉,换成这个 https://github.com/riscv-collab/riscv-gcc/commit/2c4857d0981501b7c50bbf228de9e287611f8ae5
  2. 降级 glibc 到 2.34 以下
  3. 换 llvm,并嘲笑 gcc

PS: 至今还不知道 LLVM 为什么没这个问题,是因为加了 CheckAtomic.cmake? 或者是生成了全部原子操作的 inline code? 因为没有报错,我也不知道从哪里下手去看代码。如果你知道,请告诉我!

Some questions

到底需不需要手动链接呢? 我找到了这篇 Slide: CMake 立大功:glibc 更新引出的陈年旧案。 这里边说 64-bit 不需要手动 link libatomic 的原因是 gcc 只给 32 位和 64 位的 atomic value 生成 inline code,其它的(8,16,128位)都会调用 libatomic 中的对应函数。

这么说的话,这个错误不该由下游来修啊,GCC 那边怎么说?最新的进度是,有人给 GCC 提了 PATCH 来实现 inlining subword atomic,并且已经更新到了第四版。

[PATCH v4] RISC-V: Add support for inlining subword atomic operations

第四版里已经实现了 compare_and_exchange/exchange/fetch 这几个操作,等实现完了所有的原子操作,并且合入 GCC,那么以后不需要再操心了。

Ref

除了上文中提到的链接,我还参考了很多。都看不太懂,但是很厉害的样子!

  1. https://github.com/xbmc/xbmc/pull/21743/commits/ac3213e683e4c62c50dc02fef3b168d883245094
  2. https://blog.jiejiss.com/A-RISC-V-gcc-pitfall-revealed-by-a-glibc-update/
  3. https://lists.llvm.org/pipermail/llvm-dev/2018-June/123993.html
  4. https://lists.gnu.org/archive/html/info-gnu/2021-08/msg00001.html
  5. https://www.bilibili.com/video/BV1HZ4y1U7M2/
  6. https://www.bilibili.com/video/BV1Vq4y1a7Ny/
  7. https://github.com/plctlab/PLCT-Open-Reports/blob/master/20220406-CMake%E7%AB%8B%E5%A4%A7%E5%8A%9F%EF%BC%9Aglibc%E6%9B%B4%E6%96%B0%E5%BC%95%E5%8F%91%E7%9A%84%E9%99%88%E5%B9%B4%E6%97%A7%E6%A1%88-panruizhe.pdf
  8. https://github.com/felixonmars/archriscv-packages/wiki/FAQ#%E5%A6%82%E4%BD%95%E8%A7%A3%E5%86%B3-atomic-%E7%9B%B8%E5%85%B3%E7%9A%84%E9%97%AE%E9%A2%98
  9. https://github.com/riscv-collab/riscv-gcc/issues/337
  10. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg279752.html
  11. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg281336.html
  12. https://www.mail-archive.com/gcc-patches@gcc.gnu.org/msg283119.html

使用 Phoronix Test Suite 给 GPU 跑分

Phoronix Test Suite 简称 pts, 是一套开源的测试工具, 其作者是 Phoronix News 的主要撰稿人 Michael Larabel.

依赖只需要 php cli 即可, 可以直接用包管理器中的 php.

运行测试用例也非常简单:

1
2
3
git clone https://github.com/phoronix-test-suite/phoronix-test-suite.git
cd phoronix-test-suite
./phoronix-test-suite run unigine-heaven

这样就跑了一个虚幻引擎的测试用例 unigine-heaven, 我一般会选择在 1080P 和4K 分辨率下都用全屏幕跑一遍.

Read Linux Kernel With CLion

你是如何阅读内核代码的, 是使用 xtags + vim (+ lsp ), 还是 Source Insight, 还是直接 bootlin 之类的网站在线看?

我的阅读方式是: CLion + compile_commands.json

生成 compile_commands.json

1
2
make bzImage -j$(nproc)
./scripts/clang-tools/gen_compile_commands.py

只有 buildin 的模块会在 compile_commands.json 记录编译命令, module 的不会

如果需要看 module 的代码,可以编译模块,重新生成 compile_commands.json

1
2
3
make modules_prepare
make M=drivers/input/mouse -j$(nproc) # 指定模块文件夹
./scripts/clang-tools/gen_compile_commands.py

导入项目

使用 CLion 打开 compile_commands.json 作为 Compilation DB 项目, 导入以后就可以像 CMake 项目一样使用啦

Pbuilder build package

前言

Pbuilder 是常用的编译 deb 包的工具

优点: 环境干净,本质是chroot,与宿主机隔离;

缺点: 非常依赖仓库,如果没有仓库武功自费一半。

需要:

  1. 仓库

  2. 一台相同架构的机器

Pbuilder 安装及配置

使用apt 包管理器安装Pbuilder

1
2
sudo apt update
sudo apt -y install pbuilder

修改Pbuilder 配置文件

假如我们使用的仓库为Deepin 社区仓库,在sources.list 应该是这个样子:

1
deb https://community-packages.deepin.com/deepin apricot main contrib non-free
1
sudo vim /etc/pbuilderrc
1
2
3
4
5
6
MIRRORSITE=https://community-packages.deepin.com/deepin
DEBOOTSTRAPOPTS=(
"--no-check-gpg"
"--variant=buildd"
"--include=deepin-keyring,git,ssh,vim,pbuilder,devscripts,perl-openssl-defaults,ssh,sudo"
)

MIRRORSITE:仓库地址

这里填写的是 Deepin 社区仓库的地址 https://community-packages.deepin.com/deepin

DEBOOTSTRAPOPTS: debootstrap 的参数。debootstrap 是拉取干净的文件系统(chroot)的工具

  1. "--no-check-gpg" 用于跳过 gpg 签名检查。一般用于仓库没有签名时,Deepin 社区仓库是有签名的,所以加不加都可以。
  2. "--variant=buildd" variant 后加预设的软件包,参数还可以设为–variant=minbase|buildd|fakechrootminbase 只安装必须的包和apt, buildd 是指的 build-essential 之类编译相关的包, fakechroot 会使用非root 用户来安装包
  3. "--include=" 指定额外预装的包

创建一个Pbuilder tgz

Pbuilder 使用debootstrap 拉取chroot 环境后,会将chroot 目录压缩成 tgz 文件

1
sudo pbuilder --create --distribution apricot --basetgz apricot.tgz

--distribution apricot apricot 是Deepin 社区仓库的codename

需要在 /usr/share/debootstrap/scripts/ 下存在相同文件名的文件,如 /usr/share/debootstrap/scripts/apricot,如果没有需要手动复制过去。

1
sudo install -Dvm644 /usr/share/debootstrap/scripts/buster /usr/share/debootstrap/scripts/apricot

每个仓库所有的 codename 我们都可以在仓库下的dists 目录中看到

https://community-packages.deepin.com/deepin/dists/

--basetgz apricot.tgz --basetgz 后指定保存的文件名

使用

进入Pbuilder tgz

使用 --login --basetgz 文件名的形式进入

1
sudo pbuilder --login --basetgz apricot.tgz

编辑仓库源

sudo vim /etc/apt/sources.list

1
2
deb https://community-packages.deepin.com/deepin apricot main contrib non-free
deb-src https://community-packages.deepin.com/deepin apricot main contrib non-free

编辑完后 sudo apt update 一下

拉取源码

  1. git仓库: 通过git 拉取
  2. apt仓库: 使用apt policy 包名 查看包版本号,apt source 源码包名=版本号 拉取源码

编译

在pbuilder tgz环境中 apt install pbuilder devscripts sudo 后,可以在代码目录使用 V=s debuild-pbuilder -us -uc 编译

V=s 会显示更多日志

debuilddpkg-buildpackage 的封装,debuild-pbuilder 可以用 pbuilder 的自动解析依赖的功能, -us 是不签名deb 包, -uc 是不签名 changes 文件

编译后deb 包会在上层目录

将deb包拿出来

在上层目录将deb 包打个压缩包, tar cvf debs.tar.gz *.deb, 然后把压缩包传出来,可以用python3 -m http.server 开一个8000 端口的 http

Questions & Answers

Q: 想要更多参考资料?

A: Diaspora_Packaging_pbuilder - Debian Wiki

Q: 这个环境是持久化的吗?

A: 默认Ctrl-D 退出后就销毁的,如果想要持久化,--login --basetgz sp2.tgz 后加 --save-after-login

Q: 这样打出来有版本号依赖问题怎么办?

A: 如果安装有版本号依赖问题, 可以手改依赖版本 (修改的前提是对接口变动有信心,后果自负)

  1. Step1 解包: dpkg-deb -R dde-daemon_5.13.97-1_amd64.deb dde-daemon
  2. Step2 修改依赖版本号: 修改 dde-daemon/DEBAIN/control 文件中的Depends 字段
  3. Step3 再压回去: 再用dpkg-deb -b dde-daemon dde-daemon_5.13.97-1_amd64.deb