初识LinuxBoot

在写下上一篇coreboot的粗浅认识之后,我基本上把所有的payload都试了一遍,然后最终决定所有的(除了商业支持的depthcharge等)payload都是垃圾,主要原因是实现功能完整的bootloader难度不亚于一个微型操作系统,次要原因是coreboot的功能实在过于强大(因为设计给x86这种巨型系统的)导致嵌入式社区也无法有效利用和反馈(相比之下U-Boot的社区活跃多了)。

在所有的常见的通用型payload中,SeaBIOS和edk2完全不提供安全性,grub2提供少量的安全性,dasharo提供了不错的功能和安全性但是和我的设想不太一致。 只有LinuxBoot提供了强大的定制能力,强大到我可以实现我的设计目标的同时把我需要面对的威胁全部覆盖住。

LinuxBoot,即使用Linux内核这个几乎不依赖任何BIOS功能和驱动的成熟而活跃的大型软件来完成coreboot之后的所有启动初始化工作,并最终将控制权转交给实际的操作系统的方案。之前提到过Heads是一个设计目标明确的LinuxBoot,其目标用户群往往需要了解自身面对的安全威胁,这个和Tails这种设计目标是对自身面对的安全威胁不甚了解的人群相差甚远,导致很多人刷Heads可能根本就没闹明白我为什么要这个,以及这个到底干嘛了,反而是对Heads繁杂的要求和欠缺的保护大为吐槽。对于我来说,我所面临的安全威胁并不需要Heads的模式,而我不愿意承担Heads的代价。那么,我的显而易见的选择就是制作自己的LinuxBoot。

啊这个不难啊,不是有一个项目就叫做LinuxBoot吗?啊……这个真的帮不了我,因为coreboot其实已经集成了LinuxBoot,但是没有提供LinuxBoot的定制,毕竟这东西的可定制性基本就是无上限,提供了也没什么用。所以这里缺失的是设计和组装LinuxBoot,而这等同于制作一个发行版。是的,一个LinuxBoot就是一个完整的发行版,而这个发行版的基本功能要求为:

  • 能驱动内置存储器
  • 能加载操作系统本身或其bootloader
  • 能移交控制权给操作系统

重要功能为:

  • 能驱动外部存储器(用于安装/修复系统)
  • 能驱动显示器和键盘(用于中断启动或者进行配置)
  • 能对非核心系统环境进行早期初始化

客户需求为:

  • 全盘加密,没有任何明文内容
  • 在设计安全范围内无需用户交互完成启动
  • 不考虑硬件拆解(包括开壳)的情况下没有攻击面

目标平台是X230,就是玩coreboot的基本上都玩过的那个ThinkPad X230笔记本电脑,配备了TPM 1.2,但是有一个不需要开壳就能拆卸的SATA硬盘位(这可是功能啊,但是在安全威胁日益增加的今天这个功能成了缺陷),因此硬盘上不能有任何明文是刚需。Linux天然可以解锁luks2这个优势使得不需要任何复杂的外部软件就可以实现真全盘加密,而密钥的保存就需要交给TPM了。

我这里忽略一切除了上面这个目标以外的事情,例如怎么驱动SATA和怎么kexec,这些网上都有,重点说说网上没有的。

TPM是一个设计拉垮,商家不买账用户也不买账的东西,其主要设计目的是实现DRM,即你买了这个数字音乐才能听,而且你能听你不能拷给别人听。因为设计实在是拉垮,最后把DRM这个功能几乎完全从最终的TPM 1.2规范中删除了,保留下来的都是一些通用的安全功能。除开安全协议有点老(比如只有SHA1和RSA2048),其实TPM 1.2是一个很纯粹的设计,相比之下TPM 2.0是终于把一开始的设计目标实现了:它除开通用安全功能以外最主要提供的是远程管理。但是不管怎么说,TPM 1.2落后了,所有的社区实现都不支持TPM 1.2进行启动密钥管理。因此,第一个挑战就是,凭空驱动TPM 1.2。

TPM协议栈最早由IBM设计,年代非常非常久远(2006年的)而且秉承IBM的一贯尿性,以至于我看到原始的IBM版TCG TPM Software Stack(TSS)实现的时候我觉得这个东西就是90年代写的。其设计思路是,TPM芯片本身不是多用户的,那么在多用户环境下我们就需要一个中心汇聚器,因此我们需要一个守护进程来作为TPM的唯一入口,这个入口叫做裤子TrouSerS。然后用户要和TPM交互的时候使用一些工具和裤子通讯,这个工具包叫做tpm-tools(为什么不叫裤腰带或者裤兜?)。出于原本的DRM的考虑,这个守护进程甚至可以远程运行,因此裤子和裤兜都支持TCP通讯。那么,要完整支持这个方案,我们就需要:1、多用户支持,2、TCP协议栈支持。还好我们不需要真的激活网卡,不然就是个筛子了,但是TCP本身就是一个极其复杂导致攻击面巨大,而且对于我们这个项目来说,体型过于巨大的存在。

没事,Linux用户都会自己修bug。因此我们要做的第一件事就是patch掉这两个工具的通讯协议,使得它们通过unix socket通讯,这样我们只需要启用基本网络栈,而且也不需要操心鉴权等问题因为unix socket天然就有文件系统权限保护着。

第一版上swtpm测试,首先就发现这东西设计的坑爹是名不虚传,它居然没法判定TPM目前是不是已经配置(owned)的状态,状态猜错了所有操作报错。翻了很久的贴子(毕竟快20年前的东西了)发现裤子启动的时候会去检查磁盘上是不是存在TPM ownership state文件(/var/lib/tpm/system.data),有就是owned,但是这个说是state文件,其实就是一个boolean,因为只要是owned这个文件的内容都一样……这个好办,Linux内核是可以准确看到TPM状态的,因此读一下/sys/class/tpm/tpm0/owned,如果unowned把state删掉就好。

第二版上swtpm测试,可以和TPM通讯了哎。然后撞到第二个坑爹设计,tpm-tools里面的工具大概没什么人真的用,都是鼓励你用API去二次开发的,导致各种缺功能。我们操作TPM的nvram的时候需要先检查是不是存在nvram项目以及配置是不是正确,然而tpm_nvinfo工具的输出是不可机读的,要可靠读取显然很困难。没事,Linux用户继续修bug,把tpm_nvinfo改成可机读。然后很多工具的命令行参数不一致(比如不支持命令行提供owner密码),没事,我接着patch。不支持OpenSSL 3.0,没事,我还是patch。原版的tpm-tools没有tpm_extendpcr,没事,我提供。

别问我为什么不用libtpms,一是我不想自己开发,二是我当时不知道有这么个东西。

这个过程大概持续了一个月(纯兴趣驱动,代码改得其实不多),终于可以实现:

  • 可以对TPM进行清空和初始化
  • 可以读写NVRAM的定义
  • 可以将二进制数据封存在NVRAM中并和当前PCR值绑定
  • 可以封锁对TPM的再次操作

整个安全机制的设计为BIOS完全接管TPM,操作系统不能使用TPM。硬盘上双分区,boot是一个BIOS可解锁的普通分区,和普通的boot分区没有很大区别,但是里面存放着kernel bootargs并且用luks2加密。之后的分区有几个,是不是lvm其实就没有分别了,但是为了加快开机速度,使用单luks2保护的lvm2来存放其他所有数据。

BIOS会对整个BIOS所有的内容进行measure,这个是coreboot自带的功能,其结果在PCR2当中。解锁boot的密钥和PCR2绑定,即如果BIOS被修改,boot分区即无法解锁。BIOS会将Linux kernel、initramfs和bootargs三个measure到PCR4当中,但是这个PCR并不绑定任何NVRAM,而是OS启动之后可以借此验算磁盘上的文件没有被修改。因为没找到很好的办法对内存中的内核和initramfs进行校验,只能寄希望OS启动的尽早阶段如initramfs阶段就对此进行校验,此时主分区还没有解锁,initramfs是从luks2保护的boot分区读取的,不易遭到篡改。之所以PCR4不直接绑定主分区的解锁密钥是因为在TPM 1.2中是无法对PCR值进行预先绑定的。如果你有TPM2,升级内核的时候你可以算出来正确的PCR值并且预先设定策略允许此新的PCR值在下一次启动时解锁主分区,这样可以用户无感升级。但是TPM 1.2必须是你对PCR直接进行extend之后将NVRAM绑定为”当前的PCR值“,无法指定PCR值,所以你升级内核就必然要输入密码了。

BIOS的防止未授权更改部分非常简单,就是不告诉你怎么打断启动。通常BIOS都会给你显示一句Press F2 to interrupt boot然后给你点时间打断它,但是我的设计是你根本不知道什么键位在什么时机打断(说了就不灵了哈哈)。如果启动被打断,或者启动流程失败(硬盘被拔出、替换、覆写等),那么会启动一个标准的Linux终端登录。这个登录的密码同样保存在TPM中,不可绕过,和通常的BIOS管理密码类似。

一旦OS加载,你就可以使用任何方法来继续解锁主分区了。由于要修改boot分区等效于你获得了root权限,获得了root权限的机器是没有任何可信度的,因此BIOS并不对内核进行签名校验(不过做了个Secure Boot Key Verification,反正不难),但是还是可以认为initramfs可信,所以你可以用常规手法把密钥存放在initramfs里。

你如果问,LinuxBoot的最大弊端就是无法更新,因为升级内核就需要刷BIOS,怎么解决?首先,这个BIOS的确允许你在输入BIOS密码之后刷写,所以非得要升级也不是不可以。但是这个设计的最大要点就是,没有攻击面。不考虑开壳硬刷BIOS或者窃听TPM通讯的话(这俩都不是X230能防御的,缺少BootGuard,属于硬件缺陷),BIOS正常启动流程里面通常的高危设备例如USB、网卡等等驱动统统都没有加载,除了主硬盘、显示器和键盘,没有其他硬件了。主硬盘被luks2保护,攻击等同于击穿luks2。显示器实际上是coreboot驱动的,Linux只是继续利用,所以攻击等同于击穿coreboot。唯一的入口就是键盘,而这个的攻击面在于Linux的终端驱动和键盘驱动没有漏洞,否则的话你就等效于可以直接对任何一台服务器的console login进行爆破了。

很显然,这个BIOS没有任何花哨的功能,因为它设计来就是在X230上提供一个中等安全性和高度便利性的方案。是的它的安全模型是低于Heads的,但是我不需要对抗这么高级的对手,我更希望它省心和防呆,所以它有且仅有一种工作模式。

最后,这个BIOS的主要缺点是什么?慢,非常慢,但是比Heads快。主要的原因在于考虑到启动成功率,SPI flash的读取速率是有限制的,通常是大大低于最大速度。其次,TPM extending的速度,比flash的速度又慢了一大半。最后是LinuxBoot本身就需要先启动一次系统,这里面有一些硬件比如SATA是需要时间初始化的,然后额外的两次luks2解锁消耗时间,外加上其他林林总总的。最后结果就是我的BIOS能在25秒内进桌面,不管是冷启动还是热启动。这个比起一般的BIOS能在15秒内完成就算是很慢的了,不过能有一台让人安心的便携式电脑,这个还是值得的。

最近的一些关于BIOS的心得

一台可信的计算机需要有可信的设计、制造和分发,这个可以参阅1985年彩虹丛书的安全等级的定义(商用计算机操作系统一般只能做到C2级安全性,某些领域可以达到B级安全性)。我们不可能像波音那样对飞控进行形式化验证,所以A级是肯定没希望的,但是尽可能把计算机向B级提高是有现实意义的。现代的商用计算机一方面在缓缓地提高性能,另一方面在安全性上获得了长足的进步,而这些进步绝大多数都是对最终用户不可见的,只有在特定环境中启用完全的强化才能感受到和20年前的巨大差异。在商用领域,这些努力一般被归结为可信计算(或类似的商标术语),其基本目标是确保计算机能够按照设计的使用方式完成给定的指令,而不会出现所有者无法控制甚至无法感知的非预期行为。遗憾的是这些努力一方面提高了商用计算机的可靠性,另一方面也引入了各种问题,主要有:1、高权限的安全管理系统本身设计与实现的错误可能导致系统可靠性出现额外的弱点。2、这类系统可能被供应链滥用/盗用而引入系统所有者不愿意接受的限制。因此部分技能水平比较高的机器所有者会选择性地禁用这些安全系统,以自己的方式来实现同级别或更高级别的安全要求。

