#92 setcap

2022-02-08

set file capabilities, 设置文件权限

setcap
usage: setcap [-q] [-v] [-n <rootid>] (-r|-|<caps>) <filename> [ ... (-r|-|<capsN>) <filenameN> ]

 Note <filename> must be a regular (non-symlink) file.

#91 OpenWrt 开源路由器

2022-01-31

历史

Linksys WRT54G 系列路由器(2002 年 12 月首次发布)的操作系统是基于 Linux 内核开发。
2003 年,思科旗下的 Linksys 公司面对各方面压力,开源了 WRT54G 源代码。

PS: Linksys 是美国加州的台湾移民创建, 2003 年被思科收购, 2013 年又被 Belkin 收购。2018 年 Belkin 被台湾的富士康收购。

  • 2002 年,开发者基于 WRT54G 开发了 Alchemy 项目,成为相关领域最热门的项目。
  • 2004 年,OpenWrt 项目启动。
  • 2005 年,由于 Alchemy 开发者急于商业化,社区分叉出了 DD-WRT 项目。
    现在依然是一个积极开发中的项目:https://svn.dd-wrt.com/ GitHub
    最后一个稳定版本 24 发布于 2008 年,之后十几年高频度发布 beta 版本:
    https://download1.dd-wrt.com/dd-wrtv2/downloads/betas/
  • 2006 年,Jonathan Zarate 创建 Tomato 项目。
    之前有一个 HyperWRT 项目(2004 - 2006),维护 WRT54G 代码,并支持 Linksys WRT54GS 无线路由器。
    Tomato 项目就是在 HyperWRT 的基础之上继续开发和维护。
    比较独创的是开发了一个 Web 管理界面。
    2014 年之后就没有继续更新,但是有很多子项目,包括现在仍在开发中的 FreshTomato 和 Asuswrt。
  • 2011 年 Asuswrt 项目启动,至今还在积极开发中。
  • 2013 年,Asuswrt-merlin(梅林固件)启动。
    https://www.asuswrt-merlin.net/
    https://github.com/RMerl/asuswrt-merlin
    https://github.com/RMerl/asuswrt-merlin.ng
  • 2016 年,俄罗斯开发者 Padavan 基于华硕固件(Asuswrt)开发 rt-n56u 项目,目标是寻求对华硕路由器设备硬件的绝对控制。
    人们用开发者的名字来当做项目名字 Padavan,俗称“老毛子”。
    和梅林固件专注的是华硕高端机型(封闭的博通芯片)不同,老毛子主要针对的是华硕低端设备(联发科芯片),资源要求低。
  • 2016 年,因为内部分歧,部分核心开发者出走,新起 LEDE 项目(Linux 嵌入式开发环境)。
    两年之后,双方和解,两个项目又合并到一起,采用 LEDE 的制度,继续使用 OpenWrt 品牌。
  • PandoraBox,国内项目,基于 OpenWrt,早期叫做 DreamBox。
    已经很久没有听到相关声音了。

LibreCMC

2014 年,作为 Linux 嵌入式发行版发行。
2015 年,与 LibreWRT 项目(研究项目)合并。
2017 年,基于 LEDE 17。
2020 年,基于 OpenWrt 19。

爱快

iKuai / iKuic (海外)

北京丰台一家网络设备厂商的闭源路由器系统,有商业版和免费版。
https://www.ikuai8.com/component/download

附:List of router firmware projects

  • Linux-based
  • entirely free
    • Endian
    • Floppyfw
    • IPFire
    • LEDE
    • libreCMC
    • OpenWrt
    • VyOS
    • Zeroshell
  • partly proprietary
    • AirOS & EdgeOS
    • Alliedware Plus
    • DD-WRT
    • ExtremeXOS
    • FRITZ!Box
    • RouterOS
    • SmoothWall
    • Tomato
    • Vyatta
  • FreeBSD-based
  • entirely free
    • m0n0wall
    • pfSense
    • OPNsense:pfSense 分叉
  • partly proprietary
    • Junos OS
  • proprietary
  • Cisco IOS
  • ExtremeWare
  • NX-OS
  • TiMOS
  • VRP

