两年前写过一篇关于容器流量重定向的文章, 当时对网络了解还不是非常深刻, 加上当时podman使用的是cni插件式网络堆栈, 配置起来似乎较为复杂, Podman 4.0后已经使用netavark显得之前的配置已经有一些过时了.

Podman版本: 4.9.3

整体的目标还是一致的, 需要创建一个单独的Podman network, 挂在这个网络下的容器的出网流量默认都走到一个GRE隧道里.

先创建GRE隧道

1
2
3
4
sudo ip link add dev gre12 mtu 1380 type gre local <本地网卡地址> remote <远端网卡地址>
sudo ip addr add 10.66.4.2/30 dev gre12
sudo ip link set dev gre12 up
sudo iptables -t nat -A POSTROUTING -o gre12 -j MASQUERADE

在对端也要创建GRE隧道

1
2
3
4
5
6
7
sudo ip link add dev gre12 mtu 1380 type gre local <对端网卡地址> remote <本地网卡地址>
sudo ip addr add 10.66.4.1/30 dev gre12
sudo ip link set dev gre12 up

# 注意这里 eth0 要根据对端机器上出网网卡来决定
sudo iptables -t nat -A POSTROUTING -i gre12 -o eth0 -j MASQUERADE
sudo sysctl -w net.ipv4.ip_forward=1

然后创建容器网络

1
2
sudo podman network create --ip-range=10.89.0.0/24 overseas
sudo podman network update overseas --dns-add <目标网络DNS服务器>

配置网络转发规则

1
2
3
4
5
6
# 不是发往本地网络的, 一律添加fwmark并转发
sudo iptables -t mangle -A PREROUTING -s 10.89.0.0/24 ! -d 192.168.0.0/16 -j MARK --set-mark 0x2

# 添加标记的流量, 通过GRE隧道转发
sudo ip rule add fwmark 2 lookup 55
sudo ip route add default via 10.66.4.1 table 55

Podman版本: 4.9.3

Ubuntu版本: Ubuntu 24.04.3 LTS

ufw版本: 0.36.2

问题描述

Rootful Podman容器创建后无法访问外网, 通过 -p 暴露的端口也无法直接被外部访问.

解决方案

  1. 暴露端口无法访问
1
sudo ufw allow <端口>
  1. 容器无法访问外网 (同样适用于通过podman network create创建的bridge类型网络)
1
2
sudo ufw allow in on podman0
sudo ufw route allow in on ens18 out on podman0

形成的原理是因为ufw会把filter表的INPUT, FORWARD policy都设置为 DROP, 如果没有单独配置的话从内部进来的流量(其实算forward)会被drop掉.

参考资料

UFW blocks Podman connections #27273 只搜到了这么一个issue, 但是里面并没有提到什么好的解决方案

前景提要

电信提供了公网IPv6地址, 一般情况下无需改动光猫配置. 有时需要通过管理员密码(找宽带安装师傅要)登录光猫后台启用IPv6功能.

根据观测, 大部分家庭宽带默认开启了较高等级的防火墙, 会禁止外部发起IPv6链接. 需要通过管理员密码登录光猫后台找到防火墙配置, 改为更低等级的防护或关闭防火墙.

注意, 对于家庭网络中光猫-路由器-其他设备的场景, 路由器IPv6配置方式可能需要改为 Native 才能使局域网设备也获得公网IPv6. 某些路由器(比如华硕)的 Stateless 配置似乎存在Bug导致只有路由器本身能获取到v6而下级设备无法正确获取地址.

另外根据观测, 不同ISP(例如联通和电信)之间的公网IPv6地址无法实现互通.

问题概述

两个小区之间都接入了电信的家庭宽带, 其中一端(下称A)开启了允许公网访问. 由另一端(下称B)向开启公网访问的这一端发起WireGuard连接.

