今天来讲个关于备份的小故事。
以前有个同事之前在某使用UNIX的传统行业干了多年,他们的系统可用性要求不算高,但数据非常重要, 所以备份自然也是必不可少的。为了确保备份的安全性, 他们还雇了一家专门的保全公司定期把备份磁带从办公室拿走到该保全公司的仓库。
一切看起来万无一失,直到有一天他们的系统出现了问题,需要从备份中恢复数据。
自然,有那么多份不同时间的磁带的系统管理团队是不觉得有什么可慌的, 可是等到他们拿回了一大摞磁带的时候却傻了眼。
从磁带中恢复的只有一个符号链接 (symbolic link)。
不仅如此,之前更早的磁带里也都是同一个符号链接。最终他们成功恢复了半年之前的数据, 配合数据恢复公司恢复的硬盘数据,在几个星期之后终于把东西七七八八地怼回看起来是正确的样子了。
这是怎么回事呢?原来,他们在备份的时候选择了一个目录,然后慢慢的这个目录越来越大, 直到有一天有个大聪明表示不如这样吧,我们把数据搬到一块新的、更大的盘上,然后建个符号链接。
系统运行一切正常,甚至于备份也变得快了很多呢。
这个故事告诉我们,做了自动化备份之后,时不时的就得试试看备份出来的数据是不是真的能恢复成希望的样子。
]]>目前的经济形势下,许多公司都在进行裁员。大部分情况下,作为雇员对于公司是否进行裁员所能做的影响极为有限, 因此有必要提前对可能发生的裁员进行准备,以免仓促之下做出错误决策,或是在大规模裁员导致的踩踏事故面前束手无策。
本文主要是针对在美国境内工作的普通雇员,家里没矿,也暂时还没攒够足够退休的钱。 内容主要来自近期的阅读和想到的一些东西,算是给自己留下一些笔记。
统计显示,大约26%的美国成年人会在人生的某个时期被诊断为存在某些精神问题, 包括重度抑郁、躁郁、精神分裂或强迫症。认知到问题的存在并及早采取干预十分重要, 及时向专业人士寻求帮助有助于缓解这些问题并防止出现更严重的后果。
在平时需要确保自己有足够的时间进行户外运动,并远离可能导致压力增加的不健康的环境、人或工作, 在适当的地方划好边界(在正确的时候能够认识到「关你屁事」、「关我屁事」并作出恰当的反应)。
类似裁员这类对生活产生重大影响的事件发生时,感觉不好是人之常情。给自己一些时间去消化和思考发生的事情, 不要急于立即作出回应,和有过相关经验,特别是暂时没有遇到类似压力的亲朋好友交流一下相关经验并参考他们的意见, 要好过在短时间内仓促作出决策。
每一件事都具有两个维度的属性:其一是这件事是否能影响到你,其二是你是否能影响到这件事。
优先关注那些能影响到你,并且你能影响的事。例如,在工作中适时地称赞合作的同事、学会对自己的工作成果进行适度包装和宣传、 做对自己负责的事,例如不要用公司的手机或电脑做和工作无关的事,也不要用个人的手机或电脑访问公司资源, 或是不定期地复查自己的财务状况,等等。
那些能影响到你,但你无法影响的事,可以抽时间稍微关注一下,但是没必要过度关注。例如, 如果你自己不是管理数百人的管理人员,关注公司何时会做大规模裁员就意义不大了, 在砍到自己头上时中央肯定已经钦定了,而如果没砍到自己头上的话关注它也不会减少自己被砍的机会。
大规模裁员通常并不是员工个人甚至员工本人的老板能够控制的事。在适当的时候和前同事取得联系, 认可自己在过去存在的意义,可以进一步帮自己拓展人脉并增加未来的机会。
留下来的人可能会真实地感觉到幸存者的罪恶感,这事一种真实存在的应激反应。有这种感觉是正常的, 加强和其他人的联系、与家人或朋友分享自己的感受,做冥想,或是在生活中帮助其他人有助于从这种状态中走出来。
在力所能及的范围内帮助别人,例如帮他们在 LinkedIn 上联系其他潜在的雇主、帮他们修改简历,等等。 当然,每个人的能力和时间是有限的,也应认识到其他人有可能也正在经历一些和自己类似的事情, 但行好事莫问前程。
每个人对于财务的安全感的标准不同,个人认为应确保自己有足够支付9-12个月日常开支的可以随时动用的投资(注意, 如果此类投资中有股票类的资产,应确保在出现其波动导致的损失上限时自己仍然可以卖掉这些资产并支付这些开支; 简便起见,这些用于应急的资产应包括较大比例的债券、CD等波动不太多的投资产品)。
如果应急资金不足支付这些开支,或是不知道自己9-12个月的日常开支金额,应优先确保这些资金的要求。
持续学习并提高个人的价值非常关键,个人价值将会决定一个人在求职市场上的竞争力。 在工作中寻找学习新知识的机会,特别是那些可以在其他地方使用的技能, 跟踪行业的最新进展趋势,参与同业的研讨活动,可以帮助我们了解未来的需求和成长。 合理利用雇主提供的教育资源(如 Coursera、学费资助等等),建立长期的规划并持续执行。
寻找一些你认为希望在未来五年或十年之后达到的状态的人并寻求他们的建议, 这些人的人生经验往往更具参考价值并且更具可操作性。
任何时候不要使用个人设备处理公司数据,更不要从公司设备上复制数据到个人设备上。
使用公司设备处理个人数据更是不可取。许多公司的备份管理员可能可以看到备份数据 (理论上公司设备上的数据均应被认为是公司资产,如果一个人突然离职或去世, 雇主通常有合理的理由重新获得这些数据的访问权限), 如果用公司设备处理个人数据,则各类敏感的个人资料都可能会被这些管理员看到, 并产生泄密的风险。 此外,这些设备可能需要在短时间内交回,因此不应在上面保存大量需要导出的个人资料, 因为最终不一定有时间去完成导出,而雇主也可能使用某些组策略来抹除数据。
不要做可能给人口实的事,更不要做违反合同或法律的事。 工作可以再找,但这个圈子其实很小,不要给自己挖坑。
在双方关系的末期,要小心在新签署的文本中的坑,找一个利益无关的第三方专业人士帮忙审阅有助于降低相关风险。
及时归还公司资产,在归还时要求收据,可以提前准备一份清单逐项划勾。
前面提到,应确保自己账户上有足够支付一定时间(我个人认为需要保持9-12个月)生活开支的足额资金。 这些资金可以是定期存款、活期存款,也可以是各类可以随时变现的投资产品,但采用投资产品一类可能产生价值波动的产品时, 应将其按潜在的波动最大损失的情况去折算其价值。
理解 COBRA 计划。注意 COBRA 健保通常非常贵,可以考虑转为加入配偶的健康保险计划, 或者临时找一份能够在离职之后可以无缝或至少很快接续上的工作以避免罚款。如果使用了 FSA、 LPFSA、DCFSA 等账户,需要注意在离职(或 COBRA 结束)时这些账户的钱会作废。 在最后六十天可以考虑提前去配眼镜、做口腔正畸等可以使用 FSA/LPFSA 支付的项目。 此外,在健康保险到期之前可以考虑去做一次体检。
提前将工资单 / W2 改为纸质寄到家里,或(如果系统支持)改为使用个人的邮件账号访问这些数据,股票账户亦然。
提前安排 401k / IRA 等。
保存在职期间使用员工折扣购买的产品的购买凭证(如果只发到了公司信箱的话)。
办公室尽量不要放高价值的个人物品,裁员时可能没有机会很快取走,而此时可能会比较混乱导致丢失。
如果需要保持工作时使用的手机号,并且雇主支持,可能需要提前安排将手机号转出。(个人不推荐)
整理一份自己手里的公司设备列表,以便在交还时有案可查。
提前加入不依赖公司账号的员工讨论组。
确保自己名下的公司文档和其他资源有至少一个其他同事有访问权限,给自己平时休假留条后路,也不要给别人添麻烦。
如果个人账户中使用了雇主的 U2F key,应确保自己手里有至少两个属于自己的 U2F key 并注册进了所有的个人账号。 平时尽量和雇主的 U2F key 分开。
美国法律对于劳工的保护不如欧洲全面,但以加州为例,雇主仍然负有一系列责任,例如:
在平时要保存一份雇佣合同供未来参考。对于新的法律文书,尽量仔细阅读或请专业人士帮忙看过之后再签。
最后,推荐大家看2011年的电影 Margin Call, 当然,也祝大家和我自己不要遇到裁员。
]]>Hello Xin LI, Advance Notice to POBOL PO Box Customers - January 2024 Price Change.
If your PO Box renewal fees are due in January, and you would like to renew at the existing rate, please visit usps.com/poboxes to renew before January 21, 2024. Fees paid on or after that date are subject to new rates.
我租用 PO Box 主要是为了避免出现信件被盗的事件(我家附近有邻居有时会抱怨出现一些盗窃信箱中包裹的问题, 而信用卡、证件之类的通常都是寄到家里,如果被偷走的话会比较麻烦), 其次是这样一来就不必向许多机构暴露自己的住宅地址,假如在周边地区搬家的话也不需要挨个通知这些机构, 因为 PO Box 依然在原来的地方。然而,过去几年,USPS 的 PO Box 租金价格一直在不断地上涨。 根据记账,过去几年每年的租金分别是:
年份 | 价格 |
---|---|
2018 | 96.00 |
2019 | 106.00 |
2020 | 118.00 |
2021 | 146.00 |
2022 | 182.00 |
2023 | 194.00 |
这次 USPS 的来信对于到底要涨多少十分的语焉不详。由于贵厂在去年和今年年初分别进行了两次规模较大的裁员, 加上科技业整体上都不太景气,我感觉我近期对于价格的变化明显比之前敏感了不少, 年初我甚至给 ADT、保险公司等等一系列供应商打电话和发信去砍价来削减成本。 邮局的分布是比较稀疏的,在人口密集的地方更是如此,许多地方 PO Box 甚至需要排队等位才能租到, 而便宜的地方往往有各种各样的问题(例如可能只能在上班时间才能去、不提供物理地址, 或是离家和上班的地方都太远等等),因此换的成本其实还是挺高的,但这封信激发了我的好奇心。
出于好奇,我去 Postal Explorer 上查阅了具体的价格。USPS 的价格是通过「Notice 123」公布的, 其中对很多东西没有解释。具体到我的情况, 登入 USPS 网站之后,在「Manage PO Box」 可以看到我使用的 PO Box 是「size 1」(最小的那种), 但并未找到 Fee Group。
于是我想到了另一种方法:将年度价格除以2的到半年的费用为 $97,查现行 Notice 123,发现是在
Competitive Box Size and Fee
Per Semi-Annual (6-month) Period
表中「C33」的价格。查看2024新版的价格,得到新的半年价格为 $100/半年,即 $200/一年。
更进一步,「C33」这类 fee group 是在 Publication 431 公布的,但是网上能查到的只是该文件的一些碎片, 尽管如此也够用了,在这些碎片中查找 PO Box 的邮政编码也可以得到自己的 group。
除此之外,本次对 PO Box 费用的审计中还发现按年续费和每半年续费一次的单价是一样的(每季度续费的话价格会高一些)。 另一方面,除了首次申请 PO Box 时一次交一年的租金可获得一个月的额外使用时间之外, 平时按年续费并没有这种优惠。考虑到 USPS 是每年调整一次价格, 显然半年付一次租金要更合算一些,因此也一并将自动续费的时间改成了半年。
]]>SMTP 协议是 RFC 5321 定义的邮件传输协议,通常采用 TCP
作为传输协议,在同一连接中传输指令和数据。传统上,SMTP 协议采取「一问一答」的形式,但目前正常的 SMTP
服务器和客户端普遍实现了流水线作业(RFC 2920)
来减少客户端与服务器之间的交互次数,从而减少送信延迟。采用流水线作业时,客户端必须继续遵守协议的状态机约束:
举例来说,在发出 EHLO
/ HELO
之后,客户端必须等待服务器的回应,而不能直接开始进入下一状态;
而在发出 DATA
(信体开始)指令之后,在收到服务器的 354 回应之前,也不能开始传送信体。
更进一步,如果服务器没有回应自己支持 PIPELINING
,则客户端必须采取旧式的「一问一答」形式,
而不是新式的流水线作业。
SMTP 协议与 D. J. Bernstein (“djb”) 设计的 QMTP 不同,
在传输信体时不会事先声明信体尺寸。RFC 5321 Section 4.1.1.4 规定,信体以单行的 .
,即 <CRLF>.<CRLF>
作为终结标志。RFC 3030 新增的 BDAT
指令是对此的一项补救措施。
在电传打字机时代,「回车」(<CR>
、\r
或 0x0d
) 表示将打字头挪到一行开始,而「换行」(<LF>
、\n
或 0x0a
)
则表示将打印纸向上推一行。对电传打字机来说,由于一行的长度不固定,「回车」操作所需的时间也不固定,
而「换行」操作则是电机推一个固定的长度,两部分的驱动设备是独立的,并且前一操作通常需要消耗更多时间。
从设计角度,先开始「回车」再开始「换行」意味着两个动作可以同时进行并提高速度。
在上世纪六十年代,ASA (ANSI前身) 和 ISO 分别设计了与 ASCII 类似的编码标准,
在 ASA 草案中,采用「回车+换行」(<CRLF>
) 作为新行的符号,而在 ISO 草案中,则同时接受「回车+换行」(<CRLF>
)
和 「换行」(<LF>
) 作为新行的符号。
这些差异影响了当时的操作系统设计者,他们采纳了不同的设计来表达文本中的新行。
DEC 采纳了「回车+换行」 (<CRLF>
) 来配合电传打字机,这影响了后来的 CP/M 和 MS-DOS 以及 Windows。
而 Multics 则选择了采用 「换行」<LF>
来表示新行,并在设备驱动中将其翻译成回车加换行来支持电传打字机,
这影响了其精神继承者 Unix 和各类类 Unix 系统。
在早期的 Sendmail 版本中,除了标准的 <CRLF>
行末符,
也支持只用 <LF>
作为行末符,尽管 RFC 5321 Section 2.3.8
禁止客户端这样做,但我们都知道「历史无可替代的力量」究竟有多大,
时至今日,仍然有相当多的 MTA 服务器软件选择继续支持它来确保兼容性。
如此一来,对于 <LF>.<LF>
或 <LF>.<CRLF>
而不是 <CRLF>.<CRLF>
便可以有不同的解释。
对于不接受 <LF>
作为行末符的邮件服务器来说,前两种都只是再正常不过的文本,会原样发给下一个邮件服务器;
而对于接受 <LF>
作为行末符的的邮件服务器来说,它会被解读为不同的语义,即信体结束。
而当前一种邮件服务器把邮件发给后一种邮件服务器时,
后者的不同解读可能会导致灾难性的后果:
在 <LF>.<LF>
或 <LF>.<CRLF>
后面可以「夹带」(smuggling)
一组 SMTP 指令,如果这一层邮件服务器是拥有签名权的外发邮件服务器,
并且信任上一层的服务器的话,则可以通过这种方式绕过正常的访问控制来实现不正常的外发操作。
现时,正常的邮件客户端是不应该发出不带「回车」的裸「换行」的。postfix 的作者 建议 彻底禁止这样做, 这符合 RFC 5321 的规定。 Postfix 3.8.4、 3.7.9、 3.6.13 和 3.5.23 新增的 smtpd_forbid_bare_newline 在遇到裸「换行」时会直接断开连接,从而避免这种攻击。
更早版本的 Postfix 可以通过禁止绕过状态机的流水线操作 (reject_unauth_pipelining
或更早版本的 smtpd_forbid_unauth_pipelining
,并禁止 CHUNKING 扩展) 来规避问题。
在过去二十年中的大部分时间,我采用的是 amavisd-new, 与直接使用 SpamAssassin 相比, 它还增加了病毒扫描等一系列功能和 milter 接口,这让它与 MTA 更容易集成。
最近我发现 FreeBSD.org 把反垃圾系统替换成了 rspamd, 所以在11月初把我的邮件系统也换成了 rspamd,经过两个月的使用,总体的感觉是「我tm早干嘛去了」。
FreeBSD 的 rspamd port 是 Vsevolod Stakhov (vsevolod@) 维护的,和 amavisd-new 相比, 最肉眼可见的好处就是 CPU 开销的大幅下降,除了由于它是 C 写的之外,这也得益于其 事件驱动的异步架构设计。
在 FreeBSD 上使用 rspamd 可以直接用 pkg 来安装(此处同时安装 redis 作为后端存储):
pkg install redis rspamd
需要注意,redis 默认会绑到 127.0.0.1
,如果在 jail 中运行 redis 的话,这可能会导致 redis 暴露给整个 Internet,
这很危险。解决方法是把 redis 绑到某个安全的内网,或是只使用 Unix domain socket。
例如,redis 可以如此配置:
# 禁止 TCP 监听
port 0
# 启用 Unix domain socket
unixsocket /var/run/redis/rspamd.sock
unixsocketperm 660
requirepass <某个随机密码>
maxmemory 512mb
maxmemory-policy volatile-ttl
注意上述权限配置中使用的权限是 660
,因此需要把 rspamd 的角色用户加入 redis 用户组。
与之对应地,在 /usr/local/etc/rspamd/local.d/redis.conf
中如此配置:
servers = "/var/run/redis/rspamd.sock";
password = "<某个随机密码>";
与 amavisd-new 类似,rspamd 的 milter 服务也可以添加信头来方便其他 MUA 或是 sieve 来进行拣选。
很明显,其他系统添加的此类信头应该删掉,为了便于迁移,我采用了如下的配置 (/usr/local/etc/rspamd/local.d/milter_headers.conf
):
use = ["x-spamd-bar", "authentication-results", "x-spamd-result", "x-spam-level"];
authenticated_headers = ["authentication-results"];
routines {
authentication-results {
header = "Authentication-Results";
remove = 1;
}
x-spamd-result {
header = 'X-Spamd-Result';
remove = 1;
}
x-spamd-level {
header = "X-Spam-Level";
char = "*";
remove = 1;
}
}
其他配置方面我没有做特别多的改动。
postfix 集成部分,基本上只是把 smtpd_milter 换成 rspamd。
与 sieve 集成的部分,可以用 rspamc
去连接 rspamd 的 controller 来完成 learn_spam
和 learn_ham
。
我之前的系统中长期使用了 clamav,而该系统最后一次抓到病毒是 2006 年的事情。 clamav 本身依赖许多解压缩程序,尽管它是丢掉特权运行的,但考虑到现实情况,对于我这样的食古不化型 (邮件客户端关闭了全部附件预览等一系列功能) 的邮件用户来说,反病毒的价值确实不大,因此这次顺手暂时先拆掉了。
rspamd 的控制面板可以罩在 zero trust 代理 后面,方便访问。
]]>与内存不同,外存的速度通常比内存要慢若干量级。
普通的应用程序在写数据时通常有两种选择:其一是以「同步」方式进行操作,
即在操作返回时所有的写操作皆已反映到可靠的存储介质上,
其二则是以「异步」方式进行操作,即发起一个写操作,
然后应用程序可以干别的,随后查询状态,
或者提交一个同步操作来把之前的数据保存到盘上。
有了「此调用返回时,此调用之前的(元)数据均已保存到可靠的介质上」
的保证,就可以在其上搭出事务支持了:
例如,数据库的 COMMIT
操作就可以通过等待与之对应的日志数据的同步操作来实现,
如果系统在同步操作完成之后发生断电或崩溃,数据库依然可以通过重放日志数据来恢复。
为了尽量有效地利用系统资源,现时的操作系统内核在实现读写操作时, 均采取异步方式,即发起操作时内核在发起(或者不发起,而是等操作攒的足够多的时候才开始发起) 写操作之后并不等待其完成,而是转去做其他事,并在之后等待来自设备或时钟的中断, 并在收到该中断后再次向硬件质询操作是否完成,并据此作出相应的处理。
由于内存的容量相比外存来说要少若干量级,因此操作系统必须有效地将内存用于不同的目的。 以输入输出缓冲区为例,显而易见,从磁盘中读出的、未经改变的数据在必要时可以再从磁盘中读出, 因而这类缓冲区可以随时丢弃并用作其他用途;而修改过的、还没有写盘的数据则不能随意丢弃。 为了区分缓冲区的这两种状态,内核通常会将缓冲区标记为「脏的 (dirty)」, 表示其中包含未落入可靠存储的数据,或是「干净的 (clean)」, 表示其中的全部数据已经保存到了可靠的存储中。对文件系统来说, 这两种状态还包括与之相关的文件系统元数据,例如文件引用了哪些数据块、 这些块是否已经分配给某个特定文件,等等,在这些状态之间也有一些依赖关系, 例如,文件数据写盘之后,只有在与这些数据相关的元数据也完成了写盘之后, 才应认为这些数据已经保存到了可靠的存储中,等等。
ZFS 中, dnode 是一个长度为 512 字节倍数的可变长度数据结构,用来表达 ZFS 中的对象。
与 UFS 的 inode 类似,dnode 也用来表达文件或目录,除此之外,它也可以表达 ZVOL 卷以及其它一些内部元数据。
盘上的 dnode
是存放在 struct dnode_phys
中的,
而内存中则是 struct dnode
,这两个数据结构的命名方式 (带 _phys
后缀表示存储到盘上的结构,而不带后缀表示内存中的结构) 与 ZFS
中其他一些数据结构的规则类似。
dnode 中包含了一系列用于表示文件所属的缓冲区是否已经完成写盘的描述信息,
其中包括 dn_dirty_link
(dnode 本身是否在顶层 objset 对象中的未写盘列表中) 和
dn_dirty_records
(包含未写盘数据的 dbufs
)。
许多现代文件系统中都有「稀疏文件(sparse file)」的概念,这类文件中存在大量全部为 \0
(NUL
) 的区域,
如果文件系统支持的话,这类区域可以采取在元数据中标记,而不是真的写入完整的全 0 块的方式来表达。
这样做的主要好处有两个:首先是读出数据时,操作系统不需要真的进行 I/O,因而有助于提高访问效率;
其次,它也节省了存储。在符合 POSIX 的操作系统中,稀疏文件对应用程序来说是透明的,
应用程序可以使用 truncate(2) 来扩大文件,
然后在其中 lseek(2) 在不连续的区域分别写入,
而不是在连续的位置持续写入数据来制造稀疏文件。
除此之外, lseek(2) 还可以定位文件中这类没有写盘的全 0 区域 (SEEK_HOLE
;
需要说明的是,取决于文件系统的具体实现,这些区域可能比之前写入数据时 lseek(2) 跳过的间隔要略小,
因为绝大多数情况下文件系统在存放此类空白区域时是按照完整的数据块尺寸进行的,
如果跳过的区域不在数据块的整倍数边界上,则从当前数据区域到下一个数据块之间的部分仍然需要真的写0),
或是从这类全 0 区域为起点定位到下一个包含数据的区域 (SEEK_DATA
)。
与此类似,在 ZFS 中,如果一个数据块的内容是全 0,则其数据对应的 块指针 (Block Pointer) 会做特殊标记,
称之为「洞」(hole)。 ZFS 的其他组件中使用 BP_IS_HOLE
宏来测试这类块指针,
这项优化使得对于这类数据块无需真的在盘上写 0,也无需真的从盘上读出数据,
其好处与稀疏文件中对全 0 块的处理是类似的。需要注意的是,对于「洞」的处理是在 I/O
层 (zio
) 的时候进行的,因此只有在写操作做完时,我们才能知道一组数据块最终是不是会变成「洞」。
简而言之,这次的数据损坏问题是有时 ZFS 会在进行 SEEK_DATA
时,不正确地跳过含有数据的部分。
由于新的 FreeBSD 和 Linux 的 cp(1) 均以不同的方式使用了这一功能来跳过全 0 的数据块,
因此会导致复制出来的数据中原本不应该出现全 0 的部分出现全 0 的现象。除此之外,
其他依赖 SEEK_DATA
的应用程序也可能受到影响。尽管如此, ZFS 最终写入的原始数据依然是正确的,
加上这个问题需要符合一系列比较苛刻的条件才能触发,因此普通用户可能不太容易碰到它。
文件系统对于存储的访问是独占的。读取数据时,很自然的想法是找到盘上存储数据的位置,并发起一个 I/O 操作把数据加载到缓冲区中,然后把该缓冲区通过内核接口交给应用程序。但在实践上,实际发生的操作要复杂得多: 盘上的数据可能之前已经读过并且仍然在位于主存的缓冲区中,此时显然直接将这些数据交给应用程序可以省掉一次 I/O; 对于刚刚修改过的数据,文件系统更是必须从内存中的副本来取得数据,因为内存中的这份「脏」的数据才是最新的那份。 因此,文件系统的实现中就必须对缓冲区的状态进行完整的记账,才能确保其交给应用程序的数据的正确性。
对于 ZFS 来说,这一部分更为复杂。 ZFS 是一个写时复制 (Copy-on-Write) 的文件系统,它在写数据时, 并不会覆盖掉已经存在的数据块,而需要将原数据块中不应修改的部分(如果存在的话)读出, 然后写入一个处于存储上新位置的全数据块。
ZFS 的事务组 (txg) 包含三个状态:
Open (初始状态,允许新的写操作进入。一旦积累够足够多的操作,或是达到了 vfs.zfs.txg.timeout
,
则进入下一个状态)、Quiescing (允许上一状态中还未做完的操作做完,同时开启一个新的 Open txg) 和 Syncing
(将 Quiesced 的 txg 写入可靠的存储)。
在考虑数据是否已经写盘时,需要同时考虑这三个事务组中的状态。考虑在一个已经存在的文件中先后写入两个数据块 a、b 的情况,这可能潜在地会形成两个新的 dnode 版本,这两个版本的 dnode 以及数据块 a、b 可能出现在三个不同状态的事务组中, 其 dirty 状态会随着写盘而逐渐被清除。
Quiescing 和 Syncing 状态的事务组中的数据状态未必反映应用程序认为的数据最新状态,
但前面提到,「洞」是在写操作做完时才确定的。
这意味着 SEEK_DATA
如果遇到了一段标记为「dirty」
的区域,则只有写入完成之后才能够可靠地判断它是不是「洞」。
然而我们知道 I/O 操作相对于内存操作来说是要慢很多的,
一个 txg 可能相当大,假如每次 SEEK_DATA
的时候都等待数据写完,
很明显是不经济的。那怎么办呢?考虑 SEEK_DATA
的语意,
一组全 0 的数据也可以认为是数据(相反,如果是一组非全 0
的数据被跳过则会导致问题),因此,我们可以判断 dnode 中是否包含了未写盘数据,
并针对这些数据一律返回「有数据」,而不是真的等待写入操作做完。
由于这样一来一些本应被认为是空洞部分的区域会被认为存在数据,
但如此这类操作便不必等待 txg 完全写入,
因此会改善一些应用程序的性能。
系统默认的设置 (vfs.zfs.dmu_offset_next_sync=1
),则是在进行 SEEK_DATA
操作时等待之前的事务组完成写入。
然而我们注意到,如此设置时,在测试中问题似乎更容易被触发,这又是为什么呢?
在写入 dnode 的过程中,有一个短暂的时间段, dnode 本身的 dirty 状态被清除。
但与之相关的文件数据还没有完成写盘操作。这个状态只会发生在 Syncing 阶段。
在问题得到修补之前, dnode_is_dirty()
在这种状态下会不正确地返回 B_FALSE
。
前面提到,在 SEEK_DATA
时,如果 dnode 包含未写盘的数据则需要进行特殊处理。
对于 vfs.zfs.dmu_offset_next_sync=0
的情形,此时应直接告知应用程序此区域有数据,
而对于默认情形,则应等待 txg 做完,而 txg 操作需要先把 dnode 的 dn_struct_rwlock
锁打开,如此 dnode 的状态变有可能在这段时间内发生变化,因此必须从头再做一次检查。
无论 dmu_offset_next_sync
的值是什么,最终如果 dnode_is_dirty()
不正确地返回了 B_FALSE
的话,我们都可能告诉应用一个本应被认为有数据的位置是空洞。因此,设置 vfs.zfs.dmu_offset_next_sync=0
并不能真的彻底避免问题,因为问题依旧可能发生,但 vfs.zfs.dmu_offset_next_sync=1
时,由于在等待 txg 做完之前和之后各做一次 dnode_is_dirty()
检查,
因此碰到这个边界条件的机会反而增加了。
知道了问题的原因,只要让 dnode_is_dirty()
能返回正确结果便可以修正问题。
在修正问题之前,它是如此判断 dnode 是否包含未写盘数据的:
for (int i = 0; i < TXG_SIZE; i++) {
if (multilist_link_active(&dn->dn_dirty_link[i])) {
mutex_exit(&dn->dn_mtx);
return (B_TRUE);
}
}
若 Syncing 阶段的 txg 在不恰当的时机清除了 dnode 的 dirty 状态,
则无论其是否包含 dn_dirty_records
均会导致返回 B_FALSE
。
因此解法便是增加对 dn_dirty_records
的检查:
for (int i = 0; i < TXG_SIZE; i++) {
if (multilist_link_active(&dn->dn_dirty_link[i]) ||
!list_is_empty(&dn->dn_dirty_records[i])) {
mutex_exit(&dn->dn_mtx);
return (B_TRUE);
}
}
-RELEASE
,
同时由一位 Release Engineer 开始最终的 build(对应的文件会发布到 FTP 上,
并在 网站 上提供链接),并在适当的时候将 releng/ 分支上的代码
tag 成 release。此后, Security Team 需要将 Release Engineer 签名的 -RELEASE
放到 freebsd-update builder 上再次 build、签名,并生成二进制更新所需的文件。
由于安装用的 ISO 映像文件都比较大,传统上将这些映像文件分发到全球的镜像站点上需要一些时间。 现时,云服务提供商往往还有自己的 QA 步骤,因此最终宣布 -RELEASE 的时间往往会比 FTP 上出现的时间晚上一周左右。技术上这段时间这个 build 依然只是一个发布候选版本(Release Candidate), 因此普通用户不应使用这些版本,因为在这一周的时间如果遇到一些突发状况的话可能会需要将这个版本撤回 (例如原本应发布为 FreeBSD 4.6.1 的 FreeBSD 4.6.2 就是这样的情况)。
我个人在机房的机器采用的 FreeBSD 是一套经过定制的版本,因此大版本升级时需要将本地的补丁 rebase 到新的 release 上面。
所以我采用的是使用源代码升级的方法。不过,从 去年启用了 Poudriere 之后,
我通常是在 Poudriere 上先把新的 package 全都 build 好之后用 pkg upgrade -fy
一次性完成升级了。
这次升级遇到的一些比较明显的坑:
第一个是 FreeBSD 14.0-RELEASE 去掉了配置文件中的 $FreeBSD$
版本标记。在使用 CVS 和 Subversion
的时代,这两个版本控制系统支持关键词扩展,可以将这些标记展开成包含文件路径、版本的字符串,例如:
$FreeBSD: releng/8.2/lib/libc/string/strlen.c 208051 2010-05-13 23:28:20Z delphij $
这些信息有助于在调试时知道一个可执行文件使用了哪些源文件(假如所有的源文件中都正确使用了 __FBSDID
宏,
这个宏会把这些字符串放到 ELF 文件的 .comment 段中)。迁移到 git 之后,
由于 git 不再支持关键词扩展,这些信息的意义大打折扣,于是我们在最近决定将所有的 $FreeBSD$
一并删去了。
由于 FreeBSD 的配置文件合并程序(包括 etcupdate 和 mergemaster)在合并时都是采用的三路比对,
因此如果原先的 $FreeBSD$
位置和进行的修改位置接近,则很可能会出现合并冲突,此时会需要手工干预。
如果平时做事不太细心,建议换一个头脑比较清楚的时候再做升级。
假如之前没有用过 etcupdate,个人建议在升级之前做一次初始化来让 etcupdate 认识之前的状态。
第二个坑是在安装过程中某些 shared object 库之间存在依赖关系。例如, libedit.so.8
需要用 libtinfow.so.9
,
后者是 FreeBSD 14 中 新引入的,而两个库在安装时并未遵循依赖关系先装后者,
因此如果有 cron 任务恰好在安装过程中的某个时刻进入并且可执行文件恰好用到了 libedit 的话,可能会失败。
总体上,FreeBSD 14 已经在家里的网关等机器上跑了很久,因此并没有太多其他的意外。常见的其他注意事项, 例如在升级 ZFS 存储池之前要记得更新引导记录(或UEFI ESP)基本上每个版本都需要,在此就不赘述了。
]]>为什么要换回 cable 呢?Sail 声称他们采用的设计可以避免雨季带来的干扰,事实来看确实是这样, 但是他们的服务稳定性在过去几个月出现了灾难性的下降,此外延迟由于一些未知的原因增加了不少。 我为此收集了一些数据,发现去大河以及 Google 的延迟有时竟然超过了 500ms,这个就实在是太难受了。
考虑再三,我决定还是换回了 Comcast。我使用的是之前的 SB6190,实测下载速度是 520Mbps,上传速度是 23Mbps, 和 Sail 的互有胜负,好处是他们提供了 IPv6。
余下的事情是拆除到大河的 IPsec 隧道,改用 DHCPv6,以及防火墙。总体上没什么需要记录的(不过由于此前家里的 IPv6 是写在网桥上的静态地址,这次需要将其全部拆除)。
]]>2009年的时候 kmacy@ 做了 一些初步的工作,但后续没有继续推进。 这之后 我 考虑重新把这个事给做掉,但苦于平时比较忙因此未能如愿。最终, Yoshihiro Ota 完成了大部分的工作。
这里记录一下当时所做的事情。
FreeBSD 是一个有相当长历史的项目,而 zlib 是一个很常用的库,并且当时在整个系统中有多处不同的副本, 因此我们希望这个迁移的过程尽可能平滑而尽量不要直接导致整个项目无法联编,或是需要长时间禁用某些模块的情况。
此前, Peter Wemm 在2013年把net/zlib(这是一份古老的 zlib 1.0.4 打成浆糊的版本) 能够与 ZFS 和 DTrace 中的版本并存,但他当时采用的方法是直接把几个冲突的符号改名。 此时,由于 ZFS 中附带的 zlib 版本也略显陈旧了 (1.2.3),因此,给它换成最新版本也成了一项目标。
在开工之前,首先需要摸清此时的现状。在 FreeBSD 内核中当时使用了某种版本的 zlib 的模块主要包括:
a.out
格式可执行文件的 gzip 压缩支持。它使用了一份很旧的 zlib 打成浆糊的版本,位于 sys/kern/subr_inflate.c
。sys/kern/subr_inflate.c
的 zlib 副本。sys/opencrypto/cryptodeflate.c
)。它使用的是 pppd 魔改过的 zlib 1.0.4。还有一个问题是 zlib 提供了一个 crc32 实现。crc32是一种很常用的算法, FreeBSD 中包含了 Gary S. Brown 于 1986 年撰写的一个实现。 两个实现的用法略有不同,但由于都叫 crc32 因此需要把其中一个改个名字才可以并存。
接下来是确定我们到底应该做什么。a.out
的支持已经过时(毕竟已经超过10年了,大家都应该用ELF了),
kgzldr
和 kgzip
已经不再必要(因为已经有新的功能去实现同样的目的),因此这两项功能可以直接砍掉。
魔改过的 zlib 1.0.4:Paul Mackerras 在早年对 zlib 进行了一些魔改以便适应 ppp 中的一些需要,例如 Z_PACKET_FLUSH
。
RFC 1979 Section 2.1 中规定,
deflate 的最后4个字节内容(0x00 0x00 0xFF 0xFF
)不应传出。除此之外, zlib 1.0.4 大致与最新的 zlib
相同。我们的主要目标是把这份 zlib 彻底移除。
ZFS 和 DTrace:作者采取了良好的工程实践,不对第三方代码进行不必要的改动。这份 zlib 1.2.3 的移除并没有什么技术上的特别困难。
FreeBSD 的源代码目录规范要求内核使用的代码放到 sys/
,因此首先把 zlib 挪过去。D20191, rS347244, c9083b85
为了便于逐步完成修改,我们选择了首先让两个库可以并存,然后逐渐将现有代码迁移到最新的 zlib 上的方法。
state->dummy
。D20222, rS348222, a4981878。这是早期版本 zlib 中针对编译器的一个workaround,其值对于调试意义不大。kgzldr
和 kgzip
支持。D20248, rS348225, 5e86bd60crc32()
实现拆分出来并改名为 gsb_crc32()
。D20193, rS349151, f89d2072a.out
的 gzip 压缩支持。D21099, rS350436, d4565741sys/dev/zlib
, 将当时的最新版 zlib 1.2.11 变为内核模块,砍掉 ZFS 中的 zlib (1.2.3) 并替换为新的 zlib。D19706, rS350496, 0ed1d6fbZ_SOLO
模式中(主要是为 geom_uzip 做准备)。D21156, rS350670, a15cb219DDB_CTF
改为使用 zlib 1.2.11。D21176, rS350744, 22bbc4b2总体上,除了 ng_deflate(4) 之外,大部分 zlib 1.0.4 转换为 zlib 1.2.11 的过程仅仅是把使用的头文件改为新的 zlib,并根据需要使用 sys/dev/zlib
中的分配器替换掉原有的。
这里的一个例外是 ZFS 和 DTrace,当时使用的 OpenSolaris ZFS 中做了一些额外的内存越界检查,因此它使用了自己单独的一套分配器,而现在 OpenZFS 中则是和其他分配器一样了。
采用同一套 zlib,令后续的 zlib 安全问题的修补变得容易了许多(去年的 FreeBSD zlib 安全公告也因此受益,这是后话了)。
由于 Yoshihiro Ota 和我都不是专职做这件事,整体前后用了两个月左右的时间,但由于采取的小步变动、并在提交前进行大量测试的做法,整个过程并未对用户产生可见的负面影响,
我个人对整个过程是比较满意的。
退信中给出的线索是:
550 DMARC check failed [XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
IP: 2607:f8b0:4864:20::112b]. https://service.mail.qq.com/detail/124/61.
腾讯给出的那个链接是一篇关于 DMARC 如何配置的文章。 从上下文来看,应该是由于腾讯的服务器认为 Google Workspace 的 IP 地址不在允许发信的范围内。
退信确实是 Google Workspace 的邮件服务生成的,数据来源是和对方(腾讯) MX 之间的 SMTP 会话。
我的这个域名确实启用了 DMARC。我的DMARC记录 (域名下的 _dmarc
TXT
记录) 设置如下:
v=DMARC1; p=reject; rua=mailto:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX@dmarc-reports.cloudflare.net;
pct=100; adkim=s; aspf=s
此配置要求对方检查 DKIM 和 SPF,并拒绝不匹配的邮件。
其 SPF 记录 (域名的 @ 的 TXT
记录) 设置如下。这是 Google 官方文档中推荐的设置:
v=spf1 include:_spf.google.com -all
主要的区别是,我使用的是 -all
,即拒绝 SPF 不匹配的邮件,而不是更为常见的、表示即使发现 SPF
不匹配,也仅仅做标记但允许接收的 ~all
。
这里需要说一下背景:在 2004 年左右,一些业内人士认为应该推荐采用
~all
而不是 -all
,因为当时许多大型邮件系统的架构中存在多层邮件服务器分别进行不同的过滤处理,
而在 SMTP 会话阶段直接回一个永久性拒绝 (5XX) 代码可能会让一些不适任的邮件系统管理员如此配置的邮件系统将一些伪造了来源的邮件退到不希望的地方。
我并不赞同这样的观点。事实上,在会话阶段进行退信要比在收件方服务器上生成退信并将退信退给发件人或对方域名的 postmaster 要更好:
对于垃圾邮件的发送者,这意味着他们的邮件服务器会立即知道自己的行为被直接地丑拒,
接收方明确告知他们吞下垃圾邮件,而不是继续发送。对于正常的邮件服务器,
这意味着发件人可以立即收到退信从而找到自己的邮件系统管理员,而不是告诉收件人去翻垃圾箱看看是否有自己的邮件。
另一方面,回应永久性拒绝并不妨碍收件服务器对邮件内容进行进一步分析:它完全可以在 DATA
阶段结束时再回拒绝,从而将邮件完整地接收下来但不递给收件人。
当然,历史无法假设,并已经无数次地证明人类根本不配拥有美好的事物。
DomainKey 记录 (域名下的 google._domainkey
TXT
记录) 由于与此问题无关,故在此处略去。
该域名启用了 DNSSEC。
首先是检查 SPF 设置的是否有问题。 _spf.google.com
是 Google 各类邮件服务采用的公共 SPF 记录,其出现问题的可能性不大。
不过,我还是手工查询了记录。在本次 SMTP 会话中 Google 使用的发送邮件地址 2607:f8b0:4864:20::112b
在此 SPF 记录引用的
_netblocks2.google.com
中以 ip6:2607:f8b0:4000::/36
的形式列出:
$ dig +short _spf.google.com TXT
"v=spf1 include:_netblocks.google.com include:_netblocks2.google.com
include:_netblocks3.google.com ~all"
$ dig +short _netblocks.google.com TXT
"v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20
ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21
ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19
ip4:216.239.32.0/19 ~all"
$ dig +short _netblocks2.google.com TXT
"v=spf1 ip6:2001:4860:4000::/36 ip6:2404:6800:4000::/36
ip6:2607:f8b0:4000::/36 ip6:2800:3f0:4000::/36
ip6:2a00:1450:4000::/36 ip6:2c0f:fb50:4000::/36 ~all"
$ dig +short _netblocks3.google.com TXT
"v=spf1 ip4:172.217.0.0/19 ip4:172.217.32.0/20 ip4:172.217.128.0/19
ip4:172.217.160.0/20 ip4:172.217.192.0/19 ip4:172.253.56.0/21
ip4:172.253.112.0/20 ip4:108.177.96.0/19 ip4:35.191.0.0/16
ip4:130.211.0.0/22 ~all"
用 Perl 的 Mail::SPF 验证得到的结论和我认为的一致:
Mechanism 'include:_netblocks2.google.com' matched
尽管我的做法可能不太常见,但它完全符合 RFC 7208,
事实上,其 5.2 小节中描述的流程正是以 -all
作为例子的,按照 RFC 中的流程处理并没有二义性。
至于为什么腾讯的服务器认为 2607:f8b0:4864:20::112b
不是允许发信的 IP,我还是不完全理解。一个猜测是可能他们对于 -all
的处理和 ~all
有所不同。不过我离开邮件这行多年,一时也找不到腾讯的联络人(如果本blog的读者有哪位可以帮忙联系一下的话不甚感激),于是就采取了比较偷懒的做法,
也就是直接抄 gmail.com 的作业:
v=spf1 redirect=_spf.google.com
这一做法简单粗暴地告诉收件服务器:本域名直接以 _spf.google.com
的内容作为自己的 SPF 策略。
再测试时发现可以正常发信给 qq.com 了(作为副作用,这意味着最后的规则从 -all
变成了 ~all
,测试显示,我的另一个域名使用 Google 推荐的 SPF 配置,即 ~all
时,其 Google Workspace 邮件可以正常发到 qq.com)。
尽管早在2018年 RFC 8463 Section 5 就已经将 RFC 6376 Section 3.3 修正为要求验证者必须支持 Ed25519 验证,然而时至今日主要的邮件服务提供商中, 包括 Google 的 Gmail 和 Microsoft 的 Outlook.com 以及 Apple 的 iCloud 都还不支持验证 Ed25519 DKIM 签名。ProtonMail 支持 Ed25519 的 DKIM 签名,但它并不使用 Ed25519 签名向外发出的邮件。
经测试,同时采用 RSA 和 Ed25519 签名发出的邮件可以让那些只支持 RSA 签名的邮件运营者正确验证邮件, 显然,这样做会增加邮件尺寸,也会增加 DNS 流量,这些都和采用 Ed25519 签名的希望背道而驰, 但我觉得总得做点什么来让互联网巨头开始面对这一现实,因此我打算从今天开始将我自己域名发出的邮件同时采用 RSA 和 Ed25519 签名,直到三大主要的邮件服务提供商(Google、 Microsoft 和 Apple)都支持它为止。
我目前采用的是 dkimpy-milter。 和之前使用的 OpenDKIM 相比,它的开发更为活跃(后者在2018年发布了一版beta之后就没有再发布新的版本了)。 其配置大致如下:
首先是生成签名用的密钥对:
dknewkey --ktype ed25519 ed25519-2023
dknewkey rsa-2023
这其中 ed25519-2023
和 rsa-2023
是计划使用的 selector
名字。程序会生成 .key
(私钥)和
.dns
(公钥,用来添加到域名 zone 中)两个文件。
注意 dkimpy 并未考虑 TXT 记录只能有255个字符的限制,对于 2048-bit 的 RSA 公钥,必须将其切开。 对于 Ed25519 公钥,由于其长度很短,因此没有这个问题。
接下来要更新域名的 DNS zone 来添加新的记录。为了方便回退,暂时不要删除旧的公钥。
dkimpy-milter.conf
的配置主要是以下几方面:
Domain
。这是希望签名(自己的)域名。KeyFile
和 Selector
。这是 RSA key 的私钥(PEM格式)和 selector。除此之外也可以用 OpenDKIM 风格的 KeyTable
。KeyFileEd25519
和 SelectorEd25519
。这是 Ed25519 的私钥和 selector。除此之外也可以用 OpenDKIM 风格的 KeyTable25519
。Socket
。我使用的是本地的 Unix socket (local:/var/spool/postfix/private/dkimpy-milter
)UserID
。抛弃特权后的身份。注意 socket 必须可以被 Postfix 读写,可以设置为 postfix:dkimpy-milter
启动之后将 Postfix 的 master.cf
中 submission
服务(或者其他希望接收往外发邮件的 smtpd
的服务马甲)上的
smtpd_milters
改为 -o smtpd_milters=unix:private/dkimpy-milter
就可以了。
同时使用两种签名算法时,邮件中会出现两个 DKIM-Signature:
信头。
对方邮件服务器收到之后通常会增加对应的信头说明验证情况。
在本地的局域网上建立 DNS 解析服务可以显著地改善 DNS 的安全性:所有的 DNS 查询将由可以信任的本地 DNS 解析服务进行验证, 而不是简单地相信一台不受控的远程服务器通过 UDP 提供的应答。因此,这些年来我自己的网络包括机房的网络都是自己运行一组 DNS 解析服务的。
在 DNS 查询过程中,根域 (.
) 十分关键。传统上,本地的 DNS 解析服务通常会向一组事先配死的根服务器列表查询顶级域名的域名服务器,
这样一来,在查询过程中本地 DNS 解析服务会时不时地向根域 DNS 询问顶级域名的 NS 记录,这样一来,不可避免地,本地和根域 DNS
服务器之间的网络服务业者或是攻击者就可以获知该解析服务器正在查询的顶级域名,而如果没有启用 DNSSEC 验证,攻击者还可以进一步劫持顶级域名,
从而潜在地将用户的访问转向他们指定的目标来实现一些特定的目的,例如将用户不小心输入错误产生的流量变现,
或是利用用户的行为进行 DDoS 攻击。
另一方面,有时用户本地的 DNS 解析服务访问根域可能有一定的延迟,这样他们在首次访问某个顶级域名时就会产生更多的延迟。
与 ISP 或是公共 DNS 服务,例如 Google 的 8.8.8.8
、CloudFlare 的 1.1.1.1
等等相比,
由于本地的用户数量没有那么多,用户访问的域名往往会不在 DNS 缓存中,从而让查询变得更慢。
由于根域的内容很少,因此在本地架设一份根域可以解决上面这些问题:通过直接从根 DNS 传输一份完整的根域,本地运行的 DNS 解析服务可以立即按图索骥而不是增加一次 DNS 查询,另一方面,如果用户输入了错误的顶级域名,这次查询也完全不会出现在互联网上, 因为本地的解析服务就可以立即知道该顶级域名不存在了。
ICANN 的 xfr.lax.dns.icann.org 和 xfr.cjr.dns.icann.org,以及 b、c、d、f、g、k 这六组根 DNS 服务器允许直接通过 TCP AXFR 来获得根域。需要注意的是, ICANN 和这些根服务器运营者可能会关闭 AXFR 服务,因此你使用的 DNS 软件必须有能力在传输失败且本地的镜像过期时正确地回退到使用传统的方式进行查询。
攻击者可以轻易地通过劫持这些已知 IP 的 AXFR 请求来提供伪造的根域 .
,因此在这样做时必须启用 DNSSEC 验证。
对于 unbound 来说,可以在 unbound.conf
中添加下面的配置并重启服务来启用这一功能:
auth-zone:
name: "."
primary: 199.9.14.201 # b.root-servers.net
primary: 192.33.4.12 # c.root-servers.net
primary: 199.7.91.13 # d.root-servers.net
primary: 192.5.5.241 # f.root-servers.net
primary: 192.112.36.4 # g.root-servers.net
primary: 193.0.14.129 # k.root-servers.net
primary: 192.0.47.132 # xfr.cjr.dns.icann.org
primary: 192.0.32.132 # xfr.lax.dns.icann.org
primary: 2001:500:200::b # b.root-servers.net
primary: 2001:500:2::c # c.root-servers.net
primary: 2001:500:2d::d # d.root-servers.net
primary: 2001:500:2f::f # f.root-servers.net
primary: 2001:500:12::d0d # g.root-servers.net
primary: 2001:7fd::1 # k.root-servers.net
primary: 2620:0:2830:202::132 # xfr.cjr.dns.icann.org
primary: 2620:0:2d0:202::132 # xfr.lax.dns.icann.org
fallback-enabled: yes
for-downstream: no
for-upstream: yes
注意以上的 IP 地址是目前 (2023年7月31日) 的 IP 地址。尽管这些地址基本上不会变化, 但是仍然建议和 IANA 的列表 核对之后再使用。 由于显而易见的原因,这个列表必须使用 IP 地址而不是域名。
使用 unbound-control list_auth_zones
可以列出 unbound 服务器目前提供的权威域名的 SOA
序号,该序号可以与 IANA 的版本 「Root Zone File」 核对。
下面说说我的理由。
自建 DNS 意味着完全控制,或者至少是几乎完全控制和自己域名有关的基础设施。 这可以满足从业人员的好奇心(例如可以获得用户采用的 DNS 服务、这些服务实现标准的情况, 以及与之相关的趋势等等各类 DNS 托管服务业者不可能提供的数据), 也可以在符合标准的前提下进行各种有趣的试验,例如基于 DNS 的按用户来源分配流量、 负载转移等等,这可能会带来一些精神上的愉悦。
此外,如果实施了 DNSSEC,自建 DNS 意味着可以掌控签名公钥,换言之在域名注册服务提供商, 或是域名注册机构没有出现问题的情况下域名的内容不会被篡改(当然,这依赖于用户使用了验证 DNSSEC 签名的解析服务器,但如果他们没做的话他们显然有比无法访问一个人的个人网站更严重的问题需要去担心)。
自建 DNS 意味着不和其他人共享同一组服务器,并且潜在地可能会使用不同的 DNS 服务器实现。 这样一来,就不必担心 DNS 托管业者本身的问题(例如其操作人员失误导致服务影响, 或是同一组服务器由于其他域名的问题而被一些国家的政府禁止访问)导致自己的网站无法访问。 此外,如果某一主流 DNS 服务器实现存在漏洞,自建 DNS 意味着你有可能由于采用了不同的实现而逃过某次全互联网范围的灾难。
这些经验有许多属于犀利而无用的手艺一类的默会知识,涉及的内容比较庞杂,市面上的书籍往往无法涵盖所有。
说完了好处,我们再来看看自建 DNS 的缺点。
通常,最佳实践建议使用一台隐藏的主服务器(这台服务器可能不直接接入互联网,它的作用是持有 DNSSEC 的各种密钥, 并将签名过的域数据分发给实际向互联网提供服务的服务器)。这样一来就需要至少两台机器。另一方面, DNS 对于可靠性的要求非常高,因此通常需要有多个(大部分域名要求存在至少两个权威服务器)服务器。
为了进一步提高性能和可靠性,DNS 服务还应使用 Anycast,这意味着需要架设更多的服务器, 并且接入更多的网络。
在已经建设了这些基础设施之后,在其上增加新的域名的边际成本是很便宜的。 这也就是为什么许多云服务业者能够将其作为一项免费服务提供给用户的原因。
让我们来正视一个事实:大部分人并不能紧跟安全更新,也不能全天候地盯着服务器。 专门的 DNS 服务业者往往有能力雇佣单独的团队来做这些事情, 因此他们在安全和可靠性方面很可能做的要比单独的个人要更好。
需要注意的是,和邮件服务当机4小时只会丢掉垃圾邮件不同, DNS 服务要求所采用的一组服务器能够提供接近完美的 uptime, 这是一项对于可靠性要求非常高的业务,其发生问题时将会导致邮件丢失, 由于邮件发送者是分散的,因此如果权威 DNS 出现问题, 很难找到对方并进行处理(与此相反,邮件服务器如果是在存储时出现问题, 正常的发送端会重试多次,并且接收方也有很大概率会留下一些记录)。
和邮件服务类似,运行 DNS 服务需要大量在其他地方完全用不上的犀利而无用的知识,这些知识不摔一些跟头往往不易获得。
首先当然是劝退。找一家合适的支持 DNSSEC 和 anycast 的 DNS 服务提供者并在他们那里托管对于大多数人来说是合适的选择, 早年大多数业者都不支持这么做,而现时支持 DNSSEC 的服务提供者已经很多了。
如果一定要自己建权威 DNS 服务的话,可以考虑找一些棋逢对手的精神病一起互相做备份服务。 在启用了 DNSSEC 的前提下,并不需要特别担心对方劫持自己域名的问题(当然, 对方服务器如果无法访问的话仍然会对域名解析产生一定的影响; 如果对方服务器被人攻陷提供错误数据,或是主动提供错误数据, 虽然可以被做了 DNSSEC 验证的 DNS 解析服务器发现, 但没做 DNSSEC 验证的用户就有可能被引导到错误的地方)。 无论是完全自建权威 DNS 服务,还是自建其中的一部分并配合一组从属服务器, 监控(这包括 DNS 的 SOA 记录一致性、重要地址记录的签名正确性等等)都是十分重要的。
]]>域名服务是一项互联网基础服务,它提供了容易被人记忆和识别的在线地址。对于个人和企业来说, 拥有自己的域名最大的好处在于能够掌握随时换一家供应商的议价权。集中式的平台, 包括 YouTube、Twitter、GitHub,也包括新兴的平台如 TikTok、 或是来自中国的小红书、喜马拉雅、B站等等,尽管也都是很好的产品, 有些甚至可以为内容作者带来丰厚的回报,但是其上的个人或是企业无法很容易地离开这些平台。 依赖这些平台,意味着在它们选择对内容作者采取某些行动,或是退出某项生意的时候, 当事人可能没有任何其他选择,而必须在极短的时间内通知自己的受众去其他平台, 这有时是很困难甚至不可能的。
对个人来说,邮件服务的运行成本是很高的(这里需要考虑的不仅是服务器本身的成本和运行费用, 也包括需要投入的时间精力)。但即使不自己去运行邮件服务,也仍然有许多支持独立域名的邮件服务提供商, 例如 Google Workspace、 Microsoft 365、Zoho Workplace等等。与单纯使用 Gmail 或 outlook.com 等等相比, 拥有独立的域名意味着可以在这些服务之间随时进行转移,而无需逐个通知亲朋好友。
域名系统(DNS)是全球节点最多的一个分布式数据库系统,它包括数以百万计的缓存和权威解析服务器。
13对IP v4/v6地址后面的
「根」DNS服务器完成根域名(.
)的下级域名,俗称顶级域,包括 .com
、.net
、.org
等的解析。
顶级域名 (Top-level Domain, TLD) 例如由一系列域名注册管理机构(domain registry)进行管理, 这包括维护该顶级域名的解析服务器、维护用于注册域名的数据库,等等。 顶级域名的解析是由13组根域名服务器完成的。 大部分域名注册管理机构并不直接提供针对个人或企业的域名注册服务, 这类服务通常是称做「域名注册服务提供商 (domain registrar)」 的代办机构完成的,如果把域名注册管理机构看作是批发商的话, 这些注册服务提供商可以看作是零售商。
根域,以及大部分顶级域都采用了 DNSSEC。 DNS 协议本身是在上世纪八十年代设计的,当时互联网上的攻击行为并不普遍, 因此这个协议没有任何内建的安全特性,而作为 DNS 的扩展, DNSSEC 提供了可以从根域开始的逐层验证 DNS 应答真伪的方法, 从而可以大幅提高 DNS 的安全性。然而,由于 DNSSEC 的部署有一定的难度, 许多人不敢在自己的域名上部署 DNSSEC,而许多域名注册服务提供商对于 DNSSEC 的支持也相当有限。
除了 DNS 本身的安全之外,域名注册服务提供商的安全性也相当重要。 在 2023 年,我认为支持采用 FIDO 的双因素身份验证是最最基本的要求。和短信或是 TOTP (例如 Google Authenticator, 以及 RSA SecurID 等硬件产品) 均不够可靠,容易让用户遭到钓鱼攻击。支持 FIDO U2F 的公司目前还不太多,而是否能够采用最新的安全技术,则十分反映一家公司的技术实力和对安全的重视程度。
基于上述考虑,我认为一家符合我需要的域名注册服务提供商应该满足的条件是(不分先后):
域名注册服务是一项红海业务,大部分注册服务提供商并不能从这项业务中获得充足的利润。 因此,选择的注册服务提供商最好是有其他的利润来源,而不仅仅依赖这一利润单薄的业务。
2023年6月,我考察了以下注册服务提供商。
在我选择的前三家注册商之间,我认为比较合理的选择方法如下:
首先确定自己是不是打算自行架设 DNS。采用 DNSSEC 之后,域名迁移会 复杂一些, 因此如果 DNS 是由域名注册服务提供商提供的话,这应该是一项需要考虑的问题。如果决定不自建 DNS 的话, 选择 CloudFlare,原因是其价格便宜。
如果决定自行架设 DNS,则选择 .net
选择 AWS Route 53,而 .com
选择 PorkBun。
优势 CloudFlare 的主营业务是 CDN,做网站通常会用得上。CDN 的利润比较稳定,不需要特别担心它突然不干了跑路。CloudFlare 的域名注册服务基本上是成本价。其提供的 DNS 采用了 anycast,有良好的可靠性记录,并且可以自由导入导出 BIND zone 格式的数据。DNSSEC配置简便。 缺点 不支持自定义DNS(需要升级到 Business 或更高级别的用户),域名转移过程较慢。
转移域名的大致流程如下:
发现的坑:
CloudFlare 的 DNSSEC 实现没有提供算法的选择,这个做法有好(防止用户自己给自己挖坑)有坏(不够灵活),对大部分用户来说问题不大。
优势 Amazon AWS 是 Amazon 旗下的产品,而 AWS 是 Amazon 的主营业务,AWS 没有砍用户爱用产品的不良记录。Route 53 支持 DNSSEC,也支持自架 DNS。使用 Amazon 信用卡可获得 5%。
缺点 Route 53 域名注册服务要求提供 KSK 的公钥对应的 DNSKEY
而不是直接提供 DS
记录。这一要求比较特别。域名转移过程中的反馈较少。自架 DNS 只支持 6 个 NS 记录。
转移域名的大致流程如下
发现的坑:
优势 界面较为现代化,转移域名时有详细的状态显示。 缺点 该公司业务比较单一,我对于该公司是否能一直持续提供这个业务存在一定的疑虑。DNSSEC配置略有些语焉不详。有些域名并不比竞争对手便宜。
转移域名的大致流程如下:
发现的坑:
DNSSEC配置界面有些地方不够明确,例如不能同时输入 DS
和 DNSKEY
,这一点在界面中完全可以表达的更明确一些(例如「请不要同时填写」,而不是「系统检测到了某项错误」)。实际操作中,.com
只填写 DS
部分即可注册成功。
目前最新的 DNSSEC 最佳实践指南是 BCP 237 (RFC 9364)。 值得说明的是,如果没有特别的需要,使用 DNS 服务业者的 DNSSEC 实现可以避免踩很多的坑, 尽管这样一来安全方面就完全要仰赖这些业者了, 但对普通的域名所有这来说这些服务要比他们自己去架设和管理要可靠不少。 本文并不打算深入介绍 DNSSEC 及其部署,而只是关注于在进行域名转移时需要注意的问题, 以备我个人未来进行参考。
首先我们来大致复习一下 DNSSEC 的验证过程。域名系统可以理解为一个有不同层次的分布式数据库,以 example.net.
为例,
这里一共涉及了三层:
.
即「根域」,从 2010 年 7 月起,根域就是签名的了。net.
,这是一个顶级域名,大部分顶级域名目前支持 DNSSEC。example.net.
,这是我们通常在域名注册服务提供商那里注册得到的域名。每一层都有一个或多个密钥签署密钥(「KSK」),以及一个或多个域签署密钥(「ZSK」)。其中,KSK需要由上一层签名的 DS 记录来确认其有效性,这些DS记录会有由该级ZSK签名的RRSIG记录。类似地,本层的 ZSK 会由 KSK 签名, 并且 KSK 之间也会互相签名。
如此,通过上一层的 DS 记录,DNS解析器可以验证下一级域名的 KSK 有效性,并完成验证 DNS 记录的全部密码学操作。 根域因为没有上一层 DS 记录,因此根域的 KSK 必须提前安装到 DNS 解析器里面,人们为此设计了一套机制 (RFC 5011) 来更新 DNS 解析器中的这个根域 KSK,并且设计了另一套机制 (RFC 8145) 来帮助根域的运营者了解 DNS 解析器中的 KSK 更新状况,这是题外话。
由此,支持验证 DNSSEC 的 DNS 解析器就可以从根域一直捋到最终域名的记录, 并使用这一链条上的公钥去验证每一个记录的真实性。 值得注意的是,仅仅证明知道名字的记录存在是不够的, 如果我们只是简单地签名一条不包含记录内容的消息,例如「不存在」, 那么这条消息就可以被攻击者记录下来并在收到正常查询时直接将消息传给查询者, 而一些关键记录,例如 DS 记录如果被对方认为不存在的话, 其后果将是致命的。出于安全考虑,我们也不能把签名用的密钥交给提供DNS服务的服务器, 因为那样一来一旦这些服务器出现安全漏洞,则密钥也就随之外泄了。 一个比较简单的思路就是提供一些这样的记录:某某到某某之间的记录不存在, 并对这些消息进行签名,这就是 NSEC 和 NSEC3 (两者的区别是后者采用了 HMAC,从而让枚举存在的域名记录变得困难, 但两者的核心思路是类似的)。
有了以上这些以后,想要证明一个域是未经签名的就变成了让其上级域提供一条说明不存在 DS 记录的签名消息。 对于子域中大部分未进行签名的域来说,无论采用 NSEC 还是 NSEC3 都可能会产生大量的记录, 在 NSEC3 中提供了 Opt-out 机制 (RFC 5155 Section 6) 来避免这个问题。
有了这些准备知识,现在我们来考虑一下启用了 DNSSEC 的域名在转移注册商时可能会遇到的问题。 顶级域名都是签名了的,而注册商则会在这些域中添加和删除记录。这个过程中, 如果有一段时间存在 DS 记录与 DNS 服务器上的 KSK 不匹配的情况, 就会导致域名立即无法解析。
出于对人类的缺乏信心,目前绝大多数注册商都会要求用户首先禁用 DNSSEC,然后再转移域名。 此处值得注意的是,任何变动都需要推送到全球的上级域名镜像上,这个过程很快但依然需要时间, 而由于上级域名通常都采用了 anycast,因此并没有特别可靠的方法能够确保全球一致性。 因此,理论上在禁用 DNSSEC 之后,应等待原 DS 记录过期(至少其 TTL 时间) 之后才可以认为之前的数据已经没有人持有了。
实际情况中,除非管理员有什么大病,否则通常不会有人选择在更换域名注册商的同时来一次 KSK 轮转。 这种情况下并不需要真的等待24小时:即使禁用 DNSSEC 导致的 DS 记录删除没有反映到查询 DNS 的用户那里,他们也仍然可以从域名的 DNS 服务器获得经过签名的数据。而再次启用 DNSSEC 时, 由于使用的是同一组 KSK,因此其 DS 记录也是一样的。
但如果是在换域名注册服务提供商的同时更换了 DNS 服务器(例如,从旧的注册商提供的免费 DNS
服务器换到了新的注册商提供的对应服务上),除非可以同时迁移 key,否则最好是等待 DNSSEC
禁用满 24 小时(或TTL时间,请用 dig 域名 ds
确认)之后再启用 DNSSEC,此外,
在禁用 DNSSEC 之后、转移域名之前,最好也先等待一下,以免旧 DNS 在这个过程中停止服务导致的问题。
总结一下:
如果域名的 DNS 服务器仍然是原来那一组,检查清单是:
如果域名的 DNS 服务器将换成一组新的,检查清单是:
dig 域名 ds
获得目前的 DS 记录有效期时间。传统的采用密码登录的登录方式中,用户需要用某种方式证明自己知道密码,而绝大多数实现中这意味着网站必须保存用户的密码, 或是密码的 hash 值(通常是HMAC),这意味着网站如果发生了拖库的情况,有可能会将用户的密码或其 hash 值泄漏出去。 此外,用户可能必须输入密码,并将密码通过某种方式传输到服务器上,这样一来在这个过程中便有可能由于本地的木马, 或是网站采用的 SSL/TLS 实现的漏洞导致密码泄漏。除此之外,即使用户十分小心地在每一个不同的网站都使用了不同的密码, 他们仍然需要担心被「钓鱼」的问题,所谓「钓鱼」是这样一种攻击方式:攻击者架设一个看起来很像是用户希望登录的网站的网站, 并诱骗用户输入密码。
许多网站提供了二次认证的功能。在二次认证中,用户除了密码之外还需要一个能够证明是其本人进行操作的东西再确认一次身份。 采用过时技术的网站往往会使用 SMS 短信进行二次认证,很明显,这种方式并不能彻底阻止「钓鱼」攻击: 攻击者通过架设一个假的网站诱使用户输入密码之后,可以直接模拟用户的操作去向真正的网站提出请求, 并要求用户输入收到的短信中的数字。更为严重的是,采用 SMS 短信进行二次认证时,用户手机的安全性也十分重要, 对于一些高价值的受害人来说,攻击者完全可以设法欺骗运营商让它们把用户的短信发到自己控制的SIM卡上, 从而收到这些验证短信,并利用之前钓鱼获得的密码完成登录。
与此类似,使用 TOTP (例如 Google Authenticator)作为 2FA 尽管不会有短信那样的安全问题, 但也无法彻底杜绝「钓鱼」攻击。
为了解决这个问题,Google 和 Yubico 提出了采用 U2F 作为 2FA。在这种验证模式通过公钥密码学的方法,在用户硬件上保存一个私钥,而网站只保存用来验证它的公钥。 在验证时,网站和用户采用挑战-应答认证,网站每次生成不同的挑战值,而用户用私钥签名,从而避免了同一数值被多次使用。 更进一步,在这个设计中,网站的 URL 作为 application ID, 大幅提高了制作钓鱼网站的门槛(想要成功的攻击,不仅需要制作一个样子类似的网站,还得能成功劫持域名; 如果只是注册一个域名类似的网站进行钓鱼,则无法获得正确的 U2F 应答)。
前面说了这么多,passkey 又是什么呢?和 U2F 的工作方式类似,passkey 同样使用了公钥密码学的方法,将公钥交给网站。 目前最新版本的 Apple iOS 和 Google 的 Android 上都完全实现了 passkey。其中, Apple 的实现会将 passkey 自动通过 iCloud Keychain 同步到所有登录了同一个 Apple ID 的支持该功能的 iOS 设备, 与其密码管理器类似,这些 passkey 受生物信息(指纹或刷脸)保护,并可使用屏幕解锁密码来解锁。 Android 的实现与此类似,通过 Password Manager 同步,可以用生物信息或屏幕解锁密码来解锁, 而登录 Google 账户时,Android 会自动生成一个 passkey,在 Google 账户中不能将其删除。
总体上,passkey 要比密码要安全得多:首先,采用公钥密码学的方法进行验证,意味着网站只掌握用于确认用户身份的公钥, 即使发生数据泄漏,该数据对于攻击者来说也只是能用来验证发到这个网站的用户回应是否是由用户私钥签署, 其危害远小于泄漏的密码或密码的hash。其次,passkey 可以提供比密码多得多的熵,并且无需记忆。 最后,passkey 的验证回应只会发给域名能匹配的上的网站,从而杜绝了钓鱼。
不过,我认为和 密码 + U2F 的验证方式相比,passkey 实际上类似于在手机等设备上实现了一个 U2F, 并使用它代替了两者的组合。对于普通用户来说这固然是比只用密码要安全的多的(因为 passkey 证明了用户拥有一个登录了该 Apple ID 或 Google 账户的设备,并且知道其解锁密码, 或是向设备以生物信息证明了身份),但由于完全去掉了密码, 设备本身的安全性就很重要了,在 Google 的实现中 ,锁屏密码用于生成端到端加密的密钥,因此一个能够登录 Google 账户, 并且获知了锁屏 PIN 的人便能恢复出 passkey。根据文章的说法, 通过硬件保证了 PIN 只能尝试最多十次,但总体上, 无论是 Google 还是 Apple 的实现都依赖于一直在线的手机本身的安全性, 而 U2F 设备通常并不是连接在设备上的,因此我认为尽管对普通人来说 passkey 已经足够好,但对于需要持续提高电击电压的人群来说, 使用 密码 + U2F 会更安全一些。
]]>我竟然看懂了:
“Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.”
其实后面还有: “Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.”
维基百科 上的解释。
]]>main()
的终结部分从 exit(X)
改为 return X;
,我反对了这一变动。
值得注意的是,在实践上,从 main
中 return
和调用 exit(3)
几乎等效的(此处还是有细微差别,
后面将会讨论),原因是 C 运行环境库的启动部分(这部分会在连接过程中嵌入到可执行文件中,
FreeBSD 的实现中,这部分位于 lib/libc/csu/libc_start1.c
的 __libc_start1
:
void
__libc_start1(int argc, char *argv[], char *env[], void (*cleanup)(void),
int (*mainX)(int, char *[], char *[]))
{
/* ... */
exit(mainX(argc, argv, env));
}
但是目前大部分的用户态代码和 style(9) 均采用了调用 exit(3)
而不是 return
。
前面提到,两者之间存在些许的不同:当函数返回时,属于它的栈帧就销毁掉了。而当函数调用另外一个函数时,
属于它的栈帧依然存在。在追溯内存泄漏时,比较常见的办法是从有效栈帧开始沿着所有指针遍历并标记内存块,
这样,在完成时没有标记的已分配内存块就是泄漏的内存了。习惯上,C 程序在已经知道要调用 exit(3)
或其它终止进程的函数(部分编译器支持将这类函数标记为 __attribute__((__noreturn__))
属性,
在 FreeBSD 中这类函数通常会用 __dead2
来标记)时,在此前分配的内存无需再做显式地释放。
因此,如果在 main()
中在堆上分配(例如,通过 malloc(3))
了一些缓冲区而在 return
时没有释放,则这些缓冲区有可能被视作内存泄漏,
尽管实践上内核最终仍然会回收进程的全部资源,但相关的警告有可能给开发人员带来困扰。
不过,这些差异并不总是存在。例如,程序的 main()
函数可能完全不从堆上分配内存,
那么此时两者的区别就真的可以忽略不计了。在写新程序时,我认为两者基本上没什么区别,
个人比较倾向于使用 exit
,除非 main()
由于某些原因可能在其他地方由 C
运行环境以外的其它地方调用(一个比较典型的例子是嵌入 sh(1)
的那些命令:
如果这些命令的实现中使用了 exit()
,那么嵌入的版本就必须 fork()
并 wait()
,
而不是简单地像调用函数那样使用它们,这会让 shell 脚本的性能大幅下降),
但对没有这类要求,又没有在堆上分配内存的新程序来说,两种写法都可以接受。
今天从 Thomas Yao 那里 得知, 陈皓 @haoel 老师已于当地时间周六晚因突发心梗辞世, 在此让我们一起缅怀他。
]]>其中,OpenSSL 3.0 由于对 API 的改动比较大,因此会导致一些 port 无法正常工作 (PR 258413)。 近期已经有一批 port 标记为与 OpenSSL 3.x 不兼容, 不过修掉这些可能会需要不少时间。
我个人的观点是这类不兼容的变动应该尽早引入,然后由维护者去分别解决。 我自己较早前做的 qsort_r(3) API 变动 比较简单,因此我自己就 搞定 了绝大多数需要改的 port,但 OpenSSL 的 API 变动涉及面很广, 而且这件事反正要发生(OpenSSL 已经宣布今年9月就不再支持 OpenSSL 1.1.1 了), 还不如早点发动起社区去解决它。
还有个想法是 Ed Maste 提出的把 OpenSSL 变成一个不对外提供的私有库,
我个人觉得这不是一个好主意,因为那意味着一台机器上需要安装好几份 OpenSSL
(甚至 LibreSSL 等等),立刻会出现的问题是符号冲突,当然这个可以用 #define
大力出奇迹去解决,但接下来的问题是更新系统会慢不少,毕竟把整套 ports
都重新联编一遍是很慢的。当然,如果我们真的这么做了的话,这也是一个考虑换成
LibreSSL 或是其他实现的好机会。
OpenZFS 最近引入了一些新特性并且导致了一些问题,我自己倒是还没碰到这些问题, 不过我还是暂缓了升级家里的存储系统。备份固然是有,但恢复起来还是要占用时间的。
]]>在进行加密和解密操作时, kTLS 假定内核有直接映射 (direct map) 支持。在这些平台中, 内核直接将物理内存全部「直接」映射到内核地址空间,这样一来,在内核需要访问某一物理地址时, 其 pmap_map 实现便完全无需操作页表, 因此可以大幅提高性能。FreeBSD 支持的主流 64 位平台均支持这一特性(参见平台对应的 pmap_map 实现)。
FreeBSD 附带的 OpenSSL 在 arm64/aarch64 和 amd64/amd64 平台上默认启用了 WITH_OPENSSL_KTLS
,
无需重新编译即可使用。
整体上,FreeBSD 的 kTLS 提供了三种模式:软件(software),网卡(ifnet) 和 TOE。 软件模式中,kTLS会使用软件加密(这包括加速指令如 AES-NI,也包括协处理器如 qat(4) 等等)在 socket 缓冲区层进行加解密。网卡 ifnet 模式中,加解密操作是由网卡完成的, 一些比较贵的网卡,如 mlx5en(4) 和 cxgbe(4) T6 上集成了相关功能,其中前者同时支持发送和接收。而 TOE (TCP 卸载引擎, 由网卡完成完整的 TCP/IP 操作)模式中,网卡除了完成加解密之外也负责 TCP。
我个人的网站使用了 AES256-GCM,在 FreeBSD 13.2 中加入了收发的支持,因此这次升级完系统之后打算试一试。
由于我没有使用高端的网卡,加上这个网站反正流量也不大,因此我使用软件模式,首先是加载 ktls_ocf
内核模块:
kldload ktls_ocf
在 /etc/rc.conf
中加入: kld_list="ktls_ocf"
令下次启动时也启用它。
此处 ocf
是指内核的 OpenCrypto Framework,它会自行决定是否使用类似 aesni 这样的加速机制。
然后是启用 kTLS 支持:
echo "kern.ipc.tls.enable=1" >> /etc/sysctl.conf
service sysctl start
接下来是告诉 nginx 可以用 kTLS 了。在 nginx 配置中的 server 小节中添加:
ssl_conf_command Options KTLS;
然后重启 nginx。此后,在 sysctl kern.ipc.tls.stats
中可以观察到由 OCF 完成的加密解密。
姥爷是中国人民大学的老师,1970年代在江西的中国人民大学「五七」中学教过数学。 中国人民大学在1978年正式复校,姥爷也回到了中国人民大学。
几年后有了我。我的小学、初中和高中大部分的时间都是在姥爷家度过的。姥爷家的书架上有很多书, 其中有相当一部分是数学书,姥爷也会亲自教我一些数学知识。此外,他也曾跟我说过, 数学是所有自然科学中对条件要求最少的一门学问,即使一个人身处逆境,仍然可以通过思考去研究数学, 而不必依赖更多的物质条件。 从最开始的各种趣味数学问题和尺规作图,到后来的三角函数, 姥爷让我对数学产生了浓厚的兴趣。在初中的时候, 姥爷家的一些纸张已经泛黄的英文数学书成了我的课外读物,我也经常有机会能和姥爷一起讨论数学问题, 这些对我的帮助很大。
然而时光飞逝,上大学以后,和姥爷见面的机会便越来越少了。 来美国以后,和姥爷见面的机会更是以年为单位。 最后一次见到姥爷是在2019年,然而这之后不久就爆发了疫情, 一晃已经过去四年了,我没有想到那次见面便是永别。
去年年底,我听说姥爷病了,病得很重。 我知道对于一位高龄老人来说这意味着什么,这让我十分担心和想念他。 许多往事如同电影一般,仿佛就在昨天,又显得有些不那么真实。
David Eagleman 在 《Sum: Forty Tales from the Afterlives》 中提到, 一个人会经历三次死亡:第一次是他们的身体停止运作,第二次是当他们下葬, 而第三次则是人们再也不再提起他们的时候。我想,我能为姥爷做的,也就只有不忘记他了。
姥爷走了,他是一位优秀的老师,更是一位慈爱的长者,他教会了我许多道理, 我将一直怀念他,感激他给我带来的一切。
RIP
]]>戈登·摩尔于1929年出生在美国加州旧金山,并在佩斯卡德罗度过了他的童年时光。他的家族是最早一批最早一批定居在加州的英国移民家庭之一。在他家搬去红木城之后不久,邻居家孩子的一套 BGL Chemical Set 让他第一次接触到了化学,这点燃了他对化学的兴趣,并最终让他成为了一位化学家。在圣何塞州立大学学习两年之后,他在加州大学伯克利分校获得了学士学位,并在此后的1954年在加州理工学院获得了物理化学博士学位,接着进入了马里兰州的约翰霍普金斯大学应用物理实验室。
1956年,晶体管效应发现者威廉·肖克利(William Shockley)在山景城圣安东尼奥路391号的一处小型商业区开办了肖克利半导体实验室(目前此地已拆除并重建成了脸书公司的391号楼)。起初,他希望从贝尔实验室招募过去的同僚加入,但这些人都不愿意离开东海岸,于是他亲自招募了一批他认为最有才华的年轻人来为他工作。在肖克利实验室,摩尔的工作是研究向纯净的硅中的「掺杂」工艺,这是一项关键的步骤:为了让硅能够作为开关或其他设备使用,必须在其中加入一些包含III族或V族元素的「杂质」来提供多余的电子或空穴,从而获得希望的电子特性。
好景不长,肖克利管理风格让肖克利半导体实验室的研究人员感到无法忍受,一年之后,1957年5月,8位研究人员越过肖克利,直接向投资人贝克曼表达了不满,肖克利认为自己遭到了背叛。两个月后,贝克曼改变了先前同意削去肖克利管理权的口头许诺,表示虽然会聘用一名新的经理,但肖克利作为董事仍然会保持对公司的实际的管理权。最终,在1957年9月,「八叛徒」选择辞职,并从纽约的 Fairchild Camera and Instrument 获得了一笔一百三十万美元的投资合同,成立了仙童半导体公司。
摩尔很快成为了仙童半导体最为重要的技术和管理人员。他致力于开发制造工艺,取得了许多开创性的成就,包括1960年提出的平面工艺,为1961年的首个集成电路提供了平整的二氧化硅表面。集成电路,或现在通常所说的「芯片」,是在一片硅或是其他半导体材料上通过特殊处理,在上通过化学蚀刻出可以大量微小电路并连接起来实现特定功能的元件。在摩尔的领导下,仙童半导体开发出了第一代量产的金属氧化物半导体场效应晶体管(MOSFET),时至今日,这仍然是最为常见的芯片组件。
1965年,摩尔预言在未来至少二十年内,同样面积的硅片上可以容纳的晶体管数量每年会翻番(1975年他把这一预测修正为约两年翻番),这一预测被称作「摩尔定律」。1968年7月18日,罗伯特·诺伊斯和戈登·摩尔以两个人的姓氏首字母命名,创立了新的NM电子公司,不过他们月底之前就改了一个新的名字:Intel(Integrated Electronics的的头几个字母),这个名字沿用至今。
在摩尔成长的年代,加州硅谷还只是一块不太出名的农业地区,而正是他和他的同事们开创性的工作,在这里从无到有地创造出了整个行业,并深刻地改变了人类的生活方式。正如 Intel 公司在推文中所说的那样:
「Today, we lost a visionary.
Gordon Moore, thank you for everything.」
]]>我在 2011 年注册了一个比较技术宅的 .org
的域名,但一直没有实际投入使用,
最近宏观经济形势不太好,所以索性把不用的域名出一批来减少持续的花销,
周四终于出现了一位愿意接手的朋友,于是我们找时间做了一次域名转让的操作。
域名转让的主要好处是避免域名通过常规的过期、删除等过程导致域名的注册时间复位或落入第三方手中, 一些域名贩子可能会借此收取高昂的佣金,有些无良注册机构甚至会主动提供此类业务, 而另一些机构则可能会拿域名去做一些更奇怪的事, 例如我的一位几年前去世的同事的域名现在变成了一个无关的色情网站。
扯远了,书归正传,我之前使用的注册机构是 Google Domains, 这位朋友恰好也是。在 Google Domains 中可以添加全权管理用户, 从界面上看似乎也可以在此后删除自己,但出于谨慎起见, 我们还是采用了比较传统的借助第三方注册机构来进行转移的方法, 因为这样一来可以确保与域名相关的资料在转出这一方都已经删除, 从而避免发生非预期问题的机会。
大致上做的操作包括:
然后对方需要做的操作是在另一家注册机构发起转移,输入Auth code并等待邮件。完成转移之后,大部分注册机构会自动将域名上锁,如果他们没做的话最好是手工做一下。
一些文档中建议先撤掉域名的 Whois privacy 保护,但实际操作中我们并没有在整个过程中关闭该保护。
]]>托管账户的存在可以在一定程度上降低银行的风险:地产税在清算时的债权优先级最高, 而存在托管账户可以确保这个最高优先级的债务能够有钱偿还,从而为银行争取时间来完成法拍等一系列程序。 不同的银行对于免除托管账户可能有不同的规定,例如他们可能会要求贷款价值比(Loan-to-Value) 低于80%——这确保了债务人有足够的诱因去避免法拍和按时支付地产税,因为这符合他们自己的利益。
我自己的情况中,我的贷款价值比已经很低,并且本次重贷款已经超过一年,预计不会遇到什么阻碍, 于是找时间给银行写了一封信:
Hi,
Is it possible to get the escrow account removed from my mortgage? If yes,
what would be the procedure, and is there a fee?
我原本预计银行可能会要求我填写一份申请,不过银行很快回信说他们需要花几个工作日的时间去做「research」, 大约一周后银行再次发来回信说我的申请已经获得批准,将在下一个账单日时关闭该托管账户, 并将账户余额用支票寄给我。此操作没有额外的步骤或收费。在托管账户关闭之后, 该银行将不再负责支付我的地产税和房屋保险,因此我需要自行完成这些操作。
总体上,这次操作有些出乎意料地顺利。
]]>由于 UPS 测试触发了系统停机,导致 delphij.net 部分服务停止了约40分钟。服务从 2023-01-16 15:46:20 开始受到影响,至 16:26:55 完全恢复。 如果服务未能及时修复,潜在地将会进一步影响包括权威DNS(有多个独立的冗余,但依赖于持续的数据更新)在内的一些其他关键服务。
在测试时对于 CyberPower UPS 测试特性的认识不足导致电池电量消耗至临界值, 由于对 nut 的配置未考虑这种情况直接将服务器关闭, 这使得必须亲自到机房才能完成服务的恢复。
nut 是在2023年1月13日开始近采用的,用来替换配合 APC UPS 使用的 apcupsd 的 UPS 管理软件。与 apcupsd 类似, 它也提供了在电池电量低于警戒线时关闭系统的功能。
nut 提供的针对 CyberPower SX650G UPS 的驱动中提供了 test.battery.start.deep
指令,
对于大部分其他 UPS 而言,该指令的作用是将 UPS 的电池消耗到 low battery 水平之后再恢复从交流电充电。
为了验证在使用 nut 时系统仍然可以像在使用 apcupsd 时那样正确关闭和重启系统, 于2023年1月16日对其一系列 UPS 和 nut 特性进行了测试。
此测试过程中,对于 UPS 的深度电池测试导致电池的电量低于了此前设置的 20% 水平,而 UPS 的控制器并未如其他 UPS 那样在此时恢复从交流电供电进行充电,
并且此时 nut 的 upsmon
由于收到了电池电量过低的信号,如配置那样地开始了关机过程。
本次故障导致 delphij.net 部分服务从 2023-01-16 15:46:20
开始受到影响,直至 16:26:55
完全恢复,共计约40分钟。
由于受影响的服务器提供了到某些其他服务的隧道,导致家中的网络也受到了影响。
poweroff
命令。nut_exporter
的部署,因此获得了较为完整的放电特性数据,知道了 UPS 从 100% 放电到 20% 的时间约为933秒,其中前12%的放电曲线比较陡峭,后续的变化率则变得较缓。根据UPS的计算,后续20%的电量大约可支持300秒左右,而根据前面的放电速度较为保守的估计则是240秒左右,这个时长远远超过了该系统关机所需的时间(作为参考,服务器关闭前最后一次录得的电池电量数据是19%,虽然实际最终剩余的电量一定会比这个少,但至少说明及时关机来保护数据的目的是可以达到的)试验未能达到预想的测试 UPS 电池深度测试的目标,操作人员到达机房时的首要任务是恢复服务,因此选择破坏了部分现场并导致无法获得完整的试验数据;此外,测试未能证明在实际发生长时间断电之后在供电恢复时,系统是否能在没有人在现场手工干预的情况下重新启动,这需要在后续的测试中加以解决。
作为一款消费级的产品,CyberPower SX650G UPS 的 USB 接口协议,以及其控制器的 datasheet 不易获得,这意味着想要确切了解其指令行为并不容易。nut 提供的开源驱动中,也只是简单地实现了相关接口,对于其行为是否能够达到与文档或代码预期的全部目标并无充分的测试,这并不是开源项目的责任,但作为用户应该对此有所认知,并在使用时考虑这一因素。
测试证明此前配置的关机的部分能够在需要时正确触发,并且工作正常(尽管这本身导致了服务中断)。
配置的数据采集系统在放电测试过程中记录了放电数据,证实了目前配置能够达到系统对于后备电源的需要,而这些是最初打算进行这些测试想要达到的核心目标,故而可以认为此次试验的主要目的达成了。
LOWBATT
事件处理部分进行改进,加入对于是否正在充电的处理:只有在处于 LOWBATT
并且电池处于放电状态时的情形,才应触发 shutdown。2023-01-13
完成了UPS替换操作并部署了 nut。2023-01-14
进行了首次 test.battery.start.deep
测试,注意到它在电池达到 20% 和 10% 时并未认定为测试完成并恢复充电。在耗电达到 8% 时使用 test.battery.stop
终止了测试。2023-01-14
部署了 upsmon 配置,令系统在 UPS 达到 low battery 时触发 shutdown。2023-01-16 15:29:31
进行了一次 test.battery.start.quick
测试,正常触发了 ON BATTERY
和 ON AC
事件并收到了邮件。2023-01-16 15:30:30
开始 test.battery.start.deep
测试,由于之前有1月14日的教训,以脚本循环读取电池余量,并在 Grafana 上打开相应的面板观察其他数据。2023-01-16 15:30:37
upsmon 检测到 UPS 正在以电池供电,正确触发了 ON BATTERY
报警并收到了邮件。此后电池电量从 100% 逐步减少到 20%。2023-01-16 15:46:20
电池电量达到了20%,upsmon 触发了 LOWBATT
事件并开始 shutdown 过程。由于此时已经正确配置了 shutdown 脚本,因此该脚本被执行。<故障开始>2023-01-16 15:46:40
操作人员注意到控制台报警,同时 ssh 会话中断,发现服务器仍然可以 ping 到但 ssh 已经不再响应。<检测到故障的时刻>2023-01-16 16:23:22
操作人员到达机房并手工启动机器。2023-01-16 16:23:33
服务器发现 UPS 剩余电量低,触发了 LOWBATT
事件并再次开始 shutdown 过程。2023-01-16 16:25:58
再次启动进入单用户模式,禁用 nut_upsmon 并再次启动。2023-01-16 16:26:55
所有服务正常启动,故障成功缓解<影响结束><故障结束>