分类 默认分类 下的文章

MAC 的 PATH 环境变量

在 MAC 上工作, 竟然遇到要更改 PATH 环境变量的情况. 比如有的 Java 工程是基于 JDK8 的, 有的是基于 JDK11 的, 所以经常要改这个变量. 那么我们看到的 PATH 环境变量是从哪里来的, 那些配置文件会改这些 PATH 值呢?

首先, 我们先看下当前的 PATH 变量:

~ xiatian$ echo $PATH
/usr/local//Cellar/curl/7.80.0_1/bin/:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/Wireshark.app/Contents/MacOS:/Users/xiatian/work/tools:/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/

这里面有好多路径, 那么我们好奇当系统启动的过程中, 最初的 PATH 是什么呢?

  1. MAC 上最初的 PATH 是从 /etc/paths 文件读的:

    ~ xiatian$ cat /etc/paths
    /usr/local/bin
    /usr/bin
    /bin
    /usr/sbin
    /sbin
  2. 然后, 它会把 /etc/path.d 里面的路径逐个添加到后面, 比如我们可以看到我的 wireshark 路径在上面的 PATH 里:

    ~ xiatian$ ls -l /etc/paths.d/
    total 8
    -rw-r--r--  1 root  wheel  43 Oct 19  2017 Wireshark
    ~ xiatian$ cat /etc/paths.d/Wireshark
    /Applications/Wireshark.app/Contents/MacOS
  3. 在个人的 home 目录, 还有2个文件会影响 PATH 的值: .bash_profile & .bash_rc. 他们的区别是: 当一个 shell 是 login shell 的时候, 它会先执行 .bash_profile, 当是一个非 login 交互式 shell 的时候, 它执行 .bashrc. 但是在 MAC 上, 默认都是 login shell, 所以应该都执行. 来源于这个问答. 我试过了自带的 Terminal 和 安装的 iterm, 发现我的 MAC 都不是这么玩的.
  4. 我本地是执行的 ~/.profile. 查看 bash 的 man, 你会在 INVOCATION section 看到具体的配置执行顺序:

    ~ xiatian$ man bash
    When bash is invoked as an interactive login shell, or as a non-interactive shell with the --login option, it first reads and executes commands from the file  /etc/profile,  if that  file exists.  After reading that file, it looks for ~/.bash_profile, ~/.bash_login, and ~/.profile, in that order, and reads and executes commands from the first one that exists and is readable.  The --noprofile option may be used when the shell is started to inhibit this behavior.
    When an interactive shell that is not a login shell is started, bash reads and executes commands from ~/.bashrc, if that file exists.  This may be inhibited by using the --norc option.  The --rcfile file option will force bash to read and execute commands from file instead of ~/.bashrc.

所以, 只要仔细阅读 man bash 的内容, 你就知道 PATH 是怎么变化的啦.

Linux Containers and Virtualization 学习笔记

一本简短的关于 Linux 虚拟化方面的书, 涉及到虚拟机和container的一些核心概念, 挺好的.
<<Linux Containers and Virtualization: A Kernel Perspective>>

基础部分

  1. Linux Kernel and CPU help on virtualization.
  2. process-level virtualization -> Java -> Java runtime environment virtualized the POSIX layer.
  3. VMware -> virtualizing the actual hardware like the CPU, memory, disks, etc -> VMware software(hypervisor: host->guest OS) -> ESX (type 1: OS on hypervisor) & GSX (type 2: OS on OS)
  4. 对底层计算资源的更加灵活的利用/隔离/管控 -> 不同 level 的实现/优劣/代价/
  5. 2 种常见虚拟化技术的对比(VM-based vs container-based). 思考的角度/实现的思路 -> 我要一台虚拟机->我要一个 OS-> 一个物理机跑几个 OS (底层) | 虚拟化就是资源的隔离与限制 (上层)
  6. Hypervisor -> VMM (Virtual Machine Monitor): trapping and emulating the privileged instruction set & Device model: virtualizing the I/O devices
  7. VMM 满足 3 大属性: 1. Isolation 隔离, 2. Equivalency 同等性, 3. Performance 性能.
  8. VMM 基本功能:

    1. 不允许 VM 访问 privileged 状态;
    2. 处理异常和中断, map 到对应的虚拟机;
    3. 处理 CPU 虚拟化: 大部分指令 natively, trapping 部分特权指令;
    4. 处理各 VM 的内存映射, 控制物理内存映射
  9. 内存虚拟化: guest OS 不能直接操作物理内存, 不能操作硬件 page tables. 一般Virtual Memory -> CR3 + MMU (硬件) -> 查找 page tables -> 物理内存; 对应 VMM 有了 3 层抽象

    1. Guest virtual memory;
    2. Guest physical memory;
    3. System physical memory;
  10. 内存虚拟化: 对于3 层抽象, 可能的 2 个解决方案: 1) Shadow page tables ; 2) Nested page tables with hardware support -> AMD & Intel 提供硬件扩展 -> EPT (Extended Page Table) -> 2 层 page tables;

