通过WireGuard和中继服务器加速企业网络访问

公司网络通常可以通过连接VPN来访问,但从去年年底开始,跨省市跨运营商的访问速度就变得越来越差,目前我的解决方案是用腾讯的轻量级云服务器作为中继,在这上面挂公司VPN,然后通过WireGuard将笔记本等其他终端和它组网,实现加速访问。

WireGuard的安装可以参考其官网,Linux和Windows都有,这里就略过了。

配置WireGuard各节点的密钥对

所有安装WireGuard的节点上都需要密钥对,包括中继节点和其他终端。如果是Windows节点,在GUI界面创建隧道的时候会自动生成密钥对。Linux下需要手动操作一下,方法如下。

$ sudo mkdir -p /etc/wireguard/
$ sudo chmod 700 /etc/wireguard/ 
$ wg genkey | sudo tee /etc/wireguard/privatekey | wg pubkey | sudo tee /etc/wireguard/publickey

配置中继节点

首先在中继节点上安装并连接公司的VPN,并确认下列信息:

  • 选择WireGuard子网网段:需要是一个保留的局域网IP段,并且和公司VPN网段、Docker网段等区分开。这里选择192.168.20.0/24
  • 确认公司VPN网络名称及IP段:通过ip addr可以查看公司VPN的网络名称,这里是tun0。通过ip route可以查看公司VPN网段都有哪些,这里以10.0.0.0/8, 172.16.0.0/12为例。

然后新建/etc/wireguard/wg0.conf文件如下

[Interface]
PrivateKey = [中继节点私钥]
Address = 192.168.20.1/24 # 中继节点在WireGuard子网中的IP
ListenPort = 51820 # 监听的端口号
PreUp = echo 1 > /proc/sys/net/ipv4/ip_forward # 允许IP转发
PostUp = /sbin/ufw route allow in on wg0 out on tun0 # wg0是WireGuard子网名称。如果不用ufw,请自行替换为同样作用的命令
PostUp = /sbin/iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
PreDown = /sbin/ufw route delete allow in on wg0 out on tun0
PreDown = /sbin/iptables -t nat -D POSTROUTING -o tun0 -j MASQUERADE

[Peer]
PublicKey = [第一个终端的公钥]
AllowedIPs = 192.168.20.2 # 第一个终端在WireGuard子网中的IP
PersistentKeepalive = 25

[Peer]
PublicKey = [第二个终端的公钥]
AllowedIPs = 192.168.20.3 # 第二个终端在WireGuard子网中的IP
PersistentKeepalive = 25

配置完毕后,启用WireGuard

$ sudo systemctl enable wg-quick@wg0
$ sudo systemctl start wg-quick@wg0

最后打开防火墙,以ufw为例

$ /sbin/ufw allow 51820

如果是腾讯云等服务器,可能还需要在网页控制台的防火墙上放通一下。

配置终端节点

以Linux为例,新建终端节点的/etc/wireguard/wg0.conf如下

[Interface]
PrivateKey = [该终端节点的私钥]
Address = 192.168.20.2/24  # 该终端节点在WireGuard子网中的IP

[Peer]
PublicKey =[中继节点的公钥]
Endpoint = [中继节点公网IP]:[中继节点监听端口号]
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12, 192.168.20.0/24  # 公司VPN网段+WireGuard子网网段
PersistentKeepalive = 25

然后启用

$ sudo systemctl enable wg-quick@wg0
$ sudo systemctl start wg-quick@wg0

这时公司内网IP应该已经可以从终端节点ping通了。如果公司有自己内部的DNS来解析内网网址,还需要配置一下DNS。比较简单的方法是在终端配置文件的[Interface]中增加DNS配置

[Interface]
DNS = [公司DNS服务器IP]
...

也可以仅让公司域名经过内部DNS,其他的仍然走原有DNS。删掉WireGuard配置文件中的DNS设置,然后在系统中设置如下

Linux上的DNS配置

新建 /etc/systemd/resolved.conf.d/wg0.conf 文件如下

[Resolve]
DNS=[公司DNS服务器IP]
Domains=~mycompany.com  # 公司域名

然后启用systemd-resolve

sudo systemctl enable systemd-resolved.service
sudo systemctl start systemd-resolved.service

Windows上的DNS设置

以管理员权限打开PowerShell,执行

Add-DnsClientNrptRule -Namespace ".mycompany.com" -NameServers "[公司DNS服务器IP]"

配置主动模式的FTP

通过上面的配置,一般的公司服务都可以使用了,但是我们公司的FTP是以主动模式工作的,需要额外的配置。比较简单的方法是通过ftp-proxy实现。