电信提供的公网IPv6地址似乎在一段时间之后会失效, 但是在失效前一段时间, 会同时并存多个IPv6公网地址, 例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ip a
...
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether xx:xx:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.xx.xx/24 brd 192.168.xx.255 scope global ens18
valid_lft forever preferred_lft forever
inet6 240e:xx:xx:xx::xx/128 scope global dynamic noprefixroute
valid_lft 25044sec preferred_lft 25044sec
inet6 240e:xx:321a:xx:xx:xx:xx:xx/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 177778sec preferred_lft 91378sec
inet6 240e:xx:3216:xx:xx:xx:xx:xx/64 scope global dynamic mngtmpaddr noprefixroute <--- 注意这个地址
valid_lft 91395sec preferred_lft 4995sec
inet6 fe80::xx:xx:xx:xx/64 scope link
valid_lft forever preferred_lft forever
...

大部分情况下这种情况不会持续太久, 但是有时候老的地址会在 valid_lft 之前实际上失效, 即无法通过该IPv6地址收到来自外部的回包, 但仍然能够通过该地址发送IPv6数据包.

由于WireGuard并不能设置ListenAddress这样的东西, 导致从tcpdump看到远端A能够正常收到来自 240e:xx:3216:xx:xx:xx:xx:xx 的握手包且发送了回包, 但是本端(B)无法正常收到回包导致握手失败.

进一步排查, Linux允许同一个接口有多个IPv6地址, 并且会根据 RFC 6724 来选择出网的IPv6地址的. 可以看到其中并不会根据 preferred_lftvalid_lft 进行选择. 而对比到目标地址 240e:xx:3211:... 的最长前缀, 可以发现两个地址到目标地址的最长前缀都是一样的 (0x3211=11001000010001, 0x3216=11001000010110, 0x321a=11001000011010) 因此似乎Linux内核的选择并没有问题, 但实际上会导致通讯一直中断, 直到 240e:xx:3216:xx:xx:xx:xx:xx 这条地址所指定的 valid_lft 归零才会恢复.

解决办法

解决办法有两种:

  1. 手动(或者通过定时任务)删除 valid_lft 更小的IPv6地址
1
sudo ip addr del 240e:xx:3216:xx:xx:xx:xx:xx/64 dev ens18
  1. 强制该接口重新配置IPv6, 这会立刻删除掉该接口的所有IPv6地址, 内核会重新创建 link-local address, 并完成SLAAC配置.
1
2
sudo sysctl -w net.ipv6.conf.ens18.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.ens18.disable_ipv6=0

上述命令会使Linux内核创建 fe80::... 这样的 link-local 地址.

如果没有自动获得公网IPv6地址, 对于使用 systemd-networkd 的系统, 可通过 networkctl 触发配置:

1
2
3
4
5
6
7
$ sudo networkctl
IDX LINK TYPE OPERATIONAL SETUP
1 lo loopback carrier unmanaged
2 ens18 ether routable configured

$ sudo networkctl reload
$ sudo networkctl reconfigure ens18

完成后接口将获取到新的IPv6地址. (不一定和之前的不一样, 但只会有一个)

除了使用 networkctl, 还可以使用 rdisc6 (sudo apt install ndisc6) 命令触发 SLAAC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ rdisc6 -1 ens18
Soliciting ff02::2 (ff02::2) on ens18...

Hop limit : 255 ( 0xff)
Stateful address conf. : No
Stateful other conf. : Yes
Mobile home agent : No
Router preference : medium
Neighbor discovery proxy : Yes
Router lifetime : 1800 (0x00000708) seconds
Reachable time : unspecified (0x00000000)
Retransmit time : unspecified (0x00000000)
Prefix : 240e:xx:xx:xx::/64
On-link : Yes
Autonomous address conf.: Yes
Valid time : 2993 (0x00000bb1) seconds
Pref. time : 2993 (0x00000bb1) seconds
Recursive DNS server : 240e:xx:xx:xx:xx:xx:xx:xx
DNS server lifetime : infinite (0xffffffff)
MTU : 1500 bytes (valid)
Source link-layer address: xx:xx:xx:xx:xx:xx
from fe80::f22f:xx:xx:xx

生成临时密码

1
openssl rand -base64 48

会生成类似如下的字符串 6/4tsW7PR4bYjdY+zzZWEGIsuUz8RIwrNc8FTeQLoeouGO/C3RK/JeqNi8E6nR1l

1
tr -dc 'A-Za-z0-9!?%=' < /dev/urandom | head -c 16; echo

这样生成出来的字符串更适合需要特殊字符的场景, 例如 SYqx!6J3=M8jrUeh

