跳转至

博客

常用交换机命令

背景

最近接触了 Cisco,DELL,Huawei,H3C,Ruijie 的网络设备,发现配置方式各有不同,故记录一下各个厂家的命令。

Huawei

测试型号:S5320

保存配置

<HUAWEI>save
The current configuration will be written to flash:/vrpcfg.zip.
Are you sure to continue?[Y/N]y
Now saving the current configuration to the slot 0....
Save the configuration successfully.

进入配置模式

<HUAWEI> system-view

查看当前配置

[HUAWEI] display current-configuration

查看 LLDP 邻居

[HUAWEI]display lldp neighbor brief

查看 CDP 邻居

[HUAWEI]display cdp neighbor brief

启用 LLDP

[HUAWEI]lldp enable

启用 CDP

[HUAWEI-XGigabitEthernet0/0/1]lldp compliance cdp txrx

启用只读 SNMPv1 community

[HUAWEI]snmp-agent sys-info version all
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
Warning: SNMPv1/SNMPv2c is not secure, and it is recommended to use SNMPv3.
[HUAWEI]snmp-agent community read [COMMUNITY NAME]
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y

启用 SNMP iso view

默认情况下 SNMP 会缺少一些标准的 MIB(比如 LLDP),可以打开 iso view:

[HUAWEI]snmp-agent mib-view included iso-view iso
Warning: This command may cause confliction in netconf status. Continue? [Y/N]:y
[HUAWEI]snmp-agent community read [COMMUNITY NAME] mib-view iso-view

查看 ARP 表

[HUAWEI]display arp

ARPING

[HUAWEI]arp send-packet X.X.X.X ffff-ffff-ffff interface Vlanif VLAN

启用 STP 协议

[HUAWEI]stp enable
[HUAWEI]stp mode vbst

设置 NTP 服务器

[HUAWEI]ntp-service unicast-server x.x.x.x

设置远程 syslog 服务器

[HUAWEI]info-center loghost x.x.x.x

设置 LACP 链路聚合

[HUAWEI-XGigabitEthernet0/0/1]eth-trunk 1
[HUAWEI-XGigabitEthernet0/0/2]eth-trunk 1
[HUAWEI]interface Eth-Trunk 1
[HUAWEI-Eth-Trunk1]mode lacp

DELL

测试型号:N3048

保存配置

console#copy running-config startup-config

This operation may take few minutes.
Management interfaces will not be available during this time.

Are you sure you want to save? (y/n) y

Configuration Saved!

进入配置模式

console>enable
console# configure

查看当前配置

console# show running-config

查看 LLDP 邻居

console#show lldp remote-device all

VLAN Trunk 配置

console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode trunk
console(config-if-Gi1/0/1)#switchport trunk allowed vlan xxx,xxx-xxx

VLAN Access 配置

console(config)#interface Gi1/0/1
console(config-if-Gi1/0/1)#switchport mode access
console(config-if-Gi1/0/1)#switchport access vlan xxx

查看 VLAN 配置

console#show vlan

批量配置 interface

console(config)#interface range Gi1/0/1-4

启用 SSH 服务器

console(config)#crypto key generate rsa
console(config)#crypto key generate dsa
console(config)#ip ssh server

启用 CDP(DELL 称之为 ISDP)

console(config)#isdp enable

启用只读 SNMPv1 community

console(config)#snmp-server community [COMMUNITY NAME] ro

设置 NTP 服务器

console(config)#sntp unicast client enable
console(config)#sntp server x.x.x.x

设置 NTP 服务器

console(config)#sntp unicast client enable
console(config)#sntp server x.x.x.x

设置 STP 协议

console(config)#spanning-tree mode rapid-pvst

H3C

进入配置模式

<switch>system-view
System View: return to User View with Ctrl+Z.
[switch]

查看当前配置

[switch]display current-configuration

查看 lldp 邻居

[switch]display lldp neighbor-information

保存配置

[switch]save
The current configuration will be written to the device. Are you sure? [Y/N]:y
Please input the file name(*.cfg)[flash:/startup.cfg]
(To leave the existing filename unchanged, press the enter key):y
The file name is invalid(does not end with .cfg).

批量配置 interface

[switch]interface range GigabitEthernet 1/0/1 to GigabitEthernet 1/0/24
[switch-if-range]

查看 MAC 地址表

[switch]show mac-address

打开 LLDP 和 CDP

[switch]lldp global enable
[switch]lldp compliance cdp

Mellanox

进入配置模式

switch > enable
switch # configure terminal
switch (config) #

查看当前配置

switch (config) # show running-config

查看 interface 状态

switch (config) # show interfaces brief

查看以太网端口状态

switch (config) # show interfaces ethernet status

查看 lldp 邻居

switch (config) # show lldp remote

保存配置

switch (config) # configuration write

批量配置 interface

switch (config) # interface ethernet 1/1/1-1/1/4
switch (config interface ethernet 1/1/1-1/1/4) #

查看 MAC 地址表

switch (config) # show mac-address-table

查看链路聚合状态

switch (config) # show interfaces port-channel summary

把拆分的四个 SFP 口恢复成一个

switch (config interface ethernet 1/1/1) # module-type qsfp 

把一个 QSFP 口拆分成四个

switch (config interface ethernet 1/1) # shutdown
switch (config interface ethernet 1/1) # module-type qsfp-split-4

设置链路聚合

switch (config interface ethernet 1/1) # channel-group 1 mode active
switch (config interface ethernet 1/2) # channel-group 1 mode active

模式可以选择:active(LACP)/passive(LACP)/on(Static)

设置 STP 协议

switch (config) # spanning-tree mode rpvst

设置远程 syslog 服务器

switch (config) # logging x.x.x.x

设置 NTP 服务器

switch (config) # ntp server x.x.x.x

Cisco

设置 NTP 服务器

# ntp server x.x.x.x

配置 Trunk

# config terminal
(config)# interface ethernet 1/1
(config-if)# switchport mode trunk
(config-if)# switchport trunk allowed vlan 12-34

配置 Access

# config terminal
(config)# interface ethernet 1/1
(config-if)# switchport mode access
(config-if)# switchport access vlan 1234

PCB 笔记

记录一下在学习画板子过程中学到的心得。

工具

目前使用过 KiCadlceda

  • KiCad: 开源软件,跨平台。
  • lceda:在线编辑,不需要安装,和 lcsc 有深度集成。

项目 jiegec/HT42B534USB2UART 采用的是 KiCad 5 编写的。目前正在做的另一个项目采用 lceda

流程

  1. 选择所需要使用的芯片,查找芯片的 datasheet。
  2. 寻找采用了芯片的一些设计,特别是看 schematic。
  3. 按照 datasheet 里面推荐的电路,或者是其他人的设计,画自己需要的 schematic。
  4. 设置好各个元件的 footprint,然后转到 PCB 设计。
  5. 在 PCB 里面布线,生成 Gerber 等文件。
  6. 把 Gerber 给到生产商(比如 jlc),交付生产。
  7. 如果是自己焊接,则需要购买元件,比如从 lcsc 购买。
  8. 收到 PCB 和元件后,自己按照 BOM 和 schematic 焊接各个元件。

笔记

  1. 对于一些连接很多元件的信号,比如 GND,可以留作铺铜解决。也就是说,先不管 GND,把其他所有的信号都接好以后,再在顶层铺铜;如果还是有没有连接上的 GND,可以通过过孔(Via)走到底层,在底层再铺一层铜。
  2. 对于外部供电的 VCC 和 GND,在 KiCad 中需要用 PWR_FLAG 标记一下。
  3. 在 KiCad 中设计 PCB 前,要把生产商的工艺参数设置好,不然画了也要重画。
  4. lceda 在选择元件的时候,可以直接从 lcsc 里选择,这样可以保证封装和商品可以对得上,不需要手动进行匹配。
  5. 如果要用 jlc 的 SMT 贴片,先在 SMT 元件列表 里搜索所需要的元件;推荐用基本库,如果用其他库,则要加钱;选好元件以后,用元件编号去 lceda 里搜索并添加到 schematic。
  6. 对于涉及模拟信号的设计,比如音频,需要特别注意模拟信号的电和地都是单独的:AVCCAGND。所以要特别注意 datasheet 里面不同的地的表示方法。最后,再用磁珠把 VCCAVCCGNDAGND 分别连接起来就可以了。可以参考 DE2 板子中第 19 页的音频部分设计Staying well grounded
  7. 在 schematic 里经常会出现在电源附近的电容,那么,在 PCB 中,也尽量把这些电容放在对应的电源的旁边。
  8. 耳机插座里面,一般分三种组成部件:Tip,Ring,Sleeve。只有两段的是 TS,三段的是 TRS,四段的是 TRRS。TS 是单声道,T 是声音,S 是地。TRS 是双声道,T 是左声道(或者单声道),R 是右声道,S 是地。TRRS 则是双声道加录音。一般来说,LINE IN 是双声道,MIC IN 是单声道,它们的阻抗也不同;LINE OUT 和 HEADPHONE OUT 都是双声道,但 HEADPHONE OUT 经过了额外的放大器。
  9. 遇到一个 SPI 协议没有 SPI_MISO 引脚的芯片,可能说明它是 write-only 的。
  10. 手焊的基本元件,一般用 0603 加一些 Padding 的封装;SMT 的话,则建议用 0402 封装。
  11. I2C 的信号线一般需要加一个几 K 欧姆的上拉电阻到 VCC。

JLC SMT 的基础库不需要换料费,如何寻找基础库中的元件:

  1. 电阻品牌是 UNI-ROYAL,型号命名规则是:
    1. 封装:0603/0402
    2. 功率:WA/WG/W8
    3. 误差:F(1%)
    4. 阻值:三位整数 + 一位 exp(J 表示 -1,K 表示 -2,L 表示 -3),例如 2002 表示 200*10^2=20k,1003 表示 100*10^3=100k,3300 表示 330*10^0=330,330J 表示 330*10^-1=33,330K 表示 330*10^-2=3.3 例子:要找 0402 封装的 10k 欧电阻,搜索 0402WGF1003;要找 0603 封装的 33 欧电阻,搜索 0603WAF330。
  2. 电容品牌有风华/三星/国巨,三星的电容型号命名规则是:
    1. 封装:05(0402)/10(0603)
    2. 字母:A/B/C
    3. 电容:两位整数 + 一位 exp,单位是 pF,例如 105 表示 10*10^5pF=10^6pF=1uF,104 表示 10*10^4pF=10^5pF=0.1uF 例子:要找 0402 封装的 100nF 电容,搜索 CL05B104;要找 0603 封装的 1uF 电容,搜索 CL10A105。也可以只搜电容的数字部分,可以找到更多品牌。

阻抗匹配

在传输线上,如果出现阻抗变化,就会导致信号出现反射,质量变差。因此,需要保证传输线的两端和传输线整个过程的阻抗一致。

阻抗设置为多少,一般要看协议的规定。确定好协议定义的阻抗以后,需要查看信号两端的芯片内部的阻抗,如果和协议不一致,需要额外添加电阻,并且电阻要尽量放在接近芯片的位置上。由于传输线在 PCB 上,所以和 PCB 厂商的工艺有关,需要去厂商的阻抗计算器上进行计算,例如 jlc 阻抗计算器。涉及到的参数有:

  1. 板子层数:PCB 层数,最简单的正反面就是 2 层
  2. 成品厚度:整个 PCB 加起来的厚度,例如 1.6mm
  3. 内层铜厚:夹在内部的 PCB 的铜的厚度,例如 0.5 oz,就是 1.37/2=0.685 mil
  4. 外层铜厚:PCB 上下暴露在外面的两层的铜的厚度,常见 1 oz=1.37 mil
  5. 需求阻抗:协议所要求的阻抗,例如单端 50 欧姆(SDIO),差分 90 欧姆(USB)
  6. 阻抗模式:传输线的连接方式,见下(图源 KiCad)
    1. 单端阻抗(Microstrip Line):一根线传输信号,地线在另一个平面,图中上面的长方形就是传输线,底部就是地平面
    2. 差分阻抗(Coupled Microstrip Line):差分线传输信号,地线在另一个平面,图中上方两个长方形就是差分传输线,底部是地平面
    3. 共面单端:一根线传输信号,周围就是地平面
    4. 共面差分:差分线传输信号,周围就是地平面
  7. 阻抗层:传输线所在的层
  8. 参考层:地线所在的层

由于双层 PCB 的两层铜之间距离比较远(例如 57.68 mil),如果采用单端阻抗,那么需要比较大的线宽,例如用 jlc 阻抗计算器,50 欧姆阻抗需要 106.68 mil 的线宽。如果采用四层 PCB,最上面两层之间距离缩小了很多(例如 7.99 mil),此时即使用单端阻抗,用 jlc 计算得出只需要 13.2 mil 的线宽。所以双层 PCB 更适合使用共面单端的方式,此时传输线和地线放在了同一个平面,距离比较小,就不需要那么大的线宽。

这里的单位:1 mil = 0.0254 mm,1 inch = 1000 mil = 0.0254 m,1 oz = 1.37 mil = 0.0348 mm

iDRAC 各版本

iDRAC 版本

目前接触到的 iDRAC 版本有:7 8 9。一些常见的服务器型号和 iDRAC 版本对应关系:

基本上,如果是 PowerEdge R 什么的,就看第二位数字,就可以推断出版本号了。

下面列举了一下可能会用到的版本。

iDRAC 7

iDRAC 7 在 2020 年 2 月停止更新了,最新版本是 2.65.65.65。

升级路线参考:Reddit

iDRAC 8

iDRAC 9

  • 4.00.00.00: 下载页面,2019 年 12 月版本。LLDP 连接视图。
  • 4.22.00.00: 下载页面,2020 年 7 月版本。
  • 4.40.00.00: 下载页面,2020 年 12 月版本,下一代的 iDRAC virtual console 和 virtual media,支持 Infiniband。
  • 5.00.00.00: 下载页面,2021 年 6 月版本。
  • 7.00.00.00: 下载页面,2023 年 6 月版本。

在线升级

iDRAC 可以在线从 Dell 官网下载新版本升级,在网页上选择通过 HTTPS 升级,域名写 ,具体见文档

使用 SSSD 的 LDAP 认证

前言

最近在研究替换一个老的用户系统,于是顺便学习了一下 LDAP,还有 SSSD。LDAP 是一个目录协议,顺带的,因为用户信息也可以存在里面,所以也就成了一个常见的用户认证协议。SSSD 就是一个 daemon,把系统的 NSS PAM 的机制和 LDAP 连接起来。

配置

其实很简单,安装 sssd 并且配置即可:

$ sudo apt install sssd
$ sudo vim /etc/sssd/sssd.conf
# file content:
[sssd]
config_file_version = 2
services = nss,pam
domains = LDAP

[domain/LDAP]
cache_credentials = true
enumerate = true
entry_cache_timeout = 10
ldap_network_timeout = 2

id_provider = ldap
auth_provider = ldap
chpass_provider = ldap

ldap_uri = ldap://127.0.0.1/
ldap_chpass_uri = ldap://127.0.0.1/
ldap_search_base = dc=example,dc=com
ldap_default_bind_dn = cn=localhost,ou=machines,dc=example,dc=com
ldap_default_authtok = REDACTED
$ sudo systemctl enable --now sssd

一些字段需要按照实际情况编写,请参考sssd.confsssd-ldap

协议

那么,LDAP 里面的用户是如何和 Linux 里的用户对应起来的呢?可以看到,SSSD 会查询 posixAccount:

(&(objectclass=posixAccount)(uid=*)(uidNumber=*)(gidNumber=*))

然后,可以查到 posixAccount 的 schema,里面可以见到对应 /etc/passwd 的各个字段。相应的,也有 shadowAccount 对应 /etc/shadow

按照要求配好以后(建议用 ldapvi 工具),就可以用 getent passwd 看到新增的用户了。

上面的部分是通过 NSS 接口来查询的,除了用户以外,还有其他的一些 NIS 信息可以通过 LDAP 查询。此外,如果要登录的话,则是用 PAM 认证,SSSD 则会把 PAM 认证转换成 LDAP 的 Bind:

$ su test
Password:
# sssd: bind to dn of test user with password

如果 Bind 成功,则认为登录成功;否则就是登录失败。

如果用户要修改密码,SSSD 默认用的是 RFC3062 LDAP Password Modify Extended Operation 的方式;如果服务器不支持的话,可以按照 文档 使用 ldap modify 方式来修改密码。

SSD 还可以配置 sudo 支持,也是用类似的方法,添加 objectClass=sudoRole 的目录项即可。可以参考 man sudoers.ldap 编写对应的目录项。

对于 SSH 配置,可以参考 RedHat 的文档,和参考 man sss_ssh_authorizedkeys 配置 authorized keys 命令。然后,给用户添加 sshPublicKey 属性即可,内容与 ~/.ssh/id_*.pub 一致。

相关 RFC

LDAP-Related RFCs

在 Big Sur(M1) 上解决 LaTeX 找不到楷体字体的问题

背景

最近在尝试移植 MiKTeX 到 Apple Silicon 上,添加了一些 patch 以后就可以工作了,但遇到了新的问题,即找不到 KaiTi

~/Library/Application Support/MiKTeX/texmfs/install/tex/latex/ctex/fontset/ctex-fontset-macnew.def:99:
   Package fontspec Error:
      The font "Kaiti SC" cannot be found.

miktex-fc-list 命令找了一下,确实没有找到:

$ /Applications/MiKTeX\ Console.app/Contents/bin/miktex-fc-list | grep Kaiti
# Nothing

上网搜了一下,找到了一个解决方案:字体在目录 /System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Support/FontSubsets/Kaiti.ttc 里,所以手动安装一下,就可以让 LaTeX 找到了。但我觉得,与其安装多一份在文件系统里,不如让 LaTeX 去找它。

解决方法

按照上面的线索,找到了 Kaiti.ttc 所在的路径:

$ fd Kaiti.ttc
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc

可以看到,和上面的路径又不大一样。研究了一下 fontconfig,发现可以用 miktex-fc-conflist 找到配置文件的目录:

$ /Applications/MiKTeX\ Console.app/Contents/bin/miktex-fc-conflist
+ ~/Library/Application Support/MiKTeX/texmfs/config/fontconfig/config/localfonts2.conf: No description
+ ~/Library/Application Support/MiKTeX/texmfs/config/fontconfig/config/localfonts.conf: No description
...

看了下第一个文件(localfonts.conf):

<?xml version="1.0" encoding="UTF-8"?>

<!--
  DO NOT EDIT THIS FILE! It will be replaced when MiKTeX is updated.
  Instead, edit the configuration file localfonts2.conf.
-->

<fontconfig>
<include>localfonts2.conf</include>
<dir>/Library/Fonts/</dir>
<dir>/System/Library/Fonts/</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/type1</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/opentype</dir>
<dir>~/Library/Application Support/MiKTeX/texmfs/install/fonts/truetype</dir>
</fontconfig>

可以看到,我们可以添加路径,不过建议修改的是 localfonts2.conf。按照类似的格式,修改成:

<?xml version="1.0"?>
<fontconfig>
<dir>/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets</dir>
<!-- REMOVE THIS LINE
<dir>Your font directory here</dir>
<dir>Your font directory here</dir>
<dir>Your font directory here</dir>
     REMOVE THIS LINE -->
</fontconfig>

UPDATE: 新版本 macOS 中,路径建议加上 /System/Library/AssetsV2/com_apple_MobileAsset_Font7

<dir>/System/Library/AssetsV2/com_apple_MobileAsset_Font7</dir>

这样,就可以找到 Kaiti SC 了:

$ miktex-fc-list | grep Kaiti
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Regular,標準體,常规体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Regular,標準體,常规体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Bold,粗體,粗体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Bold,粗體,粗体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti SC,楷體\-簡,楷体\-简:style=Black,黑體,黑体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: Kaiti TC,楷體\-繁,楷体\-繁:style=Black,黑體,黑体
/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/Resources/Fonts/Subsets/Kaiti.ttc: STKaiti:style=Regular,標準體,Ordinær,Normal,Normaali,Regolare,レギュラー,일반체,Regulier,Обычный,常规体

这样就搞定了,用 LaTeX 找字体的时候也没有出现问题了。

如果你用的是 TeX Live,那么直接把上面的 Kaiti.ttc 路径复制到 ~/Library/Fonts 下即可。

COMMON 符号

背景

在编译一个程序的时候,遇到了 undefined symbol 的问题。具体情况是这样的:

  1. 一开始的时候,直接把所有的源代码编译成 .o,再一次性链接,这样不会报错
  2. 后来,把一些代码编译成静态库,即把其中一部分源代码编译成 .o 后,用 ar 合并到一个 .a 中,再和其余的 .o 链接在一起,这时候就报错了:
Undefined symbols for architecture arm64:
  "_abcd", referenced from:
    ...

如果换台机器,编译(使用的是 gcc 10.2.0)就没有问题。

而如果去找这个符号存在的 .o 里,是可以找到的:

$ objdump -t /path/to/abcd.o
0000000000000028         *COM*  00000008 _abcd

在合成的静态库 .a 里,也是存在的(一个定义 + 若干个引用):

$ objdump -t /path/to/libabc.a | grep abcd
0000000000000028         *COM*  00000008 _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd
0000000000000000         *UND* _abcd

于是觉得很奇怪,就上网搜了一下,找到了一篇 StackOverflow 讲了这个问题。解决方案很简单,就是:

编译的时候打开 -fno-common 设置

而 gcc 10 不会出错的原因是,它默认从 -fcommon 改成了 -fno-common

COMMON 是什么

这时候,肯定不满足于找到一个解决方案,肯定还是会去找背后的原理。

首先,搜索了一下 COMMON 是什么,找到了 Investigating linking with COMMON symbols in ELF 这篇文章。

文章里讲了 COMMON 是做什么的:

Common symbols are a feature that allow a programmer to 'define' several variables of the same name in different source files. This is in contrast with the more popular way of doing, where you define a variable once in a source file, and reference it everywhere else in other source files, using extern. When common symbols are used, the linker will merge all symbols of the same name into a single memory location, the size of which is the largest type of the individual common symbol definitions. For example, if fileA.c defines an uninitialized 32-bit integer myint, and fileB.c defines an 8-bit char myint, then in the final executable, references to myint from both files will point to the same memory location (common location), and the linker will reserve 32 bits for that location.

文章里还讲了具体的实现方法:一个没有初始化的全局变量,在 -fcommon 的情况下,会设为 COMMON;如果有初始化,就按照初始化的值预分配到 .bss 或者 .data。链接的时候,如果有多个同名的 symbol,会有一个规则决定最后的 symbol 放到哪里;如果有冲突的话,就是我们熟悉的 multiple definition 错误了。

为啥会有这种需求,多个 variable 同名,不会冲突而且共享内存?又在别的地方看到说法,COMMON 是给 ancient 代码使用的,还有的提到了 FORTRAN。于是去搜了一下,果然,FORTRAN 是问题的关键

FORTRAN 里面的 COMMON

用关键词很容易可以搜索到讲 COMMON BLOCK in FORTRAN 的文章,FORTRAN 里面的 COMMON 是一种通过全局存储隐式传递参数的方法。拿文章里的例子:

      PROGRAM MAIN
      INTEGER A
      REAL    F,R,X,Y
      COMMON  R,A,F
      A = -14
      R = 99.9
      F = 0.2
      CALL SUB(X,Y)
      END

      SUBROUTINE SUB(P,Q)
      INTEGER I
      REAL    A,B,P,Q
      COMMON  A,I,B
      END

在函数 MAIN 和 SUB 中,都有 COMMON 语句,而 COMMON 后面的变量,就是存储在一个 COMMON 的 symbol 之中,按照顺序映射到 symbol 的内存地址。尝试编译一下上面的代码,然后看一下 symbol:

$ gfortran -g -c test.f -o test.o
$ objdump -t test.o

test.o: file format Mach-O arm64

SYMBOL TABLE:
0000000000000078 g     F __TEXT,__text _main
0000000000000000 g     F __TEXT,__text _sub_
000000000000000c         *COM*  00000010 ___BLNK__

可以看到,出现了一个叫做 ___BLNK__ 的 COMMON symbol,大小是 16 字节。看一下代码中是如何引用的:

$ objdump -S --reloc test.o

test.o: file format Mach-O arm64

Disassembly of section __TEXT,__text:

0000000000000018 _MAIN__:
;         PROGRAM MAIN
      18: fd 7b be a9                   stp x29, x30, [sp, #-32]!
      1c: fd 03 00 91                   mov x29, sp
;         A = -14
      20: 00 00 00 90                   adrp    x0, #0
        0000000000000020:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      24: 00 00 40 f9                   ldr x0, [x0]
        0000000000000024:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      28: a1 01 80 12                   mov w1, #-14
      2c: 01 04 00 b9                   str w1, [x0, #4]
;         R = 99.9
      30: 00 00 00 90                   adrp    x0, #0
        0000000000000030:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      34: 00 00 40 f9                   ldr x0, [x0]
        0000000000000034:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      38: a1 99 99 52                   mov w1, #52429
      3c: e1 58 a8 72                   movk    w1, #17095, lsl #16
      40: 20 00 27 1e                   fmov    s0, w1
      44: 00 00 00 bd                   str s0, [x0]
;         F = 0.2
      48: 00 00 00 90                   adrp    x0, #0
        0000000000000048:  ARM64_RELOC_GOT_LOAD_PAGE21  ___BLNK__
      4c: 00 00 40 f9                   ldr x0, [x0]
        000000000000004c:  ARM64_RELOC_GOT_LOAD_PAGEOFF12   ___BLNK__
      50: a1 99 99 52                   mov w1, #52429
      54: 81 c9 a7 72                   movk    w1, #15948, lsl #16
      58: 20 00 27 1e                   fmov    s0, w1
      5c: 00 08 00 bd                   str s0, [x0, #8]
;         CALL SUB(X,Y)
      60: e1 63 00 91                   add x1, sp, #24
      64: e0 73 00 91                   add x0, sp, #28
      68: 00 00 00 94                   bl  #0 <_MAIN__+0x50>
        0000000000000068:  ARM64_RELOC_BRANCH26 _sub_
;         END
      6c: 1f 20 03 d5                   nop
      70: fd 7b c2 a8                   ldp x29, x30, [sp], #32
      74: c0 03 5f d6                   ret

可以看到,在 MAIN 中引用 A 的时候,取的地址是 ___BLNK__+4R___BLNK__+0F___BLNK__+8。这和代码里的顺序也是一致的。所以在 SUB 中读 A I B 的时候,对应了 MAIN 中的 A R F。通过这种方式,可以在 MAIN 函数里面隐式地给所有函数传递参数。

此外,COMMON 还可以命名,这样就可以区分不同的参数用途:

        PROGRAM MAIN
        INTEGER A
        REAL    F,R,X,Y
        COMMON  R,A,F
        COMMON /test/ X,Y
        A = -14
        R = 99.9
        F = 0.2
        CALL SUB(X,Y)
        END

        SUBROUTINE SUB(P,Q)
        INTEGER I
        REAL    A,B,P,Q
        COMMON  A,I,B
        END

代码添加了一行 COMMON /test/,观察一下 symbol:

$ objdump -t test.o

test.o: file format Mach-O arm64

SYMBOL TABLE:
0000000000000088 g     F __TEXT,__text _main
0000000000000000 g     F __TEXT,__text _sub_
000000000000000c         *COM*  00000010 ___BLNK__
0000000000000008         *COM*  00000010 _test_

和预期的一致:出现了新的 COMMON symbol,对应了 named COMMON Block 里面的变量 X 和 Y。

再看一下汇编里怎么引用的:

;         CALL SUB(X,Y)
      60: 00 00 00 90                   adrp    x0, #0
                0000000000000060:  ARM64_RELOC_GOT_LOAD_PAGE21  _test_
      64: 00 00 40 f9                   ldr     x0, [x0]
                0000000000000064:  ARM64_RELOC_GOT_LOAD_PAGEOFF12       _test_
      68: 01 10 00 91                   add     x1, x0, #4
      6c: 00 00 00 90                   adrp    x0, #0
                000000000000006c:  ARM64_RELOC_GOT_LOAD_PAGE21  _test_
      70: 00 00 40 f9                   ldr     x0, [x0]
                0000000000000070:  ARM64_RELOC_GOT_LOAD_PAGEOFF12       _test_
      74: 00 00 00 94                   bl      #0 <_MAIN__+0x5c>
                0000000000000074:  ARM64_RELOC_BRANCH26 _sub_

可以看到,第一个参数(x0)为 _test_,第二个参数(x1)为 _test_+4,和预期也是一样的。

读到这里,就可以理解为啥有 COMMON symbol 了。可能是为了让 C 代码和 FORTRAN 代码可以互操作 COMMON symbol,就有了这么一出。也可能有的 C 库确实用了类似的方法来实现某些功能。

解决方案

但是,这种用法在现在来看是不推荐的,建议还是该 extern 就 extern,另外,在编译静态库的时候,记得加上 -fno-common

Skid Buffer

Skid buffer

Skid buffer 指的就是,对于 valid + ready 的握手信号,用空间(更多的逻辑)来换取时间(更好的时序)的一个硬件模块。

简单来说,背景就是,为了解决 valid 和 ready 信号在数据流水线上一路经过组合逻辑导致的时序问题,在中途加上一些寄存器来阻隔。当然了,代价就是延迟和面积,不过吞吐量还是需要保持的。

由于需求的不同,Skid buffer 也有不同的实现。目前,找到了四个实现,实现上有所不同,特性也不大一样。

统一约定

由于我在 SpinalHDL 语言中重新实现了下面的这些 Skid buffer,所以按照 SpinalHDL 的 Stream 定义接口:

class SkidBufferCommon[T <: Data](
    gen: => T
) extends Component {
  val io = new Bundle {
    val s = slave(Stream(gen))
    val m = master(Stream(gen))
  }
}

在这里,io.s 表示从上游取的数据,io.m 表示传递给下游的数据。

输出信号共有:io.s.readyio.m.validio.m.payload

ZipCPU 版本

第一个版本来自 ZipCPU:

博客地址:Building a Skid Buffer for AXI processing 代码地址:skidbuffer.v

它有两个参数,一个表示是否有额外的输出寄存器(outputReg),一个表示是否低功耗(lowPower)。

FPGACPU 版本

第二个版本来自 FPGACPU:

文章地址:Pipeline Skid Buffer

SpinalHDL S2M 版本

第三个版本来自 SpinalHDL Library 的 s2mPipe:

代码地址:Stream.scala L348

SpinalHDL M2S 版本

第四个版本来自 SpinalHDL Library 的 m2sPipe:

代码地址:Stream.scala L327

四个版本的对比

在研究了代码以后,可以看到这四个版本的区别:

版本 ZipCPU w/ outputReg ZipCPU w/o outputReg FPGACPU S2M M2S
io.s.ready Reg Reg Reg Reg Comb
io.m.valid Reg Comb Reg Comb Reg
io.m.payload Reg Comb Reg Comb Reg
latency 1 0 1 0 1
buffer 数量 1 1 2 1 1

注:

  1. Reg 表示从寄存器输出,Comb 表示从组合逻辑输出
  2. Latency 表示从 io.s.fireio.m.fire 的延迟
  3. Buffer 表示缓冲的 payload 个数
  4. ZipCPU w/o outputReg 和 S2M 实现的逻辑是一样的

形式化验证

为了确认上面这些类型的 Skid Buffer 都可以正常工作,按照 ZipCPU Skid Buffer 的文章,也照着写了几个 property:

1: 在 valid && ~ready 的时候,valid 需要继续保持为高,并且 payload 不变:

// When valid goes high, data is stable and valid stays high before ready
when(past(stream.valid && ~stream.ready && ~outerReset)) {
    slaveAssume(stream.valid);
    if (dataStable) {
        slaveAssume(stable(stream.payload.asBits));
    }
}

2: 在 reset 释放的第一个周期里,valid 不能为高:

参考 AXI 标准 (IHI0022E Page 38 A3.1.2) 原文:

The earliest point after reset that a master is permitted to begin driving ARVALID, AWVALID, or WVALID HIGH is at a rising ACLK edge after ARESETn is HIGH.
// Valid is low in the first cycle after reset falls
when(pastValid && past(outerReset) && ~outerReset) {
    slaveAssume(~stream.valid);
}

3: 添加 cover property,要求 io.sio.m 可以连续若干个周期 valid && ready,保证吞吐率:

cover(
    pastValid && genPast(pastValid, null, cycles) && genPast(
        ~outerReset,
        null,
        cycles
    ) && genPast(stream.fire, payload, cycles)
)

采用 yosys-smtbmc 工具验证了以上四种 Skid buffer 都满足这些属性。

在 M1 上用 QEMU 运行 Debian 虚拟机

背景

看到 @jsteward 在 M1 的 QEMU 中运行了 Windows on ARM,所以我先来试试 Debian on AArch64,这样会简单一些。

参考:https://gist.github.com/niw/e4313b9c14e968764a52375da41b4278#file-readme-md

大约需要 3G 的硬盘空间。

安装 QEMU w/ M1 patches

目前上游的 QEMU 还不支持 M1 的 Hypervisor framework,需要打 patch:

git clone https://mirrors.tuna.tsinghua.edu.cn/git/qemu.git
cd qemu
git checkout master -b wip/hvf
curl 'https://patchwork.kernel.org/series/400619/mbox/'|git am --3way
mkdir build
cd build
../configure --target-list=aarch64-softmmu --enable-cocoa --disable-gnutls
make -j4

编译后,得到 qemu-system-aarch64 的二进制

准备好文件系统

需要下载 EFI 固件Debian 安装镜像,解压前者以后把文件放同一个目录中,并且创建需要的文件:

$ ls *.fd
QEMU_EFI.fd   QEMU_VARS.fd
$ dd if=/dev/zero of=pflash0.img bs=1m count=64
$ dd if=/dev/zero of=pflash1.img bs=1m count=64
$ dd if=QEMU_EFI.fd of=pflash0.img conv=notrunc
$ dd if=QEMU_VARS.fd of=pflash1.img conv=notrunc
$ $QEMU/qemu-img create -f qcow2 disk.qcow2 40G

安装 Debian 系统

接着,执行以下的命令,然后按照提示安装系统:

$ $QEMU/qemu-system-aarch64 \
  -serial mon:stdio \
  -M virt,highmem=off \
  -accel hvf \
  -cpu cortex-a72 \
  -smp 4 \
  -m 4096 \
  -drive file=./pflash0.img,format=raw,if=pflash,readonly=on \
  -drive file=./pflash1.img,format=raw,if=pflash \
  -device virtio-scsi-pci \
  -device virtio-gpu-pci \
  -device qemu-xhci \
  -device usb-kbd \
  -device usb-tablet \
  -drive file=./disk.qcow2,if=none,id=boot,cache=writethrough \
  -device nvme,drive=boot,serial=boot \
  -drive if=none,id=cd,file=debian-10.7.0-arm64-xfce-CD-1.iso,media=cdrom \
  -device scsi-cd,drive=cd \
  -display default,show-cursor=on

需要注意的是,如果用 -cdrom 选项,Debian 会无法识别,所以需要走 SCSI。安装完成后,第一次重启可能会显示失败,不用管。另外,安装界面只在串口处显示,但不会显示在 GUI 中,估计是因为 BUG(感谢 @Harry-Chen 指出)。

启动系统

安装好后,运行下面的命令来启动 Debian 系统:

$ $QEMU/qemu-system-aarch64 \
  -monitor stdio \
  -M virt,highmem=off \
  -accel hvf \
  -cpu cortex-a72 \
  -smp 4 \
  -m 4096 \
  -drive file=./pflash0.img,format=raw,if=pflash,readonly=on \
  -drive file=./pflash1.img,format=raw,if=pflash \
  -device virtio-gpu-pci \
  -device virtio-scsi-pci \
  -device qemu-xhci \
  -device usb-kbd \
  -device usb-tablet \
  -drive file=./disk.qcow2,if=none,id=boot,cache=writethrough \
  -device nvme,drive=boot,serial=boot \
  -display default,show-cursor=on \
  -nic user,model=virtio

注意参数和上面有所不同。启动后就可以在 GUI 上看到 Debian 登录的界面了。

网络

起来以后,可以看到一个网卡 enp0s1 启动并获取 IP 地址:

$ ip l set enp0s1 up
$ dhclient enp0s1

获取到一个 IP 地址后,就可以上网了。

已知问题

在虚拟机内重启以后,可能会启动失败。退出 QEMU 进程重新启动即可。

以太网的物理接口

本文的内容已经整合到知识库中。

背景

最近逐渐接触到了一些高速的以太网的接口,被一大堆的名字搞得有点懵,所以特意学习了一下并整理成这篇博客。

更新:经 @z4yx 指出,还可以看华为的介绍文档

几几 BASE 杠什么是什么意思

在下文里,经常可以看到类似 100BASE-TX 这种写法,它表示的意思是:

  1. BASE 前面的数字表示速率,比如 10,100,1000,10G 等等
  2. BASE 之后的第一个字母,常见的 T 表示双绞线,S 表示 850nm 光纤,L 表示 1310nm 光纤,C 表示同轴电缆
  3. 之后可能还有别的字母,比如 X 表示 8b/10b 或者 4b/5b(FE)的编码,R 表示 64b/66b 的编码
  4. 之后可能还有别的数字,如果是 LAN PHY 表示的是所使用的 lane 数量;如果是 WAN PHY 表示的是传输的公里数

详见 Wikipedia - Ethernet Physical Layer # Naming Conventions 和 IEEE 802.3 1.2.3 节 Physical Layer and media notation:

The data rate, if only a number, is in Mb/s, and if suffixed by a “G”, is in
Gb/s. The modulation type (e.g., BASE) indicates how encoded data is
transmitted on the medium. The additional distinction may identify
characteristics of transmission or medium and, in some cases, the type of PCS
encoding used (examples of additional distinctions are “T” for twisted pair,
“B” for bidirectional optics, and “X” for a block PCS coding used for that
speed of operation). Expansions for defined Physical Layer types are included
in 1.4.

和 IEEE 802.3 1.4 节 Definitions 中的几个例子:

  • 100BASE-T: IEEE 802.3 Physical Layer specification for a 100 Mb/s CSMA/CD local area network. (See IEEE Std 802.3, Clause 22 and Clause 28.)
  • 100BASE-TX: IEEE 802.3 Physical Layer specification for a 100 Mb/s CSMA/CD local area network over two pairs of Category 5 twisted-pair cabling. (See IEEE Std 802.3, Clause 24 and Clause 25.)
  • 1000BASE-T: IEEE 802.3 Physical Layer specification for a 1000 Mb/s CSMA/CD LAN using four pairs of Category 5 balanced copper cabling. (See IEEE Std 802.3, Clause 40.)
  • 1000BASE-X: IEEE 802.3 Physical Layer specification for a 1000 Mb/s CSMA/CD LAN that uses a Physical Layer derived from ANSI X3.230-1994 (FC-PH) [B21]23. (See IEEE Std 802.3, Clause 36.)
  • 2.5GBASE-T: IEEE 802.3 Physical Layer specification for a 2.5 Gb/s LAN using four pairs of Category 5e/Class D balanced copper cabling. (See IEEE Std 802.3, Clause 126.)
  • 5GBASE-T: IEEE 802.3 Physical Layer specification for a 5 Gb/s LAN using four pairs of Category 5e/Class D balanced copper cabling. (See IEEE Std 802.3, Clause 126.)
  • 10GBASE-T: IEEE 802.3 Physical Layer specification for a 10 Gb/s LAN using four pairs of Class E or Class F balanced copper cabling. (See IEEE Std 802.3, Clause 55.)

各个速率对应的英文单词是什么

  • Fast Ethernet: 100Mbps
  • Gigabit Ethernet: 1Gbps
  • Multi Gigabit Ethernet: 2.5Gbps
  • Ten Gigabit Ethernet: 10Gbps
  • Forty Gigabit Ethernet: 40Gbps
  • Hundred Gigabit Ethernet: 100Gbps

常见的连接器

连接器(connector)一般来说指的就是线缆和网络设备之间的物理接口了。常见的有:

  • 8P8C:一般我们会称之为 RJ45,关于它们俩的关系,可以看 Wikipedia 上面的说明,不过在日常生活中,这两个混用其实也没有什么大问题
  • LC:一种光纤的接口,有两个突出来的插到 SFP 光模块中的突起,比较常见
  • SFP+ DAC:一般是 DAC(Direct Attatched Cable)线,线的两端直接就是 SFP+ 的接口,直接插到 SFP+ 笼子中,不需要光模块;更高速率的也有 DAC 线

对于光纤的接口,注意购买的时候要和光模块对应,不然可能插不进去。常见的有 LC-LC,SC-LC,SC-SC 等等,表示线的两端分别是什么接口。

MDI 和 MDI-X

这其实就是大家常见的 RJ45 里面 8 根线对应的信号,在十兆和百兆的时候,需要区分 MDI 和 MDI-X,在同种类型的端口之间用交叉线,在不同类型的端口之间用直通线。在后来,有了 Auto MDI-X,也就是会按照实际情况自动检测并且匹配。从千兆开始,设备都支持 Auto MDI-X 了,所以线本身是交叉还是直通就无所谓了。

各种 SFP

SFP 是很常见的,特别是在高速的网络之中。而它又分为几种,对应不同的速率:

  • SFP: 1Gbps/100Mbps
  • SFP+: 10Gbps
  • SFP28: 25Gbps
  • SFP56: 50Gbps
  • QSFP: 4Gbps
  • QSFP+: 40Gbps
  • QSFP28: 100Gbps/50Gbps
  • QSFP56: 200Gbps
  • QSFP-DD: 400Gbps/200Gbps
  • QSFP-DD112: 800Gbps
  • OSFP: 800Gbps/400Gbps

可以看到,名字前面加了个 Q(Quad),速率就翻了 4 倍,因为有 4 个 lane,同时物理接口的尺寸也变大了。所以,不带 Q 的 SFP 的物理尺寸都一样,带 Q 的 SFP 物理尺寸都一样大,但后者比前者大一些(SFP 是 113.9 mm^2,QSFP 是 156 mm^2)。OSFP 又比 QSFP 更大一些,O 表示 Octal,就是 8 个 lane 的意思。

可以在 400G QSFP Transceiver Types and Fiber Connections400G OSFP Transceiver Types Overview 看到 QSFP-DD 和 OSFP 的对比。

通常,网络设备也会支持把一个 QSFP 接口拆成多个 SFP 接口来使用,比如有的线,一边是 QSFP28,另一边是 4xSFP28,只要设备支持即可,目的是节省空间。

SFP 标准 SFF INF-8074 规定了 20 根信号线,正反面各 10 根,重要的是下面的这些(括号里写得是 Pin 的编号):

  1. Mod_ABS(6):模块是否插入
  2. RD+(13)、RD-(12):接收数据的差分对
  3. TD+(18)、TD-(19):传输数据的差分对
  4. SDA(4)、SCL(5):模块的 I2C
  5. Tx_Fault(2)、Tx_Disable(3)、Rx_LOS(8):一些状态信号

可以看到,收和发各有一个差分对共 4 条数据线。相对应的,QSFP 收和发各有四对差分对共 16 条数据线,一共 38 根线。并且有一些信号是复用了同样的 pin,这样的设计可以节省一些 pin,是很常见的。

MII

有时候,还会遇到各种 MII 接口,也就是 MAC 和 PHY 之间的接口。有时候,还会伴随着 MDIO 接口,来进行控制信息的传输。它又分不同的类型:

  • Standard MII:速率是 100Mbps(25MHz*4)或者 10Mbps(2.5Mhz*4),TX 7 根线(4 DATA+CLK+EN+ER),RX 7+2 根线(4 DATA+CLK+DV+ER+CRS+COL),加上 MDIO 2 根线共 18 根线
  • RMII:速率是 100Mbps 或者 10Mbps,频率都是 50MHz,一共 10 根线(4 DATA+CLK+TX_EN+CRS_DV+RX_ER+MDIO+MDC),数据线是 TX 和 RX 各 2 根
  • GMII:速率是 1000Mbps(125MHz*8),数据线是 TX 和 RX 各 8 根;也支持速率 100Mbps(25MHz)和 10Mbps(2.5MHz)
  • RGMII:速率是 1000Mbps(125MHz*4*2,DDR),数据线是 TX 和 RX 各 4 根;也支持速率 100Mbps(25MHz*4)和 10Mbps(2.5MHz*4),一共是 5+5+2 根线
  • SGMII:速率是 1000Mbps(625MHz*2*8/10),采用 625MHz DDR 差分对 SerDes,采用 8b/10b 的编码
  • XGMII:支持 2500Mbps/5000Mbps/10000Mbps(156.25 MHz*32*2,DDR)速率,数据线是 TX 和 RX 各 32 根

有的时候,MAC 和 PHY 是独立的,比如很多常见的 FPGA 开发板,在使用千兆网的时候,在板子上是 PHY 芯片,从 FPGA 到 PHY 通过 RGMII 连接,然后 PHY 再连接到 8P8C(RJ45)的连接器上。一般还会把 MDIO 也接到 FPGA 上面。如果有多个 PHY,就会吧 MDIO 通过总线的方式合并起来,给每个 PHY 配置不同的地址(一般是在指定的 PIN 上设置上拉/下拉电阻实现),就可以保证不冲突的访问。

扩展阅读:KXZ9031RNX Datasheet

SGMII

上面比较常见的是 GMII/RGMII/SGMII。其中比较特殊的是 SGMII,首先可以发现它信号很少,只有两对差分线 TX_P TX_N RX_P RX_N,其中时钟是可选的,因为可以从数据中恢复。你可能感到很奇怪,那么其他的信号,比如 DV/ER/CRS 等都去哪里了呢?其实是因为,SGMII 采用了 8b/10b 的编码的同时,把这些控制信号通过一定的方式顺便编码进去了。具体来说,就是从 8 位的数据信号编码为 10 位的时候,有一些特殊的 10 位符号是没有对应 8 位的数据的,因此可以用这些特殊符号来表示一些信号,比如用 SPD(Start_of_Packet Delimiter,对应 /S/)和 EPD(End_of_Packet Delimiter,对应 /T/R/ 等)表示传输数据的开始和结尾,对应 TX_EN/RX_DV 信号;用 Error_Propagation(/V/)表示错误,对应 RX_ER 信号等等。所以,SGMII 其实还是一个 GMII 的变种,只不过采用 SerDes 的方式减少了引脚,MAC 内部或者 PHY 内部也是经过一个 GMII-SGMII 的转换,而其余部分是一样的。

关于 8b/10b 的编码方式,可以阅读 IEEE 802.3 标准中的 Table 36–1a—Valid data code-groups,里面提到了两类的 Code Group:D 打头的,表示数据,有 256 种,从 8b 映射到 10b 的表达方式,并且为了保持直流平衡,有一种到两种表示方法。此外还有 12 个特殊的 Code Group:K 打头,它们的 10b 表达方式不会和数据冲突。表 Table 36–3—Defined ordered sets 中定义了 K 打头的 Code Group 含义:

  • /C/ Configuration:
  • /C1/ Configuration 1: /K28.5/D21.5/Config_Reg
  • /C2/ Configuration 2: /K28.5/D2.2/Config_Reg
  • /I/ IDLE:
  • /I1/ IDLE 1: /K28.5/D5.6/
  • /I2/ IDLE 2: /K28.5/D16.2/
  • Encapsulation:
  • /R/ Carrier_Extend: /K23.7/
  • /S/ Start_of_Packet: /K27.7/
  • /T/ End_of_Packet: /K29.7/
  • /V/ Error_Propagation: /K30.7/
  • /LI/ LPI (Low Power Idle):
  • /LI1/ LPI 1: /K28.5/D6.5/
  • /LI2/ LPI 2: /K28.5/D26.4/

IEEE 802.3 Figure 36-4 中给了一个例子,就是在发送一段数据的时候,首先是 /I/,然后 /S/,接着一系列的 /D/,最后结束的时候 /T/R/I/。

扩展阅读:

1000BASE-X 与 SFP 的关系

1000BASE-X 在 802.3 Clause 36 中定义,它的层级是这样的:

它支持三种不同的介质,对应了三个 PMD 层,也就是 LX、SX 和 CX。这些体现在设备上,其实就是不同的 SFP 模块。SFP 模块实际上就是图中的 PMD 层,SFP 接口上连接的是 1000BASE-X 的 PCS/PMA,这也就是为什么说在带有 SFP 的 FPGA 上,Xilinx 的 IP 叫做 1G/2.5G Ethernet PCS/PMA。在这里,PCS 和 PMA 层在 FPGA 内部通过 IP 实现,通过 PCB 连接到 SFP 上,光模块就是 PMD 层。见下图:

左边通过 GMII 连接到内部的 MAC,右边连接到 SFP 上,通过光模块,连接到光纤。这里光模块只需要负责光电转换。另一种比较常见的形式,就是 MAC 在 FPGA 内部,PHY(包括 PCS/PMA/PMD)都在 FPGA 外部,此时 FPGA IO 上就是各种 MII。

那么 SFP 电口模块是怎么工作的呢?我们知道,电口采用的是 1000BASE-T 标准。实际上,它里面有一个 PHY 芯片,发送的时候,首先解码 1000BASE-X 变回原始数据,再按照 1000BASE-T 的方式编码再发出去;接收的时候,按照 1000BASE-T 进行解码,再重新编码为 1000BASE-X 发送给 PMA 层。

还有一类电口模块,与上面不同的地方在于,SFP 上走的是 SGMII,而不是 1000BASE-X。这两种模式没有太大的区别,都是两对差分线,一收一发,所以很多时候二者是同时支持,可以切换的。例如 Cisco Compatible 10/100/1000BASE-T SFP SGMII Copper RJ-45 100m Industrial Transceiver Module (LOS) 就是在 SFP 上走 SGMII 协议。

推荐阅读 Designing a Copper SFP using the VSC8221 10/100/1000BASE-T PHY,它里面讲了如何将 VSC8221 芯片用于电口模块:VSC8221 芯片一头是 1000BASEX(又称 802.3z SerDes,802.3z 就是 1000BASE-X)或者 SGMII,另一头是 1000BASE-T MDI。

物理层

100BASE-TX

在 IEEE 802.3 的 Clause 24 和 25 中定义。

100BASE-TX 的物理层分为 PCS,PMA,PMD。与 MAC 的连接是 MII 接口,MII 频率是 25MHz,每周期传输 4 bit 的数据。然后 PCS 负责把 4 bit 的数据通过 4B/5B 转换为 5 bit 的 code group;PMA 使用 NRZI 进行编码;PMD 层借用了 FDDI 协议的 PMD 层,只使用 MDI 的 1-3 和 6 四根线传输,两对差分对,一收一发。

1000BASE-T

在 IEEE 802.3ab-1999 中定义,具体位置是 Clause 40。

物理层往上通过 GMII 连接 MAC,往下通过 MDI 连接其他网络设备。物理层又包括 PCS 和 PMA。

1000BASE-T 使用四对差分线,每对差分线上都是全双工传输,波特率 125Mbaud,symbol 的范围是 {2, 1, 0, -1, -2},通过 PAM5 传输。

具体来讲,PCS 从 MAC 的 GMII 接口接收要发送的数据,GMII 是 125MHz,每个周期 8 位数据。这些数据与 scrambler 一起,生成 9 位的 Sd_n[8:0],然后再编码为 (TA_n, TB_n, TC_n, TD_n),也就是在四对差分线上传输的 symbol,取值范围是 [-2, 2]。简单总结一下,就是每个周期 8 位数据,先变成 9 位数据,再变成 4 个 symbol,每个 symbol 取值范围是 -2 到 2,这就叫做 8B1Q4,converting GMII data (8B-8 bits) to four quinary symbols (Q4) that are transmitted during one clock (1Q4),把 8 位的数据转换为四个 symbol,每个 symbol 有五种取值(Quinary 表示 5)。

MDIO

MDIO 是 MAC 和 PHY 之间一个低速的通信接口,定义在 IEEE 802.3 Clause 45,可以用来配置一些寄存器。它支持读和写,多个 PHY 可以共享一个 MDIO 总线,通过 5 位的地址区分。为了让 PHY 分配到不同的地址,PHY 通常会通过某些引脚的上下拉来决定它自己的 MDIO 地址,这样可以避免冲突。以 RTL8201F 为例,它的 PHY 的 MDIO 地址配置与 LED 输出引脚是共享的,根据外部电路的上下拉不同,配置 MDIO 地址的最低两位:

也就是说,这款芯片的 MDIO 地址可以在二进制的 00000 到 00011 之间取。但不建议用 00000 地址,这是因为一些芯片会把 00000 重定义为广播,此时总线上的所有 PHY 芯片都要响应目标地址为自己的地址(非 00000)或者 00000 地址的请求(见 MDIO Addressing)。RTL8211 在它的文档里描述了这个行为:

这样的好处是可以同时往多个 PHY 芯片写入寄存器,但如果要从 00000 地址读寄存器的话,一旦多个 PHY 同时响应,MDIO 总线上就会出现冲突。不过如果只有一个 PHY 芯片连接到 MDIO 总线上,那么让 MAC 通过 00000 地址访问 PHY 也是可以的。

Rust 在 M1 上的 Code Signing 问题和临时解决方法

不久前,rust 添加了 Tier2 的 aarch64-apple-darwin 的支持,试了一下,确实可以运行,不过当我编译的时候,出现:

error: failed to run custom build command for `xxxx v1.0 (/path/to/xxxx)`

Caused by:
  process didn't exit successfully: `/path/to/xxx/target/debug/build/xxx-xxxx/build-script-build` (signal: 9, SIGKILL: kill)

看了一下 Console.app 里面的 crash 日志,发现是 codesigning 问题。解决方法是,用 codesign 命令来签名:

# for build.rs
codesign -s - target/debug/build/*/build-script-build
# for dylib of some crates
codesign -s - target/debug/deps/*.dylib
# for final executable
codesign -s - target/debug/xxx

多次编译并签名后,就可以正常运行最后的二进制了:

target/debug/xxxx: Mach-O 64-bit executable arm64

然后就可以了。等待上游添加 code signing 支持吧。

2020-12-07 更新:找了找 cargo 的 issues,找到了同样的问题,看来并不是 code signing 支持的问题,而是在 Intel 的 Alacritty 下面,运行 Apple 的 rustc 工具链的时候,才会出现的 BUG。我也自己试了一下,在 Apple 的 Terminal 下跑编译就没有问题。

ARM M1 MacBook Air 开箱

购买

我是 11.12 的时候在 Apple Store 上下单的,选的是 MacBookAir,带 M1 芯片,8 核 CPU + 8 核 GPU,加了一些内存和硬盘。今天(11.19)的时候顺丰到货,比 Apple Store 上显示的预计到达时间 21-28 号要更早。另外,我也听朋友说现在一些线下的店也有货,也有朋友直接在京东上买到了 Mac mini,总之第一波 M1 的用户最近应该都可以拿到设备了。

现在这篇博客,就是在 ARM MBA 上编写的,使用的是 Intel 的 VSCode,毕竟 VSCode 的 ARM64 版不久后才正式发布。

开箱

从外观来看,一切都和 Intel MBA 一样,包装上也看不出区别,模具也是一样的。

进了系统才能看得出区别。预装的系统是 macOS Big Sur 11.0,之后手动更新到了目前最新的 11.0.1。

顺带 @FactorialN 同学提醒我在这里提一句:包装里有电源适配器,不太环保。

体验

ARM64

首先自然是传统艺能,证明一下确实是 Apple Silicon:

$ uname -a
Darwin macbookair.lan 20.1.0 Darwin Kernel Version 20.1.0: Sat Oct 31 00:07:10 PDT 2020; root:xnu-7195.50.7~2/RELEASE_ARM64_T8101 x86_64

啊对不起我用错了,上面是在 Rosetta 里面跑的 shell 看到的结果。实际是这样子的:

$ uname -a
Darwin macbookair.lan 20.1.0 Darwin Kernel Version 20.1.0: Sat Oct 31 00:07:10 PDT 2020; root:xnu-7195.50.7~2/RELEASE_ARM64_T8101 arm64

货真价实的 ARM64 内核,系统的很多 binary 也都是 Universal 的:

$ file /bin/bash
/bin/bash: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
/bin/bash (for architecture x86_64):    Mach-O 64-bit executable x86_64
/bin/bash (for architecture arm64e):    Mach-O 64-bit executable arm64e

Rosetta

接着,就是重头戏 Rosetta 了。第一次打开 Intel 的程序的时候,会弹出窗口安装 Rosetta,确定以后立马就装好了。接着常用的各种软件啥的,都没有什么问题。

唯一能看出区别的,就是在 Activity Monitor 可以看到架构的区别:

实际体验的时候,其实没有什么感觉。默认情况下,在 Terminal 下打开的是 ARM64 架构的,如果要切换的话,只需要:

$ uname -m
arm64
$ arch -arch x86_64 uname -m
x86_64

这样就可以了。如果开了一个 x86_64 的 shell,在 shell 里面执行的命令就都是 x86_64 架构的了。

Homebrew

目前,Homebrew 的支持是这样子的,Intel 的 Homebrew 工作很正常,没有遇到任何问题。。ARM 的 Homebrew 目前还在进行移植,由于官方的 build farm 还没有支持 ARM,所以各种包都需要自己编译,试了几个常用的软件都没问题。

目前 Homebrew 推荐的方法是,在老地方 /usr/local/Homebrew 下面放 Intel 的 Homebrew,在 /opt/homebrew 下面放 ARM 的 Homebrew。虽然还是有很多警告,但目前来看基本使用都没有什么问题。Homebrew cask 也正常,毕竟基本就是一个下载器。

另外,试了一下用 ARM Homebrew 从源码编译 GCC,编译中途失败了。

其他软件

换到 ARM 上自然会想到,之前的那些软件还能不能跑。答案是,大多都可以,只是很多还是 Intel 版走翻译而已。

目前已经测试过正常使用的:VSCode、Google Chrome、Alacrity、iStat Menus、Alfred、Rectangle、Typora、Microsoft Office、Karabiner Elements、Jetbrains Toolbox、WeChat、CineBench、Dozer、Squirrel、Zoom、Tencent Meeting、Seafile、Skim、Mendeley、1 Password、Wireshark、Slack、iMazing、Office for Mac。

这些里面已经移植到 ARM64 的有 Alfred、iStat Menus、Karabiner Elements、Rectangle、Google Chrome、Slack、Typora、iMazing、Office for Mac、Zoom、VSCode Insiders。

这里有一部分是已经移植到 ARM64 的,有一些也很快就会移植过来。其中 iStat Menus 的电池健康显示有点 BUG,其他没发现问题(更新:已修复)。

另外,大家也知道 ARM Mac 很重要的一点是可以跑 iOS Apps,我们也确实跑了一些,不过都有一些问题:

  • Doodle Jump:跑起来很正常,就是卡关了,别问为什么,没有加速度计,再怎么晃电脑也不会动
  • Bilibili:部分内容可以加载出来,部分不可以,估计是什么组件没有配置好
  • QQ Music:可以跑起来,但是在启动之后的引导页面,期望用户点一下屏幕,但怎么用鼠标点都没反应
  • Weibo:毕竟正常,可以正常浏览啥都,就是 UI 有点错位,估计是因为显示窗口和实际都不大一样,小问题。
  • Network Tools:很正常,各种网络信息都可以正常取出来。
  • NFSee:没有 NFC 读卡功能,自然没法用。
  • 彩云天气(ColorfulClouds Weather):正常使用。

其他还有很多 App 还没有测试。

发热

大家也知道,这款 MBA 是没有风扇的。但我实际测试的过程中发现,确实不大需要。拿 stress 跑了一段时间 CPU 满载运行,也没感觉到电脑发热,只是在更新 macOS Big Sur 11.0.1 的时候稍微热了一点点,也只是一点点,距离烫手还有很长的距离。

续航方面目前来看也挺好的,捣鼓了一个下午,也没耗多少电。

性能测试

在不同平台上进行 OpenSSL 测试:

$ openssl speed -evp aes-128-cbc aes-256-cbc des-ede3 rsa2048 sha256
# M1 MacBookAir
OpenSSL 1.1.1j  16 Feb 2021
built on: Wed Feb 17 12:34:00 2021 UTC
options:bn(64,64) rc4(int) des(int) aes(partial) idea(int) blowfish(ptr) 
compiler: clang -fPIC -arch arm64 -O3 -Wall -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DVPAES_ASM -DECP_NISTZ256_ASM -DPOLY1305_ASM -D_REENTRANT -DNDEBUG
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
des ede3         30466.76k    30644.63k    30592.26k    30106.97k    29961.69k    29951.49k
aes-256 cbc     229863.42k   238671.82k   232654.34k   237194.70k   238092.29k   237791.91k
aes-128-cbc    1020384.58k  1427866.73k  1521123.84k  1558199.30k  1569978.99k  1566288.55k
sha256          378646.12k  1140355.52k  1894169.69k  2287211.18k  2445602.42k  2453209.09k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000561s 0.000014s   1782.0  69645.9
# AMD EPYC 7742
OpenSSL 1.1.1d  10 Sep 2019
built on: Mon Dec  7 20:44:45 2020 UTC
options:bn(64,64) rc4(8x,int) des(int) aes(partial) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-CKx7Fo/openssl-1.1.1d=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
des ede3         28734.07k    28942.08k    28982.78k    29217.91k    29136.21k    29103.45k
aes-256 cbc     176843.84k   183040.83k   183156.82k   184132.61k   184464.73k   184642.22k
aes-128-cbc     602680.15k  1178207.32k  1239931.82k  1251810.30k  1258359.47k  1261316.78k
sha256          201482.20k   513504.00k  1075572.14k  1474850.82k  1648746.50k  1663030.61k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000620s 0.000018s   1613.7  54756.4
# AMD EPYC 7282
OpenSSL 1.1.1d  10 Sep 2019
built on: Mon Apr 20 20:23:01 2020 UTC
options:bn(64,64) rc4(8x,int) des(int) aes(partial) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-8Ocme2/openssl-1.1.1d=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
des ede3         27052.31k    27392.85k    27455.57k    27569.49k    27503.27k    27514.20k
aes-256 cbc     158578.10k   168502.21k   172365.91k   173904.90k   174391.30k   174429.53k
aes-128-cbc     594506.35k  1111762.07k  1169014.02k  1184384.00k  1192793.56k  1189167.10k
sha256          194382.61k   487875.93k  1017121.56k  1390122.33k  1558735.53k  1572274.18k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000655s 0.000019s   1526.8  52089.2
# AMD EPYC 7551
OpenSSL 1.1.1d  10 Sep 2019
built on: Tue Feb 16 22:08:43 2021 UTC
options:bn(64,64) rc4(8x,int) des(int) aes(partial) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O2 -fdebug-prefix-map=/build/openssl-m9Qnvk/openssl-1.1.1d=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DRC4_ASM -DMD5_ASM -DAESNI_ASM -DVPAES_ASM -DGHASH_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
des ede3         20850.88k    21260.78k    21315.84k    21368.49k    21321.05k    21392.04k
aes-256 cbc     122059.94k   125701.42k   126591.06k   126770.52k   127049.73k   126937.77k
aes-128-cbc     441625.34k   883733.48k   928208.21k   941480.96k   944889.86k   945307.65k
sha256          151161.13k   388304.60k   809272.15k  1106645.33k  1238966.27k  1249219.93k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.001096s 0.000033s    912.8  30284.7
# Intel Xeon E5-2699 v4 (Broadwell)
OpenSSL 1.0.2u  20 Dec 2019
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) idea(int) blowfish(idx)
compiler: gcc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -Wa,--noexecstack -m64 -DL_ENDIAN -O3 -Wall -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DRC4_ASM -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
des ede3         29863.80k    30156.69k    30243.07k    30237.70k    30302.21k
aes-256 cbc     103491.45k   110240.94k   112029.95k   112400.38k   112833.88k
aes-128-cbc     734225.68k   788483.88k   802857.39k   805860.69k   807848.62k
sha256           82720.89k   184528.45k   342888.28k   425826.30k   457149.10k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.000573s 0.000017s   1745.5  60236.3
# Intel Xeon E5-2680 v4 (Broadwell) TODO
# Intel Xeon Gold 5218 (Cascade Lake) TODO
# IBM POWER8NVL
OpenSSL 1.1.1  11 Sep 2018
built on: Wed Feb 17 12:35:54 2021 UTC
options:bn(64,64) rc4(char) des(int) aes(partial) blowfish(ptr)
compiler: gcc -fPIC -pthread -m64 -Wa,--noexecstack -Wall -Wa,--noexecstack -g -O3 -fdebug-prefix-map=/build/openssl-avwOZX/openssl-1.1.1=. -fstack-protector-strong -Wformat -Werror=format-security -DOPENSSL_USE_NODELETE -DL_ENDIAN -DOPENSSL_PIC -DOPENSSL_CPUID_OBJ -DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DKECCAK1600_ASM -DAES_ASM -DVPAES_ASM -DECP_NISTZ256_ASM -DX25519_ASM -DPOLY1305_ASM -DNDEBUG -Wdate-time -D_FORTIFY_SOURCE=2
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes  16384 bytes
des ede3         25120.65k    25479.70k    25570.13k    25604.10k    25616.38k    25613.65k
aes-256 cbc      79140.44k    82350.23k    83815.94k    84183.72k    84290.22k    84306.60k
aes-128-cbc     310027.28k   647168.64k   890896.81k   984001.19k  1014827.69k  1017096.87k
sha256           58347.98k   151006.68k   286465.28k   373490.69k   411044.52k   414012.76k
                  sign    verify    sign/s verify/s
rsa 2048 bits 0.001442s 0.000040s    693.5  25212.7

总结

总的来说,还是挺香的。不错的性能,没有风扇的喧闹,没有烫手的键盘。可能有少部分软件还不能正常运行,然后很多程序还需要 Rosetta 翻译,但目前来看兼容性还是挺不错的,并且这些应该明年就都适配地差不多了吧。

在 Spack 中用 external 的 Slurm 依赖编译 OpenMPI

最近在一个集群上,需要用一个自己编译的 openmpi,但并没有 root 权限,所以需要自己搞一个 spack,在 spack 里面装 openmpi。但默认的安装选项下,它没有打开 slurm 支持,所以 srun 的话会出问题,只能 sbatch 然后指定 host 去做。于是我研究了一下怎么在 spack 里引入 external 的 slurm,然后用它来编译 openmpi

首先,编译 ~/.spack/packages.yaml

packages:
  slurm:
    buildable: False
    paths:
      "slurm@15-08-7-1%gcc@8.3.0 arch=linux-ubuntu16.04-haswell": /usr

这里 slurm 版本是 15.08.7,我就按照 spack 里面 slurm 的版本号来写了。可以用 spack spec openmpi schedulers=slurm +pmi 来确认一下外部的 slurm 确实出现在了依赖之中。

这一步配好的话,安装的时候就会直接跳过 spack 里面 slurm 的安装。但又出现了 configure 错误,找不到 pmi 的库。于是,先用 external 的 mpirun 看一下配置:

$ module load openmpi-3.0.0
$ ompi_info
...
--with-pmi=/usr
--with-pmi-libdir=/usr/lib/x86_64-linux-gnu
...

可以看到,需要两个 config 参数。然后,在 spack 的 openmpi package.py 中:

if spec.satisfies('schedulers=slurm'):
  config_args.append('--with-pmi={0}'.format(spec['slurm'].prefix))
  if spec.satisfies('@3.1.3:') or spec.satisfies('@3.0.3'):
    if '+static' in spec:
      config_args.append('--enable-static')

所以,需要加一个小 patch:

if spec.satisfies('schedulers=slurm'):
  config_args.append('--with-pmi={0}'.format(spec['slurm'].prefix))
  # patched here
  config_args.append('--with-pmi-libdir={0}/lib/x86_64-linux-gnu'.format(spec['slurm'].prefix))
  if spec.satisfies('@3.1.3:') or spec.satisfies('@3.0.3'):
    if '+static' in spec:
      config_args.append('--enable-static')

然后,就可以编译通过了。

在裸机上部署 ESXi 和 vCSA 7

之前在另一篇文章里提到过 vCSA 的安装,这次又在另一台机器上重新做了一遍,特此记录一下。

首先在官网上下载 ESXi+VCSA 7.0 ,应该得到两个文件:

7.9G VMware-VCSA-all-7.0.1-16860138.iso
358M VMware-VMvisor-Installer-7.0U1-16850804.x86_64.iso

首先安装 ESXi,用 UNetBootin 制作 ESXi 的安装光盘。注意不能用 dd,因为它是 CDFS 格式的,不能直接 boot。启动以后,按照界面要求,一路安装即可。

接着,就可以用网页访问 ESXi 进行配置。比如安装一些 Linux 发行版,然后在 Linux 虚拟机里面 mount 上面的 VCSA 的 iso:

sudo mount /dev/sr0 /mnt

接着,复制并修改 /mnt/vcsa-cli-installer/templates/install/embedded_vCSA_on_ESi.json,按照代码注释进行修改。需要注意几点:

  1. 密码都可以设为空,然后运行 cli 的时候输入
  2. ESXi 的密码和 vCSA 的密码是不一样的
  3. 可以把 ceip 关掉,设置 ceip_enabled: false

接着,进行安装:

/mnt/vcsa-cli-installer/lin64/vcsa-deploy install --accept-eula /path/to/customized.json -v

慢慢等待它安装成功即可。

安装完成后,进入 vCSA,新建一个 Datacenter,然后选择新建的 Datacenter,选择 Add host,输入 ESXi 的地址和用户密码信息即可。

IBM Power S822LC(8335-GTB) BMC 升级

背景

最近拿到一台 IBM Power S822LC(8335-GTB)机器的访问权限,这也是我第一次碰 ppc64le 指令集的服务器。然后发现,它的 BMC 版本比较老,我想连接 Remote Control 失败了,原因是 JViewer 不支持 macOS,我就想着能不能升级一下。

升级过程

首先,在网上找了一下文档,首先用 ipmitool 找一下机器型号:

$ sudo ipmitool fru print 3
 Chassis Type          : Unknown
 Chassis Part Number   : 8335-GTB
 Chassis Serial        : REDACTED

可以看到,这台机器是 8335-GTB 型号,按照这个型号在 Fix Central 上搜索,可以找到若干个版本的 firmware,其中最老的版本是 OP8_v1.11_2.1,对比了一下,和原来的版本一致:

$ sudo ipmitool fru print 47
 Product Name          : OpenPOWER Firmware
 Product Version       : IBM-garrison-ibm-OP8_v1.11_2.1
 Product Extra         :        op-build-da02863
 Product Extra         :        buildroot-81b8d98
 Product Extra         :        skiboot-5.3.6
 Product Extra         :        hostboot-1f6784d-3408af7
 Product Extra         :        linux-4.4.16-openpower1-bc83f92
 Product Extra         :        petitboot-v1.2.4-de6cda2
 Product Extra         :        garrison-xml-3db7b6e
 Product Extra         :        occ-69fb587
 Product Extra         :        hostboot-binar

于是,我下载了比较新的版本,一个 hpm 文件,然后在 BMC 网页上进行升级。第一次升级比较保守,选择了 2017 年的版本:

$ sudo ipmitool fru print 47
Product Name          : OpenPOWER Firmware
 Product Version       : IBM-garrison-ibm-OP8_v1.12_2.72
 Product Extra         :        op-build-14a75d0
 Product Extra         :        buildroot-211bd05
 Product Extra         :        skiboot-5.4.3
 Product Extra         :        hostboot-2eb7706-69b1432
 Product Extra         :        linux-4.4.30-openpower1-084eb48
 Product Extra         :        petitboot-v1.3.2-d709207
 Product Extra         :        garrison-xml-19a5164
 Product Extra         :        occ-d7efe30-47b58cb
 Product Extra         :        hostb

这次升级比较顺利,没有遇到什么障碍。但是我发现,BMC 里面显示的 BIOS 版本和 hpm 对不上,它总是认为 BIOS 版本是落后的,需要更新,而 Firmware 部分(BOOT 和 APP)是更新后的版本。但 BIOS 版本和原来的版本也不一样。于是我重新升级了几次,都没有效果,怀疑是升级出了问题。后来仔细读文档才发现,确实是 BMC 软件的问题(文档):

Note: BMC Dashboard shows an incorrect level for the BIOS caused by improper translation of the level subfields. The Bios number should reflect the PNOR level for the system of "IBM-garrison-ibm-OP8_v1.11_2.19". In this case, the BIOS version should be 1.11_2.19 but shows as 1.17.19 instead with the "11_2" converted into the "17".

The Firmware Revision for the BMC firmware shows correctly as "2.13.58".

Here is an example output of the Dashboard with an errant BIOS Version:

Dashboard gives the overall information about the status of the device and remote server.

Device Information

Firmware Revision: 2.13.58

Firmware Build Time: Oct 26 2016 11:40:55 CDT

BIOS Version: 1.17.19

一番捣鼓之后,不知道怎么了,BMC 就挂了,怎么访问都不通。只好物理断电,重新来过。按照同样的方法,升级到了 2019 年的版本:

 Product Name          : OpenPOWER Firmware
 Product Version       : IBM-garrison-OP8_v1.12_2.96
 Product Extra         :        op-build-v2.3-7-g99a6bc8
 Product Extra         :        buildroot-2019.02.1-16-ge01dcd0
 Product Extra         :        skiboot-v6.3.1
 Product Extra         :        hostboot-p8-c893515-pd6f049d
 Product Extra         :        occ-p8-a2856b7
 Product Extra         :        linux-5.0.7-openpower1-p8e31f00
 Product Extra         :        petitboot-v1.10.3
 Product Extra         :        machine-xml-c5c3

中途也遇到了几次奇怪的问题,多次通过 IPMI reset 之后就好了。

远程访问

但是,最新版的 BMC 固件中,JViewer 依然没有 macOS 支持。我也 SSH 进去确认了一下,确实没有对应的支持文件。只好在 Linux 机器上访问,安装 icedtea 以后,就可以打开 jnlp 文件,之后一切都很正常。

一个可能的替代方案:https://github.com/sciapp/nojava-ipmi-kvm

在 arm64 上使用 rust-analyzer

远程到 arm64 的机器上进行开发,发现没有 rust-analyzer 的支持。研究了一下,发现在 rustup 里面可以找到,不过要配置一下:

> rustup toolchain add nightly
> rustup component add --toolchain nightly rust-analyzer-preview

这个时候,应该可以找到 ~/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/bin/rust-analyzer 文件,接下来,配置 VSCode 插件即可:

{
    "rust-analyzer.serverPath": "~/.rustup/toolchains/nightly-aarch64-unknown-linux-gnu/bin/rust-analyzer"
}

路径在 ~/.vscode-server/data/Machine/settings.json

参考:https://github.com/rust-analyzer/rust-analyzer/issues/5256

在 Rpi4 上运行 buildroot

背景

需要给 rpi 配置一个 pxe 的最小环境,在上一篇博文了提到可以用 alpine,但发现有一些不好用的地方,所以试了试 buildroot。

PXE 设置和路由器设置

见“在 Rpi4 上运行 Alpine Linux”文章。

Buildroot 配置

下载 buildroot:

> wget https://buildroot.org/downloads/buildroot-2020.08.tar.gz
> unar buildroot-2020.08.tar.gz
> cd buildroot-2020.08
> make raspberrypi4_64_defconfig

然后运行 make menuconfig ,在 Filesystem images 中打开 initramfs,并设置 cpio 压缩为 gz。然后直接编译:

> make -j4
$ ls -al target/images
bcm2711-rpi-4-b.dtb*  boot.vfat  Image  rootfs.cpio  rootfs.cpio.gz  rootfs.ext2  rootfs.ext4@  rpi-firmware/  sdcard.img

接着,在一个单独的目录里,把这些文件整理一下

> cd ~/rpi-buildroot
> cp -r ~/buildroot-2020.08/output/images/rpi-firmware/* .
> cp ~/buildroot-2020.08/output/images/bcm2711-rpi-4-b.dtb .
> cp ~/buildroot-2020.08/output/images/Image .
> cp ~/buildroot-2020.08/output/images/rootfs.cpio.gz .
> # edit cmdline.txt: remove root= and rootwait
> # edit config.txt: uncomment initramfs rootfs.cpio.gz line
# ls
bcm2711-rpi-4-b.dtb*  cmdline.txt  config.txt  fixup.dat  Image  overlays/  rootfs.cpio.gz  start.elf

最后开启 TFTP 服务器即可:

> sudo python3 -m py3tftp -p 69

树莓派启动

连接树莓派的串口,用 115200 Baudrate 打开,可以看到启动信息:

PM_RSTS: 0x00001000
RPi: BOOTLOADER release VERSION:a5e1b95f DATE: Apr 16 2020 TIME: 18:11:29 BOOTMODE: 0x00000006 part: 0 BUILD_TIMESTAMP=1587057086 0xa049cc2f 0x00c03111
uSD voltage 3.3V
... 
Welcome to Buildroot
buildroot login: root
#

默认用户是 root,没有密码。

在 rpi4 上用 PXE 运行 Alpine Linux

背景

需要给 rpi 配置一个 pxe 的最小环境,然后看到 alpine 有 rpi 的支持,所以尝试给 rpi4 配置 alpine。

PXE 设置

第一步是设置 rpi4 的启动模式,打开 BOOT UART 并且打开 网络启动:

> cd /lib/firmware/raspberrypi/bootloader/critical
> rpi-eeprom-config pieeprom-2021-04-29.bin > config.txt
$ cat config.txt
[all]
BOOT_UART=1
WAKE_ON_GPIO=1
POWER_OFF_ON_HALT=0
DHCP_TIMEOUT=45000
DHCP_REQ_TIMEOUT=4000
TFTP_FILE_TIMEOUT=30000
TFTP_IP=
TFTP_PREFIX=0
BOOT_ORDER=0x1
SD_BOOT_MAX_RETRIES=3
NET_BOOT_MAX_RETRIES=5
[none]
FREEZE_VERSION=0
> sed 's/BOOT_UART=0/BOOT_UART=1/;s/BOOT_ORDER=0x1/BOOR_ORDER=0x12/' config.txt > config-pxe.txt
> rpi-eeprom-config --out pieeprom-2021-04-29-pxe.bin --config config-pxe.txt pieeprom-2021-04-29.bin
> rpi-eeprom-update -d -f pieeprom-2021-04-29-pxe.bin
> reboot

重启以后,可以用 vcgencmd bootloader_config 查看当前的启动配置,看是否正确地更新了启动配置。比较重要的是 BOOT_ORDER0x12 表示先尝试网络启动,再尝试 SD 卡启动。

路由器配置

第二步,需要配置路由器,以 OpenWrt 为例:

> uci add_list dhcp.lan.dhcp_option="66,ip_address_of_tftp_server"
> uci commit dhcp
> /etc/init.d/dnsmasq restart
$ cat /etc/config/dhcp
...
config dhcp 'lan'
        ...
    list dhcp_option '66,ip_address_of_tftp_server'
...

这样就配置完毕了。如果是 isc-dhcp-server,修改 /etc/dhcp/dhcpd.conf

subnet 10.0.1.0 netmask 255.255.255.0 {
    range 10.0.1.100 10.0.1.199;
    option routers 10.0.1.1;
    option tftp-server-name "10.0.1.1";
}

TFTP 服务器配置

下载 alpine linux 的 rpi boot,解压到指定目录:

> wget http://mirrors.tuna.tsinghua.edu.cn/alpine/v3.12/releases/aarch64/alpine-rpi-3.12.0-aarch64.tar.gz
> unar alpine-rpi-3.12.0-aarch64.tar.gz
> cd alpine-rpi-3.12.0-aarch64

修改 cmdline.txt ,把 console=tty1 改成 console=ttyAMA0,115200,并且去掉 quiet;修改 usercfg.txt 为:

dtoverlay=disable-bt
enable_uart=1

接着,启动 TFTP 服务器:

> sudo python3 -m py3tftp -p 69

树莓派启动

连接树莓派的串口,用 115200 Baudrate 打开,可以看到启动信息:

PM_RSTS: 0x00001000
RPi: BOOTLOADER release VERSION:a5e1b95f DATE: Apr 16 2020 TIME: 18:11:29 BOOTMODE: 0x00000006 part: 0 BUILD_TIMESTAMP=1587057086 0xa049cc2f 0x00c03111
uSD voltage 3.3V
... 
initramfs emergency recovery shell launched. Type 'exit' to continue boot
sh: can't access tty; job control turned off
/ #

然后,按照需要自定义 initramfs 即可。解压后,修改文件,然后运行:

> find . -print0 | cpio --null -ov --format=newc | gzip > ../initramfs-rpi4

把自带的 initramfs 替换掉。

用 certbot 申请 route53 上的域名的 LetsEncrypt 证书并上传到 IAM

最近遇到了 AWS Certificate Manager 的一些限制,所以只能用 IAM 证书。于是上网找到了通过 certbot 申请 LE 证书,通过 route53 API 验证的方法。

首先配置 aws 的 credential。然后,按照 certbot:

pip3 install -U certbot certbot_dns_route53

然后,就可以申请证书了:

certbot certonly --dns-route53 --config-dir "./letsencrypt" --work-dir "./letsencrypt" --logs-dir "./letsencrypt"  -d example.com --email a@b.com --agree-tos

如果申请成功,在当前目录下可以找到证书。然后上传到 IAM:

aws iam upload-server-certificate --server-certificate-name NameHere \
    --certificate-body file://letsencrypt/live/example.com/cert.pem \
    --private-key file://letsencrypt/live/example.com/privkey.pem \
    --certificate-chain file://letsencrypt/live/example.com/chain.pem \
    --path /cloudfront/

如果要用于 cloudfront,才需要最后的路径参数;否则可以去掉。这样就完成了 IAM 证书的上传。

在 k8s 中部署 Prometheus

实验了一下在 k8s 中部署 Prometheus,因为它和 k8s 有比较好的集成,很多 App 能在 k8s 里通过 service discovery 被 Prometheus 找到并且抓取数据。实践了一下,其实很简单。

用 helm 进行配置:

helm upgrade --install prometheus stable/prometheus

这样就可以了,如果已经有 StorageClass(比如腾讯云的话,CBS 和 CFS),它就能自己起来了,然后在 Lens 里面也可以看到各种 metrics 的可视化。

如果是自建的单结点的 k8s 集群,那么还需要自己创造 PV,并且把 PVC 绑定上去。我采用的是 local 类型的 PV:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-volume-1
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/srv/k8s-data-1"

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-volume-2
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/srv/k8s-data-2"

这样,结点上的两个路径分别对应两个 PV,然后只要让 PVC 也用 manual 的 StorageClass 就可以了:

server:
    persistentVolume:
        storageClass: manual

alertmanager:
    persistentVolume:
        storageClass: manual

把这个文件保存为 values.yaml 然后:

helm upgrade --install prometheus stable/prometheus -f values.yaml

这样就可以了。不过 PVC 不能在线改,可能需要删掉重来。

然后,由于权限问题,还需要在结点上修改一下两个目录的权限:

sudo chown -R 65534:65534 /srv/k8s-data-1
sudo chown -R 65534:65534 /srv/k8s-data-2

这样容器内就可以正常访问了。

各种 ecc 曲线

背景知识

椭圆曲线有如下的形式:

第一种:

\[E: y^2 \equiv x^3 + ax + b \mod{p}\]

曲线的参数共有 \((p, a, b, G, n, h)\)\(G\) 是一个点 \((G_x, G_y)\)\(n\)\(G\) 的阶。

第二种:

\[E: y^2+xy=x^3+ax^2+1\]

称为 Kbolitz curve。不同的曲线有不同的参数 \((m,f(x),a,b,G,n,h)\),对应不同的 \(GF(2^m)\) 域。

OpenSSL

看一下 openssl 支持的曲线参数(openssl ecparam -list_curves):

  secp112r1 : SECG/WTLS curve over a 112 bit prime field
  secp112r2 : SECG curve over a 112 bit prime field
  secp128r1 : SECG curve over a 128 bit prime field
  secp128r2 : SECG curve over a 128 bit prime field
  secp160k1 : SECG curve over a 160 bit prime field
  secp160r1 : SECG curve over a 160 bit prime field
  secp160r2 : SECG/WTLS curve over a 160 bit prime field
  secp192k1 : SECG curve over a 192 bit prime field
  secp224k1 : SECG curve over a 224 bit prime field
  secp224r1 : NIST/SECG curve over a 224 bit prime field
  secp256k1 : SECG curve over a 256 bit prime field
  secp384r1 : NIST/SECG curve over a 384 bit prime field
  secp521r1 : NIST/SECG curve over a 521 bit prime field
  prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field
  prime192v2: X9.62 curve over a 192 bit prime field
  prime192v3: X9.62 curve over a 192 bit prime field
  prime239v1: X9.62 curve over a 239 bit prime field
  prime239v2: X9.62 curve over a 239 bit prime field
  prime239v3: X9.62 curve over a 239 bit prime field
  prime256v1: X9.62/SECG curve over a 256 bit prime field
  sect113r1 : SECG curve over a 113 bit binary field
  sect113r2 : SECG curve over a 113 bit binary field
  sect131r1 : SECG/WTLS curve over a 131 bit binary field
  sect131r2 : SECG curve over a 131 bit binary field
  sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field
  sect163r1 : SECG curve over a 163 bit binary field
  sect163r2 : NIST/SECG curve over a 163 bit binary field
  sect193r1 : SECG curve over a 193 bit binary field
  sect193r2 : SECG curve over a 193 bit binary field
  sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field
  sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field
  sect239k1 : SECG curve over a 239 bit binary field
  sect283k1 : NIST/SECG curve over a 283 bit binary field
  sect283r1 : NIST/SECG curve over a 283 bit binary field
  sect409k1 : NIST/SECG curve over a 409 bit binary field
  sect409r1 : NIST/SECG curve over a 409 bit binary field
  sect571k1 : NIST/SECG curve over a 571 bit binary field
  sect571r1 : NIST/SECG curve over a 571 bit binary field
  c2pnb163v1: X9.62 curve over a 163 bit binary field
  c2pnb163v2: X9.62 curve over a 163 bit binary field
  c2pnb163v3: X9.62 curve over a 163 bit binary field
  c2pnb176v1: X9.62 curve over a 176 bit binary field
  c2tnb191v1: X9.62 curve over a 191 bit binary field
  c2tnb191v2: X9.62 curve over a 191 bit binary field
  c2tnb191v3: X9.62 curve over a 191 bit binary field
  c2pnb208w1: X9.62 curve over a 208 bit binary field
  c2tnb239v1: X9.62 curve over a 239 bit binary field
  c2tnb239v2: X9.62 curve over a 239 bit binary field
  c2tnb239v3: X9.62 curve over a 239 bit binary field
  c2pnb272w1: X9.62 curve over a 272 bit binary field
  c2pnb304w1: X9.62 curve over a 304 bit binary field
  c2tnb359v1: X9.62 curve over a 359 bit binary field
  c2pnb368w1: X9.62 curve over a 368 bit binary field
  c2tnb431r1: X9.62 curve over a 431 bit binary field
  wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field
  wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field
  wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field
  wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field
  wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field
  wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field
  wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field
  wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field
  wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field
  wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field
  wap-wsg-idm-ecid-wtls12: WTLS curve over a 224 bit prime field
  Oakley-EC2N-3: 
    IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
    Not suitable for ECDSA.
    Questionable extension field!
  Oakley-EC2N-4: 
    IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
    Not suitable for ECDSA.
    Questionable extension field!
  brainpoolP160r1: RFC 5639 curve over a 160 bit prime field
  brainpoolP160t1: RFC 5639 curve over a 160 bit prime field
  brainpoolP192r1: RFC 5639 curve over a 192 bit prime field
  brainpoolP192t1: RFC 5639 curve over a 192 bit prime field
  brainpoolP224r1: RFC 5639 curve over a 224 bit prime field
  brainpoolP224t1: RFC 5639 curve over a 224 bit prime field
  brainpoolP256r1: RFC 5639 curve over a 256 bit prime field
  brainpoolP256t1: RFC 5639 curve over a 256 bit prime field
  brainpoolP320r1: RFC 5639 curve over a 320 bit prime field
  brainpoolP320t1: RFC 5639 curve over a 320 bit prime field
  brainpoolP384r1: RFC 5639 curve over a 384 bit prime field
  brainpoolP384t1: RFC 5639 curve over a 384 bit prime field
  brainpoolP512r1: RFC 5639 curve over a 512 bit prime field
  brainpoolP512t1: RFC 5639 curve over a 512 bit prime field
  SM2       : SM2 curve over a 256 bit prime field

这个列表很长,主要有几个参数:

  1. 什么域:素数域还是 \(GF(2^m)\)
  2. 位数:域有多少位
  3. 标准:NIST/SECG/WTLS/X9.62/RFC 5639/SM2/Oakley 表示的是不同的标准

NIST

NIST 在 FIPS 186-4 中定义了基于素数域的 Curve P-192, Curve P-224, Curve P-256, Curve P-384 和 Curve P-521。在 RFC5656 中,这几条曲线又名 nistp192 nistp224 nistp256 nistp384 和 nistp521。

Curve P-192:

\[p = 2^{192}-2^{64}-1\]

Curve P-224:

\[p=2^{224}-2^{96}-1\]

Curve P-256:

\[p=2^{256}-2^{224}+2^{192}+2^{96}-1\]

Curve P-384:

\[p=2^{384}-2^{128}-2^{96}+2^{32}-1\]

Curve P-521:

\[p=2^{521}-1\]

另一类是基于 Binary Field(\(GF(2^m)\))的曲线,有 Curve K-163,Curve B-163,Curve K-233,Curve B-233,Curve K-283,Curve B-283,Curve K-409,Curve B-409,Curve K-571,Curve B-571。相应地,RFC 5656 里又名 nistk163,nistk233,nistb233,nistk283,nistk409,nistb409,nistt571(我觉得是 nistb571/nistk571,不知道是不是写错了)

Degree 163 (K-163/B-163) :

\[p(t)=t^{163}+t^7+t^6+t^3+1\]

Degree 233 (K-233/B-233) :

\[p(t)=t^{233}+t^{74}+1\]

Degree 283 (K-283/B-283) :

\[p(t)=t^{283}+t^{12}+t^7+t^5+1\]

Degree 409 (K-409/B-409) :

\[p(t)=t^{409}+t^{87}+1\]

Degree 571 (K-571/B-571) :

\[p(t)=t^{571}+t^{10}+t^5+t^2+1\]

SECG

SECG 在 SEC2 中定义了若干的曲线,其中一部分和上面的 NIST 是同一个曲线。首先是基于素数域的:

NIST SEC OID ANSI
nistp192 secp192r1 1.2.840.10045.3.1.1 prime192v1
secp192k1 1.3.132.0.31
nistp224 secp224r1 1.3.132.0.33
secp224k1 1.3.132.0.32
nistp256 secp256r1 1.2.840.10045.3.1.7 prime256v1
secp256k1 1.3.132.0.10
nistp384 secp384r1 1.3.132.0.34
secp384k1
nistp521 secp521r1 1.3.132.0.35
secp521k1

然后是基于 \(GF(2^m)\) 域的:

NIST SEC OID
nistk163 sect163k1 1.3.132.0.1
sect163r1 1.3.132.0.2
nistb163 sect163r2 1.3.132.0.15
nistk233 sect233k1 1.3.132.0.26
nistb233 sect233r1 1.3.132.0.27
sect239k1 1.3.132.0.3
nistk283 sect283k1 1.3.132.0.16
nistb283 sect283r1 1.3.132.0.17
nistk409 sect409k1 1.3.132.0.36
nistb409 sect409r1 1.3.132.0.37
nistk571 (RFC 5656 写的是 nistt571) sect571k1 1.3.132.0.38
nistb571 sect571r1 1.3.132.0.39

sec 命名里,第四个字符里 \(p\) 表示是素数域,\(t\) 表示是 \(GF(2^m)\) 域。后面的字母表示的 \(k\) 表示 Koblitz,\(r\) 表示 random,是参数的选取方式。

OID 有两种前缀:

1.3.132.0.x:
iso(1) identified-organization(3) certicom(132) curve(0)
1.2.840.10045.3.1.x:
iso(1) member-body(2) us(840) 10045 curves(3) prime(1)

完整列表见 OID 1.3.132.0OID 1.2.840.10045.3.1

ANSI

ANSI 也有 X9.62 标准,在附录里面也定义了若干个曲线。附录 J.5.1 里面有三个例子,就是 prime192v1 prime192v2 和 prime192v3,之后则是 prime239v1 prime239v2 prime239v3 和 prime256v1。

ANSI 别名 OID
prime192v1 nistp192/secp192r1 1.2.840.10045.3.1.1
prime192v2 1.2.840.10045.3.1.2
prime192v3 1.2.840.10045.3.1.3
prime239v1 1.2.840.10045.3.1.4
prime239v2 1.2.840.10045.3.1.5
prime239v3 1.2.840.10045.3.1.6
prime256v1 nistp256/secp256r1 1.2.840.10045.3.1.7

总结

对于同一个曲线,不同的组织给出了不同的名字,见下表:

OpenSSL NIST SECG ANSI
prime192v1 nistp192 secp192r1 prime192v1
secp224r1 nistp224 secp224r1
prime256v1 nistp256 secp256r1 prime256v1
secp384r1 nistp384 secp384r1
secp521r1 nistp521 secp521r1
sect163k1 nistk163 sect163k1
sect163r2 nistb163 sect163r2
sect233k1 nistk233 sect233k1
sect233r1 nistb233 sect233r1
sect283k1 nistk233 sect283k1
sect283r1 nistb283 sect283r1
sect409k1 nistk409 sect409k1
sect409r1 nistb409 sect409r1
sect571k1 nistk571 sect571k1
sect571r1 nistb571 sect571r1

在 RFC4492 里也可以看到一个类似的表。

FIDO U2F、FIDO2 和 CTAP 的关系

背景

2012 年,Yubico 和 Google 设计了 U2F 协议,第二年 U2F 成为 FIDO 组织的标准,之后加入了 NFC 的支持。之后,FIDO2 作为替代 U2F 的新标准产生,原来的 U2F 以兼容的方式成为了 CTAP1,而采用 CBOR 封装格式的 CTAP(CTAP2) 则是 FIDO2 的主要协议。

U2F

命令格式

U2F 定义了它的命令格式,基于 ISO7816-4 APDU(short APDU) :

CLA INS P1 P2 Lc data Le
1 byte 1 byte 1 byte 1 byte 0-1 bytes variable length 0-1 bytes

比如 U2F_VERSION 就是:

CLA INS P1 P2 Lc data Le
00 03 00 00 0 empty 00

返回的数据就是 U2F_V2 的 ASCII 加上 9000 的状态。

除此之外,它还有一种 extended length 格式的 APDU,和上面的是等价的不同表示。

传输方式

实际使用 U2F 的时候,又有三种情况,分别是 USB、Bluetooth 和 NFC。

USB

U2FHID 里面,为了让 U2F 的命令通过 HID 接口传输,它规定了两个 endpoint,分别是 Interrupt IN 和 Interrupt OUT,还有一个固定的 HID Report Descriptor。为了发 U2F 命令,首先会进行一次封装:

CMD BCNT DATA
U2FHID_MSG 4..n n bytes

添加了一个头,表示载荷是一个 U2F 的 command(自然也是 APDU)。

在 cmd 之上,还会封装一层,为了解决 USB 的 packet size 限制等问题,定义了 init packet:

CID CMD BCNTH BCNTL DATA
4 bytes 1 byte 1 byte 1 byte variable length

如果数据太长,就会拆分成一个 init 和 多个 continuation packet:

CID SEQ DATA
4 bytes 1 byte variable length

把 init 和 continuation 里面的 data 组合起来,就是 U2F 的 message,message 里面可能又有 U2F raw command,也就是 APDU。

发送的时候,先 Interrupt OUT 发送请求,再 Interrupt IN 读取回应。

Bluetooth

U2F/Bluetooth 里面,也用了一个类似的封装格式,请求:

CMD HLEN LLEN DATA
1 byte 1 byte 1 byte variable length

这里的 DATA payload 就是 extended length 格式的 APDU

NFC

U2F/NFC 里面,既然 ISO 7816-4 本来就是 NFC-native 的格式,就不要额外的封装了。只需要规定一个 Applet 的 AID 即可:A0000006472F0001

总结

总而言之,U2F raw commands 就是在 APDU 格式上定义了几个命令。在 USB 和 Bluetooth 上都加了几个小的 Header,而 NFC 上则是规定了一个 AID。这对应用程序来说很方便,核心的命令只有一套,需要的时候封装一下即可。

FIDO2

在之后,FIDO2 出现了,在保持 U2F 兼容的基础上添加了新的功能,并且出现了 WebAuthN 作为浏览器使用 FIDO2 的协议。U2F 就变成了第一代的 CTAP,称为 CTAP1,然后 CTAP 默认指的就是 CTAP2。

命令格式

FIDO2 里面,定义了一些 CTAP 命令,比如 authenticatorMakeCredential,对应 U2F 的 U2F_REGISTER 命令。然后,规定了一个 CBOR 的格式,来表示命令附带的数据。CBOR 是 RFC 7049,所以也是借用过来的格式。

传输方式

FIDO2 定义了在 USB 和 NFC 上的传输格式。

USB

在 USB 上传输的时候,定义了 CTAPHID 的协议,与 U2FHID 基本是一样的,规定了 init packet 和 continuation packet,packet 里面也是 CTAPHID 的消息,这部分是兼容 U2F 的。并且,额外添加了 CTAPHID_CBOR 消息:

CMD BCNT DATA DATA + 1
CTAPHID_CBOR 1..(n+1) CTAP command n bytes of CBOR data

它的载荷就是 CBOR 格式的请求。

类似地,它也是通过 Interrupt OUT 发送请求,从 Interrupt IN 读取回应。

NFC

在 NFC 上传输的时候,因为内部的格式是 CBOR,不再是 APDU 了,所以需要一些封装。

首先,它也定义了一个 Applet ID:A0000006472F0001,和 U2F 一样。为了保持兼容,它都支持 U2F 定义的 APDU 命令。

那怎么区分设备是否支持 CTAP1/U2F 和 CTAP2 呢?使用前面提到的 U2F_VERSION 命令即可。如果得到 U2F_V2,说明是支持 CTAP1/U2F 的;如果得到是其他的,说明只支持 CTAP2,不支持 CTAP1/U2F。

如果要发 CTAP2 的命令,就要把 CTAP command 和 CBOR 格式的数据封装到 APDU 里面:

CLA INS P1 P2 Data Le
80 10 00 00 CTAP Command || CBOR Data variable

它规定,如果请求采用的是 extended length 的 APDU,那么响应也要是 extended length 的 APDU;如果请求是 short APDU,那么响应也要支持 short APDU 的 chaining。

兼容性

可以看到,CTAP2 设计的基本都考虑了兼容 U2F,允许用 U2F 的 API 操作 U2F 和 CTAP2 的设备;也允许用 CTAP2 的 API 操作 U2F(只支持部分命令)和 CTAP2 的设备。

总结

可以看到,这里有一堆套娃的过程:

U2F:

USB HID Bluetooth NFC
4 APDU APDU APDU
3 U2F message
2 USB HID packet
1 USB Bluetooth ISO 14443-4/ISO 18092

FIDO2:

USB HID NFC
4 CTAP command + CBOR data CTAP command + CBOR data
3 CTAP message APDU
2 USB HID packet
1 USB ISO 14443-4/ISO 18092

USB/IP 模拟 USB 设备

背景

2018 年的时候发过一篇博客,讲如何用 USB/IP 协议在两台 Linux 电脑之间共享 USB 设备。最近刚好有一个需求,就是针对一个现成的 USB device 的代码,通过 USB/IP 模拟出一个 USB 设备,然后进行调试。

USB/IP 协议

USB/IP 只有一个简略的文档,为数不多的使用 USB/IP 的代码,所以有一些细节没有说的很清楚,只能一点点去尝试。

首先,USB/IP 基于 TCP,端口号 3240。客户端向服务端发送请求,服务端向客户端进行回应。

请求的类型:OP_REQ_DEVLIST OP_REQ_IMPORT USBIP_CMD_SUBMIT 和 USBIP_CMD_UNLINK

回应的类型:OP_REP_DEVLIST OP_REP_IMPORT USBIP_RET_SUBMIT USBIP_RET_UNLINK

工作的过程大概如下:

  1. OP_REQ_DEVLIST 请求获取设备列表
  2. OP_REP_DEVLIST 返回设备列表
  3. OP_REQ_IMPORT 请求 USB 设备
  4. OP_REP_IMPORT 返回 USB 设备
  5. USBIP_CMD_SUBMIT 发送 URB
  6. USBIP_RET_SUBMIT 返回 URB

(先不考虑 CMD_UNLINK 和 RET_UNLINK)

其中前面四个比较简单清晰,所需要的字段也都是 Descriptor 中对应的字段。后面两个就相对复杂一些:URB data 的长度需要根据 endpoint 类型和 direction 共同决定。URB 实际上是 Linux 内核里的一个数据结构。

USB 协议

那么,USB 协议的几种 transfer 怎么对应到 URB 的数据呢?首先看最常见的三种([ref](https://www.beyondlogic.org/usbnutshell/usb4.shtml):

  1. Control Transfer
  2. 第一种是 Control IN,一共有三个阶段,第一个阶段是 Setup,Host 发送给 Device 一个八字节的 Setup Packet;第二个阶段是 Data,Device 发送给 Host 一段数据;第三个阶段是 Status,Host 发送给 Device 一个 Zero Length Packet。此时 Setup Packet 对应 urb 中的 setup,Data 就对应 RET_SUBMIT 里面的 URB data 了,自然 CMD_SUBMIT 中是没有 URB data 的
  3. 第二种是 Control OUT,一共有三个阶段,第一个阶段是 Setup,Host 发送给 Device 一个吧字节的 Setup Packet;第二个阶段是 Data,Host 给 Device 发送一段数据;第三个阶段是 Status,Device 给 Host 发送一个 Zero Length Packet。此时 Setup Packet 对应 urb 中的 setup,Data 对应 CMD_SUBMIT 末尾的 URB data,长度由 transfer_buffer_length 指定。返回的 RET_SUBMIT 不带 URB data,但依然需要有 RET_SUBMIT。
  4. Interrupt/Bulk Transfer
  5. 第一种是 Interrupt/Bulk IN,由 Device 给 Host 发送一段数据,附在 RET_SUBMIT 中。
  6. 第二种是 Interrupt/Bulk OUT,由 Host 给 Device 发送一段数据,中 CMD_SUBMIT 的 URB data 中。返回的 RET_SUBMIT 不带 URB data,但不能不发 RET_SUBMIT。

可见,Interrupt 和 Bulk 是比较简单的,而 Control 和 Isochronous(没有提到)则比较复杂。

回到 USB/IP 协议

其实补充了这些信息以后,就可以实现一个 USB/IP 协议的服务器了。

MIFARE Classic 上配置 NDEF

背景

最近买了一堆 NFC 的智能卡拿来测试,其中一张 MIFARE Classic 的总是在 iOS 上读不出来,无论是以 Tag 模式还是 NDEF 模式。于是通过一系列的研究,终于知道上怎么一回事,然后成功地把一个 MIFARE Classic 卡配置成了 NDEF。

背景知识

NFC 有很多协议,其中 MIFARE Classic 基于 ISO 14443-3 Type A 标准,里面有一些 MIFARE 的命令。通过这些命令,就可以控制 MIFARE Classic 卡的内容。具体来说,以我使用的 MIFARE Classic EV1 4K S70 为例,这篇文章会涉及到如下的背景知识:

MIFARE Classic 内存布局

在 MIFARE Classic 中,有 Sector 和 Block 的概念,每个 Sector 有若干个 Block,其中最后一个 Block 是特殊的(称为 Sector Trailer),保存了这个 Sector 的一些信息:Key A、Access Bits、GPB 和 Key B。对于 Classic 4K,首先是 32 个有 4 blocks 的 sector,然后是 8 个 有 16 blocks 的 sector,整体的内存布局大概是:

Sector 0:
    Block 0
    Block 1
    Block 2
    Block 3(Sector Trailer)
Sector 1:
    Block 4
    Block 5
    Block 6
    Block 7(Sector Trailer)
...
Sector 32:
    Block 128
    Block 129
    ...
    Block 143(Sector Trailer)
...
Sector 39:
    ...

每个 Block 有 16 字节,一共 256 个 block,所以是 4K 大小的存储空间。Block 0 比较特殊,保存的是生产商写入的信息,不可更改。

Sector Trailer 的布局如下:

Key A Access Bits GPB Key B
6 字节 3 字节 1 字节 6 字节

其中 Key A 和 Key B 上用于当前 Sector 认证的两个 Key,用相应的 Key 认证以后就可以修改 Sector 里面 Block 的内容。既然有 Key,就会有细粒度的权限控制,就是 Access Bits。它的计算方式比较复杂,首先举个文档AN1305出现过的例子 0x7F 0x07 0x88

  1. 按字节翻转:0x88 0x07 0x7F
  2. 改写成二进制:1000 1000 0000 0111 0111 1111
  3. 拆成前半部分:1000 1000 0000 和后半部分:0111 0111 1111
  4. 如果前后部分互补,说明这是个合法的 Access Bits(这种取反拼接做校验的方法挺常见的)
  5. 取出前半部分:1000 1000 0000
  6. 从后往前取三个字节的最高位:011
  7. 从后往前取第三个字节的次高位,依此类推:000 000 000

这里的 011 表示的是 Sector Trailer 的访问权限,特别地,它表示,不能读出 Key A,只能用 Key B 认证后修改 Key A;用 Key A 或者 Key B 认证后都可以读 Access Bits,但只能在 Key B 认证后修改 Access Bits;不能读出 Key B,只能用 Key B 认证后修改 Key B。也就是说,Key A 认证只能读 Access Bits,而 Key B 认证有权限写入 Key A、Access Bits 和 Key B 字段。完整表格见AN1305 Table 7

之后的三个 000 分别对应前三个 Blocks(又称 Data Blocks,先只考虑带 4 Blocks 的 Sector)的访问权限。000 表示的是,用 Key A 和 Key B 都有完整的读写权限。完整的表格见 AN 1305 Table 8

这里可以给读者留一个练习:0x78 0x77 0x88 对应的权限上什么?

答案:对 Sector Trailer:011;对 Data Blocks:100;此时 Data Blocks 可以用 Key A 或者 Key B 认证读取,但只能用 Key B 认证写入。

如果查看完整的表格就可以发现,Key B 的权限一般是比 Key A 大的,所以 Key B 一般是保密的,而 Key A 可以是公开的。

MIFARE 命令

为了向 MIFARE Classic 卡发送命令,首先需要一个 ISO 14443-3 Type A 的接口,Android 的 NfcA 或者 libnfc 都提供了接口。这里发送的命令实际上会再经过一层解析、用 CRYPTO1 算法加密(猜测是读卡器做的?不是很确定),不过对应用程序来说是透明的。可以参考 MIFARE Classic EV1 1KA Practical Attack on the MIFARE Classic 中的描述。

MIFARE Read

读出一个 Block 的内容,每个 Block 有 16 字节。命令格式如下:

30 XX

如果要读第一个 Block,就是 30 00,如果要读第二个 Block,就是 30 01

返回的数据里刚好是 16 个字节。

MIFARE Write

向一个 Block 写入数据,命令格式如下:

A0 XX YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY YY

这里的 XX 和上面一样,也是 Block 地址;之后是十六字节的数据。

MIFARE Authentiate with A/B

注:这里和 S70 datasheet 里写的不完全一样。

这个命令会进行 Key A 或者 Key B 的认证,如果是对 Key A 认证:

60 XX YY YY YY YY ZZ ZZ ZZ ZZ ZZ ZZ

这里的 XX 也是 Block 地址,但实际上认证的粒度上 Sector,所以只要认证了 Sector 里面的一个 Block,其他 Block 也是同时认证,也是用同一个 Sector Trailer 中的信息进行认证。YY 则是 ISO 14443-3 Type A 中的 UID,如果用 Android 的 API 读取,就可以在 NfcA 中找到这个四字节的信息。ZZ 就是要认证的密钥,六个字节。

如果是对 Key B 认证,把第一个字节的 0x60 改成 0x61 即可。

认证成功后,返回一个 0x00;如果认证失败,则会断开 NFC。

NDEF 是什么

NDEF 实际上是比较高层次的数据,就像 HTML,表示了一个格式化的数组数据,数组的元素可能是文本、URI 等等。它是由若干个 Record 组成的。一个 Record 如下:

03 0B 01 07 54 02 65 6E 61 62 63 64

首先是一个 03 表示类型,然后是长度 0x0B(11,从下一个字节开始数),接着是 0x01 0x07 表示这似乎一个 Well Known 类型的 Record,内容的长度为 7,0x54(ASCII T)表示这是文本格式,0x02 表示编码是 UTF-8,0x65 0x6E (ASCII "en") 表示语言是英语,之后的 0x61 0x62 0x63 0x64(ASCII "abcd")就是文本内容。

很多个 record 连起来,最终一个 0xFE 表示结束,这就是完整的 NDEF 信息了。

在 MIFARE Classic 上使用 NDEF

NDEF 只定义了数据格式,但为了实际使用,还得看具体情况。就好像文件内容保存在硬盘上的时候,并不是直接保存,而是通过文件系统,人为定义一个路径,这样大家才知道要从 /etc/shadow 文件去读 Linux 的用户密码信息,NDEF 也需要人为定义一些规则,再作为数据存放在智能卡里的某个地方,这样大家去读取 metadata,发现上 NDEF Tag,然后才会去解析 NDEF 信息。

有些时候,这个定义很简单,比如直接把 NDEF 数据放在某个 block 里面;有的时候又很复杂,因为可能同时存在很多应用,NDEF 只是其中的一种,所以要有一种类似目录的东西去索引 NDEF“文件”。

MIFARE Classic 上采用的方法上,在特定的 Sector(比如 Sector 0)放一些元数据,元数据里注明了其他的 Sector(从 1 开始的其它 sector)分别用于什么用途,然后 NDEF 是其中一种用途。这个结构叫做 MIFARE Application Directory。具体来说,在 MIFARE Classic 里面,它规定 Block 1 和 Block 2 的内容如下:

0-1 2-3 4-5 6-7 8-9 10-11 12-13 14-15
Info & CRC AID AID AID AID AID AID AID
AID AID AID AID AID AID AID AID

第一个字节是 CRC 8,它的定义可以在这里的 CRC-8/MIFARE-MAD 里找到:初始值 0xC7,多项式上 0x1D。参与 CRC 计算的是按顺序从第二个字节开始的 31 个字节。

第二个字节是 Info Byte,用处不大,见 MAD 的文档。

之后每两个字节对应一个 Sector 的 AID(Application ID),比如 Block 1 的 2-3 字节对应 Sector 1 的 AID,Block 1 的 4-5 字节对应 Sector 2 的 AID,最后 Block 2 的 14-15 字节对应 Sector 15 的 AID。NDEF 的 AID 就是 0x03 0xE1。当软件发现这里的 AID 是 0x03E1 的时候,它就会去相应的 Sector 去读取 NDEF 信息。

一个用 TagInfo 读出来的例子如下:

Sector 0 (0x00)
[skipped]
[01]  F3 01 03 E1 03 E1 00 00
 rW-  00 00 00 00 00 00 00 00
[02]  00 00 00 00 00 00 00 00
 rW-  00 00 00 00 00 00 00 00
[03]  A0:A1:A2:A3:A4:A5  MAD access key
 WXW  78:77:88 C1
      XX:XX:XX:XX:XX:XX  (key unavailable)

可以看到,这里表示的是 Sector 1 和 Sector 2 是 03E1 NDEF。下面 [03] 行表示的是 Key A,下一行是 Access bits、GPD,最后一行是 Key B。TagInfo 会尝试从 well known 里的 Key A 和 Key B 一个个试,直到认证成功为止。常见的如下:

  1. A0 A1 A2 A3 A4 A5:MAD 的 Key A
  2. D3 F7 D3 F7 D3 F7:NDEF 的 Key A
  3. FF FF FF FF FF FF:出场默认的 Key A 和 Key B

如何在 MIFARE Classic 上配置 NDEF

如果看了这么多背景知识,你还有心情看到这里,那要给个掌声。

为什么要在 MIFARE Classic 上配置 NDEF 呢?因为直接买到的 MIFARE Classic(比如我用的 EV1 4K S70)里面都是出厂状态,Key A 和 Key B 都是 FF FF FF FF FF FF,除了 Block 0 以外数据都是 0,所以它并不能用作 NDEF,Android 也只是认为它 NdefFormattable。所以我们要做的就是,Format as NDEF。为啥要自己搞呢,也是因为试了几个现成的工具 format 都失败了。

其实整个流程在 AN1305 的 8.1 章节都写了,但看起来简单,实现起来还是有很多细节,在搞的时候也是来来回回做了很多尝试,同时也利用 TagInfo 强大的 Memory dump 配合调试。

首先复习一下我们可以用哪些命令:

  1. MIFARE Authenticate:对一个 sector 认证,认证成功了才能写操作
  2. MIFARE Read:读取一个 Block
  3. MIFARE Write:写入一个 Block

仔细观察 AN1305 的 Fig.10 和下面的文本描述,大概需要做这些事情:

  1. 修改 Block 1 和 Block 2 中的信息,符合 MAD 的格式
  2. 修改 Sector 0 的 Sector Trailer
  3. 修改 Block 4,填入一个空白的 NDEF,或者直接前面背景知识里的例子。
  4. 修改 Sector 1 和 Sector 2 的 Sector Trailer

但有一些细节:

  1. 修改 Sector Trailer 的时候要谨慎,因为会修改 Key,如果改完又忘了,这卡就废了
  2. 注意用 Key A 还是 Key B 进行认证。上面这些流程结束后,Sector 0 被保护了,需要用 Key B 才能修改数据;而 Sector 1 和 Sector 2 是开放的;如果执行完第一步和第二步以后,发现第一步写错了,就要注意权限的问题,必要时还可以先修改 Access bits 再修改数据
  3. 在这里为了简单,Key B 都用 FF FF FF FF FF FF 了,实际情况下可以用别的自己的密钥,只要记住就行

那么,按照前面的这些知识,就可以构造出每一步的 MIFARE 命令了:

注意:下面的命令不一定能工作,在执行前请仔细理解每条命令的结果,本文作者对卡的损失概不负责

第一步:

60 00 YY YY YY YY FF FF FF FF FF FF
A0 01 F3 01 03 E1 03 E1 00 00 00 00 00 00 00 00 00 00
A0 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

注意 YY 要填入 ID。这一步首先用 FF FF FF FF FF FF 认证了 Sector 0 的 Key A,然后写入了 Block 1 和 Block 2。Info Byte 用的是 0x01,然后用在线工具计算了一下 CRC=F3。

第二步:

A0 03 A0 A1 A2 A3 A4 A5 78 77 88 C1 FF FF FF FF FF FF

这一步设置了 Key A 为 MAD access key,权限是 78 77 88,GPB 是 C1,Key B 为 FF FF FF FF FF FF

第三步:

60 04 YY YY YY YY FF FF FF FF FF FF
A0 04 00 00 03 0B D1 01 07 54 02 65 6E 61 62 63 64 FE

这一步认证了 Sector 1,然后往 Block 4 写入了一个 abcd 的 NDEF 记录。

第四步:

A0 07 D3 F7 D3 F7 D3 F7 7F 07 88 40 FF FF FF FF FF FF
60 08 YY YY YY YY FF FF FF FF FF FF
A0 0B D3 F7 D3 F7 D3 F7 7F 07 88 40 FF FF FF FF FF FF

写入了 Sector 1 的 Sector Trailer,然后认证 Sector 2,再写入 Sector 2 的 Sector Trailer

这样就完成了,再用 TagInfo 等软件,就可以读取出来 NDEF 信息了。此时 iOS 也可以读出来。

上面这些过程,在实际情况下在不同 sector 的时候需要打断,每次重新认证一下。这里默认了一些卡的初始密钥,如果初始情况并不一致,可能并不会工作。

踩的坑

在这个过程中踩过很多的坑:

  1. 在空 NDEF 的时候,NFC Tools 能读出来是 Ndef,并且内容是空,但写入的时候表示 Write error,也读不出来;去 TagInfo 读内存,发现确实写进去了,但内容不对,有一个位置的长度写成了 0,可能是 BUG
  2. 上面也提到过的,就是在修改为只读以后,发现数据写错了,只好重新改成可写,把数据改好了以后再设为只读。
  3. iOS 上用 NFCNDEFReaderSession 可以读出来这个 NDEF 的内容,但 NFCTagReaderSession 并不能 poll 出来。

解释 BB84 协议

背景

这周密码学课程,来自 BUPT 的高飞老师讲了一下 qkd 里的 BB84 协议,老师讲得很好,我也想记录一下这个协议的流程和方法。我不是这方面的专业人士,如果有什么问题请指出。

背景知识

QKD(Quantum Key Distribution)目的是让通信双方获得同一个密钥,它需要同时需要量子信道和经典信道。其中经典信道被认为是可信的,它可以被监听,但不能被中间人攻击。

在 BB84(Charles H. Bennett and Gilles Brassard (1984))协议中,传输的是一个光子,它具有如下的特性:

可以用两个基去测量光子:➕️和✖️️,然后光子有四个偏振角度,分别是 ⬆️️ ⬇️️ ↘️️ ↗️️。定义一个二进制位和偏振角度的对应关系如下:

0 1 0 1
➕️ ➕️ ✖️️ ✖️️
偏振角度 ⬆️️ ⬇️️ ↗️️ ↘️️

对于一个未知光子,可以用两种基进行测量,测量的结果:

偏振角度 ⬆️️ ⬇️️ ↗️️ ↘️️
用➕️测量 0 1 0/1 0/1
用✖️️测量 0/1 0/1 0 1

这里的 0/1 表示有 50% 概率测得 0,有 50% 概率测得 1。

协议流程

假如 Alice 要和 Bob 进行 BB84 协议。那么,Alice 首先随机生成一段二进制序列,并随机生成一个基的序列,以 Wikipedia 上的例子为例:

Alice's random bit 0 1 1 0 1 0 0 1
Alice's random sending basis ➕️ ➕️ ✖️️ ➕️ ✖️️ ✖️️ ✖️️ ➕️
Photon polarization Alice sends ⬆️️ ➡️️ ↘️️ ⬆️️ ↘️️ ↗️️ ↗️️ ➡️️
Bob 生成随机的基 ➕️ ✖️️ ✖️️ ✖️️ ➕️ ✖️️ ➕️ ➕️
Photon polarization Bob measures ⬆️️ ↗️️ ↘️️ ↗️️ ➡️️ ↗️️ ➡️️ ➡️️
Bob 认为的二进制信息 0 0 1 0 1 0 1 1
通过经典信道交换信息
Shared secret key 0 丢弃 1 丢弃 丢弃 0 丢弃 1

第一步,Alice 生成随机的二进制和随机的基,按照前面谈到过的对应关系,生成带有偏振角度的光子发给 Bob。

第二步,由于 Bob 只收到光子,不知道 Alice 选取的基底信息,而且只能用一个基测量一次,所以 Bob 随机从两种基选择一个来测量,得到了一串二进制。这些二进制里,如果 Alice 和 Bob 选取了同一个基,那么这一位的数据一定是对的;如果选取了不同的基,那么这一位有一半的可能是对的。总的来说,期望有四分之一的位是不正确的。

第三步,Alice 和 Bob 在 可信 的经典信道中把双方的基底进行对比,把基底相等的部分对应的二进制位提取出来,作为最终使用的密钥。

第四步,Alice 和 Bob 在最终使用的密钥中抽取若干位,然后对比,如果这些位都一致,则这个密码是有效的。如果错误率太高,那么很大概率是被攻击了。

安全性

协议的安全性,主要是靠量子的特性:未知量子态不可克隆、对未知量子态的测量可能会改变量子态。

假如在量子信道中间有一个 Eve 想要做坏事,它如果在中间观测了一下光子,它就会影响光子的量子态,导致 Bob 的密钥和 Alice 密钥会不一致,从而在协议的第四步被发现。并且,因为 Eve 并不知道 Alice 所使用的基底(假设 Eve 只能控制量子信道、不能控制经典信道),所以得到的二进制数据也有四分之一是不正确的。即使 Eve 尝试截获并且重发光子给 Bob,Bob 得到的密钥仍然有很高的错误率。通过这个错误率,就可以判断是否被攻击了。

另外,它的安全性还依赖以下几个方面:

  1. Eve 不能控制 Alice 和 Bob 的量子密码设备
  2. Alice 和 Bob 的随机数生成器需要足够安全
  3. 经典信道是可信的

引用

Quantum key distribution - Wikipedia

在 k8s 中部署 code-server

实验了一下在 k8s 中部署 code-server,并不复杂,和之前几篇博客的配置是类似的:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: code
  labels:
    app: code
spec:
  selector:
    matchLabels:
      app: code
  replicas: 1
  template:
    metadata:
      labels:
        app: code
    spec:
      volumes:
        - name: code-volume
          persistentVolumeClaim:
              claimName: code-pvc
      initContainers:
      - name: home-init
        image: busybox
        command: ["sh", "-c", "chown -R 1000:1000 /home/coder"]
        volumeMounts:
        - mountPath: "/home/coder"
          name: code-volume
      containers:
      - image: codercom/code-server:latest
        imagePullPolicy: Always
        name: code
        volumeMounts:
          - mountPath: "/home/coder"
            name: code-volume
        resources:
          limits:
            cpu: "0.5"
            memory: "500Mi"
        ports:
        - containerPort: 8080
        env:
          - name: PASSWORD
            value: REDACTED

---
apiVersion: v1
kind: Service
metadata:
  name: code
  labels:
    app: code
spec:
  ports:
    - port: 8080
  selector:
    app: code

---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-code
  annotations:
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
    nginx.org/websocket-services: "code"
spec:
  tls:
  - hosts:
    - example.com
    secretName: code-tls
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: code
          servicePort: 8080

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: code-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 1Gi

需要注意的几个点:

  1. 用了一个 pvc 用于 /home/coder 的持久化,所以你的集群里得有相应的 pv/storage class
  2. 我用的是 Nginx Inc. 的 ingress controller,它的 websocket 支持需要一句 nginx.org/websocket-services 设置
  3. 额外添加了一个 init container,为了处理 home 目录的权限