现代X86计算机非常复杂,其主要原因是计算机的组件复杂性太高以及因为通用性要求而引入的大量标准接口。因此要启动一台现代X86计算机,必须遵守非常严格的时序,执行非常复杂的上电初始化操作。举一个例子就是现代CPU都是多核心的,上电的时候通常只会有一个核心上电,利用这个核心完成整个系统的自举之后其他核心才会启动。这类动作越来越多,越来越复杂,使得系统自举的流程复杂到难以对照着芯片datasheet直接写出自举程序。这种情况加速了自举程序的供应链封闭,即很多自举代码已经难以由芯片设计者以外的企业/用户自行实现,也使得系统更加黑盒,更加难以信任。

背景说到这里。

虽然现在的趋势是将支持芯片集成进CPU内部,或者将若干支持芯片合并在一起,但是这里还是按照传统结构,CPU->北桥(northbridge)->南桥(southbridge)->嵌入式控制器(embedded controller)和超级I/O芯片(Super I/O)。

现代BIOS都是存放在SPI闪存中,速度仍然是蜗牛级的,因此南桥负责将BIOS映射给CPU可见地址。也就是说,上电的时候,南北桥一定是早于CPU初始化的。南桥启动完成之后会读取BIOS芯片的前4K,Intel Firmware Descriptor,来确定这块芯片的内部区域布局。IFD记录着BIOS芯片内部是否存在若干区域、这些区域的位置、是否可以被读写等基本信息。由于IFD的存在,同一块闪存上就可以有BIOS、ME、GBE、EC等多种ROM并存。南桥依据这些信息将BIOS区域映射到内存地址的最后位置,此时CPU就可以通过读取内存的最高地址来访问BIOS了。如果制造商启用了BootGuard,CPU会对BIOS内容进行签名校验,从而阻止任何不可信的启动代码执行。BootGuard实际上是在ME的BUP(Bring-Up Process)当中,但是因为ME是数字签名的你没法去掉它。

现在CPU开始上电。虽然Intel在努力淘汰历史包袱,然而截止到目前,CPU(通常)仍然是以16位模式单核启动的。此时因为地址空间是16位的,最大寻址只有1M(FFFF:0000,实际代表的物理地址是0xFFFF0),因此BIOS的最后1M是唯一可以访问的内容,而最后16字节就是CPU可以寻址的最高位置,这个位置叫做Reset Vector。如果CPU被配置为仅以32位或者64位模式启动,CPU也会先将地址线内部写死使得其寻址空间最高位指向BIOS(当然就可以映射不止1M了)。CPU总是将指令指针指向Reset Vector然后将控制权交给BIOS。(这里不讨论最新的架构中引入的Firmware Information Table,其位置是最后64字节处并且早于Reset Vector执行。)因为16字节什么都做不了,BIOS通常只会在Reset Vector做一个跳转跳到真正的bootblock,其主要目的就是尽快将CPU切换到32位或64位模式下。在这个跳转之后,CPU的地址线完全解锁,BIOS就可以开始真正的初始化了。

第一个初始化阶段叫做romstage,此时BIOS完全运行在只读状态下,因为这个时候内存还没有启动,没有内存时所有的代码都只能通过寄存器工作。幸运的是现代CPU通常有巨大的高速缓存,以M为单位,完全足够高级语言作为内存使用。因此romstage通常最大的任务就是配置CPU使得其将高速缓存显示为可用的内存,这个状态称为CAR(Cache As Ram)。

CAR阶段高级语言就可以运行了,因此这个时候的BIOS代码通常就不再是汇编了。这个阶段最重要的事就是启动内存,因为DRAM和SRAM最大的不同就是它需要内存控制器准确地与之刷新频率同步。然而不同主板的内存物理位置会有区别(从而导致延迟不一致),也可能内存条本身的频率和时序有差异,因此BIOS必须要对内存控制器进行训练使得它能够稳定控制内存。这个训练通常需要很长的时间(数百毫秒)才能找到稳定的频率和延迟,可以说BIOS启动耗时里面最主要的部分就在这里。一旦内存控制器训练完成,通常BIOS都会选择保存训练数据,从而在冷启动和从S3唤醒的时候可以尝试快速启动内存。

这里插入一个经常被热议的模块,Intel Management Engine。ME是CPU中的一个单独核心,运行着单独的操作系统,其权限极高而对主系统不可见,然而其却有能力控制CPU行为和读写内存。为了实现ME的设计目的,内存控制器启用之后,BIOS必须告知ME内存已经就绪,使得ME进入运行状态并且继续运行BUP代码(Bring-Up Process),否则CPU就罢工了,这就是为什么ME是不可以被完全禁用的。同样的,因为ME可以通过板载有线网卡在关机状态接受远程控制指令,因此BIOS芯片中也保存有GBE的ROM,所以ME加载自身的时候也同时激活了网卡。这也方便了BIOS因为不需要针对网卡进行初始化了。

有了内存,CPU就可以启动高速缓存并进入完整的运行状态了,开机过程就完成了。接下来BIOS需要考虑是否进一步初始化其他的板载设备,比如显卡,然后就可以显示一个漂亮的Logo了。所以当你看到Logo的时候你认为开机过程刚刚开始,实际上却是已经结束。

继续插入一个板载组件,嵌入式控制器EC。在早期的台式机主板上通常是不存在EC的,因为主板就是主板,没有什么其他组件了。但是在笔记本(以及现代台式机)上,通常有很多指示灯、开关、键盘、显示器转轴、入侵检测螺丝等,这些设备并不通过USB或者PCIe和CPU连接而是直接连接在Super I/O或者南桥上,因此也是需要非通用的初始化的。这些设备通常由一块EC芯片控制,这块芯片独立于CPU,是主板上的第二块CPU,其启动是完全自主的。很不幸,这也意味着EC不但拥有自己的BIOS,而且往往是闭源而缺少标准的。

在EC的协助下,BIOS可以进行各种可选的硬件初始化,例如硬盘控制器和无线网卡。BIOS最后一件事就是决定将控制权交给操作系统,然而取决于操作系统本身的特性,BIOS需要提供更多的服务。早期的BIOS是通过提供16位模式中断来将已经初始化的硬件暴露给操作系统的,如果操作系统比较古老,依赖于这些中断,那么BIOS就需要有相关代码实现。如果操作系统依赖UEFI标准,那么BIOS也需要实现。如果操作系统什么都不需要,那么直接把内核加载到特定位置然后跳转就可以了。商用主板通常会同时提供BIOS模式和UEFI模式,而定制硬件例如Chromebook则可能选择直接加载内核。

接下来我们可以通过开源BIOS实现coreboot来更深入一些,这也是我这段时间折腾的一个归纳。

coreboot是目前历史最悠久名气最大的开源BIOS实现,在2012年以前基本上统治着自定义BIOS这个领域。然而2013年Intel Boot Guard的出现使得任何非主板制造商生成的BIOS都无法启动系统(制造商的公钥被烧录在南桥中,CPU启动的时候会读取并校验BIOS的签名)。这个措施推出之后coreboot社区基本上鸟兽散,因为认为随着时间推移所有的制造商都将会启用BootGuard,开源BIOS也就没有足够的市场来维持其延续了。在这之后,Google事实上接管了coreboot(核心开发者基本上都是Google员工了),并主要成为Chromebook的BIOS实现。对商用计算机的支持也就停留在了2012年的Sandy Bridge(gen2),以及并未完成的Ivy Bridge(gen3)。因此coreboot玩家如果不是购买制造商主动支持的主板(例如Chromebook、System76等),基本上能支持的最新的型号就是ThinkPad xx30(如T430,X230)了。基本上现在还在坚持玩coreboot的玩家多多少少都会有玩X230的历史/情结,毕竟别的牌子也不大可能坚持10年还能用了。

虽然没有BootGuard存在,ThinkPad也是不允许用户随便刷入非OEM发布的BIOS的,因此主流的操作是拆开笔记本,解焊BIOS芯片并且使用编程器直接烧录。因为不是所有人都有热风台和精密焊接的条件,所以也衍生出不解焊直接烧录,以及利用安全漏洞进行软件烧录的路线。其中软件烧录虽然看似安全,第一无法完全控制BIOS芯片,第二也存在失败而不得不硬刷救砖的风险,因此不管准备走哪条路线,都应该准备好编程器。

xx30有两块BIOS芯片,拆下键盘和掌托之后撕开主板保护膜的一角就可以看到,习惯上将两块芯片叫做top(4M)和bottom(8M)。其中bottom被映射为0-8M,而top被映射为8-12M。这12M空间的布局为:

  Flash Region 0 (Flash Descriptor): 00000000 - 00000fff 
  Flash Region 1 (BIOS): 00500000 - 00bfffff 
  Flash Region 2 (Intel ME): 00003000 - 004fffff 
  Flash Region 3 (GbE): 00001000 - 00002fff 

不要尝试去把这几个区域按地址排序,因为Intel对每个区域的序号是有要求的(例如IFD和BIOS必须是第一第二位)。你也可以看出来,BIOS一共有7M,前3M是在bottom的尾部,后4M是在top全部。

如果选择软刷,因为BIOS由南桥管理,南桥会依据IFD去决定怎么映射BIOS,然而IFD是锁住的,即你无法用软件刷新IFD本身(只读)和ME区域(不可读写),所以软件刷新绝对没有办法完全控制BIOS。而ThinkPad的BIOS启动之后会要求南桥对若干关键区域如Reset Vector和bootblock进行写保护,因此即使恶意软件获取系统控制权也没有办法直接刷写。然而,因为Lenovo早期版本的BIOS并没有完全保护好所有的寄存器,所以如果降级BIOS到老版本是可以绕过保护刷写BIOS区域的,而且这种方法可以刷写最后7M空间,某种意义上比编程器单芯片刷写更有优势。

还有一种不建议的方案是通过上电时短接HDA_SDO脚,这是Intel的一个维护模式使得南桥不对IFD进行写保护,从而实现软件刷写IFD来解锁整个BIOS区域。然而这个引脚处于主板背面,你需要把整个笔记本大卸八块,如果你对自己的手艺这么有信心,还是看下面的硬刷。

因为Reset Vector位于地址的最高位置,即在top当中,而coreboot可以被配置为BIOS总大小不超过4M,因此只刷写top就可以完成(一个小型的)coreboot烧录,之后就可以在coreboot下刷新整个BIOS的7M。这种方式没有解锁IFD,因此ME区域仍然是不可读写的。

最完善的方案则是双芯片烧录,即可实现修改IFD和去除ME的目标,但是要实现bottom的生成,你需要先将系统当前的bottom读出并备份,并将其写入新生成的固件中。如果你破坏了IFD,砖。如果你破坏了ME,砖。如果你破坏了GBE,网卡砖或者丢失硬件MAC地址。幸运的是,coreboot的一个衍生版Heads提供了xx30正确的IFD、ME和GBE镜像,这些镜像和我自己从X230原厂BIOS提取的内容一致,因此你也可以完全放心地刷入Heads或者基于Heads提供的内容生成自己的BIOS。

在详细说明coreboot生成的BIOS结构之前,一个预备知识就是即使IFD确定了一个区域,这个区域内部的结构是完全由这个区域的所有者自行决定的。因此GBE其实分为两个4K的镜像(内容一样),而ME和BIOS区域则有内部的分区结构。

由于ME的分区结构被解读出来,ME Cleaner就有能力对ME进行分区删除和重定位,从而将除了启动时必要的BUP和FTPR等分区保留而其他分区删除,而把空间从5M左右缩减到100K左右,不但彻底禁用了ME,同时也把空间释放给BIOS使得你可以利用超过11M的空间。修改ME区域大小需要修改IFD。虽然理论上ME精简后连同IFD和GBE可能只有100K出头,你应该在修改IFD时给出额外空间使得BIOS对齐到64K,这有利于coreboot更有效率地读取内容(早期coreboot没对齐的话干脆启不动或者卡到50秒超时能启动)。

DRAM训练这个启动过程中难度最大的环节,Intel提供了固件(MRC:Memory Reference Code)来帮助OEM完成。这个固件,巨大、完善、不透明,因此也是被认为一个不可信的环节。因此coreboot自己实现了开源的训练代码,这个代码轻巧、开源、不可靠。所以刷入coreboot之后启不动、两条内存只认出来一条、内存没有运行在最高频率等是coreboot社区的老生常谈。出现这种情况一般的建议都是把内存条换个槽,或者换内存条,因为coreboot会检查训练缓存(MRC cache)是否存在有效数据,如果有的话就不会再次训练了,换位置可以迫使coreboot重新训练。但是有的时候换位置也不起作用的时候,可能就需要重新刷一次BIOS把训练数据清空了。反之,如果coreboot挑剔你的内存条但是你成功训练出来一次,那你就会希望每次刷BIOS的时候都把这个缓存刷进去,避免重新训练。

