跳转至

博客

在 Lemote Yeeloong 上安装 Debian jessie

参考网站:

gNewSense To MIPS Run a TFTP server on macOS Debian on Yeeloong Debian MIPS port wiki Debian MIPS port

首先,进入设备的 PMON:

Press Del to enter PMON

然后,下载 Debian Jessie 的 netboot 文件:

$ wget https://mirrors.tuna.tsinghua.edu.cn/debian/dists/jessie/main/installer-mipsel/current/images/loongson-2f/netboot/vmlinux-3.16.0-6-loongson-2f
$ wget https://mirrors.tuna.tsinghua.edu.cn/debian/dists/jessie/main/installer-mipsel/current/images/loongson-2f/netboot/initrd.gz

以 macOS 为例,起一个 tftp 服务器以供远程下载:

# ln -s these files to /private/tftpboot:
# initrd.gz
# vmlinux-4.16.0-6-loongson-2f
$ sudo launchctl load -F /System/Library/LaunchDaemons/tftp.plist
# set addr manually to 192.168.2.1

回到 PMON,配置远程启动:

> ifaddr rtl0 192.168.2.2
> load tftp://192.168.2.1/vmlinux-3.16.0-6-loongson-2f
> initrd tftp://192.168.2.1/initrd.gz
> g

之后就是熟悉的 Debian Installer 界面。起来之后,就可以顺手把 tftp 服务器关了:

$ sudo launchctl unload -F /System/Library/LaunchDaemons/tftp.plist

实测滚到 stretch 会挂。因为 stretch 虽然也有 mipsel 架构,但是它的 revision 与 Loongson-2f 不大一样,会到处出现 SIGILL 的问题,不可用。靠 jessie 和 jessie-backports 已经有不少的软件可以使用了。

通过 systemd-run 直接在容器中执行命令

之前使用 systemd-nspawn 开了容器,然后通过 machinectl shell 进去,想要起一个服务然后丢到后台继续执行,但是发现离开这个 session 后这个进程总是会被杀掉,于是找了 systemd-run 的方案,即:

systemd-run --machine machine_name_here absolute_path_to_executable args_here

这样可以直接在容器中跑服务,而且用这个命令输出的临时 server 名称,还可以看到日志:

journalctl --machine machine_name_here -u unit_name_above

通过 iptables 在同一个端口根据源地址解复用(demux)

现在遇到一个场景,原来的一个服务只给一个客户端用,但现在增加了一个客户端,由于客户端配置相同,但是服务端需要区别对待两个客户端的服务端配置,所以利用 iptables 根据源地址做了一个端口转发,实现了 demux。

假设:服务器在 192.168.0.1,客户端分别在 192.168.0.2 和 192.168.0.3。客户端配置的服务端地址是 192.168.0.1:8000。之前,在服务器上只跑了一个服务,监听着 8000 端口。

现在,在服务器上再跑一个服务,监听 8001 端口,同时根据需求进行相应的配置。然后,加上如下 iptables 规则:

$ sudo iptables -t nat -A PREROUTING -s 192.168.0.3 -d 192.168.0.1 -p tcp -m tcp --dport 8000 -j REDIRECT --to-ports 8001

这样,在不需要更改客户端的情况下,完成了需要的效果。

升级 MongoDB 到 4.0

MongoDB 4.0 刚刚发布,加入了我很想要的 Transaction 功能。不过,我一更新就发现 MongoDB 起不来了。研究了一下日志,发现由于我创建数据库时,MongoDB 版本是 3.4,虽然后来升级到了 3.6,但还是用着 3.4 的兼容模式。这个可以这样来检测:

$ mongo
> db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } )

如果不是 3.6,升级到 4.0 之前,需要先执行如下操作:

$ # MongoDB version 3.6
$ mongo
> db.adminCommand( { setFeatureCompatibilityVersion: "3.6" } )

然后再升级到 MongoDB 4.0,才能正常地启动 MongoDB 4.0。之后可以考虑尝试使用 MongoDB 4.0 的 Transaction 了。不知道什么时候进入 Debian 的 stretch-backports 源中。

为了使用 MongoDB 4.0 的新特性,输入以下命令:

$ mongo
> db.adminCommand( { setFeatureCompatibilityVersion: "4.0" } )

之后会尝试一下 MongoDB 4.0 的 Transaction 功能。

Wireguard 隧道搭建

随着 Wireguard Go 版本的开发,在 macOS 上起 WireGuard Tunnel 成为现实。于是,搭建了一个 macOS 和 Linux 之间的 WireGuard Tunnel。假设 Linux 端为服务端,macOS 端为客户端。

macOS 端:

$ brew install wireguard-tools
$ cd /usr/local/etc/wireguard
$ wg genkey > privatekey
$ wg pubkey < privatekey > publickey
$ vim tunnel.conf
[Interface]
PrivateKey = MACOS_PRIVATE_KEY

[Peer]
PublicKey = LINUX_PUBLIC_KEY # Generated below
AllowedIPs = 192.168.0.0/24
Endpoint = LINUX_PUBLIC_IP:12345
$ vim up.sh
#!/bin/bash
# change interface name when necessary
sudo wireguard-go utun0
sudo wg setconf utun0 tunnel.conf
sudo ifconfig utun0 192.168.0.2 192.168.0.1
$ chmod +x up.sh
$ ./up.sh

配置 Linux 端:

$ git clone https://git.zx2c4.com/WireGuard
$ make
$ sudo make install
$ sudo fish
$ cd /etc/wireguard
$ wg genkey > privatekey
$ wg pubkey < privatekey > publickey
$ vim wg0.conf
[Interface]
Address = 192.168.0.1/24
PrivateKey = LINUX_PRIVATE_KEY
ListenPort = 12345

[Peer]
PublicKey = MACOS_PUBLIC_KEY
AllowedIPs = 192.168.0.2/24
$ wg-quick up wg0

经过测试,两边可以互相 ping 通。

后续尝试在 Android 上跑通 WireGuard。

UPDATE 2018-07-11:

成功在 Android 上跑通 WireGuard。在 Google Play 上下载官方的 App 即可。麻烦在于,将 Android 上生成的 Public Key 和服务器的 Public Key 进行交换。

然后又看到WireGuard 在 systemd-networkd上的配置方案,自己也实践了一下。首先,如果用的是 stretch,请首先打开 stretch-backports 源并把 systemd 升级到 237 版本。

然后,根据上面这个连接进行配置,由于都是 ini 格式,基本就是复制粘贴就可以配置了。有一点要注意,就是,要保护 PrivateKey 的安全,注意配置 .netdev 文件的权限。

Verilog 初体验

自己以前一直对硬件方面没有接触,但是大二大三很快就要接触相关知识,所以自己就先预习一下 Verilog HDL,以便以后造计算机。听学长们推荐了一本书叫《自己动手写 CPU》,由于自己手中只有很老的 Spartan-3 板子,手上没有可以用来试验的 FPGA,所以选择用 Verilog + Verilator 进行模拟。既然是模拟,自然是会有一定的问题,不过这个以后再说。