流式加解密

1
2
3
4
5
6
7
8
9
10
# 加密

cat input.raw | openssl enc -aes-256-cbc -e -pbkdf2 -iter 100000 -salt > output.raw
enter AES-256-CBC encryption password:
Verifying - enter AES-256-CBC encryption password:

# 解密

cat output.raw | openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 -salt > decrypted.raw
enter AES-256-CBC decryption password:

分块传输(对网盘上传比较友好)

1
2
3
4
5
6
7
8
9
10
# 加密后分块

cat input.raw | openssl enc -aes-256-cbc -e -pbkdf2 -iter 100000 -salt | split -b 1G -d -a 3 - chunk_
enter AES-256-CBC encryption password:
Verifying - enter AES-256-CBC encryption password:

# 合并后解密

cat chunk_* | openssl enc -aes-256-cbc -d -pbkdf2 -iter 100000 -salt > decrypted.raw
enter AES-256-CBC decryption password:

CBC加密后分块可以配合tar命令使用, tar可以保证缺块的情况下仍然能够最大限度的提取出可用的文件.

split 命令, -d 为使用数字结尾(而不是aaa,aab这样), -a 2 表示补全到两位, 需要预估总输入大小确保不会出现 01, 02, ..., 99, 100 这样的情况. 否则 cat 的时候可能会错乱.

基于GPG的对称流式加解密

gpg能做非对称加解密已经是老生常谈了, 但是对称加密之前用到的不多

1
2
3
4
5
6
7
8
9
10
11
# 加密后分块
passphrase=$(openssl rand -base64 128 | tr -d '\n')
echo "Passphrase: $passphrase"
exec {passfd}<<<"$passphrase"
tar -cvf - input_dir | pv | gpg --symmetric --s2k-cipher-algo AES256 --s2k-digest-algo SHA512 --s2k-count 65536 --no-compress --batch --pinentry-mode loopback --passphrase-fd $passfd | split -b 1G -d -a 3 - chunk_
unset passphrase
exec $passfd>&-

# 合并后解密
exec {passfd}<<<"$passphrase"
cat chunk_* | gpg --decrypt --batch --pinentry-mode loopback --passphrase-fd $passfd | pv | tar -xvf -

GnuPG使用的是 aes-256-cfb 模式进行的加密(不能调整模式). exec {passfd}<<<"$passphrase" 是从字符串创建一个fd, 需要注意的是bash/zsh实际上会创建一个 /tmp/tmp.XXXXXX 的文件然后立刻删除. exec $passfd>&- 会关闭这个fd. 可以使用这个命令查看当前shell打开的fd: ls -l /proc/$$/fd

需要注意要加 --no-compress 选项. gpg默认是开启压缩的, 但是内部的压缩组件似乎有问题, 会导致莫名其妙的报错比如: gpg: Fatal: zlib inflate problem: invalid distance code. 在网上翻了一下没看到有什么特别好的解法, 大部分都是说数据本身出现了错乱才会报错. 在流式加密的场景里, 可以换成其他的压缩方式, 例如 tar ... | pv | zstd -19 -T0 | gpg ...

如果磁盘空间不能同时容纳 tar chunks 和提取出来的东西, 但是足够容纳部分 tar chunks 和全部提取出来的东西, 可以考虑FIFO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 创建命名管道(named pipe)
mkfifo $(mktemp -u | tee /dev/tty)

# 会返回类似 /tmp/tmp.iblsCdwJJo 的输出

file /tmp/tmp.iblsCdwJJo
# /tmp/tmp.iblsCdwJJo: fifo (named pipe)

# 先开启读端
cat /tmp/tmp.iblsCdwJJo | gpg --decrypt --batch --pinentry-mode loopback --passphrase-fd $passfd | pv | tar -xvf -

# 再开启写端
exec {chunkfd}>/tmp/tmp.iblsCdwJJo
cat chunk_000 >&3
cat chunk_001 >&3
cat chunk_002 >&3
...
# 关闭写端口
exec $chunkfd>&-

注意不能直接 cat chunk_000 > pipe 因为cat结束之后会关闭STDOUT, 进而导致读端收到EOF.

手动cat chunk这个过程可以使用一个简单的python脚本来半自动化:

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
import os
import time