BIOS区域则一般来说会有会有3-4个分区。上面提到DRAM训练结果的缓存,这个缓存一般是一个64K的区域保存在BIOS头部(你可以理解为什么要对齐了么,不对齐读不出来就启不动DRAM)。之后可能会有一个可写的区域作为NVRAM,通常在128K到256K。为什么不用CMOS?因为UEFI规范要求NVRAM有足够大的空间以及可以确保任何情况不失,所以如果你开启UEFI就会在闪存上分配一个空间,真正实现永久保留(思考一下Secure Boot Keys)。此外BIOS的最后部分是bootblock,最后剩下的空间就是位于BIOS区域中间的其他所有空间。

coreboot使用了一个轻量级(即假的)文件系统称为CBFS,这使得coreboot在剩余的空间中保存不定数量的文件并且可以在一定程度上进行热修改。CBFS中保存着各种coreboot自身的模块,payload,以及payload自己的数据。如果payload自身没有操作CBFS的能力(毕竟是和硬件相关的),coreboot还可以在BIOS区域中划分一个分区作为存储区,并向payload提供一个API来读写这个分区。

coreboot的payload是开机完成之后转移控制权的目标,因为coreboot真的就只负责开机,之后的操作系统启动什么的完全不管。如果没有payload,你就实现了开机即死。很遗憾的是因为社区分崩离析,现在除了几个元老级payload还没死,基本上就没什么真正实用的payload了。这几个元老包括并不限于:

  • SeaBIOS:提供16位BIOS中断,可以启动任何能在传统BIOS下启动的操作系统。够简单纯粹,不挑食,用在什么地方都行。
  • Tianocore edk II:UEFI的代表实现之一,在OEM进行开发的情况下有独立启动的能力,然而对coreboot的支持是比较晚和一言难尽的。但是MrChomebox对Tianocore进行了惨绝人寰的patch之后,可用性得到了一定提升。主要在Chromebook上用,也能在ThinkPad上提供Secure Boot。
  • Heads:严格来讲这是一个基于coreboot的完整的BIOS项目,只支持ThinkPad部分型号,提供较强安全保证,包括验证启动过程的完整性以及通过YubiKey、NitroKey等进行额外保护。
  • LinuxBoot、grub2、Linux内核直接启动:都是有相当局限性的方案,例如你要更新Linux kernel就得刷BIOS。Heads实现就是一个BIOS内的精简Linux,然后做完所有验证之后kexec启动硬盘上的Linux。但是普通人走这个方案需要相当的技术实力。

那么为什么要折腾coreboot呢?虽然这似乎应该是这篇文章的开头,但是纸上得来终觉浅,折腾完了再总结更合理一点。我可以看到的和原厂BIOS的若干区别有:

  • 启动速度更快。这个是在牺牲了原厂BIOS的皮实耐操的代价下换来的,即coreboot更容易启不动或者不稳定(主要是内存),对主板上组件的POST和对EC的控制均没有原厂可靠/完整。实际上这里节省个1-2秒在整个启动过程中也占不了多大比重,可以认为这是一个没什么实用价值的优势。
  • coreboot提供了更详细的启动日志,如果有硬件串行口可以看到启动每一步做了什么,但是这个对最终用户没有任何价值。
  • 不会对你的硬件进行限制。ThinkPad等臭名昭著的硬件白名单在coreboot下不复存在,你可以随意更换无线网卡、WWAN卡或者电池。
  • 略微不同的配置项。原厂BIOS通常允许你进行非常细节的配置,哪怕是你觉得什么有用的选项都没有的精简版BIOS菜单也比coreboot提供的选项多多了。但是coreboot提供少量原厂不提供的选项,例如软禁用ME,嗯大概也就这个了。
  • 不同的安全性。coreboot实际上不提供任何有意义的安全性,其所谓的安全性功能vboot和measured boot都依赖于payload实现。如果你刷Heads这种可以利用measured boot的payload,或者ChromeOS这种vboot的真正使用者,那么coreboot可以比原厂提升一点安全性。但是如果你刷SeaBIOS或者Tianocore,基本上你的电脑就门户大开,因为这两者都是没有BIOS密码功能的,物理接触然后一顿改就完了。

额外说一句软禁用ME。ME设计来就是上电即启动,不依赖主系统而可以操作板载有线网卡,控制CPU电源状态和读写内存。因此在不使用ME Cleaner的情况下,你插上电源和网线,或者允许电池下接受管理并插上网线,ME就启动并接受控制了,这个时候BIOS甚至没有开始执行。但是BIOS启动的时候是可以请求ME停止工作的,这个功能称为Soft Temporarily Disable,请注意这个功能是“请求”而不是“强制”,因此存在可能ME不服从,不过至少在早期的系统上ME还是会接受的。所以如果使用了coreboot进行软禁用,你被ME操纵的条件就是主板有电、插着网线、主机处于关机状态。

再游马德里

七年半之后再次来到马德里,这次呆得久一点,所以也准备尝试一下更接近本地人的生活方式。

首先,季节不同。夏季的马德里湿度要高一些,雨水也多一些,当然仍然是非常的干,补充水分和涂身体乳还是不可或缺。其次,还是季节不同,这次来是夏令时生效期间,导致本来就很怪异的时区变得更加怪异,真的就是早上天一亮就到了起床时间(这个时候正确的时区的话其实就该才4-5点)然后晚上10点了天还没黑(相当于7-8点吧)。所以整个时间都是和平常偏差2-3个小时的感觉。什么感觉呢?晚上好长啊~虽然是幻觉,因为时间还是按标准走的(6点下班,11点睡觉),但是就是直到你开始准备洗漱,天才黑,给人虚幻的“现在还早”的感觉。

西班牙每天四餐。第一早餐,和普通的习惯差不多,冷餐。第二早餐,10-11点,冷餐,在法国也会在这个时间有个茶歇(其实叫香烟歇……)但是不会真的去吃顿饭。餐,嗯西班牙语把午餐就叫做,餐,因为这是一天中最重要的一顿饭,甚至可以只有这一顿饭是正正规规吃饭的,热餐!午餐时间一般在1-3点,但是其实到4点餐厅里经常还坐着不少人。最后是晚餐,8-10点,通常也只有冷餐。

这次花了点力气研究公共交通系统,毕竟要呆比较久的时间。首先一个好消息,马德里终于贴近时代,开始发放塑料卡片的公交卡了,不再是一张磁条小纸片了。卡片叫做TTP,这是一张NFC卡片,而且还提供了,嗯,仅西班牙语的官方app,可以直接在手机上查询和充值。还不能把这张卡片加进Apple Wallet,这张塑料卡是一定要携带的。对于普通的成人来说有两种卡,一种叫做TTP Multi,多人使用的卡,不记名,不限制单人使用,也就是传统的10次票你可以自己刷进去把卡扔出来第二个人刷进去的操作。另一种叫做TTP Personal,记名,单人使用,卡片上有姓名和照片。Multi可以充游客票,Personal可以充月票,两者都可以充单程票和10次票。

非常有趣的是赶上了交通部优惠,去年一次,今年又搞了一次,恰好覆盖了我的逗留时间。10次票半价,月票四折,这还有什么好说的,赶紧办月……嗯周末不上班不能办个人卡。于是我只能先买Multi,充了2张10次票(因为同类型10次票最多充2份也就是20次票),卡费2.5欧,10次票半价6.1欧,一共14.7欧,口袋里多出来三个钢镚儿。是的,7年过去了,信用卡还是无法被自动售票机接受,传说这个和欧盟要求4位PIN码有关,大部分售票机也不支持感应式付款,没有专程去找感应式去看能不能收信用卡。不过没关系,一旦拿到卡,以后就手机充值了。

马德里的交通和其他使用单一钱包的地区很不同。钱包式公交卡,你充钱进去,然后不同交通体系扣除不同金额,你自己不需要去管买什么票的。马德里使用的是票据式,公交卡里保存不同类型的票据,不同的交通系统扣除对应的票据。也就是说,你得搞清楚你要使用的票据,然后充票。马德里一共有3.5种不同的交通系统。

EMT,包含地铁(Metro)、轻铁(ML)和市区巴士(City bus,蓝色),除了比较少见的票种,你可以认为就是单程票和10次票。在市区(Zone A)运行,每一程扣一张票。因为地铁是可以互相换乘的所以一次进站一次出站扣一张,而巴士则是每次上车扣一张。如果你很不幸是巴士换地铁再换巴士,那么就要扣三张。

城际铁路(Renfe),C字头的铁路。有自己的单程票和10次票,和EMT不通用。因为是城际,你得看你要去的目的地究竟有多远来决定买什么价格的票,也就是说单程票和10次票实际上要整体乘以区域数,那可是非常多的组合了。

城际巴士(Suburban bus,绿色),同样是有自己的单程票和10次票,同样是乘以区域数。

最后,机场。来往机场可以使用任何以上的三种交通,因为机场在A区所以你按照A区买票,但是额外加3欧机场附加费。附加费地铁的话在售票机上充,巴士要现金或者信用卡给司机。信用卡似乎是2021年开始才支持的,终于不用携带现金了。

在说月票之前说一下对公共交通的感受。因为这次都在市区活动所以没有使用城际交通。地铁很显然是很古老的风格了,车型一般是4-6节编组,周期也偏长,早高峰能做到2分钟一班已经是极限,不可能像香港那样30-45秒。市中心的地铁站非常非常深,就跟防空掩体似的,每下一层可能还有多条通道去往不同方向。但是标记非常清楚,西语英语指示牌,标记了几号线、终点站、楼梯还是扶手电梯还是直升电梯。我就没在这么复杂的地下迷路,我觉得这些指示牌非常优秀。另外地铁要注意区域,有一些线会离开A区,买错票会出不去。

轻铁就比较迷糊了,因为我就没在任何网站上找到怎么搭乘的。地铁,刷卡进站,推闸出站,很直观。巴士,上车刷卡,下车直接下,很直观。轻铁,有的站是有闸机的,有的站直接就在露天一个月台,上面没有闸机也没有读卡器,然后车厢里有读卡器所以上车拍。那么请问在有闸机的站上车,还要不要拍车上的读卡器呢?答案根据目测本地人的行为,是不需要的。

巴士,表扬一下,这可能是给我体验最好的一个。首先,马德里的巴士并没有“抱起婴儿,折起婴儿车”的要求,轮椅、婴儿车都是直接上来停泊在专门区域即可。其次,交通部非常自豪地宣布“我们所有的巴士都配置了底盘升降系统”,这不是广告,真的每一站不管有没有老人或者婴儿车,司机都会把底盘降低车身倾斜来上下客,显然有系统性培训要求。第三,巴士的行驶很平稳,基本没有急加速急刹车,可能也是对应着对老人和婴儿车的友好。另外,优先座以及车前部的普通座里面会有几个宽座位,可以容纳胖子。最后,巴士的报站极其清晰,前面几站分别是什么,都要多久,然而竟然还有下一站可以换成哪些巴士以及这些巴士的下一班是几分钟后,这个就非常先进/便利了。要说缺点,可能就是所有的座位都是硬塑料的,不像香港那样有一些软座位。

回过头来说月票。市区单程票是1.5欧,没有任何折扣的。10次票标准价12.2欧,不坏,但是如果你真的是巴士转地铁转巴士这种走法,三天一充显然不是办法。所以本地人其实会选择月票,54.6欧,整个市区范围(Zone A)随便坐而且免机场附加费。注意这个月票不是按自然月计算,而是激活日开始30天整,滚动计算的,你3月20日激活就可以用到4月18日,童叟无欺。2023年6月30日前还有四折优惠,21.8欧用30天,不要太开心。但是月票不可以加载到TTP Multi上,必须先有TTP Personal。这种卡除了收费4欧比Multi贵一点以外,是实名带照片的,但是并不要求你是本地居民,外国护照一样可以办。你可以选择网上申请,上传自己的精修图并信用卡付款,然后等半个月寄到你的住址,或者网上预约卡服务中心现场办卡,当场出卡。很遗憾的是,两种方式都只有西班牙语网站。我选择现场办卡,毕竟早拿到一天就省一天,预约只能在周一到周五工作时间,半小时服务一个申请(可以多人使用一个申请),所以自己看哪个中心比较方便然后抢时间吧。到了约定的时间进去给护照,交4欧,电脑摄像头吧唧一个大头照(很丑),然后卡到手。卡中心可以充月票,也可以自己手机上充,充好之后再去拍读卡器,就会显示“有效期到哪天”的提示了,告别充票的痛苦生活。

