#621 Python 源码学习 09: set

2021-09-12

我都有点怀疑我这个学习计划是否是正确的。

同样的废话,就懒得说了,只说有价值的信息。

  1. 疑问:代码中的 clinic 相关文件不知道是做什么用的。
  2. set 和 frozenset 的定义在一起。不是这次看代码,我简直忘了还有 frozenset 这个类型的存在。

汇总了一下,所有的类型:

  1. PyAsyncGen_Type
  2. PyBaseObject_Type
  3. PyBlake2_BLAKE2bType
  4. PyBlake2_BLAKE2sType
  5. PyBool_Type
  6. PyBufferedIOBase_Type
  7. PyBufferedRandom_Type
  8. PyBufferedReader_Type
  9. PyBufferedRWPair_Type
  10. PyBufferedWriter_Type
  11. PyByteArrayIter_Type
  12. PyByteArray_Type
  13. PyBytesIO_Type
  14. PyBytesIter_Type
  15. PyBytes_Type
  16. PyCallIter_Type
  17. PyCapsule_Type
  18. PyCArg_Type
  19. PyCArray_Type
  20. PyCArrayType_Type
  21. PyCData_Type
  22. PyCell_Type
  23. PyCField_Type
  24. PyCFuncPtr_Type
  25. PyCFuncPtrType_Type
  26. PyCFunction_Type
  27. PyClassMethodDescr_Type
  28. PyClassMethod_Type
  29. PyCMethod_Type
  30. PyCode_Type
  31. PyComplex_Type
  32. PyContextTokenMissing_Type
  33. PyContextToken_Type
  34. PyContext_Type
  35. PyContextVar_Type
  36. PyCoro_Type
  37. PyCPointer_Type
  38. PyCPointerType_Type
  39. PyCSimpleType_Type
  40. PyCStgDict_Type
  41. PyCStructType_Type
  42. PyCThunk_Type
  43. PyCursesWindow_Type
  44. PyCursesWindow_Type;
  45. PyDictItems_Type
  46. PyDictIterItem_Type
  47. PyDictIterKey_Type
  48. PyDictIterValue_Type
  49. PyDictKeys_Type
  50. PyDictProxy_Type
  51. PyDictRevIterItem_Type
  52. PyDictRevIterKey_Type
  53. PyDictRevIterValue_Type
  54. PyDict_Type
  55. PyDictValues_Type
  56. PyEllipsis_Type
  57. PyEnum_Type
  58. PyFileIO_Type
  59. PyFileIO_Type;
  60. PyFilter_Type
  61. PyFloat_Type
  62. PyFrame_Type
  63. PyFrozenSet_Type
  64. PyFunction_Type
  65. Py_GenericAliasType
  66. PyGen_Type
  67. PyGetSetDescr_Type
  68. PyHKEY_Type
  69. PyIncrementalNewlineDecoder_Type
  70. PyInstanceMethod_Type
  71. PyIOBase_Type
  72. PyListIter_Type
  73. PyListRevIter_Type
  74. PyList_Type
  75. PyLongRangeIter_Type
  76. PyLong_Type
  77. PyMap_Type
  78. PyMemberDescr_Type
  79. PyMemoryView_Type
  80. PyMethodDescr_Type
  81. PyMethod_Type
  82. PyModuleDef_Type
  83. PyModule_Type
  84. PyODictItems_Type
  85. PyODictIter_Type
  86. PyODictKeys_Type
  87. PyODict_Type
  88. PyODictValues_Type
  89. PyPickleBuffer_Type
  90. PyProperty_Type
  91. PyRangeIter_Type
  92. PyRange_Type
  93. PyRawIOBase_Type
  94. PyReversed_Type
  95. PySeqIter_Type
  96. PySetIter_Type
  97. PySet_Type
  98. PySlice_Type
  99. PyStaticMethod_Type
  100. PyStdPrinter_Type
  101. PySTEntry_Type
  102. PyStringIO_Type
  103. PyST_Type
  104. PySuper_Type
  105. PyTextIOBase_Type
  106. PyTextIOWrapper_Type
  107. PyTraceBack_Type
  108. PyTupleIter_Type
  109. PyTuple_Type
  110. PyType_Type
  111. PyUnicodeIter_Type
  112. PyUnicode_Type
  113. PyWindowsConsoleIO_Type
  114. PyWindowsConsoleIO_Type;
  115. PyWrapperDescr_Type
  116. PyZip_Type