配置中继到FTP的连接

首先需要中继服务器自己能够连上FTP服务器,对于主动模式,需要允许来自FTP服务器的所有入站链接(腾讯云等可能需要在网页控制台也配置一下)

sudo ufw allow from [公司FTP服务器IP]

然后用Filezilla等测试一下,如果没有问题可以进行下一步配置。

配置中继的FTP代理

以Debian/Ubuntu为例,在中继服务器上安装ftp-proxy

sudo apt-get install ftp-proxy

然后编辑 /etc/proxy-suite/ftp-proxy.conf,找到 DestinationAddress 这行,并修改为

DestinationAddress [公司FTP服务器IP]

如果有需要,也可以找到 Port 这行,修改监听端口。

然后修改/etc/default/ftp-proxy,将“RUN_DAEMON”设置为“yes”。

修改完毕后,启用ftp-proxy

sudo systemctl enable ftp-proxy
sudo systemctl start ftp-proxy

最后在防火墙放通一下监听端口,假设是默认的2121

sudo ufw allow from 192.168.20.0/24 to any port 2121

如果目标FTP服务器是主动模式,还需要额外放通来自FTP服务器的连接

sudo ufw allow from [FTP服务器IP] to any

配置终端的FTP

在终端节点,允许来自中继节点的所有入站连接

sudo ufw allow from 192.168.20.1

然后在连接FTP时,使用中继服务器的IP和2121端口进行连接即可。

简易并行调试的实现

利用GNU Screen在Linux上实现一个简单的MPI程序并行调试方法。

代码支持

基本方法是自动创建screen的session,然后自动在里面开N个gdb窗口并将每个进程attach上去。示例性代码如下,除了直接使用gdb,也支持通过vim插件来attach。

#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <mpi.h>
#include <cstdlib>
 
int main(int argc, char** argv) {
    int rank, size;
    MPI_Init(&argc, &argv); // 初始化MPI环境
    MPI_Comm_rank(MPI_COMM_WORLD, &rank); // 获取当前进程的rank
    MPI_Comm_size(MPI_COMM_WORLD, &size); // 获取进程总数
 
    // Command line parameters
    bool isDebug = false;
    bool withVim = false;
    for (int i = 0; i < argc; i++) {
        if (std::string(argv[i]) == "-debug") {
            isDebug = true;
        }
        if (std::string(argv[i]) == "-vim") {
            withVim = true;
        }
    }
 
    // Parallel debugging support
    if (isDebug) {
        const int pid = getpid();
        const std::string display = std::getenv("DISPLAY");
        // Construct unique screen session name
        int uniqueIdent = pid;
        MPI_Bcast(&uniqueIdent, 1, MPI_INT, 0, MPI_COMM_WORLD);
        const std::string screenName = "mpiDebug." + std::to_string(uniqueIdent);
        // Construct attach command
        std::string attachCommand = "gdb attach " + std::to_string(pid);
        if (withVim) {
            attachCommand = "vim -c 'Termdebug -p " + std::to_string(pid) + "'";
        }
        // Create screen session and attach process in the first rank
        if (rank == 0) {
            const std::string cmd = "DISPLAY=" + display + " screen -dm -S " + screenName + " " + attachCommand;
            system(cmd.c_str());
            std::cout << "Rank = " << rank << ", Command = " << cmd << std::endl;
        }
        // Syncronize to make sure screen session is created
        MPI_Barrier(MPI_COMM_WORLD);
        // Create other gdb windows
        if (rank != 0) {
            const std::string cmd = "screen -S " + screenName + " -X screen " + attachCommand;
            system(cmd.c_str());
            std::cout << "Rank = " << rank << ", Command = " << cmd << std::endl;
        }
        // Sleep to wait for above commands
        sleep(5);
        // Syncronize
        MPI_Barrier(MPI_COMM_WORLD);
    }
 
    std::cout << "Hello world from process " << rank << " of " << size << std::endl;
 
    MPI_Finalize(); // 结束MPI环境
 
    return 0;
}

Screen + GDB

采用Screen + GDB是一个比较基础的方法,资源占用也低,缺点是没法同步看源代码,需要另外开窗口去看。

如果要使用这种方法调试,请在运行时提供-debug选项,并通过以下命令寻找名为mpiDebug.[第一个进程的PID]的screen会话

screen -ls

然后根据获取的信息attach这个会话

screen -raAd [会话名称]

如果需要统一操作这些gdb窗口,比如在所有窗口输入c命令继续执行程序,可以在另外的命令行窗口(不是这个会话内!)输入以下命令

