跳转至

Recent Blog

download music to ipod shuffle 4

install ipod-shuffle-4g.py

Python script for building the Track and Playlist database for the newer gen IPod Shuffle. Forked from the shuffle-db-ng project and improved my nims11 and NicoHood.

automatically or manually mount ipod

mount /dev/sdb /mnt

copy mp3 or m4a file into ipod

/mnt/iPod_Control/Music/F00/

command of update play list

sudo ./ipod-shuffle-4g.py -d 1 /mnt/

copy music to iPhone in Linux

sudo apt update
sudo apt install ifuse libimobiledevice-utils

pair iphone, unlock and trust pair in the iphone

idevicepair pair

list supported app installed in the iPhone

ifuse --list-apps

mount vlc's app directory to Linux

ifuse --documents org.videolan.vlc-ios ~/iphone_vlc

copy Music file to ~/iphone_vlc

finally fusermount -u ~/iphone_vlc

virtio nic configure multi-queue

virtio虚拟出来的网卡默认是单队列的,ip link 显示如下。 多核系统下,单队列要要阻塞收发, 影响性能, 多队列可以并发,内核也支持(TUN/TAP)。这里记录开启多队列的方式。 因为virtio的限制,队列最大256个。

2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 52:54:00:16:b5:50 brd ff:ff:ff:ff:ff:ff

libvirt 或者 virsh 修改网卡xml文件

<interface type='network'> 
      <source network='default'/> 
      <model type='virtio'/> 
+     <driver name='vhost' queues='N'/> 
</interface> 

qemu

qemu-kvm -netdev tap,id=hn0,queues=N -device virtio-net-pci,netdev=hn0,vectors=2M+2 ...

openstack

openstack image set <IMG_ID> --property hw_vif_multiqueue_enabled=true

验证

ip link 的输出多了mq

$ ip link
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000    

$ ethtool -l enp1s0
Channel parameters for enp1s0:
Pre-set maximums:
RX:             0
TX:             0
Other:          0
Combined:       256
Current hardware settings:
RX:             0
TX:             0
Other:          0
Combined:       40

参考

https://docs.paloaltonetworks.com/vm-series/10-1/vm-series-deployment/set-up-the-vm-series-firewall-on-kvm/performance-tuning-of-the-vm-series-for-kvm/enable-multi-queue-support-for-nics-on-kvm https://cloud.vk.com/docs/en/computing/iaas/how-to-guides/vm-multiqueue https://www.linux-kvm.org/page/Multiqueue

lxc enable tun device

在lxc中运行tailscale时,默认使用tun模式,就是创建一个虚拟网卡,然后用路由规则将出去的流量都路由到tun的虚拟网卡中。但是lxc中默认没有'/dev/net/tun',这时tailscaled 启动时会尝试执行'modprobe tun',结果lxc的内核版本和宿主的不一致,所以导致下面的报错。解决办法有2个,一是使用tailscale的userspace networking(也就是sock5代理),二是将宿主机的tun设备绑定到lxc容器中。

Aug 21 15:20:22 debian-dockers tailscaled[22673]:Linux kernel version: 6.1.10-1-pve
Aug 21 15:20:22 debian-dockers tailscaled[22673]: is CONFIG_TUN enabled in your kernel? `modprobe tun` failed with: modprobe: FATAL: Module tun not found in directory /lib/modules/6.1.10-1-pve
Aug 21 15:20:22 debian-dockers tailscaled[22673]: kernel/drivers/net/tun.ko found on disk, but not for current kernel; are you in middle of a system update and haven't rebooted? found: linux-image-5.10.0-32-rt-am>
Aug 21 15:20:22 debian-dockers tailscaled[22673]: wgengine.NewUserspaceEngine(tun "tailscale0") error: tstun.New("tailscale0"): CreateTUN("tailscale0") failed; /dev/net/tun does not exist

使用userspace networking

修改/etc/default/tailscaled文件,修改FLAG为'FLAGS="--tun=userspace-networking"',这样tailscaled就不会去创建tun设备,而只是暴露一个端口,供应用来使用。