#619 自然常数

2021-09-11
  • 实数
  • 有理数: 有限位小数或无限循环小数,也就所有可以用分数表示的数(整数也是分数的一种)
    • 整数
    • 自然数: 0 + 正整数
  • 无理数: 无限不循环小数
  • 虚数

  • 浮点数

  • 科学计数法

有理数 & 无理数

代数数

可作为有理系数多项式方程的根
超越数是不能作为的数 [2] ,即不是代数数的数。因为欧拉说过:“它们超越代数方法所及的范围之外。(1748年)”而得名。

超越数

不是代数数的数称为超越数

  • 圆周率 π
  • 自然常数 e

自然常数 e

假设 $x = e^n$,那么 $n = ln(x)$
前者是指数运算,x 等于 底数 e 的 n 次幂
后者是对数运算,n 等于以 e 为底数,x 的对数

自然对数就是以 e 为底的对数,数学函数表示为 ln(x),表示 e 的多少次幂等于 x,因此自然常数也被叫做:自然对数的底数。

无限不循环小数,无理数,约为 2.718281828459
18 世纪,瑞士数学家欧拉提出来的,和复利计算问题的研究有密切关系。

假设有这么一间银行,它二十年期定期存款年利率 100%,到期自动续存,复利模式。
一开始存入 1w,

  • 如果结息 1 次,二十年后,本息 $10000 \times (1 + 1) = 20000$
  • 如果结息 2 次,二十年后,本息 $10000 \times (1 + \frac{1}{2})^2 = 22500$
  • 如果结息 3 次,二十年后,本息 $10000 \times (1 + \frac{1}{3})^3 = 23703$
  • 如果结息 5 次,二十年后,本息 $10000 \times (1 + \frac{1}{5})^5 = 24883$
  • 如果结息 10 次,二十年后,本息 $10000 \times (1 + \frac{1}{10})^10 = 25937$
  • 如果结息 100 次,二十年后,本息 $10000 \times (1 + \frac{1}{100})^100 = 27048$
  • 如果结息 1000 次,二十年后,本息 $10000 \times (1 + \frac{1}{1000})^1000 = 27169$
  • 如果结息 10000 次,二十年后,本息 $10000 \times (1 + \frac{1}{10000})^10000 = 27181$

存款的增长率函数 $f(n) = (1 + \frac{1}{n})^n$ (其中,n 为结息次数)。
可以推导出,最后这个增长率无限逼近一个数,我们将这个数称之为 e

而 e 就是表示 n 无限大时的增长率,采用极限表达式:

$$
e = \lim_{{n \to \infty}} \left(1 + \frac{1}{n}\right)^n
$$

展开成阶乘方式:

$$
e = 1 + \frac{1}{1!} + \frac{1}{2!} + \frac{1}{3!} + \frac{1}{4!} + \cdots
$$

PS:$n!$ 表示 $n$ 的阶乘,计算方式:

$$
n! = n \times (n-1) \times (n-2) \times \ldots \times 2 \times 1
$$

#616 zsh: you have running jobs.

2021-09-09

我定义了一个 gitg 的快捷方式:

function _new_gitg () {
    # alias g='gitg --all >/dev/null 2>&1 &'  # 覆盖 g=git
    if [ -z "$1" ]; then
        nohup gitg --all >/dev/null 2>&1 &!
    else
        nohup gitg -s $1 >/dev/null 2>&1 &!
    fi
}
alias g='_new_gitg'

但是每次退出 shell 会有提示:zsh: you have running jobs.

