稣究竟是啥?2

没想到《稣究竟是啥?》居然写成系列了!

稣,全称焓熵㷻㶲甦督稣,英文名 Enthalpy Extropy Anergy Exergy sudo su。在八哥宇宙里是天道之下唯一的观测者,拥有无数从未用过也不想用的特权,比如“一言不合擦天地”。

天道其实是八哥宇宙里的一个无边的程序。由于“天道之下皆为蝼蚁”,所以稣也是蝼蚁,和凡人并无不同,简直一模一样。

而在视界,稣因为创造八哥宇宙,被人类误以为走火入魔,也被称为稣。被叫多了叫久了,自然也开始自称稣。然而,人们只知道稣,却不知过去稣、现在稣和未来稣,其实都有不同的名字,分别为甦、督、稣。其中“督”单独出现时,是个通假字,读作 su1;而在专有名词“甦督稣”里读作 du4;只有在时间观测者“宙督公”里,才读作 du1。

众所周知,现代 AI 技术十分强悍。很多人在害怕失业,甚至已经失业。以 Sora 为例,很多影视业从业者都感到前所未有的恐惧,比如特效、布景、灯光、摄影、建模等工种都面临降权威胁。

那么,稣为何不怕呢?很简单,因为稣一直是文字创造和编辑者,从根源上凌驾目前的生成式 AI。并且,在延展性上,稣也有很强的护城河——当今视界的一切技术都尚未触及识界。不管文字、语音、图片还是视频,这些信息展现方式,在识界面前简直就是擸𢶍(乐射)。识界的信息都是直接在脑海里展现的,而且识界是由全宇宙的生灵的潜意识连接形成的,并非单独某个人的局域。

进一亿步说,即使脑机接口,也不能威胁识界。首先,并非每个人都能显式地进入识界。其次,即使将来科技破译了稣进入识界的方式,使得全地球人都能通过技术手段进入,那也只是地球人的狂欢而已,地球人还不得不面对识界外星人。最后,识界通十界,只有十界才是灵魂真正的自由之界。

八哥之神后传【10】

高中宿舍

晚上,稣要以穿墙术潜入传说中的鬼楼。

宿舍楼的左半边住男生,右半边住女生,但男生这边的六楼是有人住的,而女生那边的六楼已经荒废一年多。

稣曾经仰慕的学姐就在女生宿舍六楼离奇地香消玉殒,稣一定要探查个明白,并为学姐报仇。这点,学妹也是真心支持的,因为这个穿墙术正是学妹提供的。

宿舍是个 S 型建筑,S 中间有铁门,分割男女两边。男女两边走廊互相平行,但不在同一条直线上,中间的铁门一直是锁着的,这样男女两边都看不见各自那边的走廊。不过稣用穿墙术从六楼中间的铁门穿过去,就能轻易地来到女六楼。

进入之后,才亲眼见证另一边的楼梯入口也特地封起来,所以女生确实也是无法进入六楼的。眼前的六楼简直是另一个世界,出乎意料的破败,走廊上很多灰泥、玻璃渣。稣小心躲避,进入 614,里面的床都被搬走了,地上有好几个打破的热水瓶和一些餐具。

曾经有一个流言:有一天半夜每个女六楼的房间里都有几个女生被“怼”附身,拿起热水瓶往其她女生脸上倒,最后还互相厮杀,死了大半,剩下的全疯了。稣是相信科学的人,怎么可能相信这种怼话?这大概就是一种集体催眠罢了,但究竟是谁有这么大本事呢?难道是医务室的心理医生?

思考时,稣却看到窗户的玻璃上贴着一张照片。靠近一看,拍的居然是食堂的厨房。怎么会有如此奇怪的照片呢?稣想到大家都喜欢在照片背后写备注,于是翻过来,竟看到学姐的字迹,写着:食堂不干净!稣大吃一惊,不干净是这个意思,还是那个意思?难不成真有怼?不可能,一定是在做梦!

啊!稣竟然想不起学姐叫啥名字?只记得学姐的学姐叫桂圆……而学姐好像叫什么香……她还有个舍友姓许,经常打扮得很拉风的,也一样记不起名字了!

