搭建 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 ,这里并没有写内网地址,因而可以正常使用。

Read More

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

陈老师打开他的服务器,突然发现 CPU 莫名高负载,然后发现是有一个用户被远程登录拿来挖矿了。但是这台机器在 NAT 后,所以登录的源地址全是 NAT 路由,所以不知道对方的地址是什么。我们为了能使用 fail2ban 来禁用多次尝试失败的 IP ,但又不想因为别人把 NAT 路由的地址给禁了,这样我们自己也用不了了。所以必须要让这台机器能够知道 ssh 的源地址,我们现在简单的 socat 方案不能满足这个需求。 需求: 可以在外网连 NAT 路由的高端口(如2222)来访问这台机器。 在内网中,既可以直接连它的内网地址,也可以连 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 然后配置策略路由:

Read More

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

由于经常要从宿舍、教室等不同的 Wi-Fi 之间切换,但是 ssh 连接又总是断,所以想用 mosh 代替 ssh 。但是 mosh 也有它的问题: 不能滚动。这个可以在 mosh 中嵌套一层 tmux 解决。我目前写了一些自动 mosh 后打开 tmux 并且开启鼠标支持的脚本,但还是有缺陷。 在高端口 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 为例)

Read More

在 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 验证。

Read More

在服务器上安装 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 和静态地址。

Read More

使用 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.

Read More

把 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"

Read More

近来做 Stanford CS140e 的一些进展和思考(8)

在上一篇文章之后,我其实还是很忙,但是一直心理惦记着这件事,毕竟只剩最后的一点点就可以做完了,不做完总是觉得心痒。 今天做的部分是调度。我们目前只在 EL0 运行了一个 shell ,每当触发 exception 时回到 kernel 进行处理,再回到原来的地方。但现在,我要实现一个 preemtive round-robin scheduler ,就需要管理当前的所有进程,并且维护当前的进程状态,当时钟中断到来的时候,决定下一个 time slice 要执行的进程,再切换过去。这个过程当然会遇到不少的坑。 首先,我们需要判断一个进程是否可以执行了。考虑到阻塞的 IO ,作者提供了一个优雅的方法:如果这个进程阻塞在 IO 上,那么,提供一个函数,在 scheduler 中调用,判断所需要的数据是否到达。这样,我们就可以一个循环把下一个 time slice 要执行的线程找到。如果找不到,就等待 interrupt 再尝试。 困难的地方在于,在启动的时候,切换到一个起始线程。并且在上下文切换的时候,在 process 1 -> kernel -> process 2 这两步过程中,有许多寄存器都需要仔细考虑如何实现。并且在这个过程中,我也发现了之前写的代码中的问题,最终修复了(目前来看是 working 了)。 我的代码实现在 这里 。下一步就要写 syscall 了。希望能在期中前抽时间赶紧把这个做完。 18:54 PM Update: 刚实现完了 sleep 的 syscall 。比预想中要简单。果然找到了自己实现的调度器的 BUG 。此系列大概是完结了。 2019-02-12 Update: 下一篇文章。

Read More

近来做 Stanford CS140e 的一些进展和思考(7)

在上一篇文章之后,我很长时间都没有在继续我这个项目,清明节刚好闲下来了我就回来继续啃它。Stanford那边已经结课,最后的 3-spawn 也只有一部分,剩下的部分不知道什么时候作者才会填上去了。 这次主要要写的代码就是,对异常的处理。这里的异常并不是我们编程语言中的 catch/throw ,而是硬件的异常。AArch64 和 x86 一样,也有不同的特权级别的区分,前者是 EL0~EL3 ,后者则是 RING0 和 RING3 。特权级别高可以往特权级别低转换,但是反过来,只能通过异常的方式提高特权等级,并且切换特权等级后只有固定的一些代码可能会跳转,这就是 exception handler/vectors 。这些函数可以知道是什么原因调用了他们,根据硬件规定好的文档,我们可以知道发生了什么事情,是对齐出错了呢,还是用户调用了 syscall 呢,等等。根据不同的情况,我们需要进行不同的处理。当处理完之后,我们需要考虑,跳转回用户代码的时候,回到哪里,提供什么值,不提供什么。 实现的话,需要很多步骤。首先是构造好 exception vector ,这里作者已经写好了一个宏(这里 @BenYip 遇到了一个 assembler 的 BUG ),直接用宏就可以把它写出来。然后,我们需要把它加载到当前 EL 的 VBAR_ELx 寄存器中,当 CPU 抛出异常的时候,就会找到这里相应的处理器进行处理。进到这里以后,我们首先先不考虑太多上下文保存的事情–我们先保证能处理异常,恢复也是个有很多坑的步骤,作者也是在这里分成了两个 Subphase 。首先还是从 ESR_ELx 中解析到错误的来源的具体内容,如果是我们在 shell 中自己调用的 brk 2 指令,我们就自己新开一个 shell ,修改了提示符以示区别。这样,我们就成功地捕捉到了这个异常。由于我们还无法恢复回去,所以我们直接死循环。 接下来我们要做的是,从异常中恢复出来。由于用户代码可能在各种地方抛出异常,异常也分同步和异步两种情况,这里有许多需要考虑的问题。为了简化,我们目前只考虑同步的 brk 2 导致的 Brk 异常。为了能恢复之后能够正常运行,我们需要把所有的寄存器都保存下来,即 TrapFrame 。保存的时候需要讲究 AArch64 平台下 SP 寄存器的对齐问题。我们也要把一些特殊的寄存器保存下来。还有一点,就是,因为 exception handler 中调用了 context_save 函数,所以此时的 lr 本身也需要进行保存,这个地方也卡了我很久。最后,再把这些一个一个地恢复到原来的样子,调整 ELR_EL1 使得退回到原来的状态时,会跳过当前的 brk 2 指令,调用它的下一调指令。这样,我们就成功地在遇到异常时,弹出一个 shell ,而且还可以回退回来。

Read More