screen -S [会话名称] -X at '#' stuff 'c^M'

Screen + Vim + GDB

如果需要进一步同步查看源代码,类似一般的图形化调试器那样,可以考虑引入Vim。这里采用Vim自带的插件TermDebug来实现,该插件需要Vim 8.1以上版本和GDB 7.12以上版本。

为了正常使用鼠标并自动载入插件,需要修改~/.vimrc进行相关配置

set ttymouse=sgr
packadd! termdebug

如果你还同时在使用Vim的buftabs插件,请修改buftabs.vim文件,将Buftabs_show()函数的最后一段316行

let &l:statusline = s:list . w:original_statusline

改成

if exists("w:original_statusline")
  let &l:statusline = s:list . w:original_statusline
end

如果要使用这种方法调试,请在运行时提供-debug -vim选项,之后screen的操作方法和之前一样。

请注意,如果程序是在图形界面或者设置了正确的DISPLAY参数的终端下启动的,那么创建的screen会话就包含正确的DISPLAY参数,同时Vim也是图形版本的。否则Vim将是终端版本,部分功能会受限。

这里Vim启动时并不会自动打开当前程序位置对应的源代码文件,但如果用n等命令步进一下,就可以自动打开。这个地方我还没有仔细看是否可以在启动的时候就打开。

通过这种方式进行调试,偶尔会遇到screen会话卡住的问题,重新attach一般能解决。如果会话的窗口大小不对,一般进行操作后(比如在gdb窗口输入字符)可以自动缩放到正确大小。

辅助脚本

可以写一个辅助脚本来进行attach到会话以及向会话发送命令的功能,比如

mpiDebug.sh
#!/bin/bash
 
ATTACH=false
RUNCMD=""
NAMEID=0
while true; do
    case "$1" in
        -a) ATTACH=true; shift ;;
        -r) RUNCMD=$2; shift 2;;
        -i) NAMEID=$2; shift 2;;
        * ) break ;;
    esac
done
 
candidates=($(screen -ls | grep "mpiDebug\.[0-9]*" | awk '{print $1}'))
 
# Validate
if [ $NAMEID -gt ${#candidates[@]} ]; then
    echo "Session index out of range, here are valid sessions:"
    for index in "${!candidates[@]}"; do
        echo "ID = $index, Name = ${candidates[index]}"
    done;
    exit 1
fi
 
sessionName=${candidates[NAMEID]}
if $ATTACH; then
    screen -raAd $sessionName
elif [ ! -z "$RUNCMD" ]; then
    if [[ $RUNCMD == ^* ]]; then
        screen -S $sessionName -X at '#' stuff "$RUNCMD"
    else
        screen -S $sessionName -X at '#' stuff "$RUNCMD^M"
    fi
fi

这样可以通过下列命令进行attach,

mpiDebug.sh -i [会话序号] -a

并通过下列命令运行指令,

mpiDebug.sh -i [会话序号] -r [命令]

这里的会话序号是名称符合mpiDebug.XXX的会话的顺序编号,比如

There are screens on:
        28477.mpiDebug.3      (Detached)
        28050.mpiDebug.2      (Detached)

序号0就是28477.mpiDebug.3。会话序号也可以省略,默认值是0。

自动上传剪贴板的图文到FTP

目前向公司研发内网传数据只能通过FTP,如果总是用Filezilla等FTP软件传输有时会显得比较繁琐,效率不高。一个可行的解决方案是编写一个能够将剪贴板的图片或者文字上传到FTP的脚本,并绑定到某个快捷键上,这样在复制文字或者图片后,就可以直接按快捷键实现传输。

Linux上的脚本可以从这里获取:https://github.com/lainme/dotfiles/blob/master/bin/clipboardToFTP.py

需要安装以下Python库才能使用:

这个脚本运行时会从命令行参数或者配置文件中读入FTP连接信息等配置,并将当前剪贴板内容上传到FTP,其中图片的保存名称是clipboard.png,文字的保存名称是clipboard.txt

通过命令行指定参数的方法如下:

python3 clipboardToFTP.py --host [FTP地址] \
    --username [用户名] --password [密码] --active \
    --remote [用于保存文件的FTP路径,默认是根路径]

通过配置文件指定参数的方法如下:

config.ini
[Server]
host=[FTP地址]
username=[用户名]
password=[密码]
active=true
remote=[用于保存文件的FTP路径,默认是根路径]

然后在命令行指定

python3 clipboardToFTP.py --config config.ini

这里的--active或者active=true表示用主动模式进行FTP连接,如果不需要可以省略。

如果你不想在命令行或者配置文件中写入密码,也可以使用系统keyring来存储密码。首先向keyring中增加密码,可以直接用Python的Keyring库,如下所示:

lainme@home-server:~$ python
Python 3.11.3 (main, Apr  5 2023, 15:52:25) [GCC 12.2.1 20230201] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import keyring
>>> keyring.set_password("[FTP地址]", "[用户名]", "[密码]")

然后在命令行指定--keyring或者在配置文件中写入keyring=true即可。

在ThinkPad Neo 14上安装ArchLinux遇到的问题和部分解决方法

无法识别麦克风

扬声器是正常工作的,但找不到麦克风设备,目前这一问题只能通过自己给内核打Patch编译来解决。

Kernel.org上的BUG报告如下:https://bugzilla.kernel.org/buglist.cgi?quicksearch=neo%2014

基本的内核编译方法请参考Arch Wiki:https://wiki.archlinux.org/title/Kernel/Arch_Build_System

编译的时候不需要修改prepare()函数的内容和更新checksum,只需要把patch放到PKGBUILD的同目录,并增加到source=()一栏,相应的sha256sums写SKIP就可以。

patch如下(这是在6.2.2版本下生成的,如果是别的版本可能第一行的行号会有所不同)

--- a/sound/soc/amd/yc/acp6x-mach.c
+++ b/sound/soc/amd/yc/acp6x-mach.c
@@ -49,6 +49,13 @@
 		.driver_data = &acp6x_card,
 		.matches = {
 			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "21EF0002CD"),
+		}
+	},
+	{
+		.driver_data = &acp6x_card,
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "LENOVO"),
 			DMI_MATCH(DMI_PRODUCT_NAME, "21D0"),
 		}
 	},