虽然还没有验证,但是月票其实有一个缺陷,就是市区月票是不可以使用城际巴士(绿色巴士)的。城际巴士进入市区之后通常还会有几个站,有时这个站会比EMT巴士站更便利去某些市区换乘站,然而市区月票是不允许使用这种站的。但是Renfe没有这个问题。理论上可以加一点钱买B1区月票,就看你是不是愿意多花点钱换取少走几分钟了。

注意翻译成英文之后月票和游客票都叫做Travel Pass,游客票叫Tourist Travel Pass但是其实各大网站都直接叫Travel Pass,导致很难找到月票的信息(毕竟游客也很少买月票)。如果看到告诉你Travel Pass有1-7天有效期然后分Zone A和Zone T的,这是游客票。告诉你Travel Pass分30天和一年,然后分区从A一直到E2的,这才是月票。

最后说一下和意大利的区别。西班牙人遵守吸烟区规定,不会到处都被迫吸二手烟。另外,西班牙有公共厕所,虽然不多,但是确实是免费的,不像意大利的私营1欧厕所那么丧心病狂。当然,最方便的还是吃饭的时候顺便上个厕所吧,毕竟这里不像香港到处都是大型商场。

路由技术超简单科普

当一堆设备想要互相通信时,网络就出现了。网络可以小到两个设备,也可以大到整个互联网。当规模逐渐变大的时候,各种不同的技术就被发明出来,最终形成了今天谁也闹不清楚它是怎么连起来的但是就是连起来了的互联网。互联网是个奇迹,因为任意给你一个IP地址,不管这个IP地址地理上位于哪里,你和它之间有多远的距离,你都不需要关心,就可以和对方建立双向通讯。

从最小的情况说起,两个设备想要通信。显然,一条双向链路就足够了,而且你们俩互相都知道这条链路对面有且仅有一个设备。需要任何地址吗?不需要的,吼一声就行了:“喂,你。”对面传来:“哎,啥事儿,对面的?”这就是最简单的点对点通讯。

当三个设备想要通讯的时候,建立三条点对点链路就可以了。四个设备需要六条,五个设备需要十条,这就开始吃不消了。在此忽略掉早期的组网技术(令牌环!),现在的做法就是一股脑把所有的设备往一条总线上一接。总线是需要地址的,我得知道我说这句话是给谁听的,谁说了一句话是给我讲的。继续忽略早期的组网技术(IPX!),IP网络横空出世。在一个网络内部,每个设备需要一个唯一的IP地址,这个地址可能是32位也可能是128位的,这不重要,只要唯一就行了。在这个网络内部,任何人都可以嗷一嗓子:“哎所有人听着!”(组播/广播)也可以:“小李,这儿有你一封信。”(单播)

有时房间不够大,需要几个小房间打通来办公。每个小房间就是一个交换机,交换机与交换机之间互联的时候需要额外注意不要兜圈子(A房间穿到B房间然后穿到C房间然后不小心回到了A房间然后继续穿到B房间然后……),因此交换机使用生成树协议(STP)来规定递件的路线。总之,这就是一个大房间,里面熙熙攘攘挤着几十个人在办公,关系都挺紧密的。

上面这个小网络可能在信息处用得好好的,但是当网络规模开始变大,现在保卫处人事处各个都要来互联,不能吼一声小李然后全单位都停下来应一声。于是每个科室需要隔离起来变成子网,广播只能在子网内部进行了,跨子网需要穿过科室大办公室的门卫(gateway/网关)。现在需要寻址的时候,不能只叫“小李”了,要叫“保卫处的小李”,其中“保卫处的”是网络前缀,“小李”是主机ID,不过一般来说没人在意这个区别,只要记住“保卫处的小李”就可以吼到那个胖子就行了。网关除了帮科室看大门的,也有顺便帮顺路的科室递下手的,甚至有专门负责倒手的,这种力气够大可以帮人递件的路由器叫做transit router。

既然一个办公室是一个封闭的网络,在更大规模的单位网看来每个科室其实就是一个点,所以问题转化为N个点之间怎么建立正确和经济的互联网络。显然子网不多的时候还是几条点对点连接就可以了,但是规模大了,需要挂总线,或者请求transit。总线在这个时候并不是很好用,因为总线需要全通(任意两点间可以线速通讯)是很不经济的,而且任意两点可能线速还不同,因此这个规模上又回到了横七竖八的点对点时代,几个科室合计合计画了一张路由图出来(保卫处要经过信息科转手送人事处),这张图是静态的,画好之后每个网关看了看自己在图上的位置,记住自己要和哪些网关打交道,这个应酬列表就是静态路由表。

有一天,单位一把手发话了,整个单位大楼,全都连起来。这下麻烦了,整个单位几十个科室,除了经常打交道的,别的科室老死不相往来啊,还时不时有科室组建和裁撤,手工维护路由变得不太实际。动态路由协议这个时候被制定出来,路由图不再是手工绘制的,而是网关们互相商量(发现)出来的。用OSPF为例,发现过程就是一个网关站在门口,往左边吼了一嗓子:“我这儿保卫处,那边是谁?”对面吼回来:“我这儿档案室,我还可以帮你递给会计科和宣传部。”然后网关往右边又吼了一嗓子:“我这儿保卫处,我左边还有档案室、会计科和宣传部,那边是谁?”对面吼回来:“我这儿人事处。”“我这儿办公室。”于是保卫处网关写出了自己的路由表:我左边是一条走廊(点对点),走廊另一头是档案室,而且会计科和宣传部在档案室后面;我右边是一个门厅(总线),人事处和办公室的网关都在那儿蹲着。每隔一段时间,网关就得这么吼一顿,不断更新路由表。

然后邮差来了,他绕着办公楼走了一圈,说你们来个人收一下信。收发室老头嗷一嗓子出来了,给我给我。邮差问:“你这是江南皮革厂不,我这儿有寄给你们小王的信,给你了。”收发室老头说:“给我吧。另外我这儿有我们厂里黄鹤寄给他小姨子的信,你给带走。”邮差并不关心厂里的组织结构怎么样,路由怎么走,因为江南皮革厂是一个自治系统(Autonomous system),邮差只要记得这个自治系统的门牌号(ASN)是12345就可以了。收发室老头是边界路由器,坐在AS和AS之间。在这个例子中,江南皮革厂只有一个对等连接(peer),而这个peer是它通往整个外界的唯一渠道,因此邮差就是其默认路由,任何不在本系统内的东西都交给默认路由就可以了。

在现实世界中,只有一个peer的AS往往是没有独立门牌号码的,因为其上级AS是它的唯一通路,只要上级AS知道怎么到达这个AS就可以了,互联网上其他AS是不需要知道这个细节的。此时,上级AS是下级AS的唯一ISP,通常下级AS只需要设定一个默认路由就可以了,下级不需要关心全球的AS互联信息。上级AS会把下级AS的IP段直接合并到上级的IP段中,其他AS需要和下级AS通讯时只是扔给上级AS,其他就属于上级AS的事情了。最常见的家用网络结构就是这种简单的单ISP边界路由后面挂个交换机,接个几台设备。

在讨论AS互联之前,需要先讨论一下为什么会出现AS这种东西。从前面的讨论中我们可以发现,每当网络规模扩大一级的时候,只需要把原来更小的那个网络缩成一个点,然后再加一层汇聚就可以了。这个结构,是一棵树,树的最顶端是一个或者一组最高级的互联节点。然而这个结构有一个最显然的问题,就是树越靠近顶端,就需要transit更大规模的流量,而且一旦高级节点挂了,下面的所有子树就会裂解成独立的子网。在小规模的情况下,这是很自然的选择,但是规模大了就肯定撑不住了。

为了增加互联带宽,避免中心出现,各个节点之间需要增加更多的连接,形成一个大量互联链路的网格(mesh)。由于mesh不存在中心结构,所有的节点都可能出现互联(实际上也要考虑地理限制),因此每个节点都需要设定边界,边界内就形成了一个黑箱(AS),并且分配全球唯一的节点ID(ASN)。由于AS的边界是预先定义(也就是人手分配的),这样运行全局算法的开销才能控制到合理范围内。与小规模时尽量剪枝(STP和OSPF的目标都是建立最小生成树/最短路径树)相反,这个规模的网络是尽量尽量利用每一条可用链路,因此需要一个完全不同的算法。一个伟大的协议——BGP——诞生了。

BGP(Border Gateway Protocol)顾名思义就是只在边界路由器之间运行,其特性包括:尽量高的可靠性(使用TCP),算法开销尽量低(peer全部人工定义而不是自动发现,节约开销,反正跨AS链路也没几条),快速的算法收敛,当然还有去中心化。

每个单位需要到邮局去申请自己的门牌才能加入到国际邮政网络中,然后为了照顾各方面的感情,世界上一共有五个这样的顶级邮局(Regional Internet Registry),它们同时负责IP和ASN的分配。在RIR之下还有LIR(Local Internet Registry),一般来说每个ISP就是一个LIR。相对于IP地址,ASN其实是需求更加稀少的存在,因为AS才是真正的地理集中的一个组织,而IP则可以天马行空,所以正如一开始IP地址只有32位一样,ASN最早只有16位,后来才升位成32位。行文之时,全球一共也只有8万多个AS,但是有大约100万个IP段。

回到之前江南皮革厂的例子。邮差是默认路由,但是“默认路由”其实是不存在的概念,邮差们必须知道每一个IP段怎么走。两点之间一定是存在路径的(否则会网络不可达),但是因为是mesh,可能会不止唯一一条路径。此时BGP需要进行一系列的计算,来试图寻找一条最优的路径(不一定最快,不一定最大带宽),然后把包送给这个路径上的下一跳。不同于OSPF,最优路径并不全网共识,当包送给下一跳时,下一跳却可能算出不同的最优路径,因此包最终可能会走和出发点计算不一样的路径。并且因为大量的信息在路由生成的过程中无法传递过去(信息量太大了,只送出去最基本的信息),因此本地计算出来的最优路径也可能根本就不合理,导致包莫名其妙从地球另一边绕一圈回来的情况。全局路由优化是一个世界性难题,到今天还没有很有效的办法。

运行BGP需要首先定义本AS的边界及互联关系,这个过程叫做peering。与家用网络不同,mesh里面每个AS都可能和很多个AS进行peering,而且每个peer都是能够transit的(不能transit的AS比如你家,一般来说没有全局ASN,不参与BGP),也就是说理论上你发包给任何一个peer最终它都能到达目的地。BGP根据一些规则计算出某些目的IP应该走peer A,有些走peer B,效果会比较好。BGP机房其实就是一个机房同时连接几个ISP(电信、联通、教育网……),然后根据规则决定这个包走哪条。然而反过来,因为BGP不存在全局共识,你一个包发给了电信,回包可能从联通回来了,卡死在回家路上。因此BGP不但要接收全局的路由信息,更重要的是怎么把自己的路由信息尽量准确地发出去(announce)。

BGP广播的目的是让其他AS知道有这么一些IP段在我这里,如果有发给这些IP段的包,请送我这里来。然后每当这样的广播向外传播一跳,中继AS就会根据它的其他peer的状况决定一下反向路由,然后把这样的路径再次向外传播。可见BGP的内容并不只是“保卫处在我后面”这么简单,而是“从我到保卫处应该先过收发室,再过信息科。”下一跳会继续将这个路径延长,直到这条路径最终到达一个AS,这个AS的所有peer都表示已经有了关于这个源AS的路由信息为止。换句话说,全球每个IP都知道了到达这些IP的路径,这就是互联网可以两个IP之间都可以双向通讯的奥义。之所以要把整条路径都广播出去,也是因为不存在全局共识,能多送出去点信息就多送出去点,这样万一路径中间成环了可以尽快发现,或者断了,全网震荡的时间更短,因为全局“大体上”是有这个源AS的拓扑信息的。

然后就发现好像什么地方不太对。一个transit AS必然有两个或以上peer,如果这两个peer都连接在同一个路由器上,问题是很显然的,从peer A来的transit包,发给peer B就可以了。但是如果peer A和peer B不在同一个路由器上呢?假设peer A连接在router A上,peer B连接在router B上,那么这个包必须穿过AS内部的路由网络,以便从router A到达router B。router A是怎么知道如何到达router B的呢?