我就加上 nohup,以为不会有提示,但提示依旧(不过强制退出也不会关闭 gitg 就是了)。

在 SO 上搜到相同问题,这个答复就可以很好的解决我的问题:

If you want to not see that message, simply pass the job id to disown, like so:

> disown %1
> ```
>
> Or, start the job with &! (zsh-specific trick):
>
> ```sh
> nohup ./my_script.sh &!
> ```

```sh
function _new_gitg () {
    if [ -z "$1" ]; then
        nohup gitg --all >/dev/null 2>&1 &!
    else
        nohup gitg -s $1 >/dev/null 2>&1 &!
    fi
}
alias g='_new_gitg'

经过测试,这个技巧在 bash 5.1.4 下也有效。

#615 转载:用户态 tcpdump 如何实现抓到内核网络包的?

2021-09-08

在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包的呢?有的同学知道 tcpdump 是基于 libpcap 的,那么 libpcap 的工作原理又是啥样的呢。如果让你裸写一个抓包程序,你有没有思路?

按照飞哥的风格,不搞到最底层的原理咱是不会罢休的。所以我对相关的源码进行了深入分析。通过本文,你将彻底搞清楚了以下这几个问题。

  • tcpdump 是如何工作的?
  • netfilter 过滤的包 tcpdump 是否可以抓的到?
  • 让你自己写一个抓包程序的话该如何下手?

借助这几个问题,我们来展开今天的探索之旅!

一、网络包接收过程

图解 Linux 网络包接收过程一文中我们详细介绍了网络包是如何从网卡到达用户进程中的。这个过程我们可以简单用如下这个图来表示。

找到 tcpdump 抓包点

我们在网络设备层的代码里找到了 tcpdump 的抓包入口。在 __netif_receive_skb_core 这个函数里会遍历 ptype_all 上的协议。还记得上文中我们提到 tcpdump 在 ptype_all 上注册了虚拟协议。这时就能执行的到了。来看函数:

//file: net/core/dev.c
static int __netif_receive_skb_core(struct sk_buff *skb, bool pfmemalloc)
{
    ......
    //遍历 ptype_all (tcpdump 在这里挂了虚拟协议)
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if (!ptype->dev || ptype->dev == skb->dev) {
            if (pt_prev)
                ret = deliver_skb(skb, pt_prev, orig_dev);
            pt_prev = ptype;
        }
    }
}

在上面函数中遍历 ptype_all,并使用 deliver_skb 来调用协议中的回调函数。

//file: net/core/dev.c
static inline int deliver_skb(...)
{
 return pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
}

对于 tcpdump 来说,就会进入 packet_rcv 了(后面我们再说为啥是进入这个函数)。这个函数在 net/packet/af_packet.c 文件中。

//file: net/packet/af_packet.c
static int packet_rcv(struct sk_buff *skb, ...)
{
 __skb_queue_tail(&sk->sk_receive_queue, skb);
 ......
}

可见 packet_rcv 把收到的 skb 放到了当前 packet socket 的接收队列里了。这样后面调用 recvfrom 的时候就可以获取到所抓到的包!!

再找 netfilter 过滤点

为了解释我们开篇中提到的问题,这里我们再稍微到协议层中多看一些。在 ip_rcv 中我们找到了一个 netfilter 相关的执行逻辑。

//file: net/ipv4/ip_input.c
int ip_rcv(...)
{
 ......
 return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, dev, NULL,
         ip_rcv_finish);
}

如果你用 NF_HOOK 作为关键词来搜索,还能搜到不少 netfilter 的过滤点。不过所有的过滤点都是位于 IP 协议层的。

在接收包的过程中,数据包是先经过网络设备层然后才到协议层的。

那么我们开篇中的一个问题就有了答案了。假如我们设置了 netfilter 规则,在接收包的过程中,工作在网络设备层的 tcpdump 先开始工作。还没等 netfilter 过滤,tcpdump 就抓到包了!