print("Opening pipe...")
pout = open("/tmp/tmp.iblsCdwJJo", "wb")

for i in range(0, 115): # 这里需要提前知道一共有多少个chunk
while True:
fname = "chunk_{:03d}".format(i)
if os.path.exists(fname.format(i)):
print("chunk {} exists".format(i))
time.sleep(3)
print("reading chunk {}...".format(i))
with open(fname, "rb") as f:
content = f.read()
print("{} bytes read".format(len(content)))
pout.write(content)
print("chunk {} written".format(i))
break
else:
print("chunk {} not exists, wait...".format(fname))
time.sleep(5)

# 记得关闭FIFO
pout.close()

参考资料

split(1) — Linux manual page

GnuPG - ArchWiki

GPG(1) manpage

【少帅进行曲】折风渡夜(DJ名龙版) - 泽国同学“斑驳的岁月 从不肯忘却”2024抖音热门音乐

DJ楓楓《特別的愛給特別的你》DJ版 #农人dj枫枫 #柳州dj楓楓 #djfengfeng

lucky小阳 - 游京 (抖音热播DJ版) Du Kinh (Remix) - Tiểu Dương『我走在长街中,听戏子唱京城,人杂乱戏小丑,叶黄退入长秋』【抖音火流行歌曲推荐TikTok】

Lazer Boomerang - Time To Pretend (Official Audio) | 【哈基米音乐】Time to Pretend

VØJ, Narvent - Memory Reboot (4K Music Video) | 【哈基米音乐】Memory Reboot

MONTAGEM MIAU | 【完整版】哈基米:Montagem Miau🐱

往期优秀作品推荐

2025年8月

Strongswan侧的配置与之前写的 通过BGP与Google Cloud Router建立高可用链接 文中的过程, 使用到的脚本基本上保持一致, 本文主要介绍FortiGate侧的配置步骤.

目前正在使用的FortiOS版本为 v7.0.17

  1. 登录FortiGate, 选择 VPN -> IPSec Tunnels, 点击 Create New, 在下拉菜单中选择 IPSec Tunnel

  2. 会自动打开 IPSec Wizard. Template type 选择 Custom, 填写一个名字, 然后点下一步

  3. 在打开的新页面中, Network部分做如下配置:

    Remote Gateway 选择 Static IP Address, IP Address 填入对端的地址, Interface 选择外网直接的接口, 勾选 Local Gateway 并选择 Primary IP.

    NAT Traversal 调整至 Enable

    Dead Peer Detection 调整至 On Idle

    Advanced 菜单展开, 确保 Add Route 选项是 Enabled

  4. 配置 Authentication

    Method 选择 Pre-shared Key, 填入 PSK

    IKE Version 调整至 2

  5. 配置 Phase 1 Proposal

    删掉不需要的Encryption配置, 保留 Encryption: AES256GCM, PRF: PRFSHA512.

    Diffie-Hellman Group 注意只勾选需要的组.

  6. 配置 Phase 2 Selectors

    Local Address 选择 Subnet, 0.0.0.0/0.0.0.0

    Remote Address 选择 Subnet, 填入端到端的IP. /24 CIDR 段使用 255.255.255.0 作为 mask, /30 CIDR 段使用 255.255.255.252.

    展开 Advanced, 去掉不需要的 Encryption, 保留 AES256GCM

    确保 Enable Replay Detection 勾选

    确保 Enable Perfect Forward Secrecy (PFS) 勾选

    Diffie-Hellman Group 注意只勾选需要的组.

  7. 点击 OK 保存配置, 回到 IPsec Tunnels 页面, 点击新的隧道一行的 Status 列, 在新打开的页面中, 找到对应的隧道(会高亮), 右键 在打开的菜单中选择 Bring Up -> All Phase 2 Selectors

  8. 打开 Policy & Objects -> Firewall Policy, 配置防火墙策略

  9. 打开 Policy & Objects -> Central SNAT, 配置出网NAT

  10. 打开 Network -> Static Routes, 将端到端的IP CIDR添加为静态路由.

  11. 打开 Network -> DNS Servers, DNS Service on Interface 栏点击 Create New, 在新打开的页面中 Interface 选择新创建的隧道, Mode 选择 Recursive, 点击 OK 保存配置

  12. (可选) 打开 Network -> Interfaces, 找到新创建的隧道, 双击打开新页面. 在 Address 栏中, IP 填入端到端的本端IP, 注意不需要填写CIDR后缀或mask. 对端IP填写端到端的对端IP+mask, 使用空格分开.