然后就是模仿着这本书的例子,写了指令的获取和指令的解码两部分很少很少的代码,只能解码 ori (or with immidiate) 这一个指令。然后,通过 verilator 跑模拟,输出 vcd 文件,再用 gtkwave 显示波形,终于能够看到我想要的结果了。能够看到,前一个时钟周期获取指令,下一个时钟周期进行解码,出现了流水线的结果。这让我十分开心。

接下来就是实现一些基本的算术指令,然后讲计算的结果写入到相应的寄存器中。这样做完之后,就可以做一个基于 verilator 的简易 A+B 程序了。

我的代码发布在jiegec/learn_verilog中。最近马上到考试周,可能到暑假会更频繁地更新吧。

在 ArchLinux 上编译 LineageOS for Huawei Angler

实践了一下如何在 ArchLinux 上编译自己的 LineageOS。本文主要根据官方文档 进行编写。

$ # for py2 virtualenv and running x86 prebuilt binaries(e.g. bison)
$ sudo pacman -Sy python2-virtualenv lib32-gcc-libs 
$ mkdir -p ~/bin
$ mkdir -p ~/virtualenv
$ # build script is written in python 2
$ cd ~/virtualenv
$ virtualenv2 -p /usr/bin/python2 py2
$ mkdir -p ~/android/lineage
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
$ chmod a+x ~/bin/repo
$ vim ~/.config/fish/config.fish
set -x PATH ~/bin $PATH
set -x USE_CCACHE=1
$ exec fish -l
$ cd ~/android/lineage
$ repo init -u https://github.com/LineageOS/android.git -b lineage-15.1
$ # alternatively, follow https://mirrors.tuna.tsinghua.edu.cn/help/lineageOS/
$ repo sync
$ source ~/virtualenv/py2/bin/activate
$ source build/envsetup.sh
$ breakfast angler
$ vim ~/.config/fish/config.fish
$ ccache -M 50G
$ cd ~/android/lineage/device/huawei/angler
$ ./extract-files.sh
# Plug in Nexus 6P, maybe over ssh, see my another post
$ cd ~/android/lineage
$ croot
$ brunch angler
$ # Endless waiting... (for me, more than 2 hrs)

编写 eBPF 程序和利用 HyperLogLog 统计包的信息

前段时间在写概率论与数理统计的期末论文,讨论的主题是如何对一个十分巨大的多重集合(或者是流)中相异元素个数进行估计,写的是 HyperLogLog 等算法。联想到前段时间 LWN 上多次提到的 eBPF 和 BCC 的文章,我准备自己用 eBPF 实现一个高效的估计 inbound packet 中来相异源地址的个数和 outbound packet 中相异目的地址的个数。经过了许多的尝试和努力,最终是写成了 jiegec/hll_ebpf ,大致原理如下:

由于 eBPF 是一个采用专用的 bytecode 并且跑在内核中的语言,虽然我们可以用 clang 写 C 语言然后交给 LLVM 生成相应地 eBPF bytecode,但仍然收到许多的限制。而且,我很少接触 Linux 内核开发,于是在找内核头文件时费了一番功夫。首先是核心代码:

struct bpf_map_def SEC("maps") hll_ebpf_out_daddr = {
    .type = BPF_MAP_TYPE_PERCPU_ARRAY,
    .key_size = sizeof(u32),
    .value_size = sizeof(u32),
    .max_entries = 256,
    .pinning = 2 // PIN_GLOBAL_NS
};

SEC("out_daddr")
int bpf_out_daddr(struct __sk_buff *skb) {
  u32 daddr = get_daddr(skb);
  u32 hash = Murmur3(daddr, 0);
  update_hll(&hll_ebpf_out_daddr, hash);
  return 0;
}

首先是声名一个类型为 PERCPU_ARRAY 的 eBPF MAP 类型。这里的 MAP 不是字典,Array 才是真是的数据结构,只不过提供的 API 是类似于字典的。SEC 宏则是指定这个东西要放在哪一个段,这个在后面会提到。这个函数的作用就是,获取 IP 包的目的地址(其实应该判断一下是否是 IPv4 的),然后根据 HyperLogLog 的要求,进行哈希(这里采用的是 Murmur3),然后对得到的哈希值分段,前一部分用于索引,后一部分的 nlz(clz, whatever)用于估计。具体算法详情可以参考 HyperLogLog 的论文。

接着,我们可以把这个 eBPF 函数进行编译,并且应用起来:

$ export KERN=4.16.0-2 # or use uname -r with awk, see Makefile
$ clang -O2 -I /usr/src/linux-headers-${KERN}-common/include -I /usr/src/linux-headers-${KERN}-common/arch/x86/include -emit-llvm -c bpf.c -o - | llc -march=bpf -filetype=obj -o bpf.o
$ export IFACE=en0 
$ sudo tc qdisc add dev ${IFACE} clsact || true
$ sudo tc filter del dev ${IFACE} egress
$ sudo tc filter add dev ${IFACE} egress bpf obj bpf.o sec out_daddr
$ sudo tc filter del dev ${IFACE} ingress
$ sudo tc filter add dev ${IFACE} ingress bpf obj bpf.o sec in_saddr

我们需要在用户态读出上面这个 MAP 中的内容。由于它是全局的,我们可以在 /sys/fs/bpf/tc/globals 中找到他们。然后,把统计得到的数据进行综合,得到结果:

void read_file(const char *file) {
  int fd = bpf_obj_get(file);
  const static int b = 6;
  const static int m = 1 << b;
  int M[m] = {0};
  int V = 0;
  double sum = 0;
  for (unsigned long i = 0; i <m; i++) {
    unsigned long value[2] = {0};
    bpf_map_lookup_elem(fd, &i, &value);
    M[i] = value[0] > value[1] ? value[0] : value[1]; // assuming 2 CPUs, will change later
    if (M[i] == 0)
      V++;
    sum += pow(2, -M[i]);
  }
  double E = 0.709 * m * m / sum;
  if (E <= 5 * m / 2) {
    if (V != 0) {
      E = m * log(1.0 * m / V);
    }
  } else if (E> pow(2, 32) / 30) {
    E = -pow(2, 32) * log(1 - E / pow(2, 32));
  }
  printf("%ld\n", lround(E));
}

可以手动通过 nmap 测试,例如扫描一个段,可以看到数据会增长许多。如果扫描相同的段,则数字不会变化,但如果扫描新的段,数字会有变化。这是一个 利用了 eBPF 的 HyperLogLog 的实现。

调整 Nginx 和 PHP 的上传文件大小限制

之前迁移的 MediaWiki,有人提出说无法上传一个 1.4M 的文件。我去看了一下网站,上面写的是限制在 2M,但是一上传就说 Entity Too Large,无法上传。后来经过研究,是 Nginx 对 POST 的大小进行了限制,同时 PHP 也有限制。

Nginx 的话,可以在 nginx.conf 的 http 中添加,也可以在 server 或者 location 中加入这么一行:

client_max_body_size 100m;

我的建议是,尽量缩小范围到需要的地方,即 location > server > http。

在 PHP 中,则修改 /etc/php/7.0/fpm/php.ini:

post_max_size = 100M

