更改 macOS 屏幕亮度的按键
由于我打开了「Invert Fn」功能,所以需要调亮度的时候,是采用 Fn+F1/F2 的方法。但是,我的机械键盘则是,不按 Fn 时为 1-9,按着 Fn 时为对应的 F1-F9,但是就无法调整亮度和声音了。
然后捣腾了一下,发现可以用 ScLk 和 Pa/Br(名称在各个键盘上不大一样)调整亮度。不过,还没发现如何更改音量。。。
由于我打开了「Invert Fn」功能,所以需要调亮度的时候,是采用 Fn+F1/F2 的方法。但是,我的机械键盘则是,不按 Fn 时为 1-9,按着 Fn 时为对应的 F1-F9,但是就无法调整亮度和声音了。
然后捣腾了一下,发现可以用 ScLk 和 Pa/Br(名称在各个键盘上不大一样)调整亮度。不过,还没发现如何更改音量。。。
首先参照OpenWRT Wiki - Lenovo Y1 v1找到刷固件教程:
然后就是常规的密码设置,opkg 源设置为 tuna 的源,配置 ssh 和 公钥。
接下来,我们为了使用学校的 SLAAC,采用 ebtables 直接把学校的 IPv6 bridge 进来,而 IPv4 由于准入系统,需要 NAT。
参考 Bridge IPv6 connections to WAN,下载 v6brouter_openwrt.sh 到某个地方,然后修改一下里面的一些参数:
然后跑起来之后,自己的电脑可以成功拿到原生的 IPv6 地址了,不需要用难用的 NAT66 技术。
下一步是采用z4yx/GoAuthing。
$ go get -u -v github.com/z4yx/GoAuthing
$ cd $GOPATH/src/github.com/z4yx/GoAuthing/cli
$ env GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build main.go
$ mipsel-linux-gnu-strip main
$ scp main root@192.168.1.1:~/GoAuthing
$ ssh root@192.168.1.1
$ opkg install ca-certificates
$ ./GoAuthing
这里参考了解决 GO 语言编译程序在 openwrt(mipsle 架构) 上运行提示 Illegal instruction 问题,配置了 GOMIPS 环境变量。为了访问 HTTPS 网站,参考了OpenWRT Wiki - SSL and Certificates in wget。有毒的是,这个环境变量,在 macOS 上不能正常工作,而在 Linux 机子上是没有问题的。
然后就可以成功地跑起来 GoAuthing,解决了上校园网认证的问题。
感谢宇翔编写的 GoAuthing 小工具。
更新:简化了一下 v6brouter 脚本:
#!/bin/sh
BRIDGE=br-lan
WAN_DEV=$(/sbin/uci get network.wan.ifname)
WHITELIST1="00:11:22:33:44:55"
WHITELIST2="55:44:33:22:11:00"
brctl addbr $BRIDGE 2> /dev/null
brctl addif $BRIDGE $WAN_DEV
ip link set $BRIDGE down
ip link set $BRIDGE up
brctl show
ebtables -F
ebtables -P FORWARD ACCEPT
ebtables -L
uci set dhcp.lan.ra='disabled'
uci set dhcp.lan.dhcpv6='disabled'
uci commit
/etc/init.d/odhcpd restart
echo 2 > /proc/sys/net/ipv6/conf/$BRIDGE/accept_ra
ebtables -t broute -F
ebtables -t broute -A BROUTING -i $WAN_DEV -p ! ipv6 -j DROP
ebtables -t broute -A BROUTING -s $WHITELIST1 -p ipv6 -j ACCEPT
ebtables -t broute -A BROUTING -d $WHITELIST1 -p ipv6 -j ACCEPT
ebtables -t broute -A BROUTING -s $WHITELIST2 -p ipv6 -j ACCEPT
ebtables -t broute -A BROUTING -d $WHITELIST2 -p ipv6 -j ACCEPT
ebtables -t broute -A BROUTING -p ipv6 -j DROP
ebtables -t broute -L
注意,这里添加了两个 WHITELIST 的 MAC 地址,表示只让这两个 MAC 地址的设备访问 v6。一般来说,外面网关的 MAC 地址也要放进来,不然可能接收不到 RA。如果不需要白名单的话,可以去掉 ebtables 的后几行规则。
一直对 Linux 的启动很感兴趣,但对 initrd 和 initramfs 等概念不大了解,于是上网找了资料,自己成功地看到了现象。
参考资料:
具体步骤:
$ cat hello.c
#include <stdio.h>
#include <unistd.h>
int main() {
for (;;) {
printf("Hello, world!\n");
}
}
$ gcc -static hello.c -o init
$ echo init | cpio -o -H newc | gzip > initrd
$ qemu-system-x86_64 -kernel /boot/vmlinuz-linux -initrd initrd -nographic -append 'console=ttyS0'
# Use C-a c q u i t <Enter> to exit
可以看到过一会(三四秒?),可以看到满屏的 Hello world 在输出。
Debian Stretch 9.5 刚刚更新,自己手上有不少 stretch 的机器,于是顺手把他们都升级了。不过,这个过程比较繁琐,于是我采用了 MuSSH 的方法,让这个效率可以提高,即自动同时 SSH 到多台机器上进行更新。
首先编写 hostlist 文件,一行一个地址,分别对应每台机器。
然后采用 MuSSH 对每台机器执行同样的命令
此时要求,ssh 上去以后有相应的权限。这个有许多方法,不再赘述。然后就可以看到一台台机器升级,打上安全补丁,爽啊。
参考网站:
Ubuntu 使用 fbterm 无法打开 fb 设备的解决及 fcitx-fbterm 安装 Fcitx - ArchWiki 完美中文 tty, fbterm+yong(小小输入法) 让 linux console 支持中文显示和 fcitx 输入法
考虑到 lemote yeeloong 机器的 cpu 运算性能,跑一个图形界面会非常卡,于是选择直接用 framebuffer。但是,显示中文又成了问题。于是,采用了 fbterm 和 fcitx 配合,加上 gdm 的方法,完成了终端下的中文输入。
首先,安装相关的包:
接着,基于以上参考网站第一个,编写 zhterm 文件:
$ echo zhterm
#!/bin/bash
eval `dbus-launch --auto-syntax`
fcitx >/dev/null 2>&1
fbterm -i fcitx-fbterm
kill $DBUS_SESSION_BUS_PID
fcitx-remote -e
$ chmod +x zhterm
$ zhterm
# Use C-SPC to switch input source
另:找到一个映射 Caps Lock 到 Escape 的方案:
参考网站:
gNewSense To MIPS Run a TFTP server on macOS Debian on Yeeloong Debian MIPS port wiki Debian MIPS port
首先,进入设备的 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 服务器关了:
实测滚到 stretch 会挂。因为 stretch 虽然也有 mipsel 架构,但是它的 revision 与 Loongson-2f 不大一样,会到处出现 SIGILL 的问题,不可用。靠 jessie 和 jessie-backports 已经有不少的软件可以使用了。
之前使用 systemd-nspawn 开了容器,然后通过 machinectl shell 进去,想要起一个服务然后丢到后台继续执行,但是发现离开这个 session 后这个进程总是会被杀掉,于是找了 systemd-run 的方案,即:
这样可以直接在容器中跑服务,而且用这个命令输出的临时 server 名称,还可以看到日志:
现在遇到一个场景,原来的一个服务只给一个客户端用,但现在增加了一个客户端,由于客户端配置相同,但是服务端需要区别对待两个客户端的服务端配置,所以利用 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 刚刚发布,加入了我很想要的 Transaction 功能。不过,我一更新就发现 MongoDB 起不来了。研究了一下日志,发现由于我创建数据库时,MongoDB 版本是 3.4,虽然后来升级到了 3.6,但还是用着 3.4 的兼容模式。这个可以这样来检测:
如果不是 3.6,升级到 4.0 之前,需要先执行如下操作:
然后再升级到 MongoDB 4.0,才能正常地启动 MongoDB 4.0。之后可以考虑尝试使用 MongoDB 4.0 的 Transaction 了。不知道什么时候进入 Debian 的 stretch-backports 源中。
为了使用 MongoDB 4.0 的新特性,输入以下命令:
之后会尝试一下 MongoDB 4.0 的 Transaction 功能。
随着 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 HDL,以便以后造计算机。听学长们推荐了一本书叫《自己动手写 CPU》,由于自己手中只有很老的 Spartan-3 板子,手上没有可以用来试验的 FPGA,所以选择用 Verilog + Verilator 进行模拟。既然是模拟,自然是会有一定的问题,不过这个以后再说。
然后就是模仿着这本书的例子,写了指令的获取和指令的解码两部分很少很少的代码,只能解码 ori (or with immidiate) 这一个指令。然后,通过 verilator 跑模拟,输出 vcd 文件,再用 gtkwave 显示波形,终于能够看到我想要的结果了。能够看到,前一个时钟周期获取指令,下一个时钟周期进行解码,出现了流水线的结果。这让我十分开心。
接下来就是实现一些基本的算术指令,然后讲计算的结果写入到相应的寄存器中。这样做完之后,就可以做一个基于 verilator 的简易 A+B 程序了。
我的代码发布在jiegec/learn_verilog中。最近马上到考试周,可能到暑假会更频繁地更新吧。
实践了一下如何在 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)
前段时间在写概率论与数理统计的期末论文,讨论的主题是如何对一个十分巨大的多重集合(或者是流)中相异元素个数进行估计,写的是 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 的实现。
之前迁移的 MediaWiki,有人提出说无法上传一个 1.4M 的文件。我去看了一下网站,上面写的是限制在 2M,但是一上传就说 Entity Too Large,无法上传。后来经过研究,是 Nginx 对 POST 的大小进行了限制,同时 PHP 也有限制。
Nginx 的话,可以在 nginx.conf 的 http 中添加,也可以在 server 或者 location 中加入这么一行:
我的建议是,尽量缩小范围到需要的地方,即 location > server > http。
在 PHP 中,则修改 /etc/php/7.0/fpm/php.ini:
回到 MediaWiki 的上传页面,可以看到显示的大小限制自动变成了 100M,这个是从 PHP 的配置中直接获得的。
最近在做前后端分离,前端在用 Vue.js 逐步重写,后端则变为 api 的形式。同时,我尝试了用 autocannon 和 clinic 工具测试自己的 api endpoint 的性能,一开始发现有几个延迟会特别高,即使是一个很简单的 api 也有不正常的高延迟。
于是,我用 clinic 生成了 flamegraph,发现了一些问题:
现在遇到这么一个需求,访问根下面是提供一个服务,访问某个子路径(/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 自带的系统没有允许 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,然后刷入。
进入 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 就安装成功了。但是遇到了一些问题:
为了测试一个硬件的 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 为例:
这样就可以看到一个登录的界面了。
在 macOS 上 (https://superuser.com/questions/1059744/serial-console-login-on-osx):
看到 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 不止为啥起不来,但是不用管)。
可以验证一下我们的系统:
首先获取 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 server,但是访问 X11 server 我们需要两个东西:DISPLAY 和 XAUTHORITY 两个环境变量。但是,由于它们在不同的发型版和 Display Manager 下都有些不同,所以花了不少功夫才写了一些。
为了验证我们是否可以连上 X11 server,我们使用这一句:
它尝试打开当前的 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 了。牺牲了一点可读性,但是开发起来比较轻松。
一直听说 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 上配置:
新建 /usr/local/etc/tinc/example/tinc.conf:
编辑 /usr/local/etc/tinc/example/tinc-up:
和 /usr/local/etc/tinc/example/tinc-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
然后将它们都设为可执行的:
编辑 /usr/local/etc/tinc/example/macos:
执行 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 服务器迁移到 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 命令中相应修改了端口范围,现在工作一切正常。
我们的 VMware ESXi 在一台 NAT Router 之后,但是我们希望通过域名可以直接访问 VMware ESXi。我们首先的尝试是,把 8443 转发到它的 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,参考 WebSocket proxying:
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 的东西了。