使用tailscale的方法

ALL_PROXY=socks5://localhost:1055/ HTTP_PROXY=http://localhost:1055/ http_proxy=http://localhost:1055/ ./my-app

另外也可以手动启动tailscaled

tailscaled --tun=userspace-networking --socks5-server=localhost:1055 --outbound-http-proxy-listen=localhost:1055 &
tailscale up --auth-key=<your-auth-key>

给lxc容器添加tun设备

将下面两行添加到容器配置文件,例如容器id为100, 则配置文件的路径是/etc/pve/nodes/lxc/100.conf

lxc.cgroup2.devices.allow: c 10:200 rwm
lxc.mount.entry: /dev/net dev/net none bind,create=dir

lxc enable fuse

当lxc挂载 webdav 文件系统时,依赖fuse 驱动,可以在pve的lxc选项里添加feature。 参考

参考

https://tailscale.com/kb/1112/userspace-networking https://github.com/tailscale/tailscale/issues/6941

获取其他进程打开的文件描述符

fd是文件描述符,每个进程的fd是不共享的,只能在当前进程操作。可以通过命令ls /proc/self/fd -l看到打开的fd。例如一眼能猜到0,1,2就是输入输出和错误输出都关联到了终端。那如果要从其他进程写入打开的文件呢,那就要先获取到fd, 当然不能复制fd的值,因为这个值只对本进程有效。

fd

# ls /proc/self/fd -l
total 0
lrwx------ 1 hst hst 64 Jul 24 14:59 0 -> /dev/pts/13
lrwx------ 1 hst hst 64 Jul 24 14:59 1 -> /dev/pts/13
lrwx------ 1 hst hst 64 Jul 24 14:59 2 -> /dev/pts/13
lr-x------ 1 hst hst 64 Jul 24 14:59 3 -> /proc/2453780/fd

而sendmsg 和 CMSG可以将本进程打开的文件描述符发送给其他进程,其他进程也能读写该文件。当然是在同主机下。

注意 fork, CMSG, pidfd_getfd 获得的文件node是共享的,包括写入读取的偏移量

sendmsg/recvmsg 发送接收文件描述符

发送

void send_fd(int socket, int fd_to_send) {
    struct msghdr msg = {0};
    char buf[CMSG_SPACE(sizeof(int))];
    memset(buf, 0, sizeof(buf));

    struct cmsghdr *cmsg;
    char iobuf[1] = {0};
    struct iovec io = {
        .iov_base = iobuf,
        .iov_len = sizeof(iobuf)
    };

    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = buf;
    msg.msg_controllen = sizeof(buf);

    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));

    memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));

    if (sendmsg(socket, &msg, 0) == -1) {
        perror("sendmsg");
        exit(-1);
    }
}

接收

int recv_fd(int socket) {
    struct msghdr msg = {0};
    char m_buffer[1] = {0};
    struct iovec io = {
        .iov_base = m_buffer,
        .iov_len = sizeof(m_buffer)
    };

    char cmsgbuf[CMSG_SPACE(sizeof(int))];
    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = cmsgbuf;
    msg.msg_controllen = sizeof(cmsgbuf);

    if (recvmsg(socket, &msg, 0) == -1) {
        perror("recvmsg");
        exit(-1);
    }

    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
    int fd;
    memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
    return fd;
}

更简单的方法获取文件描述符 pidfd_getfd

Linux 5.6后,pidfd_getfd 可以用来获取其他进程打开的fd,因为glibc没有包装这个函数,所以用syscall,汇编代码里也看到syscall。

       int syscall(SYS_pidfd_getfd, int pidfd, int targetfd,
                   unsigned int flags);

       Note: glibc provides no wrapper for pidfd_getfd(), necessitating
       the use of syscall(2).

还有更简单的fork继承文件描述符

fork前先打开文件,这样两个进程都能读写文件了。

int fd = open("text.txt", O_RDONLY);
fork();

reference

https://www.man7.org/linux/man-pages/man3/cmsg.3.html https://man7.org/linux/man-pages/man2/pidfd_getfd.2.html https://stackoverflow.com/questions/2358684/can-i-share-a-file-descriptor-to-another-process-on-linux-or-are-they-local-to-t