回到 MediaWiki 的上传页面,可以看到显示的大小限制自动变成了 100M,这个是从 PHP 的配置中直接获得的。

最近写 Node.js 遇到的若干坑

最近在做前后端分离,前端在用 Vue.js 逐步重写,后端则变为 api 的形式。同时,我尝试了用 autocannon 和 clinic 工具测试自己的 api endpoint 的性能,一开始发现有几个延迟会特别高,即使是一个很简单的 api 也有不正常的高延迟。

于是,我用 clinic 生成了 flamegraph,发现了一些问题:

  1. 我在 session 里保存了一些缓存的信息,这部分内容比较大,express-session 在保存到数据库前会先 JSON.stringify 再 crc 判断是否有改变,如果有改变则保存下来。但是由于我的这个对象嵌套层数多,所以时间花得很多。我调整了这个对象的结构,缩小了很多以后,果然这部分快了很多
  2. 有一个 API 需要大量的数据库查询,原本是 O(结点总数)次查询,我考虑到我们数据的结构,改成了 O(深度),果然快了许多
  3. 之前遇到一个小问题,就是即使我没有登录,服务器也会记录 session 并且返回一个 cookie。检查以后发现,是 connect-flash 即使在没有使用的时候,也会往 cookie 中写入一个空的对象,这就导致 express-session 认为需要保存,所以出现了问题。解决方案就是,换成了它的一个 fork:connect-flash-plus,它解决了这个问题

在 Nginx 将某个子路径反代

现在遇到这么一个需求,访问根下面是提供一个服务,访问某个子路径(/abc),则需要提供另一个服务。这两个服务处于不同的机器上,我们现在通过反代把他们合在一起。在配置这个的时候,遇到了一些问题,最后得以解决。

upstream root {
    server 1.2.3.4:1234;
}
upstream subpath {
    server 4.3.2.1:4321;
}

server {
    listen 443 ssl;
    server_name test.example.com;

    # the last slash is useful, see below
    location /abc/ {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        # the last slash is useful too, see below
        proxy_pass http://subpath/;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://root;
    }
}

由于并不想 subpath 他看到路径中 /abc/ 这一层,导致路径和原来在根下不同,通过这样配置以后,特别是两个末尾的斜杠,可以让 nginx 把 GET /abc/index.html 改写为 GET /index.html,这样我们就可以减少许多配置。当然,我们还是需要修改一下配置,现在是 host 在一个新的域名的一个新的子路径下,这主要是为了在返回的页面中,连接写的是正确的。

向 Nexus 6P 中刷入 LineageOS 实践

Nexus 6P 自带的系统没有允许 Root,所以需要自己解锁 bootloader 并且刷上别的系统。我选择了 LineageOS。Nexus 6P 的代号为 angler,首先可以找到官方的安装教程

我们需要下载的东西:

$ wget https://mirrorbits.lineageos.org/full/angler/20180521/lineage-15.1-20180521-nightly-angler-signed.zip
$ wget https://mirrorbits.lineageos.org/full/angler/20180521/lineage-15.1-20180521-nightly-angler-signed.zip?sha256 -O lineage-15.1-20180521-nightly-angler-signed.zip.sha256
$ wget https://mirrorbits.lineageos.org/su/addonsu-15.1-arm64-signed.zip
$ wget https://mirrorbits.lineageos.org/su/addonsu-15.1-arm64-signed.zip?sha256 -O addonsu-15.1-arm64-signed.zip
$ wget https://github.com/opengapps/arm64/releases/download/20180527/open_gapps-arm64-8.1-full-20180527.zip
$ wget https://github.com/opengapps/arm64/releases/download/20180527/open_gapps-arm64-8.1-full-20180527.zip.md5
$ wget https://dl.twrp.me/angler/twrp-3.2.1-0-angler.img
$ wget https://dl.twrp.me/angler/twrp-3.2.1-0-angler.img.asc
$ wget https://dl.twrp.me/angler/twrp-3.2.1-0-angler.img.md5
$ gpg --verify *.asc
$ md5sum -c *.md5
$ sha256sum -c *.sha256

其中 Open GApps 可以自己考虑选择 full 还是其它的选择。

接下来,按照教程,先解锁 bootloader。连接手机,进入 USB Debugging Mode,重启进入 bootloader 并且解锁:

$ adb reboot bootloader
$ fastboot flashing unlock
# Confirm unlocking, and then the data should be wiped

接下来刷入 TWRP。还是进入 bootloader,然后刷入。

$ fastboot flash recovery twrp-3.2.1-0-angler.img
# Select recovery, and enter it

进入 TWRP 后,把我们刚刚下载的 zip 文件都 push 到手机上,并用 TWRP 安装:

# Select Wipe -> Advanced Wipe, Select Cache, System and Data and wipe then
# Install lineageos, opengapps, addonsu and follow on-screen instructions
# Reboot into system

经过一段时间的等待,LineageOS 就安装成功了。但是遇到了一些问题:

  1. 开机时提示 vendor image 版本与打包 LineagesOS 时采用的版本不同。 于是我下载了官方的 factory image,找到其中的 vendor.img,用 TWRP 刷到了 vendor 分区中。并且执行了 flash-bash.sh 更新 bootloader 和 radio。重启的时候这个错误就解决了。2018-06-12 更新 注意:不要下载 Driver Binaries 里面的 vendor, 刷上去系统还是提示版本 mismatch,建议还是下载完整的 factory 镜像。
  2. 检测不到 SIM 卡。 回到 bootloader 看 Barcode, 是有 IMEI 等信息的,说明分区没有被写坏。在网上搜索一段时间以后,发现禁用登录密码重启一次后即可使用,之后把密码加回来即可。

在 WSL 上开启一个 getty 到串口的方法

为了测试一个硬件的 terminal,想在 Windows 上向串口开一个 tty,跑各种软件来测试。这件事情在 Linux 上和 macOS 上都有实践,但一直不知道 Windows 上怎么搞。经过了一番搜索,找到了 https://blogs.msdn.microsoft.com/wsl/2017/04/14/serial-support-on-the-windows-subsystem-for-linux/ 和 https://unix.stackexchange.com/a/123559 的方案。

以 COM5 为例:

$ sudo chmod 666 /dev/ttyS5
$ sudo agetty -s 115200 ttyS5 linux

这样就可以看到一个登录的界面了。