所以,在接收包的过程中,netfilter 过滤并不会影响 tcpdump 的抓包!

二、网络包发送过程

我们接着再来看网络包发送过程。在25 张图,一万字,拆解 Linux 网络包发送过程一文中,我们详细描述过网络包的发送过程。发送过程可以汇总成简单的一张图。

找到 netfilter 过滤点

在发送的过程中,同样是在 IP 层进入各种 netfilter 规则的过滤。

//file: net/ipv4/ip_output.c
int ip_local_out(struct sk_buff *skb)
{
    //执行 netfilter 过滤
    err = __ip_local_out(skb);
}

int __ip_local_out(struct sk_buff *skb)
{
    ......
    return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
            skb_dst(skb)->dev, dst_output);
}

在这个文件中,还能看到若干处 netfilter 过滤逻辑。

找到 tcpdump 抓包点

发送过程在协议层处理完毕到达网络设备层的时候,也有 tcpdump 的抓包点。

//file: net/core/dev.c
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, struct netdev_queue *txq)
{
    ...
    if (!list_empty(&ptype_all))
        dev_queue_xmit_nit(skb, dev);
}

static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
{
    list_for_each_entry_rcu(ptype, &ptype_all, list) {
        if ((ptype->dev == dev || !ptype->dev) && (!skb_loop_sk(ptype, skb))) {
            if (pt_prev) {
                deliver_skb(skb2, pt_prev, skb->dev);
                pt_prev = ptype;
                continue;
            }
            ......
        }
    }
}

在上述代码中我们看到,在 dev_queue_xmit_nit 中遍历 ptype_all 中的协议,并依次调用 deliver_skb。这就会执行到 tcpdump 挂在上面的虚拟协议。

在网络包的发送过程中,和接收过程恰好相反,是协议层先处理、网络设备层后处理。

如果 netfilter 设置了过滤规则,那么在协议层就直接过滤掉了。在下层网络设备层工作的 tcpdump 将无法再捕获到该网络包

三、TCPDUMP 启动

前面两小节我们说到了内核收发包都通过遍历 ptype_all 来执行抓包的。那么我们现在来看看用户态的 tcpdump 是如何挂载协议到内 ptype_all 上的。

我们通过 strace 命令我们抓一下 tcpdump 命令的系统调用,显示结果中有一行 socket 系统调用。Tcpdump 秘密的源头就藏在这行对 socket 函数的调用里。

# strace tcpdump -i eth0
socket(AF_PACKET, SOCK_RAW, 768)
......

socket 系统调用的第一个参数表示创建的 socket 所属的地址簇或者协议簇,取值以 AF 或者 PF 开头。在 Linux 里,支持很多种协议族,在 include/linux/socket.h 中可以找到所有的定义。这里创建的是 packet 类型的 socket。

协议族和地址族:每一种协议族都有其对应的地址族。比如 IPV4 的协议族定义叫 PF_INET,其地址族的定义是 AF_INET。它们是一一对应的,而且值也完全一样,所以经常混用。

//file: include/linux/socket.h
#define AF_UNSPEC 0
#define AF_UNIX  1 /* Unix domain sockets   */
#define AF_LOCAL 1 /* POSIX name for AF_UNIX */
#define AF_INET  2 /* Internet IP Protocol  */
#define AF_INET6 10 /* IP version 6   */
#define AF_PACKET 17 /* Packet family  */
......

另外上面第三个参数 768 代表的是 ETH_P_ALL,socket.htons(ETH_P_ALL) = 768。

我们来展开看这个 packet 类型的 socket 创建的过程中都干了啥,找到 socket 创建源码。

//file: net/socket.c
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
    ......
    retval = sock_create(family, type, protocol, &sock);
}

int __sock_create(struct net *net, int family, int type, ...)
{
    ......
    pf = rcu_dereference(net_families[family]);
    err = pf->create(net, sock, protocol, kern);
}

在 __sock_create 中,从 net_families 中获取了指定协议。并调用了它的 create 方法来完成创建。