namespace

  1. Namespaces kernel 内部逻辑隔离, 通过限制系统资源可见性的方法 主体: process, 对象: (内核资源) mnt, pid, uts, ipc, net, time, cgroup, user.
  2. namespace 实现-> kernel 的内部数据结构 -> task-struct->nsproxy (task-struct->cred (user only)).
  3. namespace 相关的 syscall 1. clone; 2). unshare; 3. setns.
  4. 查看一个进程的 ns -> /proc/<pid>/ns/
  5. net namespace 相关的命令

    1. ip netns add myns
    2. ip netns del myns
    3. ip netns exec myns sh ## 执行 sh 在 myns namespace
    4. ip link add veth0 type veth peer name veth1  ##创建 veth 对 (veth0 ~ veth1)
    5. ip link set veth1 netns myns ## 把 veth 的一端加到 myns namespace

cgroup

  1. cgroup(control group): kernel 里的: cgroup controller + cgroupfs 组成;
  2. cgroup v1 ~ v2
  3. mount cgroup v2: mount -t cgroup2 none <mount_point>
  4. CPU group:

    1. CFS scheduler vs RT scheduler
    2. CFS -> cpu.shares vs cpu.cfs_quota_us/cpu.cfs.period_us (us:微秒)
    3. CFS -> 数据结构: task_struct -> sched_entity -> vruntime;
    4. CFS -> 每次 schedule 的时候, 计算权重,剩余时间,控制用量, 排序 -> 根据 CPU 个数, 进程数, 配置, 等信息
    5. CFS -> 数据结构: CONFIG_CFS_BANDWIDTH
  5. Block I/O cgroup

    1. 块设备 I/O 的读写 bytes 和 iops (数据量和次数);
    2. fairness + throttling
    3. process -> (Page Caches + VFS) -> File System -> Blocker Layer -> Driver -> Disk
    4. Block I/O cgroup 实现在 Blocker Layer;
    5. 数据结构: request_queue -> request -> bio;
    6. 控制结构: blkcg <- 块 I/O cgroup, 每个块 I/O cgroup 映射到一个 request queue. -> blkcg_gq;
    7. CFQ (Complete Fair Queuing) -> 每个 block I/O cgroup 以 group 为单位. 所有队列形成一个 queue tree;
    8. 数据结构: cfq_group -> vdisktime

Layered File System

  1. File system 屏蔽了底层的真实情况, 抽象成 文件读写. block 设备 vs non-block 设备 -> VFS;
  2. 所有的文件系统都注册到 VFS -> File -> (1:1) Inode (metadata). Dentry 内存里面用来根据 filename 查找遍历 Inode. Superblock (block, device metadata). File -> Dentry -> Inode -> page cache (address_space) -> memory page.
  3. mount -> 数据结构: vfsmount -> superblock;
  4. pdflush -> page dirty flush to disk;
  5. Layered File System:

    1. 节省磁盘 shared on disk
    2. 节省内存 -> 内存共享 (page cache). 快速启动 (已经加装在内存);
  6. Whereas in the case of a layered file system, the file system is broken into layers and each layer is a read-only file system. Since these layers are shared across the containers on the same host, they tend to use storage optimally. And, since the inodes are the same, they refer to the same OS page cache. This makes things optimal from all aspects.
  7. Union File System:
  8. OverlayFS: 一种 union FS. 大概可以理解为 Layered File System 是一个概念, UnionFS 是一个 API, OverlayFS 是一个实现

image.png

关于 Wireshark 分析 https 的 tcpdump 中的 Encrypted Alert

使用 Wireshark 分析 https 数据的时候, 尤其是分析网络为什么 reset 的时候, 经常看到有些数据行标注为: "Encrypted Alert", 紧接着就会看到连接主动/被动关闭或者 reset. 比如下图:
encryptedAlert.png

看到这 2 个单词, 我们很自然想到是不是加密过程出了什么问题? 为什么它提示 Alert? 今天我们具体去研究一下, 这个 "Encrypted Alert" 到底是什么, 跟连接断掉有什么关系?

首先, 我们研究一下这个 tcp 包的数据, 从下图可以看到, 它其实是传输层的数据, 没有应用层数据. 传输层的具体数据是: 它的内容类型(Content Type)是 Alert, 定义的 ID 是 21, 具体的 Alert Messge 就是: "Encrypted Alert". 从这里我们只能看到这个加密的会话出现了一个加密相关的告警, 没有其它可以告诉我们的.
ssl.png

另外, 在第一个截图中, 我们看到在 60 结尾的主机发 FIN 之前, 先发了一个 "Encrypted Alert" 消息, 接着发了 FIN. 之后 236 结尾的主机, 发了一个 "Encrypted Alert", 还没有发 FIN, 60 结尾的主机立马回了一个 RST.