回到AS内部,OSPF这类IGP(Interior Gateway Protocol)相对于BGP这类EGP(Exterior Gateway Protocol)来说,是专门为AS内部建立路由表而设计的,因此存在一些并不适合外部使用的假设。虽然道理上讲,一个包从peer A到达了router A,它一定可以穿过IGP建立的内部路由到达router B,然后发给peer B,这个过程peer是不需要干预的。但是对于router A和router B来说,它们俩会很困惑。router A和peer A建立了peering,当它收到包的时候,它会想我代表的就是这个ASN啊,但是我明明没有通往peer B的链路(因为它并没有和peer B建立peering),也就是说本AS并不能到达peer B,为什么我可以发给peer B包?

这种IGP和EGP之间信息的不对称将会导致EGP无法正确完成AS内部transit的描绘,因此需要一个桥梁可以把两者整合起来。因为EGP的绝对主力是BGP,所以这个协议还需要尽量和BGP兼容。或者,干脆就是BGP本身呢?

于是BGP就分裂成了iBGP和eBGP两个子类。eBGP也就是传统的用于EGP的BGP,而iBGP则是使用BGP协议但是仅仅在AS内部和IGP交换数据用的特殊用法。说得玄乎,实现上其实特别直白:设定一个peer,其ASN和本路由器的ASN相同。当BGP发现这种同ASN间互联时,就会询问IGP:“我怎么到达router B呢?”然而又同时假装IGP不存在似的,和另一个路由器直接BGP交换EGP数据,仿佛两个路由器是点对点直连的一样,从而使得router A和router B即达成EGP的一致性(都知道本AS是同时peering peer A和peer B的了),又能利用IGP的路由信息实现互联。

到这里,江南皮革厂终于可以实现每个科员都可以和世界上任何一个IP通讯了。为了实现这个目的,厂里需要同时运行OSPF、iBGP和eBGP。

8万多个AS在互联网上互相通告来通告去,这个过程是没有任何权威机构进行管理的,也就是一个完全的去中心化系统。去中心化系统的优点是没有办法仅破坏系统的一部分,来瘫痪其余的正常部分,因此没有一个组织可以因为故意或者失误而破坏互联网。

是吗?

答案是否定的。互联网从未被设计为一个可信任的系统,互联网上的各个层级都存在严重的安全问题,这些问题来源于早期设计缺陷。为了解决去中心化系统的权威问题(即信息是可信任的),高级的协议已经设计了一系列的半中心化或者中心化修正方案。例如,HTTP通过给网站颁发证书的方式来证明这个网站的所有者信息,而DNS则通过DNSSEC来提供未被篡改证明。然而,更底层的BGP,尚未形成这样的广泛认可的安全机制。

破坏互联网的方法出人意料的简单。由于互联网是一个mesh,这也就意味着任何一个peer都存在可能连接到任何一个你想要到达的IP,但是当一个peer声明它可以很低成本到达目标IP的时候,它是可以说谎的。目前有两种说谎的方式,两种都实际发生过互联网崩溃级别的灾难。

第一种是声明本AS包含目标IP。有一天邮差来到江南皮革厂门口,说哎你这儿都有些什么科室啊?门卫老头说,我这儿有保卫科、人事处、宣传部、紫光阁、档案室。邮差一听很开心,哎呀我这儿正好有发给紫光阁的信,你拿去。江南皮革厂是不可能拥有一个北京的IP地址的,但是信已经送进来了,要么查无此人退回去,更多的情况是直接扔垃圾箱了。如果遇到敌意AS,直接伪造IP地址也是可以的,导致发给别人的包被敌意服务器截获。这类事故发生了好几次,基本上每次都发生在敌意AS上(也就是说是故意试图拦截、窃听等)。由于BGP是全网大体上知晓其拓扑的,所以当事故发生时(比如某某网站突然全球打不开啦),可以非常快定位到始作俑者,然而始作俑者往往一句“哦配错了不好意思啊”就搪塞过去了。

为了减少这类攻击/失误,IRR(Internet Routing Registry)被发明。IRR是一个可以去中心化但是目前是中心化的数据库,试图维护一些关于路由信息的附属资料,例如一个IP段是谁注册的,目前这个IP段应当是由哪个AS承载,等等。当BGP通告发出时,接收方可以查询IRR记录以确定AS和IP的对应关系(浙江的工厂咋能有个北京的IP段呢?)从而丢弃非法的广播。由于IRR信息是静态的(相对于路由表来说),因此其信息可以被离线证明,然后通过密码学进行在线保护。如果大部分的transit AS都实现了强制IRR检查,就不会再发生AS错误地广播不属于它的IP的情况。

那万一一个敌意AS说这个IP不是我的,但是我到目标AS非常近呢?这就是第二种说谎,伪造不存在的peering,使得别人相信去往这个IP的权威AS的最近的路,是通过这个敌意AS进行transit。虽然未必骗得到整个互联网,但是靠近敌意AS的AS很可能被劫持。

很遗憾,目前还没有办法防御这种攻击。毕竟ISP们在提高互联互通的道路上还是非常努力的,有时,两个地球背面的AS会决定拉一条几乎直线的海底光缆,使得本来两个遥远的AS突然变成peer。而且不光是凭空伪造peering这种事,两个真的peering的AS,敌意AS也可以在中途篡改流量,毕竟你通过我transit,信件就在我的掌控之下了。所以虽然说现代互联网想了很多办法试图阻止IP伪造,但是当你遇到国家力量的时候,你的数据可以完全被敌意窃听和篡改,哪怕你看着地球仪,完全想不明白你到目标的流量,怎么会在地球另一端的毫无联系的路由器上绕了一圈。

截止到这里,路由的核心一直都是路由表,不管是静态的,还是动态的,路由的最终目标都是维护一张合理的路由表,然后每当有一个包需要transit,就取出其目的地址,查一下路由表,找到下一跳,发出去。

“取出起目的地址,查一下路由表,找到下一跳。”这是一条规则!如果,路由的时候,允许不同的规则,世界会变成什么样?这个念头一产生,整个路由技术就出现了巨大的变化,因为从理论上,路由表本身就可以被逐条拆解为规则:如果目标地址是毛纺厂,走奋进路;如果目标地址是牛马市,走三环;其他的都交给建外大街上一个叫老王的收发室老头。然而,规则这个东西,可没有说必须得是 (目标地址->路径)这个写法。(源地址)可不可以?(目的端口)可不可以?(时间)可不可以?都可以!

这种基于规则的,可能仍然保留路由表但是也可能不保留的技术,叫做策略路由(PBR:Policy Based Routing)。这个东西解决了困扰大家多年的一个问题:负载均衡。和业务负载均衡不同,网络如果带宽耗尽了,你再开一条一模一样的路是没有用的,因为根据目标地址,你只能走一条路,新修的路放那儿就闲置了。虽然目的地址可能是一个大块,你设法把这个大块拆散成一些小块然后分别走两条路,是可以解决问题的,但是且不说维护成本,就是单个IP都能比其他另外100个IP的流量总和还大这种情况,真是没法弄。

想要负载均衡,本质上就是一个合适的分类,使得一些包走线路A,另一些走线路B,使得两条线路的流量正好和其最大带宽成正比。假设一个理想情况,所有的包一样大,那么我们只需要计数就可以了。如果线路A带宽300M,线路B带宽200M,我们给每个包都计数,AAABB AAABB,或者ABABA ABABA,打上标签。路由策略写成:任何带标签A的包走线路A,任何带标签B的包走线路B,没标签的查路由表。

然后发现好像包大小不一样啊……其实也不难,做一个带宽监控,统计统计流量上不同包出现的概率,也可以算出合适的分配比例来。你看,统计学、机器学习,都可以拿来为路由技术服务了。而策略路由是不依赖路由表的,这东西不但可以写成一些简单的条件,甚至它就可以写成一段代码,其可能性就完全不受限制了。

遗憾的是到今天为止,策略路由还不像传统的路由表信息那样有成熟的交换协议。传统的基于目的地址的路由交换,世界上任何一个AS吼一句“这个IP段在我这里”,所有的路由器都会很快学习到“凡是去那个IP段的包都想方设法送到那个AS去”。然而策略路由,就拿最简单的双线负载均衡,你就没办法让回包一定从去程线路上回来,因为对方不知道你用的什么策略分类的啊,这个策略可不能像目的地址那么简单地交换给对方。

然后就出现了一个骚操作:那要不我们把策略包装成一个可以交换的信息?

(待续)

台北再闻

时隔两年半来到台北,说长不长说短不短的时间。

首先,我们有了桃园经新北到台北的捷运系统,160TWD,比巴士略贵一些但是运行时间只有半个多小时。值得称道的是,这条线路是直达车和普通车混跑的,价格相同,站台共通。反观某些城市故意设定机场快轨所在的地方就没有普通地铁,或者虽然两者都有但是价格悬殊巨大,这个设定真的是非常良心。换个角度来说就是普通捷运的价格是不是就被抬高到机捷水准了,见仁见智。当然,因为是混跑,路轨质量确实就不那么赞了,晃悠晃悠是不可避免。

台北大部分出租车统一样式和涂装(某家公司势力太大了),内饰和计价器也干净现代。值得表扬的是上车压表立马来一句语音:按照春节费率执行,表上也明显提示目前费率与平日不同。我猜测可能是出租车公司少,疑似一眼看去都是同一家的,而这家(垄断)公司在服务标准化上投入了很大精力。大陆或者香港,一个城市要调一下表,经常是耗时几个月,司机靠一纸通知手工计算新费率。

住在台北101附近,走走路就可以走到全台最繁华的商场。再次表扬台湾税务系统的效率,所有的商场消费的时候,都会自动打一张电子发票(嗯先不吐槽必出发票而且还带抽奖的税务系统实际上意味着什么),这张票和小票是一体连打,开票机构体积很小,票本身也很小,想必不需要大费周章去买什么税控电脑和税控打印机,而是替换了所有的小票机。

说到消费,尴尬的事情来了。超市刷卡买完东西后,在麦当劳试图用同一张卡买个夜宵,作为欧美快餐,麦当劳往往是支付体系最为先进的,在台北101这样的地标区域内,连银联和支付宝的广告都随处可见的情况下,麦当劳,刷卡失败。店员首先一脸惊异地看到一张非台湾发行的信用卡,窃窃私语后,尝试非接支付。喂喂,不是每一张VISA都可以PayWave的好吗?然后我反复告诉她们这张卡不能感应,于是她们又试图插卡,然后发现读不出,不是授权失败,是直接读不出。于是店员一脸坚毅地告诉我:只可以刷台湾发行的信用卡哦。我一脸惊异,啥啥啥,你这pos竟然没有外卡权限?难道是遇到了银联附体?

悲愤地掏出钱包拿现金的时候,发现,这个汉堡59块?你可不可以告诉我为什么不舍入一下?我到台北一共三小时内,兜里的硬币已经攒下来10几个了。因为自动售货(票)机也只找不收硬币,硬币根本没有去处。如果不是恰好攒够合适的硬币,那就需要去找硬币兑换机,可以把硬币兑换成纸币,这机器只有少量地方有,而且也不吃10元以下。为什么其他地方不觉得硬币很烦?大陆的基本支付单位是元,1元硬币和1元纸币花出手的效力相同(同样香港10元纸币和10元硬币可以互通),角币凑够支付单位也很快,而欧元美元的基本支付单位就是硬币(10欧分和1欧2欧常用,10美分和25美分常用),流通速度快,你兜里不会剩下几个。但是台湾你找人1元硬币,等价于我找你一个分币。1元硬币攒一张纸钞,需要100个。没有谁给你机会把一坨1元硬币花出去,换10元硬币的机会都没有,所以你拿到1元硬币基本都烂手里了。但是这东西可不像分币那么轻,这是一个标准体积标准重量硬币!

说到这里就得说一下电子支付。基本上,台湾的电子支付能力差到爆,还让人很困扰。比如刷地铁,捷运自己的悠游卡不能全台通用,要知道长三角珠三角主要城市公交卡已经通用,全国ETC也都通用了。竟然还干不死第三方的卡(icash和一卡通发卡量都达到了千万级,悠游卡虽然发行多但是考虑台湾2300万人,活跃卡也就2000万,而游客没人买icash或者一卡通),这也不能说人家太强,就是悠游卡运营水平太烂,两年换一版卡,能刷的地方又少。八达通和西瓜为什么那么强?什么都能买什么都能干,便利店自己的卡还有什么市场?然后信用卡,感觉台湾的信用卡发卡量是不是太少了,商场转一圈没看到人用卡呀,都是现金现金现金,很多刷卡机都是摆设。