net_families 是一个数组,除了我们常用的 PF_INET( ipv4 ) 外,还支持很多种协议族。比如 PF_UNIX、PF_INET6(ipv6)、PF_PACKET 等等。每一种协议族在 net_families 数组的特定位置都可以找到其 family 类型。在这个 family 类型里,成员函数 create 指向该协议族的对应创建函数。

根据上图,我们看到对于 packet 类型的 socket,pf->create 实际调用到的是 packet_create 函数。我们进入到这个函数中来一探究竟,这是理解 tcpdump 工作原理的关键!

//file: packet/af_packet.c
static int packet_create(struct net *net, struct socket *sock, int protocol, int kern)
{
    ...
    po = pkt_sk(sk);
    po->prot_hook.func = packet_rcv;

    //注册钩子
    if (proto) {
        po->prot_hook.type = proto;
        register_prot_hook(sk);
    }
}

static void register_prot_hook(struct sock *sk)
{
    struct packet_sock *po = pkt_sk(sk);
    dev_add_pack(&po->prot_hook);
}

在 packet_create 中设置回调函数为 packet_rcv,再通过 register_prot_hook => dev_add_pack 完成注册。注册完后,是在全局协议 ptype_all 链表中添加了一个虚拟的协议进来。

我们再来看下 dev_add_pack 是如何注册协议到 ptype_all 中的。回顾我们开头看到的 socket 函数调用,第三个参数 proto 传入的是 ETH_P_ALL。那 dev_add_pack 其实最后是把 hook 函数添加到了 ptype_all 里了,代码如下。

//file: net/core/dev.c
void dev_add_pack(struct packet_type *pt)
{
    struct list_head *head = ptype_head(pt);
    list_add_rcu(&pt->list, head);
}

static inline struct list_head *ptype_head(const struct packet_type *pt)
{
    if (pt->type == htons(ETH_P_ALL))
        return &ptype_all;
    else
        return &ptype_base[ntohs(pt->type) & PTYPE_HASH_MASK];
}

我们整篇文章都以 ETH_P_ALL 为例,但其实有的时候也会有其它情况。在别的情况下可能会注册协议到 ptype_base 里了,而不是 ptype_all。同样, ptype_base 中的协议也会在发送和接收的过程中被执行到。

总结:tcpdump 启动的时候内部逻辑其实很简单,就是在 ptype_all 中注册了一个虚拟协议而已。

四、总结

现在我们再回头看开篇提到的几个问题。

1. tcpdump 是如何工作的

用户态 tcpdump 命令是通过 socket 系统调用,在内核源码中用到的 ptype_all 中挂载了函数钩子上去。无论是在网络包接收过程中,还是在发送过程中,都会在网络设备层遍历 ptype_all 中的协议,并执行其中的回调。tcpdump 命令就是基于这个底层原理来工作的。

2. netfilter 过滤的包 tcpdump 是否可以抓的到

关于这个问题,得分接收和发送过程分别来看。在网络包接收的过程中,由于 tcpdump 近水楼台先得月,所以完全可以捕获到命中 netfilter 过滤规则的包。

但是在发送的过程中,恰恰相反。网络包先经过协议层,这时候被 netfilter 过滤掉的话,底层工作的 tcpdump 还没等看见就啥也没了。

3. 让你自己写一个抓包程序的话该如何下手

如果你想自己写一段类似 tcpdump 的抓包程序的话,使用 packet socket 就可以了。我用 c 写了一段抓包,并且解析源 IP 和目的 IP 的简单 demo。

源码地址:https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/network/test04/main.c

编译一下,注意运行需要 root 权限。

# gcc -o main main.c
# ./main 

运行结果预览如下。

#614 TOML

2021-09-07

我之前说过,YAML 太过复杂,复杂到根本不适合用来作为配置文件。应该是有很多人和我一样想,然后就设计了 TOML 语言。