参考资料与拓展阅读

#90 Linux 小工具汇总

2022-01-16

常规的系统自带工具就不提了。

  • htop 代替 top
  • jq JSON
  • bat 代替 cat
  • dog DNS 查询
  • tldr 替代 man 文档
  • ack ag 代替 grep
  • fzf
  • fuck
  • mtr 代替 traceroute
  • httppie 代替 wget 和 curl
  • ncdu 代替 du
  • duf 代替 df
  • pydf 代替 df
  • fd 代替 find
  • eva 代替 ls
  • neovim 代替 vim
  • lftp 代替 ftp
  • aria2
  • nnn

参考资料与拓展阅读

#88 plocate

2021-12-15

Fedora 36 将采用 plocate 替代 mlocate 成为默认的查找索引,据说更快。
而且 Debian 也早采纳了 plocate。

安装

apt show plocate
# Package: plocate
# Version: 1.1.8-2
# Priority: optional
# Section: universe/utils
# Origin: Ubuntu
# Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
# Original-Maintainer: Steinar H. Gunderson <sesse@debian.org>
# Bugs: https://bugs.launchpad.net/ubuntu/+filebug
# Installed-Size: 510 kB
# Depends: libc6 (>= 2.33), libgcc-s1 (>= 3.3.1), libstdc++6 (>= 6), liburing1 (>= 0.7), libzstd1 (>= 1.4.0)
# Suggests: systemd-sysv | powermgmt-base, systemd-sysv | nocache
# Breaks: mlocate
# Replaces: mlocate
# Homepage: https://plocate.sesse.net/
# Task: xubuntu-desktop, ubuntu-budgie-desktop, ubuntu-budgie-desktop-raspi
# Download-Size: 119 kB
# APT-Manual-Installed: yes
# APT-Sources: https://mirrors.cloud.tencent.com/ubuntu impish/universe amd64 Packages
# Description: much faster locate
#  plocate is a locate(1) based on posting lists, giving much faster searches
#  on a much smaller index. It is a drop-in replacement for mlocate in nearly
#  all aspects, and is fast on SSDs and non-SSDs alike.

sudo apt install -y plocate

使用

根据 apt show 信息,plocate 会 break mlocate。
updatedb 命令会改由 plocate 提供。

ll /usr/bin/locate /usr/bin/updatedb /etc/alternatives/locate /etc/alternatives/updatedb
lrwxrwxrwx 1 root root 16 2021-12-15 11:51:56 /etc/alternatives/locate -> /usr/bin/plocate
lrwxrwxrwx 1 root root 26 2021-12-15 11:51:56 /etc/alternatives/updatedb -> /usr/sbin/updatedb.plocate
lrwxrwxrwx 1 root root 24 2021-12-15 11:51:56 /usr/bin/locate -> /etc/alternatives/locate
lrwxrwxrwx 1 root root 26 2021-12-15 11:51:56 /usr/bin/updatedb -> /etc/alternatives/updatedb

根据官网提供的演示数据,其 DB 只有 mlocate 的 40%,查询时间只有 mlocate 的万分之四。

其具体操作和 mlocate 类似:

sudo updatedb

locate go.mod

有没有 2500 倍的提升我不知道,但是感觉好像确实快。挺好!

参考资料与拓展阅读

#87 Linux 文件创建时间

2021-12-12
stat /tmp/edge_shutdown_crash.txt
  文件:/tmp/edge_shutdown_crash.txt
  大小:2          块:8          IO 块:4096   普通文件
设备:801h/2049d   Inode:9306564     硬链接:1
权限:(0664/-rw-rw-r--)  Uid:( 1000/ markjour)   Gid:( 1000/ markjour)
最近访问:2021-12-12 10:24:17.318846619 +0800
最近更改:2021-12-12 10:42:27.982887293 +0800
最近改动:2021-12-12 10:42:27.982887293 +0800
创建时间:2021-12-12 10:24:17.318846619 +0800