参观台大。虽然说现在是假期,学校里没有什么人是正常的,不过,老校区满目的基建老化,这个应该是长久失维造成的。这是一个没有大门(真的找不到一个地方可以拍个校名)的校园,里面的建筑大多是几十年前文物风格,整体上没有现代文化冲击造成的不和谐感。仔细观察会发现,现代科学、工程学,甚至人文社会学,在这里面的占比都偏小,农学畜牧学的比重却比较显著。

围绕着老校区的就是零零散散的新楼和宿舍区,很明显的年代断层。(还偶遇了老居民房着火)

不过我倒是有点喜欢学校附近的商业区,长得特别让人觉得这是属于学生的商业区。

结果不小心逛到了另一个大学,这个有校门!

然后就是一座桥

嗯?徒步走穿了首都?

最后101镇楼。

梦回巴黎

算好时间到达机场,外面夜深人静,里面灯火通明,值机安检通关一气呵成。还有两个小时,佯装准备飞北京的金主,在DFS里询价然后假意离去。对我来说需要买的仅仅是地不地道无所谓但是一看就知道是香港零食的东西,但是却发现这个时间所有特产店铺都关门了。幸好在CX酒廊旁边发现了一个专门卖特产的DFS,盘算来盘算去,竟然被我找到60HKD一盒16片的饼干,可喜可贺可喜可贺。CX贵宾厅丰富和舒适,餐饮酒吧按摩椅阅读区观景窗一应俱全,在这么小的空间里排开这么多东西,竟也不觉得拥挤。整备好行装静静等候,看着周围各式牛仔裤帆布包圆领衫凉拖鞋的商务精英们,发现自己的行头很是合拍。

然后,就晚点了。

CX久负盛名的服务就是会称呼你的名字,而且实际上,是根据你的名字猜测你的母语是什么。很凑巧的就是我周围英语法语粤语普通话(我)一样一个,于是我就听着这个帅哥把各种语言版本的问候语轮了一遍,别的我说不好,普通话是真的很标准。当然后来发现他向其他妹子传达指示的时候也是普通话的时候我就完全理解了。这个帅哥是我见过的服务态度最好的空乘,表情到位,服务到心。早餐的时候因为我要的红枣粥+肠粉没有了,他不知道从哪里薅来一碗生滚牛肉粥(不在菜单上哦),然后不顾我努力拒绝,坚持向我提供了另一组菜单的广式早茶。虽然,吃不下。半夜起飞,看着Google Maps上的小蓝点慢慢穿过一条条国境线,被太阳一路追击,终于阳光明媚。

戴高乐机场,第一反应,巨小,第二反应,巨大。低矮的楼层,狭窄的通道,让人觉得置身于二线机场。但是上了机场巴士,路线要把所有航站楼绕一圈才开往市区,这个时候才发现戴高乐机场有8个航站楼,而我是第一站上的车,在机场绕了半个小时,才把机场逛完。然后一路沿着机场高速进城,没有什么车,这个时候巴黎人大概还没起床。大剧院,巴黎核心区,下车,打量路对面的老佛爷百货,呼吸寒冷干燥的空气。空无一人的街道,满目欧式老房子,破损的人行道,零零散散还没起床的流浪汉,以及一地垃圾。

默默走到酒店,进门,小小的柜台,以及醒目的欢迎使用支付宝。然后就此生第一次遇到了需要自己拉开外门的电梯,此生第一次遇到了内门是折叠门(就是老式公交那种)的电梯,此生第一次遇到了准载4人面积仅够转身的电梯,而你可以轻松爬楼超过这个电梯的速度。就这样的酒店,一晚上要2000人民币。

就算是维修,也很贴近原本的风格。

赌钱吗?

这个是久负盛名的蔬菜大棚及其灌溉设施,每年都吸引大量农业工作者前来取经。

走到香街,然后,身边就出现了法国骑兵,然后就封路了。整个人蒙蔽了一会儿,才理解竟然撞上了小圆饼同学的就职典礼。于是和人群一起默默等待着小圆饼的到来。法国警察都很帅,马也很帅,骑兵的长相就相对随意了,最主要的是,养马真的很费钱。空调车、清扫车,马站久了会累还要警察牵着去散散步,站久了不高兴骑兵还要安抚安抚,比如,骑一会儿。然后,街上就充满了马粪味儿。

两个小时的等待,等过了一场雨,终于等到小圆饼现身10秒钟。(雨停了?)

吉普车上站着的就是小圆饼。

然后这个大避雷针大家应该都认识。

这个无名烈士墓大家应该也认识。

这个认识的可能就少一点,这是古埃及文物,埃及人送的。出于绅士风度,法国人也给美国人送了个纪念品。

这货是充电的,蓄满能量就可以射出一道死光击毁敌人的坦克。

然后就饿了。这是美食闻名的法国,找吃的不像西班牙那样让人绝望,因为这里有很多中餐馆。价格还不算太贵,如果按照收入折算过的话,而且法国工作的人可以买餐券,工作日就是半价了。

不过当地居民其实更喜欢那些中国人吃起来比较吃力的东西。

塞纳河饺子馆。

工作日,巴黎总算恢复了活跃,车水马龙,西装革履,行色匆匆。这里汽车还是保持了一贯的礼貌,而行人则习惯性闯红灯。街上的女人们,没有谁穿艳丽的衣装,也没有人拎耀眼的手包。

当然,老佛爷附近就是另一副光景了,这里属于中国领土,洋人与狗不得入内。

老佛爷里中国人毫无障碍,除了国人喜爱的化妆品和箱包必然有至少一个真·中国售货员,或者是一个虽然不会普通话但是长相也很亲切的华裔法国人,真的小店只有法国妹子的那种,英语也是倍儿溜,沟通毫无障碍。所有人都会第一时间帮你计算退税多少,折算成人民币多少,还会很亲切地告诉你一定要记得几点钟以前去退税哦。中国人在这里可以随便要求看任何一款名贵的产品,没人会因为你穿得随便而轻视你。因为我是逛了一身汗进去的,还被LA MER友好地喷了一脸水。

在巴黎,到处都有纪念当年犹太人被迫害的历史事物,从未有那么深的感觉,历史就在你身边。这个是奥斯维辛集中营的焚化炉烟囱的仿制品。

拯救过犹太人的法国人。

巴黎也是很讲究环保的,这是共享单车。

共享电动汽车。

巴士站,倒计时很贴心,还有免费USB充电器。

但是大站快车要小心,你不呼他他不停。

玻璃瓶回收。

入夜。

夜巴黎,据说治安不好,大家不要随便在外面逛。

退税是老生常谈,但是,不知道为什么,老佛爷家改规矩了三个月了,网上竟然没有任何一篇文章介绍新规矩,让我好生担忧了一阵。老佛爷和巴黎春天不需要回邮了,只要你拿到的是老佛爷自家的信封,到海关自助机扫描一下条形码就可以走了,不用拿登机牌,不用出示物品,不用放进信封扔邮筒(除非你不从法国出境,那还是要贴邮票回邮)。另外虽然没有T标,但是这个信封在法国境内回邮也不贴邮票,害怕海关不靠谱的孩子可以顺便回邮一下。

戴高乐机场自动化程度很高,如果是经济舱,是没有值机柜台的,通通去自助机上打登机牌,然后第二个自助机上打行李托运条,再去自助行李托运区排队把托运条按要求粘在行李上(就是平时值机柜台会帮你做的事)然后扔上自助传送带,全过程没有人帮你,但是会有一个妹子指导你怎么操作这些机器。遇到三个香港人打包一大堆空的橙盒托运商务舱,所以大家不用担心某些店卖的货不正宗,确实是人家从巴黎运回去的,至少盒子是。

然后机场就封闭了。

和商场区不同的是,机场的工作人员英语可能是不好,或者不乐意跟你说清楚的。首先是机场广播,说本区域需要紧急关闭,然后在没有搞明白发生什么事的情况下,整个区域就被关闭了,所有人被工作人员(或者枪)赶到了隔壁区域。只有一两个工作人员在向大家解释,我们发现了被遗弃的行李,为了安全我们必须关闭整个区域。这红色贝雷帽长得真帅,帅到我怀疑他的战斗力。最后等了快半个小时,才有一个欧洲男子拖着行李箱离开管制区,然后机场重新开放。恐袭的阴影萦绕在整个欧洲大陆上空。

贴一张两年前西班牙拍的照片,那个时候的欧洲人还很傻很天真。

最后放一点美景。

马德里的悲催经历

香港并没有直飞马德里的航班,需要途径土豪洲(Middle East)土豪国(Qatar)土豪城(Doha)土豪机场(Hamad International Airport)转机。由于显而易见的原因(时差、转机等候时间、签证等)并没有离开机场安全区。当然,这一点都不影响土豪机场的心情,因为这个机场实在是太xyz大了。一个具备五个候机厅,每个候机厅都有庞大的免税店,每个免税店都装满了世界顶级化妆品以及标配中国售货员(中国到底是多有钱)的机场,装修风格显而易见选择了金闪闪。当然,隔壁那个更加金闪闪的土豪(Dubai)可能更亮瞎眼一些,毕竟Hamad机场并没有发现黄金自动售货机。

穿过马德里国际机场口岸,终于进入了传说中的申根区。在这个区里面,一个签证可以通行20几个国家,和欧元区与欧盟有很大的交集。如果不是太在意一些次发达国家(和英国),完全可以认为欧盟(European Union)、欧元区(Euro Area)、欧洲经济区(European Economic Area)、申根区(Schengen Area)就是一个东西。在这里,货币一样,车辆随便开来开去,货物没有关税,任何国家的国民都能跑到别的国家呆半年。但是,有一个最大的问题没有解决。

西班牙说西班牙语,西班牙说西班牙语,西班牙说西班牙语。重要的事情说三遍。

于是,苦逼的历程就此展开。虽然机场里是英文标识,入境官也说英文,但是一旦离开机场,英文就完全没用了。打车,司机果断听不懂英文。酒店预订单是英文写的,司机果断也看不懂。在司机漫长的手机工作后,总算是上路了。出租车很干净很整洁,计价器居然是内嵌在后视镜里的,非常漂亮,不过起手就是20欧让我颇为嗯嗯。手边的价格表是英西双语的,原来机场出发和到达有专用的计价规则,平时起价只是2.4欧而已。每公里1.05欧(白天)或者1.20欧(夜间),计价器每五分钱跳一次,所以我就心惊胆战地看着计价器疯狂地5分5分跳,比香港的出租车还快。香港好歹200米一跳,马德里可是50米一跳啊。

然后,司机靠边,试图询问我们是这个酒店吗?(我去,你看了半天手机都搞不清楚酒店啊。)结果,他成功地在一个错误的酒店下客了。幸好前台服务生的英文非常好,真的,可以连成句子的好,告诉我们正确的酒店也就是15分钟路程。于是,离开家24小时,飞行16小时后,马德里的清晨,寒风刺骨,第一次欧洲自由行走。

马德里在北纬40度,地中海气候,温差和北京一样,晚上能结冰,中午能出汗。空气非常干燥,必须不断补水。总的来说,这里和北京呆着感觉挺像的。早上时不时可以看到地上有一块冰,我某一天早晨10点走过天桥的时候,看到天桥顶上的冰纷纷融化跟下雨一样。但是到了下午如果还穿着棉衣,就可能要悲剧。

酒店的服务生,英语也相当好,可以连成句子的好,帮我们完成了check-in。吃早饭,洗澡,然后开始睡觉。长途飞行真的很累人,等到正午起床开始寻觅午饭,第二个问题就出来了。

西班牙人三点吃午饭,十点吃晚饭。西班牙人三点吃午饭,十点吃晚饭。西班牙人三点吃午饭,十点吃晚饭。重要的事情说三遍。

西班牙火腿,即使你从未了解这个国家,你也一定知道西班牙火腿,就是那种在商场里一条猪腿卖5000块钱的那个西班牙火腿。西班牙最著名的食物就是西班牙海鲜饭和各种火腿,以及其他加工肉类比如培根,再配上传统的炸土豆或者餐包这样的西方主食。好的火腿真的是150欧以上一条的,当然也有50欧的便宜货,装在特制的火腿袋里,然后排排挂在橱窗上挂一整板,引人注目。味道嘛,