TOML 就是 Tom's Obvious Minimal Language 的缩写,翻译过来就是汤姆的最精简语言(作者全名 Tom Preston-Werner)。
然后,Slogan 就是 A config file format for humans 也就是:以人为本的配置文件格式。
它的定位已经很明确了吧!

PS: 据说这个语言来自 GitHub(作者的另一个身份是 GitHub 联合创始人)。

其语法说明就只有一个不长的单页面:https://toml.io/cn/v1.0.0
其语法 ABNF 文件,算上注释和空行,一共 243 行。

#613 阮一峰的 C 语言教程学习

2021-09-07
  1. https://wangdoc.com/clang/
  2. GitHub, https://github.com/wangdoc/clang-tutorial

目录

  • 1. 简介
    历史,标准,Hello World
  • 2. 基本语法
    基本语法,printf 格式化
  • 3. 变量
  • 4. 运算符
  • 5. 流程控制
  • 6. 数据类型
  • 整型的不同进制表示
  • limits.h 中的极限值
  • 关于布尔型,int, _Bool(C99, int 别名), bool/true/false (stdbool.h)
  • 字面量后缀 (l, ll, u, f)
  • 类型转换
  • 可移植类型 (stdint.h)
  • 7. 指针
  • 8. 函数
  • 9. 数组
  • 10. 字符串
  • 11. 内存管理
  • 12. struct 结构
  • 13. typedef 命令
  • 14. Union 结构
  • 15. Enum 结构
  • 16. 预处理器
  • 17. I/O 函数
  • 18. 文件操作
  • 19. 变量说明符
  • 20. 多文件项目
  • 21. 命令行环境
  • 22. 多字节字符
  • 23. 标准库
  • 23.1. assert.h
  • 23.2. ctype.h
  • 23.3. errno.h
  • 23.4. float.h
  • 23.5. inttypes.h
  • 23.6. iso646.h
  • 23.7. limits.h
  • 23.8. locale.h
  • 23.9. math.h
  • 23.10. signal.h
  • 23.11. stdint.h
  • 23.12. stdlib.h
  • 23.13. stdio.h
  • 23.14. string.h
  • 23.15. time.h
  • 23.16. wchar.h
  • 23.17. wctype.h

关键字

// 数据类型 12
short   int         long    double  float
char    struct      union   enum    typedef
signed  unsigned

// 变量 6
auto    register    extern  const   volatile
static

// 函数 2
void    return

// 控制语句 11
if      else    for     continue    while  do
switch  case    default goto        break

// 其他 1
sizeof

数据类型

  • 整数:short, int, long, long long
  • signed, unsigned
  • 浮点数:float, double
  • 字符:char, char *
#include <stdio.h>

int main(){
    printf("size of int:         %2ld\n", sizeof(int));
    printf("size of short:       %2ld\n", sizeof(short));
    printf("size of long:        %2ld\n", sizeof(long));
    printf("size of long long:   %2ld\n", sizeof(long long));
    printf("size of float:       %2ld\n", sizeof(float));
    printf("size of double:      %2ld\n", sizeof(double));
    printf("size of long double: %2ld\n", sizeof(long double));
    printf("size of char:        %2ld\n", sizeof(char));
}
$ uname -a
Linux dell 5.11.0-34-generic #36-Ubuntu SMP Thu Aug 26 19:22:09 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ gcc --version
gcc (Ubuntu 10.3.0-1ubuntu1) 10.3.0
Copyright (C) 2020 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc /tmp/test.c -o /tmp/test && /tmp/test
size of int:          4
size of short:        2
size of long:         8
size of long long:    8
size of float:        4
size of double:       8
size of long double: 16
size of char:         1

#612 转载:Diss Golang

2021-09-06

作者可能比较喜欢 C# (C# 的特性你让我丢掉哪一个我都觉得少块肉), 对 Golang 进行了一些批评,认为其设计缺乏远见,存在很多缺陷:

Anders Hejlsberg 和 Microsoft 把最佳设计都端到眼前了,其他语言纷纷各取所需,但是 Golang 的设计者却不为所动。