Non-constant time comparison bug

Tailscale 给我发的邮件说到了这个BUG, 发现挺有趣的。

Hello, We recently fixed an insecure non-constant time comparison bug in the Tailscale DERP server that could have caused DERP servers to leak their private mesh keys via a timing side channel. We have deployed this fix to all Tailscale-operated DERP servers and rotated their mesh keys.

BUG原因

非固定时长的比较,就是比较次数约多,耗时越长,例如常见for循环,还有'memncmp'。利用这个时间差异,也许能够猜到密码。

for (i = 0; i < len; i++) {
    if (a[i] != b[i]) return false;
}

解决办法

使用固定时间(constant time)的比较函数,例如改成

flag = true
for (i = 0; i < len; i++) {
    if (a[i] != b[i]) flag = false;
}
return flag;

Learn GPIO

GPIO是通用目的的输入输出引脚,可以配置很多功能,因为每个GPIO引脚都有许多寄存器,例如4个控制寄存器,2个数据寄存器,1个复位寄存器,1个时钟寄存器,2个复用功能寄存器。
树莓派,ESP32, arduino(Atmel328p)都有GPIO引脚。

GPIO 的几种模式

  • 输出: 点亮灯泡。又分为推挽(PUSH_PULL) 这是默认的输出模式(高/低电平); 开漏(OPEN_DRAIN)用得少,这是只能将引脚设置成低电平,要靠外部电路实现高电平,应用在例如i2c总线。
  • 输入: 按钮控制。又分为内部上拉 PULL_UP 引脚默认高电平; 内部下拉 PULL_DOWN 引脚默认低电平。

GPIO 输出数字信号和PWM

无论是PUSH_PULL还是OPEN_DRAIN,GPIO引脚只有高电平和低电平两种状态,这也叫数字信号,例如下面的发送摩斯码。 PWM信号实际是高低电平的串行信号组(例如__------),ESP32的所有GPIO支持输出PWM信号,但推荐使用 GPIO 4, 5, 12–14, 18–19, 21–23, 25–27, 32–33。 PWD信号用来驱动步进电机。

GPIO 输出模拟信号

模拟信号(analog)可以驱动喇叭播放声音,ESP32中只有GPIO 25-26 支持输出模拟信号。

from machine import DAC, Pin

dac = DAC(Pin(25))     # Use GPIO25
dac.write(128)         # Output ~1.65V (128/255 × 3.3V)

GPIO 按钮实现发送摩斯码

from machine import Pin
import time

button = Pin(15, Pin.IN, Pin.PULL_UP)
morse = ""
press_start = 0
last_event_time = time.ticks_ms()

MORSE_TABLE = {
    ".-":"A","-...":"B","-.-.":"C","-..":"D",".":"E",
    "..-.":"F","--.":"G","....":"H","..":"I",".---":"J",
    "-.-":"K",".-..":"L","--":"M","-.":"N","---":"O",
    ".--.":"P","--.-":"Q",".-.":"R","...":"S","-":"T",
    "..-":"U","...-":"V",".--":"W","-..-":"X","-.--":"Y",
    "--..":"Z", "": " "
}

def decode(morse_code):
    return MORSE_TABLE.get(morse_code, "?")

while True:
    now = time.ticks_ms()

    if button.value() == 0:  # Press detected
        press_start = now
        while button.value() == 0:
            time.sleep(0.01)
        press_duration = time.ticks_diff(time.ticks_ms(), press_start)
        symbol = "." if press_duration < 300 else "-"
        morse += symbol
        last_event_time = time.ticks_ms()
        print("Symbol:", symbol)

    # Check for end of character
    if morse and time.ticks_diff(now, last_event_time) > 1000:
        char = decode(morse)
        print("Char:", char)
        morse = ""

    time.sleep(0.01)

引用

https://blog.51cto.com/u_4029519/13530340 https://blog.csdn.net/makeryzx/article/details/78915955

pci passthrough