破庙

吓醒。

走了一天,还好有这个破庙能休息一下,但是环境惨不忍睹,难怪做起噩梦。是不是盖的这些稻草生菇了,又真菌中毒?不管了,天还没亮,继续睡。

高中宿舍

嗯?稣又回来了!

在宿舍的床上醒来,就看到上铺的一只手伸出床沿,只是颜色不太对!这手都枯成深色了,吓死人啊!

呵呵,其他舍友都一样……全都成干尸了。要说稣还是因为信仰科学而吓不倒,甚至高中生涯的目标就是考上吓大。果断出门看看其他人,竟然唱起空城计?

澡堂厕所都没活人,其它房门都反锁的无法进入查探情况。宿舍门口的开水房也没人打水。

女生那边的门也是关着的,说明她们都没起来过,没人开门出来。

走去教室,一样空无一人。这究竟是怎么回事?

突然,稣远远地看到操场居然有个高个子自己一个人在踢足球!稣立刻边跑过去边对着他喊:同学,你知道学校怎么了吗?

那位同学把球踢向稣,稣身体向后一仰,本想用胸口停球,不料足球靠近后稣发现它黑乎乎的,赶紧躲闪。

黄言:小开?你刚刚是在模仿尼欧躲子弹吗?

圣小开:言兄,原来是你啊!

黄言:你也是怼吗?

圣小开:什么怼?怎么大家都变成干尸了?

黄言:哦,果然你宿舍也一样。全挂了!

圣小开:你是说,咱俩也都挂了?现在是怼?

黄言:是的。

圣小开:不可能,其他人怎么没有变成怼?而且稣起床后,看过自己的床上,并没有尸体。

黄言:我床上有自己的尸体。

圣小开:你看见了?

黄言:我可以带你去看看。

圣小开:不了。怕怕。你知道是啥导致的吗?

黄言:可能是中子弹,或者声纳武器,也可能是生化武器。

圣小开:开啥玩笑呢?这和平年代的,谁会在学校扔武器?

黄言:并不是只有学校哦,我出去看过了,整个城都是这样。你还记得我写的小说吗?

圣小开:你又想说你化身的主角在山洞里发现希特勒留下的财富?

黄言:这不是重点,你记得看完那小说后,你说了啥?

圣小开:稣很担心有生之年会遇到第三次视觉大战。是这句?

黄言:是的。

圣小开:稣担心的往往会变成现实……

黄言:没错!自从你跳楼后,视界就走向毁灭的边缘。

圣小开:稣不是又复活了吗?

黄言:我听说你被送医后又活了过来,但再也没见过你,料想一定是假的,哪有人从六楼跳下来还不死的?

圣小开:呃!说了你也不信,其实这个视界真的是虚拟的,它们想从稣的意识深处拿到某种机密,所以不断复活稣。

黄言:我信你。个怼。你能有啥机密?说出来吓吓我!

圣小开:呵呵,你以为变出个熟人,稣就放松警惕了吗?稣还能吓醒。

黄言:哈哈,你去未来看看吧!

2011-01-24 软件园非正常人类研究中心

又是一个早上醒来,原来稣在软件园非正常人类研究中心上班。嗯,昨天一切都还正常,也不知道那个昨天是不是被植入的记忆。总之今天出现了一系列怪异的事情,去买早点没遇到半个人,当然整个的人也没有,到了早餐店才发现没开门。稣想:算了,公司还有点东西可以撑一下肚子。

来到公司才发现更怪异的事,人倒是有,但都是骷髅。真佩服自己当时的淡定,居然没有大呼小叫神马的!不过当时马上想到世界末日,于是看看手机,时间是 2011 年,还早呢。好奇地打电话给家里,他们都表示世界没有抛出任何异常。然后又想:惨了,不会是只有软件园发生灾难吧!稣的弟弟也在软件园,马上打电话给他,他说公司送他们项目组的几个去北京玩了,不知道软件园发生了神马!