# %w     time of file birth, human-readable; - if unknown
# %W     time of file birth, seconds since Epoch; 0 if unknown
# %x     time of last access, human-readable
# %X     time of last access, seconds since Epoch
# %y     time of last data modification, human-readable
# %Y     time of last data modification, seconds since Epoch
# %z     time of last status change, human-readable
# %Z     time of last status change, seconds since Epoch
stat -c "%w" /tmp/edge_shutdown_crash.txt
2021-12-12 10:24:17.318846619 +0800

sudo debugfs -R 'stat /tmp/edge_shutdown_crash.txt' /dev/sda1 | cat
debugfs 1.46.3 (27-Jul-2021)
Inode: 9306564   Type: regular    Mode:  0664   Flags: 0x80000
Generation: 3654863154    Version: 0x00000000:00000001
User:  1000   Group:  1000   Project:     0   Size: 2
File ACL: 0
Links: 1   Blockcount: 8
Fragment:  Address: 0    Number: 0    Size: 0
 ctime: 0x61b56193:ea56adf4 -- Sun Dec 12 10:42:27 2021
 atime: 0x61b55d51:4c04da6c -- Sun Dec 12 10:24:17 2021
 mtime: 0x61b56193:ea56adf4 -- Sun Dec 12 10:42:27 2021
crtime: 0x61b55d51:4c04da6c -- Sun Dec 12 10:24:17 2021
Size of extra inode fields: 32
Inode checksum: 0xd223c644
EXTENTS:
(0):39635628

#86 Linux-libre

2021-10-29

内核需要和硬件打交道,中间少不了硬件厂商的支持,可是有些厂商不愿提供相关的资料,只提供了一些二进制文件,无法审查,无法修改。
Linux 内核中一直包含着很多这样的二进制 blob。

#85 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 下也有效。

#84 转载:用户态 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 

运行结果预览如下。

#83 UMIDIGI 与 GPL 协议

2021-09-01

最近发生的一件事情挺有意思的,在得到 “上门自取” 的回复之后,有位美女替外国程序员依照 GPL 协议上门向国内的一家厂商索要内核代码。

深圳公司 UMIDIGI 生产安卓手机(联发科 Mediatek),面向海外用户。

波兰开发者 Patrycja (@ptrcnull) 想将移植 postmarketOS (基于 Alphine Linux) 到 UMIDIGI 的一款设备上,结果发现缺少 ft8719_dsi_fhdplus 屏幕驱动。

Patrycja 尝试联系 UMIDIGI,得到了以下回复:

Twitter

就是说,如果你要的话,你可以上门来取。我们可以认为这是想让 Patrycja 知难而退。

可是 Patrycja 八月 17 号在 Twitter 上抱怨之后,深圳科技博主 Naomi Wu 机械妖姬(@RealSexyCyborg,混 Youtube)主动提供帮忙,表示愿意代替 Patrycja 上门索取源代码。

然后机械妖姬 08/20 就拿着自拍杆勇闯 UMIDIGI 公司。
UMIDIGI 行政人员表示需要之前发邮件的 BEN 已经离职(我觉得很可能就坐在旁边看美女),然后提供源代码的事情需要先向老板请示,之后在相对友好的氛围下,机械妖姬离开了。

机械妖姬前往 UMIDIGI 公司

随后:

  • 08/25 UMIDIGI 向机械妖姬提供了相关文件。
  • 08/26 Patrcja 完成了系统移植,并向机械妖姬和 UMIDIGI 表示感谢。

这件事件我的评价是:

UMIDIGI 之前的邮件回复十分愚蠢,直接向法务部门咨询之后,通过合法的途径(可能涉及联发科的知识产权)将代码提供给他不就好了吗?
不过,如果不是事情闹大了,UMIDIGI 会不会提供源代码呢?这就不知道了,我们也不能以恶意揣度之。只能就事论事,在这次事件中,各方的表现都非常好。