很难吃,很难吃,很难吃。重要的事情说三遍。

真的,又硬又咸的火腿肉,不管是生的还是熟的。生火腿是最昂贵的,通常都只是待客,熟的一般是烤,切片烤。但是不管怎么烹饪,这东西就是难吃。培根倒是早就知道难吃。

嗯?西班牙海鲜饭?你觉得中国日本泰国这几个米饭大国出来的人真的会觉得西班牙海鲜饭好吃吗?

西班牙引以为豪的语言、食物,以及作息,在未来的几天不断地骚扰我们,以至于麦当劳是我认为最美好的公司,因为24小时有吃的,有鸡肉和牛肉,而且菜单上都有图片。不过,营业员并不懂英文,点餐仍然需要很多周折,甚至有一次我调戏了四个营业员才终于点到吃的。

欧洲的酒店还有一个问题,就是不给拖鞋。开始以为是我们下榻的nh hotel的问题,查了一下才发现不给浴袍和拖鞋是欧洲宾馆的通行做法。也没有牙刷,嗯。另外,也没有烧水的电热壶。当然这里没有人喝热水,但是欧盟并没有强制要求自来水都可以饮用,有游客表示在巴塞罗那就喝水之后不适。最后只能超市去扛了一桶矿泉水回来放着。

马德里地铁,又一个让我难过的东西。首先,这里的地铁是高度自动化的,一般没有人类在值守,你需要按help召唤他们。自动售票机是多语言的,但是没有解释清楚每种票都是怎么个用法。第一次坐,不管那么多,来个单程票,点啊点啊点,插入信用卡,拒绝。嗯?密码肯定没错啊,再来一次,拒绝。囧,我的大妈行信用卡居然被拒付了。第一次在马德里刷卡被拒,搞得我后来都很担心这张卡不能用,还好证明仅仅是地铁站拒收。于是只好去附近的咖啡店想办法把纸币换成硬币(不要问我怎么点菜的),店员看到100欧的大钞时脸色绝对阴了一下。幸好没有给他传说中的500大钞。

50欧算大票,50欧算大票,50欧算大票。重要的事情说三遍。

国内那些银行是在干什么啊,动不动就是500块……100块的还是我提前预约才从大妈行取出来的相对小额的钞票,但是其实20块以下的才是真的能够流通的钞票。500块,会把店员吓晕的。不过,这边的人的数学好像比美国好多了,能正确快速收零找零。

单程票是一张小纸片,背面有一个磁条,正面写着你的始发和目标站。进站插票,开门,票没弹出来……我人都走进去了,票突然弹了出来,这反应速度……乘10号线从Las Tablas出发前往市区,路过Tres Olivos时,车竟然停下了,所有人下车了。慌了,根据经验,这肯定是要所有人换乘,但是我明明是10号线还要继续南下的啊,我不换乘啊。广播一直在诉说什么,站台上也有很多指示牌,都在努力地告诉我发生了什么事,除了,我看不懂!试图问其他乘客,果然也不会英文。我绞尽脑汁试图用Google Maps+手势表达我需要什么,但是对方也没能理解。

最后证明,10号线太长了,是区段运行的,我需要换市区车。然后哐当哐当坐到了地方,把那个小纸片拿出来,准备出站,然后……闸机上没有插票的地方!我整个人都不好了。默默站在旁边看其他人出站,他们也没有刷卡!于是走到闸机前轻轻一推,果然闸门自己打开了,不需要验票。

后来自然学乖了,没人买单程票的啦,都是应急用。一般会买10次票。按照经验,多次通行就会有交通卡啦,我可是交通卡收集爱好者。于是屁颠屁颠投了12.2欧,买了一张10次票,然后售票机吭哧吭哧地吐出了一张——小纸片。What?!没有卡的啊?马德里的交通票非常多的种类,只有月票是真的给你一张卡片的,但是月票很贵很贵,根本不是一般意义的买来充值可以收藏的那种卡,就是好几十欧的货真价实的月卡,而且还是要登记个人信息然后给你寄过来那种。于是我还是放弃了买个交通卡做纪念的念头。

有了10次票就可以坐10次地铁或者公交或者其他东西,只要不出市区范围,不去机场。然后下一个问题又来了。小纸片的磁条,会消磁!我才刷了两次的10次票,居然跨区出站的时候没给出去(过了Tres Olivos就需要验票,因为票价不一样)。然后幸好旁边有一个family也遇到票有问题,有人类在帮忙处理。于是发动我唯一会的一句西班牙语(hola)召唤小姑娘过来帮忙。售票机除了售票,还有一个功能就是重写磁(Recharge),终于成功出站。

10次票可以给别人用,合法的,一个人刷卡之后进站然后把票拿给身后的人刷就可以了,不需要一人一票。

马德里的地铁里可能没有手机信号。地面上倒是还可以。神奇的是,我使用mobile data居然无法连接Google,不管漫游到movistar还是vodafone还是Orange,这导致我查找地图遇到巨大的问题。不过只要连上任何一个wifi,就可以正常用。很好奇这在技术上是怎么实现的。

马德里几乎没有公共厕所,马德里几乎没有公共厕所,马德里几乎没有公共厕所。重要的事情说三遍。

在皇宫遇到了这个问题。皇宫的厕所是要进入皇宫才能用的,外面居然没有。主要景点附近根本找不到厕所,只能去寻找商场。餐馆可能也可以,但是因为饭点的问题,你很可能遇到餐馆都关门的状态。最后一直走到太阳门才遇到大型商场。商场的好处是厕所使用的是国际惯例,就是一男一女站在一起的那个图标。如果是在世界上最好吃的麦当劳,可能没有这个标志。所以要认准西班牙语的厕所:aseos(据说不同时态可能不这么写,比如可能是baño)。

想到再补充吧,反正悲催事情挺多的。

同步和异步

孩子们总分不清楚同步(synchronized)和异步(asynchronized)究竟有什么区别,实际上,两者仅仅是相差一个字母“a”,并不是什么大不了的事情,除了,这个“a”是“not”的意思。

同步和异步,在不同的场合下,实际上代表着完全不同的含义。通常可以区分为两件事:协议和实现。

协议:针对连接。一个请求被回应之前,是否可以发出第二个请求?如果必须一问一答,那么这个协议一定是同步的。同步协议最大的问题就在于网络延迟,为了能够加大吞吐量,就必须并发请求,就必须维持大量连接。而异步协议允许请求和回应以任意顺序发送,不要求有序,不要求一一对应,基本上什么都不要求。异步协议需要会话管理来追踪请求超时和乱序回应等问题,但是可以在单连接上就跑出最大效能,也就不会有爆TIMEWAIT什么的。

实现:针对线程。一个线程被一个请求占用之后,在这个请求的回应包被完全生成之前,这个线程可以转而去处理另一个请求吗?这个问题主要是在于生成回应包时可能需要的资源并未就绪,因此必须挂起业务代码。如果可以插入其他请求,就是异步的,否则线程就必须挂起,发生上下文切换。同步实现的最大好处就是省心,开发速度快而质量很高,但是运行效率嘛。

仔细分析上面两段话就可以发现一些有趣的东西。比如nginx都快是高性能的代名词了,但是因为HTTP协议是同步的,不管nginx内部速度有多快,你一条连接都跑不起来多少qps,要压力测试nginx必须开个几千连接并发压。又比如redis,如此高效而且是单线程的程序,大概是异步实现吧?其实,这货是完全的同步实现,只是因为内部内存效率太高,处理完一个请求基本不耗时,排队做就行了。

协程是一个妖孽,因为这货看起来好像是同步实现,但是其实不知不觉给做成了异步实现。当然,不管框架如何妖,既然本质是异步实现,就不单独讨论了。

怎么设计(适配)一个程序,主要就是在思考怎么设计(适配)协议的同步性和实现的同步性。如果你有得选,协议一定得是异步的,无他,网络延迟造成的影响跟维护会话所需要的CPU相比,实在是太太太大了。而实现却需要思考一下,看看是不是有不能异步化的限制(比如用了一个同步的SDK,说的就是你libmysqlclient)或者是生成回应所需要的状态机步骤过多,会使得代码开发变得巨漫长而bug满天飞(说的还是你,libmysqlclient)。

一般在进行程序开发的时候,可以细分为8种情况,或者16种情况,甚至32种情况,显然这是一个2的幂,就像这样的8种情况:

对外协议 内部实现 依赖协议 对策
1 同步 同步 同步 一个连接一个线程是最合理的做法,做原型测试挺好的。PHP用curl调另一个PHP就是这样的,开发巨快,效率巨低。
2 同步 同步 异步 HTTP后挂fastcgi然后调用远程的thrift服务就是这个样子的。仍然是一个连接一个线程,不过访问后端只需要一个连接。典型的如thrift client,会自动挂起没有得到回应的线程,根据收到回应或者超时来唤醒对应线程。
3 同步 异步 同步 因为前后端都是同步协议,所以前后端肯定都是大量连接,但是由于是异步实现,中间只需要少量线程,一个可能就够了,然后两侧分别多路复用。工作在HTTP-HTTP代理模式下的nginx就是这个样子。
4 同步 异步 异步 nginx工作在HTTP-HTTP2代理时就是这个样子。前端N个连接,后端一个连接就做完了。
5 异步 同步 同步 一个请求一个线程是最合理的,然后需要在对外侧准备一个请求的分发器和收集器,收到请求找一个线程去做,做完了再发回去。HTTP2后面跑PHP就是这样。如果你要操作mysql,基本跑不掉这个模式。
6 异步 同步 异步 HTTP2后面挂fastcgi跑thrift client?
7 异步 异步 同步 nginx工作在HTTP2-HTTP代理时就是这个样子。前端一条连接跑个多会话,后端就只能打很多连接出去了。
8 异步 异步 异步 性能绝对无敌。鉴于开发难度,作为异步-异步代理是最合适的,如果中间带了复杂状态机,真的是会掉头发的。

实际情况下,前后端都有可能不是协议直接暴露给业务的,有一些情况是通过SDK进行了封装。不管封装了之后跟协议本身属性是否一致(同步协议给同步SDK,异步协议给异步SDK),都可能造成麻烦。

thrift client,上表多次提到。这是一个很有趣的SDK,因为thrift协议本身是双向异步,但是官方只提供了nonblocking的server版(实际业务还是同步做的),而client必须是同步的。它的做法就是连接尽量复用,你发起调用时,存异步状态机之后把线程挂起(好暴力),后面有一个线程按照标准的异步写法去和后端通信。后端如果回包了,或者某个包超时了没回来,找到应该唤醒的线程,唤醒它并且返回信息。这个设计的主要原因就是简单,业务代码直接同步写法,不需要thrift开发很多API来支持异步。

其实,异步协议同步化还有个最简单办法,就是开N条连接,每条只发一个请求,然后sleep等回包。PHP调用异步协议这么做还真挺简单的,什么?开销?我都用PHP了我管什么开销?

libmysqlclient则是另一种情况。本来mysql协议就是同步的,不过我们仍然可以通过状态机来单线程带动多个连接,这样就可以通过打出大量后端连接但是业务实现不用大量起线程。但是官方的libmysqlclient是发现I/O挂起了就直接把线程挂起了,不给你机会切换到另一条连接接着做。理由同thrift,API简单啊。

有没有搞怪的SDK,把同步协议暴露成异步的呢?当然有,比如libcurl。HTTP是同步协议,那么做一个连接(请求)管理器来管理很多条连接就好了。libcurl的multi接口就是你可以非阻塞地操作N个同步连接,让你感觉好像HTTP也是异步似的。

如果我们遇到了一个异步SDK,想要把它同步化,那么学thrift的做法做一些挂起管理就好了,基本没开销(相比于业务代码而言)。反过来就麻烦了,因为SDK会迫使线程挂起,因此必须开一大堆线程去顶,也就是把同步协议那种情况开多连接,直接换成开多线程,然后在线程上做会话管理。这个是有开销的,而且和业务代码相比,这个开销不容小视。知道我为啥讨厌libmysqlclient了不。

不过说句公道话,操作mysql的状态机之复杂,一个事务从建立连接开始动不动就是10句SQL,就把程序分割成了11个以上的片段,这种状态机写起来,恐怕还真不如同步实现算了。也正是因为这个,协程才开始兴起,协程版的libmysqlclient也受到了比较多的关注。Facebook做了个异步版的libmysqlclient,我不看好,不是不看好库的质量,只是不看好写这么复杂状态机的程序员。