Linux中可以将硬件分配给虚拟机独占使用,或者给应用独占(dpdk),此时内核不负责该硬件。这涉及一系列东西(内核驱动卸载,vfio, iommu等)

dpdk

对于支持vfio(支持或不支持iommu)的主机,dpdk或者宿主机可以绑定vfio驱动。除此之外dpdk还支持uio。

sr-iov 基于硬件虚拟化

绑定vfio-pci驱动后,可以支持一些硬件offload功能

IOMMU 是 DMA 的进化

当CPU要读取外设的数据时,发内存传输指令,由DMA执行,此时cpu可以做别的事情。而DMA是直接内存映射,需要连续的空间来支持大块的内存,而IOMMU则不需要,而且还能支持申请比系统寻址能力更大的内存,(例如32位只能支持4G, IOMMU能支持4G以上)。
但IOMMU需要硬件支持的功能,intel vt-d 和 amd amd-vi支持iommu,但arm不支持。

iommu

reference

https://pve.proxmox.com/wiki/PCI(e)_Passthrough https://doc.dpdk.org/guides/linux_gsg/linux_drivers.html#vfio-noiommu https://en.wikipedia.org/wiki/Input%E2%80%93output_memory_management_unit

defualt route in linux

默认路由非常重要,当对端地址在路由表中查不到从哪个网口出去时,就走默认路由对应的网卡。

即使是只有一张网卡,也应该配默认路由

最近遇到问题,使用命令ip addr add 10.9.3.211/24 dev enp1s0给虚拟机加ip后,路由表如下:

#ip route
10.9.3.0/24 dev enp1s0 proto kernel scope link src 10.9.3.211 
能ping网关和其他同网段主机,但ping不同其他网段的主机,显示'network unreachable', 反向也不行。使用命令ip route add default via 10.9.3.1 dev enp1s0配置默认路由,问题就解决了。而通过网卡配置和网卡启动脚本例如ifup,或者网络管理工具NetworkManager,是会自动添加默认路由的,所以不存在这个问题。

拓展1,路由Metric

所有匹配不到的包就走默认路由指定的网关,如果存在多个网卡,可以有多个默认路由如下,这时候靠metric来指定优先级,metric在配置里指定

default via 10.14.160.1 dev enp1s0 proto static metric 90 
default via 10.14.162.1 dev enp10s0 proto static metric 100 
default via 10.14.161.1 dev enp9s0 proto static metric 100 
这时10.14.160.0/24网段的包走enp1s0,10.14.162.0/24走enp10s0, 10.14.161.0/24走enp9s0,因为enp1s0的metric最小,优先级最高,其他网段的包走enp1s0

手动再添加路由,优先级比默认路由高

例如指定1.1.1.1走非默认路由,

ip route add 1.1.1.1 via 10.14.161.1 dev enp9s0 
这时ping 1.1.1.1就不走默认路由了。

lookup 设置表的优先级

上面的手动添加路由可以指定非3个网段的包走非默认路由,但是无法超越同网段的优先级,意思就是无法指定10.14.161.0/24的包走10.14.160.1这个网关。这时就需要创建新的路由表,通过表的优先级来实现

echo "200 custom1" | sudo tee -a /etc/iproute2/rt_tables
sudo ip route add 10.14.162.100 via 10.14.160.1 dev enp1s0 table custom1
sudo ip rule add to 10.14.162.100 lookup custom1

不过似乎大部分情况没意义,我遇到有意义的情况是使用了tailscale exit node时,所有包都走了隧道。如果希望本地某个主机不走隧道,就需要lookup 自定义路由表。

dpdk-testpmd to test NIC speed

启动,进入dpdk 交互模式

./dpdk-testpmd -l 0-3 -n 4 --vdev=net_tap0,iface=tap0 -a 0000:07:00.0 -- -i --port-topology=chained 
./dpdk-testpmd -l 0-3 -n 4   --vdev 'net_bonding0,mode=0,member=0000:08:00.0,member=0000:09:00.0' --vdev=net_tap0,iface=tap0 -- -i --port-topology=chained