在 macOS 上 (https://superuser.com/questions/1059744/serial-console-login-on-osx):

$ screen /dev/tty.SLAB_USBtoUART 115200
# type C-b : exec ::: /usr/libexec/getty std.115200

体验 Fedora on RISCV

看到 RISCV 很久了,但一直没能体验。最近工具链不断更新,QEMU 在 2.12.0 也正式加入了 riscv 的模拟。但是自己编译一个内核又太麻烦,就找到了 Fedora 做的 RISCV port,下载下来试用了一下。之前试过一次,但是遇到了一些问题,刚才总算是成功地搞出来了。

官方文档地址:https://fedorapeople.org/groups/risc-v/disk-images/readme.txt 首先下载 https://fedorapeople.org/groups/risc-v/disk-images/ 下的 bbl vmlinux 和 stage4-disk.img.xz 三个文件,然后解压 stage4-disk.img.xz,大约有 5G 的样子。之前作者在脚本里作死开得特别大,导致我以前光是解压这一步就成功不了。现在终于解决了。

然后启动 qemu 命令打开虚拟机:

qemu-system-riscv64 \
  -nographic \
  -machine virt \
  -m 2G \
  -kernel bbl \
  -object rng-random,filename=/dev/urandom,id=rng0 \
  -device virtio-rng-device,rng=rng0 \
  -append "console=ttyS0 ro root=/dev/vda" \
  -device virtio-blk-device,drive=hd0 \
  -drive file=stage4-disk.img,format=raw,id=hd0 \
  -device virtio-net-device,netdev=usernet \
  -netdev user,id=usernet,hostfwd=tcp::10000-:22

这段命令摘自 readme.txt,区别只在于把 -smp 4 去掉了。不知道为什么不能正常工作,可能和作者提到的 FPU patch 有关。然后系统就可以正常起来了(firewalld 和 systemd-logind 不止为啥起不来,但是不用管)。

可以验证一下我们的系统:

$ uname -a
Linux stage4.fedoraproject.org 4.15.0-00046-g48fb45691946 #27 SMP Mon May 14 08:25:14 UTC 2018 riscv64 riscv64 riscv64 GNU/Linux

在 VMware ESXi 上部署 vCSA 实践

首先获取 vCSA 的 ISO 镜像,挂载到 Linux 下(如 /mnt),然后找到 /mnt/vcsa-cli-installer/templates/install 下的 embedded_vCSA_on_ESXi.json,复制到其它目录并且修改必要的字段,第一个 password 为 ESXi 的登录密码,一会在安装的过程中再输入。下面有个 deployment_option,根据你的集群大小来选择,我则是用的 small。下面配置这台机器的 IP 地址,用内网地址即可。下面的 system_name 如果要写 fqdn,记得要让这个域名可以解析到正确的地址,不然会安装失败,我因此重装了一次。下面的密码都可以留空,在命令行中输入即可。SSO 为 vSphere Client 登录时用的密码和域名,默认用户名为 Administrator@domain_name (默认的话,则是 Administrator@vsphere.local) 这个用户名在安装结束的时候也会提示。下面的 CEIP 我选择关闭,设置为 false。

接下来进行安装。

$ /mnt/vcsa-cli-installer/lin64/vcsa-deploy install /path/to/embedded_vCSA_on_ESXi.json --accept-eula

一路输入密码,等待安装完毕即可。然后通过 443 端口进入 vSphere Client, 通过 5480 端口访问 vCSA 的管理页面。两个的密码可以不一样。

2018-05-21 Update: 想要设置 VMKernel 的 IPv6 网关的话,ESXi 中没找到配置的地方,但是在 vSphere Client 中可以进行相关配置。

在脚本中寻找 X11 的 DISPLAY 和 XAUTHORITY

之前在搞一个小工具,在里面需要访问 X11 server,但是访问 X11 server 我们需要两个东西:DISPLAY 和 XAUTHORITY 两个环境变量。但是,由于它们在不同的发型版和 Display Manager 下都有些不同,所以花了不少功夫才写了一些。

为了验证我们是否可以连上 X11 server,我们使用这一句:

DIMENSIONS=$(xdpyinfo | grep 'dimensions:' | awk '{print $2;exit}')

它尝试打开当前的 DISPLAY,并且输出它的分辨率。接下来,我对不同的一些发型版,综合网上的方法,尝试去找到正确的环境变量。

对于 Debian:

DISPLAY=$(w -hs | awk -v tty="$(cat /sys/class/tty/tty0/active)" '$2 == tty && $3 != "-" {print $3; exit}')
USER=$(w -hs | awk -v tty="$(cat /sys/class/tty/tty0/active)" '$2 == tty && $3 != "-" {print $1; exit}')
eval XAUTHORITY=~$USER/.Xauthority
export DISPLAY
export XAUTHORITY
DIMENSIONS=$(xdpyinfo | grep 'dimensions:' | awk '{print $2;exit}')

对于 Archlinux:

DISPLAY=$(w -hs | awk 'match($2, /:[0-9]+/) {print $2; exit}')
USER=$(w -hs | awk 'match($2, /:[0-9]+/) {print $1; exit}')
eval XAUTHORITY=/run/user/$(id -u $USER)/gdm/Xauthority
export DISPLAY
export XAUTHORITY
DIMENSIONS=$(xdpyinfo | grep 'dimensions:' | awk '{print $2;exit}')

最后一种情况很粗暴的,直接找进程拿:

XAUTHORITY=$(ps a | awk 'match($0, /Xorg/) {print $0; exit}' | perl -n -e '/Xorg.*\s-auth\s([^\s]+)\s/ && print $1')
PID=$(ps a | awk 'match($0, /Xorg/) {print $1; exit}')
DISPLAY=$(lsof -p $PID | awk 'match($9, /^\/tmp\/\.X11-unix\/X[0-9]+$/) {sub("/tmp/.X11-unix/X",":",$9); print $9; exit}')
export DISPLAY
export XAUTHORITY
DIMENSIONS=$(xdpyinfo | grep 'dimensions:' | awk '{print $2;exit}')

中间混用了大量的 awk perl 代码,就差 sed 了。牺牲了一点可读性,但是开发起来比较轻松。

在 macOS 和 Linux 之间搭建 tinc 网络

一直听说 tinc 比较科学,所以尝试自己用 tinc 搭建一个网络。这里,macOS 这段没有固定 IP 地址,Linux 机器有固定 IP 地址 linux_ip。假设网络名称为 example , macOS 端名为 macos 地址为 192.168.0.2, linux 端名为 linux 地址为 192.168.0.1。

2018-11-11 注:本文用的 tinc 版本为 1.0.x,而不是 1.1-pre,两个分支命令不同,但协议可以兼容。

在 macOS 上配置:

brew install tinc
mkdir -p /usr/local/etc/tinc/example

新建 /usr/local/etc/tinc/example/tinc.conf:

Name = macos
Device = utun0 # use an unused number
ConnectTo = linux

编辑 /usr/local/etc/tinc/example/tinc-up:

#!/bin/sh
ifconfig $INTERFACE 192.168.0.2 192.168.0.1 mtu 1500 netmask 255.255.255.255

和 /usr/local/etc/tinc/example/tinc-down:

#!/bin/sh
ifconfig $INTERFACE down

还有 /usr/local/etc/tinc/example/subnet-up:

#!/bin/sh
[ "$NAME" = "$NODE" ] && exit 0
/usr/local/opt/iproute2mac/bin/ip route add $SUBNET dev $INTERFACE

以及 /usr/local/etc/tinc/example/subnet-down:

#!/bin/sh
[ "$NAME" = "$NODE" ] && exit 0
/usr/local/opt/iproute2mac/bin/ip route del $SUBNET dev $INTERFACE

然后将它们都设为可执行的:

chmod +x tinc-up
chmod +x tinc-down
chmod +x subnet-down
chmod +x subnet-down

编辑 /usr/local/etc/tinc/example/macos:

Port = 655
Subnet = 192.168.0.1/24

执行 tincd -n example -K 生成密钥。

到 Linux 机器上: 编辑以下文件:

$ mkdir -p /etc/tinc/example/hosts
$ cat /etc/tinc/example/tinc.conf
Name = linux
$ cat /etc/tinc/example/tinc-up
$!/bin/sh
ip link set $INTERFACE up
ip addr add 192.168.0.1/24 dev $INTERFACE
$ cat /etc/tinc/example/tinc-down
$!/bin/sh
ip addr del 192.168.0.1/24 dev $INTERFACE
ip link set $INTERFACE down
$ cat /etc/tinc/example/hosts/linux
Address = linux_ip
Port = 655
Subnet = 192.168.0.1/24
$ tincd -n example -K

接着,把 linux 上 /etc/tinc/example/hosts/linux 拷贝到 macos 的 /usr/local/etc/tinc/example/hosts/linux,然后把 macos 上 /usr/local/etc/tinc/example/hosts/macos 拷贝到 /etc/tinc/example/hosts/macos。在两台机器上都 tinc -n example -D -d3 即可看到连接的建立,通过 ping 即可验证网络建立成功。

2018-05-29 Update: Android 上,利用 Tinc GUI 也可以把 Tinc 运行起来,只是配置不大一样:

$ cat tinc.conf
Name = example
Device = /dev/tun
Mode = switch
ConnectTo = remote
ScriptsInterpreter = /system/bin/sh
$ cat tinc-up
#!/bin/sh
ip link set $INTERFACE up
ip addr add local_ip/24 dev $INTERFACE
$ cat tinc-down
#!/bin/sh
ip addr del local_ip/24 dev $INTERFACE
ip link set $INTERFACE down
$ cat subnet-up
$!/bin/bash
[ "$NAME" = "$NODE" ] && exit 0
ip route add $SUBNET dev $INTERFACE metric $WEIGHT table local
$ cat subnet-down
#!/bin/bash
[ "$NAME" = "$NODE" ] && exit 0
ip route del $SUBNET dev $INTERFACE table local

注意 table local 的使用。需要 Root。

搭建 FTP server behind NAT

我们出现新的需求,要把以前的 FTP 服务器迁移到 NAT 之后的一台机器上。但是,FTP 不仅用到 20 21 端口,PASV 还会用到高端口,这给端口转发带来了一些麻烦。我们一开始测试,直接在 Router 上转发 20 和 21 端口到 Server 上。但是很快发现,Filezilla 通过 PASV 获取到地址为(内网地址,端口高 8 位,端口低 8 位),然后,Filezilla 检测出这个地址是内网地址,于是转而向 router_ip:port 发包,这自然是不会得到结果的。

此时我们去网上找了找资料,找到了一个很粗暴的方法:

iptables -A PREROUTING -i external_interface -p tcp -m tcp --dport 20 -j DNAT --to-destination internal_ip:20
iptables -A PREROUTING -i external_interface -p tcp -m tcp --dport 21 -j DNAT --to-destination internal_ip:21
iptables -A PREROUTING -i external_interface -p tcp -m tcp --dport 1024:65535 -j DNAT --to-destination internal_ip:1024-65535

有趣地是,macOS 自带的 ftp 命令(High Sierra 似乎已经删去)可以正常使用。研究发现,它用 EPSV(Extended Passive Mode)代替 PASV,这里并没有写内网地址,因而可以正常使用。

这么做,Filezilla 可以成功访问了。但是,用其它客户端的时候,它会直连那个内网地址而不是 Router 的地址,于是还是连不上。而且,使用了 1024-65535 的所有端口,这个太浪费而且会影响我们其它的服务。

我们开始研究我们 FTP 服务器 (pyftpdlib) 的配置。果然,找到了适用于 FTP behind NAT 的相关配置:

     - (str) masquerade_address:
        the "masqueraded" IP address to provide along PASV reply when
        pyftpdlib is running behind a NAT or other types of gateways.
        When configured pyftpdlib will hide its local address and
        instead use the public address of your NAT (default None).
     - (dict) masquerade_address_map:
        in case the server has multiple IP addresses which are all
        behind a NAT router, you may wish to specify individual
        masquerade_addresses for each of them. The map expects a
        dictionary containing private IP addresses as keys, and their
        corresponding public (masquerade) addresses as values.
     - (list) passive_ports:
        what ports the ftpd will use for its passive data transfers.
        Value expected is a list of integers (e.g. range(60000, 65535)).
        When configured pyftpdlib will no longer use kernel-assigned
        random ports (default None).

于是,我们配置了 masquerade_address 使得 FTP 服务器会在 PASV 中返回 Router 的地址,并且在 passive_ports 中缩小了 pyftpdlib 使用的端口范围。

进行配置以后,我们在前述的 iptables 命令中相应修改了端口范围,现在工作一切正常。

使用 Nginx 转发 VMware ESXi

我们的 VMware ESXi 在一台 NAT Router 之后,但是我们希望通过域名可以直接访问 VMware ESXi。我们首先的尝试是,把 8443 转发到它的 443 端口,比如:

socat TCP-LISTEN:8443,reuseaddr,fork TCP:esxi_addr:443

它能工作地很好(假的,如果你把 8443 换成 9443 它就不工作了),但是,我们想要的是,直接通过 esxi.example.org 就可以访问它。于是,我们需要 Nginx 在其中做一个转发的功能。在这个过程中遇到了很多的坑,最后终于是做好了(VMware Remote Console 等功能还不行,需要继续研究)。

首先讲讲为啥把 8443 换成 9443 不能工作吧 -- 很简单,ESXi 的网页界面会请求 8443 端口。只是恰好我用 8443 转发到 443,所以可以正常工作。这个很迷,但是测试的结果确实如此。VMware Remote Console 还用到了别的端口,我还在研究之中。

来谈谈怎么配置这个 Nginx 转发吧。首先是 80 跳转 443:

server {
        listen 80;
        listen 8080;
        server_name esxi.example.org;

        return 301 https://$host$request_uri;
}

这个很简单,接下来是转发 443 端口:

server {
        listen 443 ssl;
        server_name esxi.example.org;
        ssl_certificate /path/to/ssl/cert.pem;
        ssl_certificate_key /path/to/ssl/key.pem;

        location / {
                proxy_pass https://esxi_addr;
                proxy_ssl_verify off;
                proxy_ssl_session_reuse on;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}

此时,打开 https://esxi.example.org 就能看到登录界面了。但是仍然无法登录。从 DevTools 看错误,发现它请求了 8443 端口。于是进行转发:

server {
        listen 8443 ssl;
        server_name esxi.example.org;
        ssl_certificate /path/to/ssl/cert.pem;
        ssl_certificate_key /path/to/ssl/key.pem;


        location / {
                if ($request_method = 'OPTIONS') {
                        add_header 'Access-Control-Allow-Origin' 'https://esxi.example.org';
                        add_header 'Access-Control-Allow-Credentials' 'true';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Max-Age' 1728000;
                        add_header 'Access-Control-Allow-Headers' 'VMware-CSRF-Token,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Cookie,SOAPAction';
                        add_header 'Content-Type' 'text/plain; charset=utf-8';
                        add_header 'Content-Length' 0;
                        return 204;
                }

                add_header 'Access-Control-Allow-Origin' 'https://esxi.example.org';
                add_header 'Access-Control-Allow-Credentials' 'true';
                proxy_pass https://esxi_addr:443;
                proxy_ssl_verify off;
                proxy_ssl_session_reuse on;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
        }
}

主要麻烦的是配置 CORS 的相关策略。我也是看了 DevTools 的错误提示半天才慢慢写出来的。这样配置以后,就可以成功登录 VMware ESXi 了。

20:02 更新:现在做了 WebSocket 转发,目前可以在浏览器中打开 Web Console 了。但是,在访问 https://esxi.example.org/ 的时候还是会出现一些问题,然而 https://esxi.example.org:8443/ 是好的。

转发 WebSocket:

map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
}

server {
        listen 8443 ssl;
        server_name esxi.example.org;
        ssl_certificate /path/to/ssl/cert.pem;
        ssl_certificate_key /path/to/ssl/key.pem;


        location / {

                if ($request_method = 'OPTIONS') {
                        add_header 'Access-Control-Allow-Origin' 'https://esxi.example.org';
                        add_header 'Access-Control-Allow-Credentials' 'true';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Max-Age' 1728000;
                        add_header 'Access-Control-Allow-Headers' 'VMware-CSRF-Token,DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Cookie,SOAPAction';
                        add_header 'Content-Type' 'text/plain; charset=utf-8';
                        add_header 'Content-Length' 0;
                        return 204;
                }

                add_header 'Access-Control-Allow-Origin' 'https://esxi.example.org' always;
                add_header 'Access-Control-Allow-Credentials' 'true' always;

                proxy_pass https://esxi_addr:443;
                proxy_ssl_verify off;
                proxy_ssl_session_reuse on;
                proxy_set_header Host $http_host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
        }
}

20:29 更新:找到了 VMware Remote Console 的端口:902,用 iptables 进行 DNAT 即可:

iptables -A PREROUTING -i wan_interface -p tcp -m tcp --dport 902 -j DNAT --to-destination esxi_addr:902

2018-05-09 08:07 更新:最后发现,还是直接隧道到内网访问 ESXi 最科学。或者,让 443 重定向到 8443:

server {
        listen 443 ssl;
        server_name esxi.example.org;
        ssl_certificate /path/to/ssl/cert.pem;
        ssl_certificate_key /path/to/ssl/key.pem;

        return 301 https://$host:8443$request_uri;
}
这样,前面也不用写那么多 CORS 的东西了。

使用 iptables 和策略路由进行带源地址的 forwarding

陈老师打开他的服务器,突然发现 CPU 莫名高负载,然后发现是有一个用户被远程登录拿来挖矿了。但是这台机器在 NAT 后,所以登录的源地址全是 NAT 路由,所以不知道对方的地址是什么。我们为了能使用 fail2ban 来禁用多次尝试失败的 IP,但又不想因为别人把 NAT 路由的地址给禁了,这样我们自己也用不了了。所以必须要让这台机器能够知道 ssh 的源地址,我们现在简单的 socat 方案不能满足这个需求。

需求:

  1. 可以在外网连 NAT 路由的高端口(如 2222)来访问这台机器。
  2. 在内网中,既可以直接连它的内网地址,也可以连 NAT 路由的高端口来访问这台服务器。此时,由于连 ssh 的机器就在同一个子网中,如果保留了源地址,服务器发的包会直接回来不经过 NAT。所以我们还是保留了 socat 的方案。

实现方法:

在 NAT Router 上配置 DNAT,这样发到 NAT Router 上的包就可以转发到服务器上:

iptables -t nat -A PREROUTING -i external_interface -p tcp -m tcp --dport 2222 -j DNAT --to-destination internal_server_ip:22

但是,从服务器回来的包到了 NAT Router 上后,由于路由表的配置问题,默认的路由并不能把包送达对方。

方法 1: 我们首先给包打上 mark:

iptables -t mangle -A PREROUTING -i internal_interface -p tcp -m tcp --sport 22 -j MARK --set-mark 0x2222

然后配置策略路由:

ip rule add fwmark 0x2222 table 2222
ip route add table 2222 default via gateway_address

方法 2: (UPD 2018-07-07) 利用 ip rule 直接达成同样的效果

ip rule add from internal_ip/prefix table 2222
# or
ip rule add iif internal_interface table 2222
ip route add table 2222 default via gateway_address

这样就可以保证 ssh 的回包可以原路返回了。

由于前面提到的原因,上面我们配置的 DNAT 规则只对外网过来的包有效。为了内网的访问,我们仍然采用了 socat 的方式:

socat TCP-LISTEN:2222,reuseaddr,fork TCP:internal_server_ip:22

从不同的机器测试,都可以在 who 看到,地址确实是我们想看到的源地址。接下来配置 fail2ban即可。

利用 UPnP 协议进行 mosh NAT 穿透的研究

由于经常要从宿舍、教室等不同的 Wi-Fi 之间切换,但是 ssh 连接又总是断,所以想用 mosh 代替 ssh。但是 mosh 也有它的问题:

  1. 不能滚动。这个可以在 mosh 中嵌套一层 tmux 解决。我目前写了一些自动 mosh 后打开 tmux 并且开启鼠标支持的脚本,但还是有缺陷。
  2. 在高端口 60000+ 监听 UDP,这使得 NAT 后的服务器难以直接通过端口转发。如果直接转发到 NAT 后的机器,那么 NAT 后面如果有多台机器,这又失效了。

于是找了找网上的 NAT 穿透的一些文章,看到了 UPnP 的方法。大致就是,用户可以向路由器注册一个临时的转发规则,路由会自动在 iptables 上配置转发。但是,这样也会遇到一个问题:路由上的 mosh-server 不知道这个转发的存在,所以它可能会尝试监听同样的端口。解决方案下面会提到。

需求:

Server <---> NAT Router <---> My Laptop
On NAT Router, port 8022 is forwarded to Server:22
1. mosh router # works
2. mosh --ssh="ssh -p 8022" router # works

首先在 NAT Router 上配置 miniupnpd(以 Debian 为例)

sudo apt install miniupnpd
# you will get a dialog upon installation
# input your wan interface and listening ip accordingly
sudo vim /etc/default/miniupnpd
# edit START_DAEMON=0 to START_DAEMON=1
sudo vim /etc/miniupnpd/miniupnpd.conf
# edit ext_ifname, listening_ip accordingly
# set secure_mode=yes
# add 'allow 60000-60023 internal_ip/prefix 60000-60023'
# before the last line 'deny 0-65535 0.0.0.0/0 0-65535'
sudo systemctl enable --now miniupnpd

现在,复制 我修改的 mosh-wrapper.js 到用户的 home 目录下,在 Server 安装 miniupnpc 然后通过:

mosh --ssh="ssh -p 8022" --server=~/mosh-wrapper.js user@router

这样,mosh 首先会通过 ssh 和 Server 协商一个 AES 的密钥和 UDP 端口(如 60001),之后的通信都通过 UDP 端口走加密后的流量。我的 mosh-wrapper.js 通过 miniupnpc 向路由器请求把该 UDP 端口转发到 Server 上,这样, mosh-server 就能通过 NAT 路由穿透到后面的 Server 上。

等会!问题来了:

mosh 默认的 IP 范围是 60000-61000 ,根据我的观察,它会从 60001 开始尝试监听本机地址,如果已经被占用,则 60002, 60003, ... 但是!Router 和 Server 实际上占用了相同的端口空间,并且 mosh 只知道本机哪些端口被占用了,而不知道 Router 和 Server 共同占用了多少端口。

我想到了一些可能的解决方案:

  1. 在 Router 上让 miniupnpd 监听对应的端口,占住这个坑。这样,Router 上的 mosh-server 就不会用和 Server 相同的端口
  2. 如果有多个 Server,则会出现抢夺相同端口的情况。我目前的想法是,让 upnpc 去询问 Router 找空闲的端口,然后再传给 mosh-server 使用。另一种方法则是,给不同的 Server 划分不同的端口范围,比如 Router 用 60001-60005, 然后 Server1 用 60006-60010, Server2 用 60011-60015 如此下去。

然后,新的问题又发现了:

当我在和 Server 同一个子网的时候,由于 miniupnpd 配置的 iptables 规则中来源只有 WAN interface,所以我在内网发的包是不会被转发的。当然,既然在内网了,为啥不直接用内网 IP 呢,不知道 mosh 有没有提供设置备用 IP 的功能。

在 Archlinux 上用 winbind 配合 pam 配置 Windows AD 认证登录

作为不清真的网络管理员,为了配置一套完整的统一认证系统,陈老师采用了 Windows AD 的方法给这里配置统一认证。重装了系统,自然要把之前的统一认证再配到新装的 Archlinux 上。

参考资料: Active Directory Integration

首先安装相应的包:

pacman -S samba

我们还没有配好 Kerberos,所以跳过。

然后配置 /etc/samba/smb.conf,以下是一个例子。可以根据文档微调。

[global]
        security = ads
        realm = YOUR-AD-HERE
        workgroup = YOUR-GROUP-HERE
        idmap uid = 10000-20000
        idmap gid = 10000-20000
        winbind enum users = yes
        winbind enum groups = yes
        template homedir = /home/%D/%U
        template shell = /bin/bash
        client use spnego = yes
        client ntlmv2 auth = yes
        encrypt passwords = yes
        winbind use default domain = yes
        restrict anonymous = 2

这样,域上的用户 user 会拿到 home 目录为 /home/YOUR-DOMAIN-HERE/user,uid 在 10000-2000 范围内的用户。在一会经过配置之后,可以通过 getent passwd 验证。

接下来,需要把本机的 samba 登入到域的管理员,并且启动服务。

net ads join -U your-user-name
systemctl enable --now smb
systemctl enable --now nmb
systemctl enable --now winbind

更改 /etc/nsswitch.conf,在 passwd, shadow 和 group 都增添 winbind:

passwd: files mymachines systemd winbind
group: files mymachines systemd winbind
shadow: files winbind

接下来,可以进一步验证配置是否正确:

wbinfo -u
wbinfo -g
getent passwd
getent group
net ads info
net ads lookup

接下来可以配置 PAM 了。这一部分踩到了一些坑,现在终于做得差不多了。

需求:

  1. 如果一个用户名既有本地用户也有域上的用户,选择前者
  2. 用户要修改密码的话,如果是域用户,则要求走 Windows AD 那套方法改密码;否则仅修改本地用户密码。

实现:

修改 /etc/pam.d/system-auth:

第一部分:auth

auth [success=1 default=ignore]         pam_localuser.so
auth [success=2 default=die]            pam_winbind.so krb5_auth krb5_ccache_type=FILE cached_login try_first_pass
auth [success=1 default=die]            pam_unix.so nullok_secure
auth requisite                          pam_deny.so
auth optional                           pam_permit.so
auth required                           pam_env.so

首先利用 pam_localuser.so 匹配用户名和 /etc/passwd ,如果有, success=1 代表跳过下面一条规则,故会跳到 pam_unix.so 这一行。如果失败,default=ignore 表示忽略它的结果。如果是本地用户,匹配 pam_localuser.so 成功后跳到 pam_unix.so,如果成功了则跳到第五行,pam_permit.so 代表通过,最后由 pam_env.so 配置环境变量。如果是域用户,则由 pam_winbind.so 处理,如果成功,同样跳到第 5 条。如果本地用户和域用户都失败,就 pam_deny.so 认证失败。

第二部分:account

account required                        pam_unix.so
account [success=1 default=ignore]      pam_localuser.so
account required                        pam_winbind.so
account optional                        pam_permit.so
account required                        pam_time.so

这一部分仍有疑问。留待以后来补充。

第三部分:password

password [success=1 default=ignore]     pam_localuser.so
password [default=die]                  pam_echo.so file=/etc/pam.d/messages/ad_reject_change_passwd.txt
password optional                       pam_echo.so file=/etc/pam.d/messages/local_user_passwd.txt
password [success=1 default=die]        pam_unix.so sha512 shadow
password requisite                      pam_deny.so
password optional                       pam_permit.so

这里实现了我们的需求:如果是本地用户,提醒用户当前要修改的是本地用户的密码;如果是域用户,则输出信息后直接拒绝。

这里的 /etc/pam.d/messages/ad_reject_change_passwd.txt 内容如下:

Hi %u, please go to xxxxxxx to change your Active Directory password!

第四部分:session

session   required                      pam_limits.so
session   required                      pam_mkhomedir.so skel=/etc/skel/ umask=0022
session   required                      pam_unix.so
session   [success=1 default=ignore]    pam_localuser.so
session   required                      pam_winbind.so
session   optional                      pam_permit.so

这里与 Wiki 上内容无异。

然后修改 /etc/pam.d/passwd :

password        required        pam_cracklib.so difok=2 minlen=8 dcredit=2 ocredit=2 retry=3
password        include         system-auth
#password       requisite       pam_deny.so
#password       required        pam_unix.so sha512 shadow nullok

首先判断密码强度。通过后则直接用刚才更改的 system-auth 中的 password 部分规则。

这样就配好了认证。自己对这套东西的理解还不够深,以后遇到了要继续钻研。

扩展阅读: PAM 配置简介 - 王邈

在服务器上安装 Archlinux 记录

有一台服务器的 Ubuntu 挂了,我们想在上面重装一个 Archlinux。我们首先下载了 archlinux-2018.04.01 的 ISO, 直接 dd 到 U 盘上,但是遇到了问题。

首先遇到的问题是,一启动之后就会花屏。我们一开始怀疑是 NVIDIA 驱动的问题,于是想改 kernel param 但是发现,这个 ISO 是 hybrid 的,我们在 macOS 和 Windows 上都不能 mount 上这种类型的盘。于是我们选择自己搞分区表。我们把 U 盘插到电脑上,然后在 Linux 虚拟机内重新分区为 GPT,然后 mount 到 /mnt/usb,再重新下载 archlinux iso,不过此时刚好上游更新了 archlinux-2018.05.01 的影响。我们把 ISO 中根分区 mount 到 /mnt/iso 上来,然后 cp -a /mnt/iso/* /mnt/usb 。调整了 grub 中的内核参数,仍然无果。我们认为问题可能在显卡上,就把那张显卡拔下来了,果然显示就正常了,但是新的问题就来了。

一启动,fstab 尝试把 LABEL=ARCHISO_201805 挂在上来,但是失败。于是我们把 U 盘插到 mac 上,用 Disk Utility 给分区命了名,再插回去,然后这个 Live CD 的 Systemd 就成功起来了。接下来就是根据官方的 Installation Guide 进行安装各种东西。安装完后,在 /boot/EFI 的操作上也出现了一些问题,一开始忘记调用 grub-mkconfig ,导致重启以后进入 grub-rescue,所以又回到 Live CD 重新 grub-mkconfig 。同时对 systemd-networkd 也进行了相应的调整,这样开机以后可以配好网络。主要就是在网卡上配上两个 VLAN 和相应的 DHCP 和静态地址。

接下来对以前的东西进行迁移。主要就是按照十分详细的 Arch Wiki 进行相应的配置。由于空间所限,我们把原来的 home 目录直接 mount --bind 到 /home,但是不可避免地,会出现用户 id 不对应的问题。于是我们把需要用到的用户的 /etc/{passwd,group,shadow} 统统拷贝到新的系统的相应地方。然后是配置 winbind,就是按部就班地按照 Arch Wiki 和以前的配置进行更新,然后成功地把 AD 上的用户获取到。此时再次出现了 uid 不对应的问题,此时我们使用 chown -R user:user /home/user 的方法。

剩下的工作就是琐碎的安装各种常用软件。不必多说。

P.S. 我研究出了一个很好用的 mosh + tmux 的 fish function: (但是有时工作有时不工作,不明白什么回事)

function tmosh
    mosh $argv -- tmux new-session bash -c 'tmux set -g mouse on; tmux setw -g mode-keys vi; fish'
end

使用 Cisco AC + AP 组合搭建网络实践

有一台已配置好直接可用的 AC 在地址 ac-address。我们需要搭建交换机 + AP 的网络,并且用一台 Linux 服务器进行 DHCP 从而给 AP 分发 AC 的地址。这里以 systemd-networkd 为例。

我们约定,vlan 2 上联外网,vlan 3 为 Linux 服务器和 AP 的内部网络。

接下来,配置交换机给 Linux 服务器的端口为 trunk 口,然后将下联 Cisco AP 的端口都设为 access vlan 3 模式。接下来在 Linux 服务器上配置 DHCP 服务器和 NAT。

如果 Linux 服务器的 interface 名称为 eno1 :

配置两个 VLAN interface:

$ cat /etc/systemd/network/eno1.network
[Match]
Name=eno1

[Network]
VLAN=eno1.2
VLAN=eno1.3

相应添加 VLAN 配置:

$ cat /etc/systemd/network/eno1.2.network
[NetDev]
Name=eno1.2

[VLAN]
Id=2
$ cat /etc/systemd/network/eno1.3.network
[NetDev]
Name=eno1.3

[VLAN]
Id=3

配置上行的 eno1.2 interface 的静态地址:

$ cat /etc/systemd/network/eno1.2.network
[Match]
Name=eno1.2

[Network]
Address=123.123.123.123/24
Gateway=123.123.123.1
DNS=1.2.4.8

配置内部网络 eno1.3 interface:

$ cat /etc/systemd/network/eno1.3.network
[Match]
Name=eno1.3

[Network]
Address=192.168.1.1/24

配置 dhcpd (isc-dhcp-server):

$ cat /etc/default/isc-dhcp-server
INTERFACESv4="eno1.3"
$ cat /etc/dhcpd.conf
option space Cisco_LWAPP_AP;
option Cisco_LWAPP_AP.server-address code 241 = array of ip-address;

subnet 192.168.1.0 netmask 255.255.255.0 {
  range 192.168.1.100 192.168.1.200;
  option routers 192.168.1.1;
  vendor-option-space Cisco_LWAPP_AP;
  option Cisco_LWAPP_AP.server-address $ac-address;
}

配置 iptables 做 NAT:

iptables -t nat -A POSTROUTING -o eno1.2 -j MASQUERADE
iptables-save > /etc/iptables/iptables.rules

打开 ipv4 forwarding:

echo 1 > /proc/sys/net/ipv4/ip_forward
echo 'net.ipv4.conf.all.forwarding=1' >> /etc/sysctl.d/99-ipv4-forwarding.conf

把 GDB 降级到 8.0.1

在 macOS 上使用 GDB 需要 codesigning。但是在 GDB 升级到 8.1 后这种方法不知道为何失效了。所以我安装回了 GDB 8.0.1 并且重新 codesigning,现在又可以正常升级了。

对 Formula 进行 patch:

diff --git a/Formula/gdb.rb b/Formula/gdb.rb
index 29a1c590..25360893 100644
--- a/Formula/gdb.rb
+++ b/Formula/gdb.rb
@@ -1,14 +1,15 @@
 class Gdb < Formula
   desc "GNU debugger"
   homepage "https://www.gnu.org/software/gdb/"
-  url "https://ftp.gnu.org/gnu/gdb/gdb-8.1.tar.xz"
-  mirror "https://ftpmirror.gnu.org/gdb/gdb-8.1.tar.xz"
-  sha256 "af61a0263858e69c5dce51eab26662ff3d2ad9aa68da9583e8143b5426be4b34"
+  url "https://ftp.gnu.org/gnu/gdb/gdb-8.0.1.tar.xz"
+  mirror "https://ftpmirror.gnu.org/gdb/gdb-8.0.1.tar.xz"
+  sha256 "3dbd5f93e36ba2815ad0efab030dcd0c7b211d7b353a40a53f4c02d7d56295e3"

   bottle do
-    sha256 "43a6d6cca157ef70d13848f35c04e11d832dc0c96f5bcf53a43330f524b3ac40" => :high_sierra
-    sha256 "fe7c6261f9164e7a744c9c512ba7e5afff0e74e373ece9b5aa19d5da6443bfc2" => :sierra
-    sha256 "cd89001bcf8c93b5d6425ab91a400aeffe0cd5bbb0eccd8ab38c719ab5ca34ba" => :el_capitan
+    sha256 "e98ad847402592bd48a9b1468fefb2fac32aff1fa19c2681c3cea7fb457baaa0" => :high_sierra
+    sha256 "0fdd20562170c520cfb16e63d902c13a01ec468cb39a85851412e7515b6241e9" => :sierra
+    sha256 "f51136c70cff44167dfb8c76b679292d911bd134c2de3fef40777da5f1f308a0" => :el_capitan
+    sha256 "2b32a51703f6e254572c55575f08f1e0c7bc2f4e96778cb1fa6582eddfb1d113" => :yosemite
   end

   deprecated_option "with-brewed-python" => "with-python@2"