如果我们把SDK的情况也添加到表格里,就会得到一个16行的表,甚至32行的表,显然,这不是什么好主意,读者自行脑补好了。

台北见闻

新台币(TWD)不是中国大陆认可的货币,直到最近两年才终于建成了两岸结算系统,不过也仅限于上海和厦门。因此如果你准备按照习惯先在银行兑换一点现钞,你很可能发现连牌价都没有。台湾认可度比较高的货币是美元,所以带一点美元是可以的,在桃园机场就有货币兑换点,收30新台币手续费,汇率不坏。用不完的新台币也不要带回来了,除了有纪念价值,都没法兑换回来,所以临走时在机场免税店花光吧。另外如果你带的是支票,务必在机场换成新台币,机场兑换点支票比现钞买入价高一点点,但是酒店里居然低了10%。

刷卡实测结果:银联最优惠,大约1个点结成人民币。VISA走当地货币大概2个点,走DCC的话4个点,出来还是美元,再换人民币继续被吃一个点。银联认可度?好像还可以。台湾的VISA线路出单一定是DCC选择单,自己务必钩一下走本地货币,不然默认DCC。

桃园机场顾名思义就是在桃园啦,离台北还有一个半小时车程,没有地铁没有机场快线,需要坐巴士,145TWD可以到台北市政府。市内交通要办一张悠游卡,100TWD押金,退卡扣20TWD,地铁8折,地铁公交双向换乘立减7TWD,不需要买单日卡啦。市内单程跑一趟也就30-40TWD,和北京差不多。值得提醒的是,台北的公交很复杂,招手停哦,不然开过站台不理你。按下车铃才停哦,不然开过站台不理你。最搞笑的是,公交分三种收费方式:上车刷卡、下车刷卡,和上下车都刷卡。上车的时候务必看清楚。计程车比较贵,嗯。另外台北地铁是第一个在台北101站这种地方居然不上盖的,出站还要走过一个很短的露天空地,遇到倾盆大雨,一大堆人站在101的出口看着20米外的地铁口看海。

即使是台北,即使是市中心,你也会发现一大堆摩托车在机动车道呼啸而过。街道的感觉和二线(三线?)城市差不多,实在对不住首都的地位。这里的商店一律不明牌,需要店员帮你查,或者你自己把标签翻出来看。价格就不用太在意了,大于等于香港。

相对于香港,台北要更加适合国人生活居住。这里说国语(新台语),也就是嗲嗲的台湾腔,很少有人说闽南语。汽车靠右行驶,汽车貌似也是中规车。服务生态度好得要死,香港的服务相形见拙。根据我的研究,这里的工资水平、交通成本、房价都和北京差不多,不过吃的东西价格和香港差不多。总之,走在台北街头,你觉得跟北京差别不大。

著名的台北故宫博物院,一个字:小!藏品系列很完整,门票也便宜,250TWD,当然比北京便宜是因为比北京小……

著名的台北101,两个字:不值!500TWD的门票,上去就是看看他们的风阻球吉祥物。至于能够看到台北市区全景耶,那是当然的哦,因为台北除了101附近几座高楼哦,全部都是低矮的房子哦,一眼就看到城市的边界了哦。

100元的嗅探利器

最近手头的工作需要和手机应用打交道,不过在手机上抓包调试是一件很让人不愉快的事情。就算是手机允许你挂载调试器到特定应用上抓包,这也不包含操作系统本身的一些网络流,比如DNS解析,而手机操作系统根本就不允许你在普通情况下获得本机抓包的能力。

退而求其次,从手机外面抓,电脑开一个热点,然后在电脑上抓。麻烦一点的是这需要电脑有无线网卡,而我手头偏偏只有台式机。笔记本也需要装很多软件,热点控制、DHCP、DNS等等一大堆,最后把自己活生生变成一台路由器。

看着手头的一堆路由器,把这些现成的路由器改装成嗅探器不就好了。于是折腾了三天,终于实现了利用TL-WR720N来多种方式嗅探,电脑上只要装一个嗅探器就可以了,不用特殊配置。

TL-WR720N,是我最熟悉的tplink系列里稍微比较特殊的一款,三模式硬件切换开关,双以太网接口,全功能USB接口,802.11n无线网卡。特殊就在双以太网上,这是一般的便携路由不具备的。基本思路是wifi的流量从wan口路由出去,但是把流量全部镜像到lan口上,使得电脑可以捕获。为了测试运营商网络,也贡献了手里一个闲置的3G猫,实创兴贴牌的WCDMA猫,联通定制,型号SRT-H800。

第零步,效果。

开关拨到AP,这就是一款传统的路由器,wifi和lan都通过路由穿过wan口,没别的功能。
开关拨到3G,wifi流量通过路由穿过3G猫,同时lan口上可以嗅探到。lan不能上网。
开关拨到Router,wifi流量通过路由穿过wan,同时lan口上可以嗅探到。lan不能上网。

第一步,刷OpenWRT。

从官网git下载了trunk今年5月9号的版本,支持TL-WR720N。
除了基本的配置以外,需要增加以下包:

  • kmod-usb-serial-option:USB转串口驱动,实测SRT-H800这种杂牌猫可以驱动。
  • kmod-usb-storage:支持3G猫上的micro-SD口。
  • kmod-scsi-cdrom:支持3G猫切换模式之前的cdrom。
  • iptables-mod-tee:支持TEE模式镜像流量。
  • usb-modeswitch:用于把3G猫从cdrom模式切换到modem模式。
  • comgt:用于modem拨号。

此外需要启用内核的debugfs,支持硬件切换开关。

其他一些包也可以加,比如我把cdrom和vfat也加上了,这样真的可以去读那个micro-SD。为了防止电脑突然不舒服,也加了tcpdump-mini,可以紧急情况下直接在路由器上抓包。(赠送一点:要想挂载U盘或者SD卡的FAT32或者NTFS文件系统,要加上kmod-nls-cp437和kmod-nls-utf8,不然mount的时候会报字符集搞不定。)

第二步,支持三模式硬件切换开关。

既然有硬开关,就可以在不修改闪存的情况下启用三种不同的配置,方便,也节约闪存寿命。在OpenWRT里,硬件通知并不是通过udev完成,而是使用hotplug。由于hotplug只是在硬件状态改变时得到通知,如果路由器掉电的时候有人拨动了模式开关,那就会工作不正确。TL-WR720N里要读到开关的当前状态,就必须用debugfs的gpio状态,没找到别的办法。

TL-WR720N的三模式开关使用了两个gpio口,gpio-18(sw1)对应BTN_0,gpio-20(sw2)对应BTN_1。读取很简单,/sys/kernel/debug/gpio文件grep+awk即可。

AP:sw1-hi,sw2-hi
3G:sw1-hi,sw2-lo
Router:sw1-lo,sw2-hi

为了节约模式切换时的闪存寿命,把/etc下必要的配置文件全部软链接到/tmp/profile,然后开机时根据模式修改/tmp/profile下的文件即可。

#!/bin/sh /etc/rc.common
# Copyright (C) 2006 OpenWrt.org

START=15
start() {
    local sw1=`fgrep sw1 /sys/kernel/debug/gpio | awk '{ print $NF }'`
    local sw2=`fgrep sw2 /sys/kernel/debug/gpio | awk '{ print $NF }'`
    local profile='router'

    if [ x"${sw1}" = x"hi" ] && [ x"${sw2}" = x"hi" ]; then
        profile='ap'
    elif [ x"${sw1}" = x"hi" ] && [ x"${sw2}" = x"lo" ]; then
        profile='3g'
    fi

    mkdir -p -m 0755 /tmp/profile
    ln -sf /etc/config/profile-"${profile}"/* /tmp/profile/
}

然后加入运行时检测开关拨动的脚本/etc/hotplug.d/button/10-profile

if [ x"${BUTTON}" = x"BTN_0" ] || [ x"${BUTTON}" = x"BTN_1" ]; then
    reboot
fi

这样如果发现开关被拨动,就会重启路由器,然后重新初始化一次。

第三步,支持3G猫。

很多3G猫有一个功能就是插到电脑上显示是一个cdrom,里面放着这个猫的驱动,这样就不需要再另外提供光盘了。然后驱动程序会发送指令命令3G猫把自己切换成猫状态,就可以正常拨号了。对于Linux系来说,这个动作通过usb_modeswitch工具完成。很幸运的是SRT-H800是一个比较多见的杂牌猫,所以usb_modeswitch原生支持。如果你的猫比较特别,可能需要自己找一下模式切换的指令,并且加入到/etc/usb-mode.json里。

紧接着就很莫名其妙了,不管怎么修改配置,usbmode -s总是报错无法发送模式切换命令。经历了几个小时的探索也没能解决这个问题,却发现如果是上电的时候猫就插在USB口上,那么第一次模式切换就会成功,之后插上的就不行了。网上的建议是可能有一些USB相关的内核模块存在配合上的问题,修改了几个配置,甚至rmmod再insmod,的确能看到有一些变化,但是就是不能switch。这个问题就放弃了,要求用户在使用运营商抓包的时候必须插好猫再上电。如果成功切换模式,会看到猫的硬件ID从ven=0x1e89 dev=0xf000变成ven=0x1e89 dev=0x1a20。

模式切换之后3G猫就是猫形态了,现在需要给它加载猫驱动。本质上所有的modem都是串行协议,所以需要载一个usb-serial驱动。很不幸,SRT-H800作为杂牌猫,3.10内核并不原生支持。不过发现usbserial_generic或者更加推荐的usbserial_option都可以驱动这个猫,只是需要命令驱动去激活这个不认识的设备。

加入/etc/hotplug.d/usb/80-srt-h800文件:

HTPID='1e89/1a20/0'
USPID='1e89 1a20 ff'

if [ x"${PRODUCT}" = x"${HTPID}" ]; then
    if [ x"${ACTION}" = x"add" ]; then
        echo "${USPID}" >/sys/bus/usb-serial/drivers/option1/new_id
    fi
fi

这样开机的时候就会把1e89:1a20这个设备加到usbserial_option的激活列表里。修改好配置重启,应该会看到/dev下出现了ttyUSB0~2三个串口设备,3G猫就激活了。比较坑爹的一点是,开始用ttyUSB0怎么都无法和猫通讯,后来一个一个试发现SRT-H800居然是用ttyUSB2来通讯的。3G拨号的设置和OpenWRT官网上的说明是一样的,而且发现即使默认用的是TD-SCDMA拨号指令,这个猫也可以拨通联通3G网络。手里没有电信猫所以就不知道能不能也拨通电信了。额外注意一点的是SIM卡一定要开PIN,不然有可能出奇怪的问题。

第四步,配置网络。

TL-WR720N一共有eth0、eth1、wlan0三个物理网卡,然后还有一个3G猫生成的动态网卡,为了和pppoe的习惯一样叫做ppp0。三个模式分别定义为:
AP:eth1和wlan0桥接,然后通过NAT穿过wan,ppp0禁用。
3G:wlan0通过NAT穿过ppp0,eth0禁用,eth1不配置forward。
Router:wlan0通过NAT穿过eth0,ppp0禁用,eth1不配置forward。

出于嗅探方便的考虑,eth1上开DHCP,并且把地址池设置为只有一个IP,其实就是静态IP,但是不需要电脑配置。为了尽可能不干扰被嗅探网络,把wifi的地址池设置为比较奇怪的C段。也为了减少干扰,wifi上开启加密。写法psd-mixed/ccmp+tkip,这是最兼容的模式,WPA+WPA2,TKIP+AES全都支持。不建议开启HT40-模式,就用HT20,兼容性好。

网络都通了以后,配TEE模块。因为OpenWRT并不原生支持TEE,所以修改/etc/firewall.user,在里面添加相关命令:

iptables -t mangle -I PREROUTING -i wlan0 -j TEE —gateway=192.168.0.5
iptables -t mangle -I POSTROUTING -o wlan0 -j TEE —gateway=192.168.0.5

两个注意点:1、OpenWRT建议使用-I而不是-A。2、gateway是eth1上唯一的那个IP。

第五步,电脑侧。

Wireshark或者Network Monitor,随便。由于使用的是TEE模块,所以网卡不需要开启混杂就可以嗅探。所有的wifi包(不包括wifi自己的链路包比如WPA握手)都是以明文传递给eth1的,你可以看到MAC是从路由器发往电脑,而IP却是wifi发往外网。只需要注意这种情况下电脑不要打开forwarding就好了。