参考

Route-Based VPN Tunnel FortiGate <-> Cisco ASA

Phase 2 configuration | FortiGate / FortiOS 7.6.4 | Fortinet Document Library

因为不是很想把PBS放到NAS或者另外的单独设备上, 目前打算把PBS作为一个VM装在PVE里, 然后这个VM单独走PVE的SMB Backup (磁盘分20G应该足够了) 其他的VM走PBS做备份, 这样可以享受PBS提供的差分备份方案.

唯一的问题在于PBS默认是不支持SMB的, 目前只支持本地磁盘, 可移动设备, S3存储(实验性). 所以大概的思路就是先进shell手动mount一下然后再作为”本地磁盘”添加datastore.

1
2
3
4
apt install cifs-utils
mkdir /mnt/nas-store
chown -R backup:backup /mnt/nas-store
chown -R 770 /mnt/nas-store

编辑 /etc/fstab, 添加smb mount point

1
//<NAS地址>/pbsbackup /mnt/nas-store cifs vers=3.0,iocharset=utf8,file_mode=0777,dir_mode=0777,uid=backup,gid=backup,username=<SMB用户名>,password=<SMB密码> 0 0

使挂载生效

1
2
systemctl daemon-reload
mount -a

回到PBS web端, 添加datastore的时候, backing path 填写 /mnt/nas-store 即可.

参考

PBS - Mounting Windows SMB Share as Datastore

盧盧快閉嘴 / 劉思达LOFTHESTAR - 猜不透 (說唱版)「如果忽遠忽近的灑脫 是你要的自由 那我寧願回到一個人生活」【動態歌詞/PinyinLyrics】♪

若月亮沒來 (若是月亮還沒來) - 王宇宙Leto/喬浚丞『若是晨風還沒來,晚風也可吹入懷』【動態歌詞】

【循环歌曲】《Bloody Mary》(Slowed Instrumental)“人们对Bloody Mary的开发程度不足百分之一”(无损音质) 彩礼视频一转买车BGM

《航天の小曲》《赛博の小曲》《人类进步の小曲》《群星の小曲》《高燃の小曲》 R3cover-Lazer Boomerang | Lazer Boomerang - R3cover (Original Mix)

《进步の小曲》《进部の小曲》《考公の小曲》鸳鸯戏 (DJ完整版) —— 略略略

MENTE MÁ - NAKAMA (Official Lyric Video) 刀马刀马

DIA DELÍCIA

Minecraft 1.18 New Music Disc “otherside”

ATLXS - PASSO BEM SOLTO (SLOWED) | ATLXS - PASSO BEM SOLTO (SLOWED) so bay so~

黄凯芹-若生命等候 (DJ Candy版)

热歌新好听版!浮生未歇:不能不想你,挣扎在爱与痛的缝隙!4K无损好视听 我只能在爱与痛之间喘息 不能不想你(好听版)

口是心非 (煙嗓版) (原唱:張雨生)- 半噸兄弟『在我最需要妳的時候,於是愛恨交錯人消瘦』【動態歌詞】

往期优秀作品推荐

2025年5月

Win10 22H2版本, 通过蓝牙连接音箱之后, 在操作系统内调整音量不起效果, 只能通过外接音箱上的音量调节改变音量大小…

找到一个类似的问题, 解决方案大概如下:

  1. 打开注册表管理器 计算机\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Bluetooth\Audio\AVRCP\CT

  2. 找到DisableAbsoluteVolume值,修改为1,如果没有,右键新建DWORD32位,建立对应值。修改后重启

这样操作下来之后确实可以调整操作系统内音量了, 但是总感觉哪里不太对, 调整外部音箱的音量仍然会修改类似【音量上限】, 而操作系统内的音量总感觉是相对这个上限的某个百分比…

参考

解决WIN10 1803及以后版本的蓝牙音量(绝对音量)问题