TLS 协议

首先, TLS 协议根据内容类型(Content Type)有四种可能的记录(record)类型.
tlsP.png
根据 TLS 1.2 RFC5246, 这 4 种记录类型的编码分别是: 20, 21, 22, 23

enum {
          change_cipher_spec(20), alert(21), handshake(22),
          application_data(23), (255)
      } ContentType;

具体到 TLS 的 record, alert(21) 类型的数据, 只用到了 Content Type, TLS 协议大小版本号, 数据长度, 数据.
alert_record.png

对应我们上面截图中的数据:

  1. Content Type 是 21, 16 进制就是 0x15, 对应的是 Alert;
  2. 我们使用的版本号是 1.2, 对应的是 0x0303;
  3. 数据长度是 26 字节, 对应的 16 进制就是 0x001a;
  4. 对应的 payload 就是剩余的黄色里面的 26 字节的数据;
    alertPro.png

TLS 协议中的 Alert 数据

根据 TLS 1.2 RFC5246, Alert 的数据分为 AlertLevel 和 AlertDescription 2 部分. AlertLevel描述严重性: Warning 和 Fatal. AlertDescription 描述具体的 Alert 原因. 不过他们都是 enum 类型. 具体的数据结构是:

      enum { warning(1), fatal(2), (255) } AlertLevel;

      enum {
          close_notify(0),
          unexpected_message(10),
          bad_record_mac(20),
          decryption_failed_RESERVED(21),
          record_overflow(22),
          decompression_failure(30),
          handshake_failure(40),
          no_certificate_RESERVED(41),
          bad_certificate(42),
          unsupported_certificate(43),
          certificate_revoked(44),
          certificate_expired(45),
          certificate_unknown(46),
          illegal_parameter(47),
          unknown_ca(48),
          access_denied(49),
          decode_error(50),
          decrypt_error(51),
          export_restriction_RESERVED(60),
          protocol_version(70),
          insufficient_security(71),
          internal_error(80),
          user_canceled(90),
          no_renegotiation(100),
          unsupported_extension(110),
          (255)
      } AlertDescription;

      struct {
          AlertLevel level;
          AlertDescription description;
      } Alert;

那么有了上面比较全面的 Alert 的数据可能值, 我们能不能看出, 我们上面例子中具体的 Alert leve 和 alert decription 呢? 我们把看到的26 字节的数据和上面的任意一个对比, 都不能找到匹配的, 为什么呢?

根据上面的协议描述, 我们知道 Alert 的数据部分(level 和 description) 属于 payload 的部分, 而 payload 部分根据 RFC5246 是被加密和压缩的, 所以我们不能像找 header 部分的对应关系一样, 找到对应的数据.

Like other messages, alert messages are encrypted and compressed, as specified by the current connection state

如何找出具体的level 和 description 对应关系

我们知道, 可以通过 log sslkey 的方式, 把加密数据的关键key 记录下来, 然后让 Wireshark 解密的时候使用. 参考:
MAC 上解密 https 协议
Windows 上解密 https 协议

如果你使用 curl, 那就更方便了:

  1. 设置 key 文件的位置
    export SSLKEYLOGFILE=~/Downloads/keylog.log
  2. 开始抓包

    $ host www.tianxiaohui.com
    www.tianxiaohui.com has address 103.144.218.5
    $ sudo tcpdump host 103.144.218.5 -w ~/Download/tianxiaohui.pcap
  3. 访问 https 网站
    curl -vvv https://www.tianxiaohui.com/
  4. 设置 Wireshark
    菜单 -> 选项 -> 协议 -> TLS -> (Pre)-Master-Secret log filename 为我们上面的 key 文件

如此设置之后, 我们就看到了解密之后的 payload: 它的 level 是 warning, 对应 0x1, 它的 description 是 0x0, 对应 close_notify.
clear_msg.png

TLS 协议的结束消息

看到这里, 大家可能突然明白, 针对我们最后使用 curl 访问 https 的这个例子, 这里的 close_notify 原来就是 TLS 协议的结束消息, 告诉对方, 这个 TLS session 要结束了. 类似于 tcp 协议的 FIN 消息. 当然 Alert 类型的里面, 还有更多的不同的 description, 不过我们这里的正好是 close_notify.
这样印证了, 在 Wireshark 知道 key 之前, 它根本无法知道具体的 alert 是什么. 所以, 我们仅仅根据 "Encrypted Alert" 这个消息, 也无法推断具体的 alert 是什么类型, 到底发生了什么.

参考:

  1. RFC5246 https://datatracker.ietf.org/doc/html/rfc5246#section-6.2.1
  2. https://hpbn.co/transport-layer-security-tls/
  3. https://slideplayer.com/slide/8107590/
  4. https://www.openssl.org/docs/manmaster/man3/SSL_shutdown.html