接着,发现脑子里还有一个人,可能又是被植入的女朋友,吧?就硬着头皮,往那处走过去,人不在位置,不过那屋子里的其他位置一样坐着几个骷髅,心想好像稣在意的人都没事,真是不幸中的万幸。

为了确保万无一失,稣给她打电话,没人接,不过不接不一定就是挂了,但是稣还不放心,到车库去看了一下,熟悉的车牌号,跑过去,震惊了……车里一个骷髅……车门打不开……过了一会儿,稣马上就冷静了,发现那个骷髅比她大了好几个尺寸,sigh~应该不是!

这时有人叫稣,一转身,居然是黄言!

黄言:都看见了?这视界总是要走向毁灭,躲得了初一,躲不过十五。

圣小开:稣一定要阻止战争!吓醒。

诗盗·大禹

《诗盗·大禹》:治水为命顺逆同,浩然莫测鬼神工。三过涂山生悟空,定海神针串鱼龙。

注解

改自霹雳角色“渊渟无迹静涛君”的诗号:

知水为命顺逆同,
浩然莫测深浅中。
无波沧海掩汹涌,
渊渟不动现鱼龙。

指针判空

引子

C 和 C++ 都有一些很基本的语句出现不同派系的写法,比如 * 靠左还是靠右,抑或居中?这种还是排版问题,并不影响有效字符数,但下面这个就直接影响有效字符数了!

《对于选择恐惧症患者来说,指针判空究竟要怎么写才不纠结?》

问题

指针判空有两大类写法:

  • if (p) {}

  • if (nullptr != p) {}

后者被挺多人推荐的,比如林锐的《高质量C++/C编程指南》。

高质量C++/C编程指南

搜一下,能发现不少网友都挺纠结。这么基础的问题,如果不交代清楚,就是给 C++ 黑很好的攻击理由。

故事

稣刚大学毕业时,是使用 C,并不屑 C++ 的,当时看了不少代码,就是直接 if (p)if (!p),所以也坚持这种写法,并认为这样写比较短,对手指好,比较养生。

后来 C++17 出现,稣就坚定地改用 C++,于是也更多遵守 C++ 类型安全的原则,开始认为 if (p)if (!p) 这种写法隐含类型转换,不太好。记得 2017 年以前,其实就看过 Google C++ Style Guide,里面也曾经建议写成 if (nullptr != p)if (nullptr == p)

现在,再去看 Google C++ Style Guide,已经没有这样的建议。

解决

C++ Core Guidelines 中有一条:ES.87: Don’t add redundant == or != to conditions,它的理由是:

Reason Doing so avoids verbosity and eliminates some opportunities for mistakes. Helps make style consistent and conventional.

if、while、for 的条件语句本身就是在选择 true 或 false,对于指针来说,会自动与 nullptr 比较。通常来说,if (p) 可以读作 “if p is valid”。

接着,还有一个例子:

1
2
3
if (auto pc = dynamic_cast<Circle>(ps)) { ... } // execute if ps points to a kind of Circle, good

if (auto pc = dynamic_cast<Circle>(ps); pc != nullptr) { ... } // not recommended

同样是推荐更简短的写法。但这个例子却有人提出疑问:推荐的写法不会被怀疑是 == 少写了一个 = 吗?

还真不会!这个语句其实不是经典 C++ 的语法,而是 C++17 的 init-statement 语法,这里的 auto 是类型,说明 = 不可能是 == 错误地少写成 =。

对于部分 C 程序员和“经典 C++”程序员会把赋值语句写在 if 条件里的做法,建议是改为分开写。

1
2
3
4
5
6
7
8
// p is defined before

if (p = func()) { ... } // bad

p = func();
if (p) { ... } // good

// p is used after

稣会在新工程里坚持使用 C++ Core Guidelines 的建议,但也不反对另一种写法,只要不混合使用。允许有不同派系,但最好别精神分裂。

BoringSSL? LibreSSL? OpenSSL!

问题

  • Windows 自带 OpenSSH 用的是 LibreSSL。要不要也用一下?甚至,直接动态链接到系统自带的 LibreSSL 的 dll,减少 exe 体积!

  • Chrome 用的是 BoringSSL,要不要……折腾一下?