之后编译安装,增加用自己编译的内核启动的启动项。

然后还需要修改/etc/pulse/default.pa文件,将里面的

load-module module-udev-detect

改成

load-module module-udev-detect use_ucm=0 

然后重新启动,看麦克风是否正常。

VirtualBox启动报错

启动时报错“Failed to create the VirtualBoxClient COM object”。安装libvpx可以解决

$yaourt -S libvpx

休眠 (suspend) 不工作

如果用s2idle模式,休眠后可以唤醒,但唤醒时nvme就掉了,各种fs error,只能强制重启,没有找到解决方法。如果用s3模式,休眠后无法唤醒,只能强制重启,没有找到解决方法。

最后把各种target都mask了,眼不见心为静

$sudo systemctl mask sleep.target suspend.target hybrid-sleep.target

Wayland拖影和停止响应的问题

移动窗口的时候会有拖影,界面毫无征兆的会卡住。刚开始以为是硬件不兼容或者什么驱动问题,后来换Xorg登陆就正常了,所以是Wayland自身的问题。

MailSpring随系统最小化自启动时无法从托盘打开

MailSpring如果设置了随系统自启动(通过~/.config/autostart),启动后点击托盘上的图标会没有反应,托盘插件是gnome-shell-extension-appindicator。

没有找到解决方法,刚开始尝试了延迟启动也没有用,现在只能暂时关掉了自启动。

在ArchLinux上安装H3C的iNode客户端

之前写过在CentOS7上安装H3C的iNode客户端,在Arch上的安装方式基本一致,只是需要额外解决几个小问题。

首先在执行/opt/iNodeClient/install_64.sh之前准备以下依赖的动态连接库

$ yaourt -S ncurses5-compat-libs libxcrypt-compat libjpeg6-turbo 

由于iNodeClient的安装脚本是通过rc.local来启动其服务的,并且会把服务脚本放到/etc/init.d,所以我们先手动创建init.d目录

$ sudo mkdir -p /etc/init.d/

并新建/etc/systemd/system/rc-local.service,内容如下

[Unit]
Description=/etc/rc.local
ConditionPathExists=/etc/rc.local

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
StandardOutput=tty
RemainAfterExit=yes
SysVStartPriority=99

[Install]
WantedBy=multi-user.target

然后激活这个服务

$ sudo systemctl enable rc-local.service

当然,你也可以注释掉安装脚本里相应的内容,后面直接用systemd来启动服务。

之后就可以执行安装脚本了,执行完成后需要手动把desktop文件移动过去(脚本只针对它认识的发行版执行这个操作,Arch不在列表里……)

$ sudo cp /opt/iNodeClient/iNodeClient.desktop /usr/share/applications/

这样应该就可以运行了。这部分是我安装好后回忆的,或许有疏漏,发现了再补充吧。