#确认
testmpd> show port summary all 

#设置转发
testpmd> set fwd io 

#启动转发
testpmd> start 

设置tap设备的ip

ip add add 10.9.108.211/24 dev tap0
ip route add 10.9.3.13 via 10.9.108.211 dev tap0
#启动
iperf -s

dpdk bonding

Bonding mode 要在dpdk-testpmd创建1个bond网卡,转发bond网卡和tap之间的流量。启动时要注意EAL日志,参数写错了会报错了。

# ./dpdk-testpmd -l 0-3 -n 4  --vdev=net_tap0,iface=tap0 -- -i --port-topology=chained

Configuring Port 0 (socket 0)
Port 0: D0:9F:D9:70:6E:77
Configuring Port 1 (socket 0)
Port 1: D0:9F:D9:70:6E:76
Configuring Port 2 (socket 0)
Port 2: C6:44:56:84:8C:92
Checking link statuses...
Done

  1. 创建bond网卡,1 表示 mode, 0 是socket id。输出显示创建的bond网卡id是3

    testpmd> create bonded device 1 0
    Created new bonded device net_bonding_testpmd_0 on (port 3).
    

  2. 打印所有网卡状态, 这里0 和1 是将被bonding的网卡,2是tap设备, 3是bond网卡。

    testpmd> show port stats all
    

  3. 添加slave网卡。注意建议先关掉bond网卡,再添加slave网卡。

    testpmd> add bonding slave 0 3
    testpmd> add bonding slave 1 3
    

  4. 查看bond网卡配置, 如果添加slave网卡前没有停bond网卡,就会出现下面的打印,虽然Slave网卡显示有两个,但是Active的是空的。这时需要关闭再启动bond网卡。

    testpmd> show bonding config 0
    show bonding config 3
            Bonding mode: 1
            Slaves (2): [0 1]
            Active Slaves: []
            Primary: [0]
    

  5. 关开bond网卡
    testpmd> port stop 3
    Stopping ports...
    Done
    testpmd> port start 3
    Configuring Port 3 (socket 0)
    
  6. 设置转发网卡id,以及启动转发。

    testpmd> set portlist 2,3
    testpmd> start
    

  7. 修改Bond模式

    set bonding mode 3 2
    

using nmcli to create bond device

nmcli connection add type bond con-name bond0 ifname bond0 bond.options "mode=active-backup"
nmcli connection add type bond con-name bond0 ifname bond0 bond.options "mode=4"
nmcli connection add type ethernet slave-type bond con-name bond0-port1 ifname enp9s0 master bond0
nmcli connection add type ethernet slave-type bond con-name bond0-port2 ifname enp10s0 master bond0

nmcli connection modify bond0-port1 ipv4.method disabled ipv6.method disabled
nmcli connection modify bond0-port2 ipv4.method disabled ipv6.method disabled

nmcli connection modify bond0   ipv4.addresses 10.13.32.200/24 ipv4.gateway 10.13.32.1

---

nmcli connection add type bond con-name bond1 ifname bond1 bond.options "mode=active-backup"
nmcli connection add type ethernet slave-type bond con-name bond1-port1 ifname enp7s0d2 master bond1
nmcli connection add type ethernet slave-type bond con-name bond1-port2 ifname enp7s0d3 master bond1

nmcli connection modify bond1-port1 ipv4.method disabled ipv6.method disabled
nmcli connection modify bond1-port2 ipv4.method disabled ipv6.method disabled

nmcli connection modify bond1   ipv4.addresses 10.13.31.200/24 ipv4.gateway 10.13.31.1

HOST Machine

#Bond1 32.200
i40e 0001:08:00.0 enP1s6f0: renamed from eth0
i40e 0001:08:00.1 enP1s6f1: renamed from eth0

#Bond0  31.200
rnpm 0000:05:00.1 ens2f1d2: NIC Link is Up 10 Gbps, Flow Control: RX/TX
rnpm 0000:05:00.1 ens2f1d3: NIC Link is Up 10 Gbps, Flow Control: RX/TX

Virtual Machine