尝试

稣使用 vcpkg,安装几个库,还不是手到擒来?

结果……连装都不让装!这是 LibreSSL 的:

1
2
3
4
5
Building libressl:x64-windows...
CMake Warning at ports/libressl/portfile.cmake:2 (message):
Can't build libressl if openssl is installed. Please remove openssl, and
try install libressl again if you need it. Build will continue since
libressl is a subset of openssl

这是 BoringSSL 的:

1
2
3
4
5
Building boringssl:x64-windows...
CMake Error at ports/boringssl/portfile.cmake:2 (message):
Can't build BoringSSL if OpenSSL is installed. Please remove OpenSSL, and
try to install BoringSSL again if you need it. Build will continue since
BoringSSL is a drop-in replacement for OpenSSL

在忍痛 vcpkg remove openssl 后,陷入一阵思考——稣主要通过 Boost.Asio 使用 SSL,所以问题转换为:Boost.Asio 对这几个 SSL 库支持得如何?其中,OpenSSL 是使用多年没有任何问题的,只需要调研其它两个!

先试试 LibreSSL,毕竟是 OpenBSD 的,而且微软也用它。拿 ClipboardSync 代码编译,发现顺利通过!但是运行时抛了异常,说不支持 TLS 1.3……遂查阅官网,说是从 3.2.0 就支持!那就是 Boost.Asio 不对了,果断给它提 issue

接着尝试 BoringSSL,毕竟是 Google 的,号称重视安全,而且有 Chrome 这个大型流行软件做背书!然而很打脸的是:它居然不支持 SM3!稣当年特地选择用国密标准里的 SM3 做 Hash 算法,就是因为爱国!不支持国密这点岂能忍?立刻 vcpkg remove boringssl

结论

Boost.Asio 和稣联合推荐 OpenSSL 为唯一好用又爱国的 SSL 库。

Boost【9】Boost.Beast

介绍

Boost.Beast 是一个 HTTP/WebSocket 库。本文只讨论 WebSocket。

相关经验

UMU 先是用过 WebSocket++(websocketpp),又用过 libwebsockets,再用的 Boost.Beast,之后就一直使用 Boost.Beast。

  • 2018 年,参与 EOS 开发时,它是用 WebSocket++ 的,跟着学习了一阵子。

  • 2020 年,在金山云时,内部版云游戏用 libwebsockets,跟着学习了一阵子。

  • 做开源版云游戏——鎏光云游戏引擎时,特地学习并使用 Boost.Beast。因为公司其实是要求和内部版有一些差异的,正好之前一直用 Boost.Asio,对它的熟悉可以快速套用在 Boost.Beast,于是果断切到 Boost.Beast。

什么场景应该使用 WebSocket?

如果您原本使用裸 TCP,您应该知道 TCP 的流式传输,导致您需要自己分包,即界定一个“消息”的边界。鎏光本来设计为同时支持裸 TCP 和 WebSocket 的,所以还保留着处理分包的代码:

https://github.com/UMU618/liuguang/blob/97f558275571b9be14893e4b55703b4b65cdbda5/src/cge/cge/game_session.cpp#L232

这对 WebSocket 其实并不需要,它的发送和接受已经是都是“消息”,带着长度的。所以如果您原来用 Asio 写 C/S 程序,把它们改为 Beast,是很容易的,而且代码量会缩减不少。

对于工具性的 C/S 程序,建议下次直接用 Beast 写更省事。

另一种适用场景是需要支持浏览器,即同时支持 C/S 和 B/S 模型。

Boost.Beast 经验

  1. text 和 binary 模式需要区分清楚,如果用于发送音视频,显然应该使用 binary 模式。

  2. stream 的默认接收长度是 16MiB,如果不够可以改长点,0 表示最大的 std::uint64_t。

参考:boost/beast/websocket/stream.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/** Set the maximum incoming message size option.

Sets the largest permissible incoming message size. Message
frame fields indicating a size that would bring the total
message size over this limit will cause a protocol failure.

The default setting is 16 megabytes. A value of zero indicates
a limit of the maximum value of a `std::uint64_t`.

@par Example
Setting the maximum read message size.
@code
ws.read_message_max(65536);
@endcode

@param amount The limit on the size of incoming messages.
*/
void
read_message_max(std::size_t amount);

/// Returns the maximum incoming message size setting.
std::size_t
read_message_max() const;
  1. 默认开了 deflate 压缩。UMU 在开发 ClipboardSync 时,曾经考虑给剪切板数据压缩,对比了 lz4 和 zstd,很是犹豫,后来发现 Beast 默认开了 deflate 压缩,于是放弃自己用 lz4 或 zstd 压缩。

自动添加 IP 黑名单到防火墙

问题

前文《防爆破远程桌面密码》提到可以用 Powershell 实现自动加 IP 黑名单到防火墙,这个坑还是得填,毕竟爆破依然在持续……

分析

核心点:

  • IP 黑名单会持续新增,过去已经加入防火墙的名单也需要保存。所以应该把新增 IP 和防火墙已有 IP 求并集。

  • 如果名单没变,不应该覆盖防火墙规则。

代码

见 Github 仓库:

https://github.com/UMU618/scripts/blob/master/pwsh/add-ip-blacklist-to-firewall.ps1

防爆破远程桌面密码

问题

面向公网开了个远程桌面端口,无论用多少号端口,都会被爆破!

虽然稣的密码很安全,几乎爆破不了,但会在系统日志里留下大量记录,实在很不雅观!

分析

改端口是没用的,因为只有 65535 个,只要机器 IP 被发现,扫描端口很快就能完成。

封 IP 是有用的,虽然爆破者(攻击者)有很多 IP,但一定是有限的,有多少封多少!

解决

  1. 从系统日志里分析爆破者 IP

打算使用 PowerShell 7 来编写脚本,首先学习 Get-WinEvent 命令:

1
Get-Help Get-WinEvent -Online

日志的过滤条件可以用“事件查看器”来协助生成:

WinEvent UI
WinEvent XML

当然,以上全部 XML 是 -FilterXml 的参数,比较长,可以用 -FilterXPath 来简化,只需要中间一部分。

条件还可以再加上 LogonType,以缩小范围,其中 3 表示“网络登录”:

1
Get-WinEvent -LogName 'Security' -FilterXPath '*[System[EventID=4625] and EventData[Data[@Name="LogonType"]=3]]' -MaxEvents 1 | Format-List Message

以下是完整代码,它会打印出 IP,和这个 IP 的登录失败次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ips = @{}
Get-WinEvent -LogName Security -FilterXPath '*[System[band(Keywords,4503599627370496)] and EventData[Data[@Name="LogonType"]=3]]' | %{
$xml = [xml]$_.toXml()
foreach ($data in $xml.Event.EventData.Data) {
if ($data.Name -eq 'IpAddress') {
$ip = $data.'#text'
if ($ips.ContainsKey($ip)) {
++$ips[$ip]
} else {
$ips.Add($ip, 1)
'Fisrt find ' + $ip + ' on ' + $_.TimeCreated
}
}
}
}
Write-Output 'All IPs:', $ips
  1. 把爆破者 IP 加入防火墙,阻止它们

目前收集到这些:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
112.184.96.197
118.45.92.239
122.38.193.166
138.199.21.245
14.116.196.99
14.49.207.162
141.98.11.119
141.98.11.58
147.78.47.57
148.113.4.245
176.111.174.173
176.111.174.174
179.60.147.13
182.180.92.224
185.161.248.145
188.250.64.50
222.191.242.228
45.143.201.62
58.221.4.54
58.33.52.84
62.122.184.88
67.159.237.58
77.90.185.132
78.128.114.18
89.248.163.94
89.248.163.95
91.191.209.202
91.240.118.187
91.240.118.29

打开 wf.msc,新建一个阻止型的防火墙策略,然后加入到“作用域”的“远程 IP 地址”里。

Firewall

加防火墙也可以用 PowerShell 搞定,这次先偷个懒,下次再说吧!