#bond0  31.200
rnpm 0000:05:00.0 enp5s0d2: renamed from eth2 
rnpm 0000:05:00.0 enp5s0d3: renamed from eth3

#bond1  32.200
i40e 0000:06:00.0 enp6s0: renamed from eth1
i40e 0000:07:00.0 enp7s0: renamed from eth0

ipsec 和 vowifi

记录了ipsec和vowifi的心得。

ip 分片

当ip包长度大过MTU(以太网通常是1500)时,网卡(内核)会分成多个ip包发送。

ipsec隧道的MTU

以太网的MTU都是1500, 但是因为ipsec带来的overhead, MTU会设置小一点避免路由二次分包。

NAT模式,手机连接路由器, 真实vowifi场景

进入adb可以看到ipsec隧道ipsec272@lo 此时没有注册。
ipsec tunnel

手机注册vowifi成功后,获得了虚拟ip, 修改了ipsec隧道网卡的MTU。
ipsec tunnel ipsec tunnel

非NAT,tunnel模式

先了解ESP带来的消耗

wireshark * 外层ip头总长度1496 * ESP SPI 字段长度4 * ESP Sequence 字段长度4 * ESP IV 长度16 AES-CBC * ESP ICV 长度12 SHA1-96 * ESP Pad 长度1 无padding * ESP Next Header 长度1

ESP总共是38 byte。外层ip总长度(1496) - 内层ip总长度(1438) - 外层ip头(20)= 38 byte。 外层ip头+ESP一共是58。
所以: 1. 避免路由下一跳二次分包,需要预留空间给ipsec头。ipsec隧道网卡的MTU应该设置为1500 - 58 = 1442 2. 为了避免本地网卡分包,ip包的大小也应该小于MTU 1442。则: * TCP MSS = 1442 - ip头(20) - tcp头20 = 1402 * UDP payload = 1442 - ip头(20) - udp头8 = 1414

dpdk

dpdk 支持 ipsec 的加解密,可以管理硬件加解密设备(智能网卡)和软件加解密设备(通用cpu上计算)。这里都是软件加解密,x86和arm上用不同的软件加解密库。

advantages of RTE_SECURITY_ACTION_TYPE_CPU_CRYPTO over RTE_SECURITY_ACTION_TYPE_NONE

None模式的没走SECURITY的api , 要自己往队列里送op(operation), 所以有 ipsec_process 和 drain 操作。 而cpu模式是串连到一起的。 没有送和收,直接调用加解密设备(软件/硬件)。

tcp over ESP 分析小记

在排查网络问题时,不得不分析tcp报文,积累了一些知识:

  1. wireshark中用方括号[]包起来的是wireshark分析提供的,而不是报文的字段。例如[TCP Retransmission]
  2. wireshark中用用 tcp.seq = SEQ 和tcp.ack = SEQ+LEN 来定位发送和回复报文
  3. TCP重发间隔,TCP的防庸塞,重传的间隔是变化的,第一次重传可能200ms, 第二次可能400ms, 第三次可能1600了,感觉是指数递增。
  4. TCP重传间隔还跟ACK等待有关,ACK会晚一点回复,看是否有更多包过来,然后合并ACK。
  5. TCP发送时,关键字段有SEQ和LEN, 分别表示数据偏移起点和数据长度。而ACK消息的ACK表示收到了多少字节数据,而且ACK总是累计值,就是全部收到,而不是部分收到。例如发送端发了两个报文SEQ=100/LEN=50, SEQ=150/LEN=50, 然后对端回复ACK=200,意思是两个报文都收到了。

SACK(Selective ACK)

前面说了TCP的ACK总是累积的(cumulative), 而当启用了SACK, 我遇到是默认启用了。如果第一个包没有收到,就收到第二个包和后面的包,会回复SACK包,SACK的关键信息如下

[TCP Dup ACK #NB1] SLE=NB2 SRE=NB3 
这表示接收端缺少了SEQ=NB1, LEN这个包,并收到了从NB2->NB3的包。接收端请求重发NB1包,并且不会丢弃NB2->NB3之间的包。

参考

SLE/SRE