跳转至

Welcome

A self-host blog - Status Page
- Email to Me


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

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 自定义路由表。

使用yt-dlp工具下载QQ音乐的MV

获取m3u8文件

  • Right click -> Inspect -> Network tab.
  • Enter m3u in the filter box.
  • Refresh the page, then click play on the video.
  • Several results will pop up in the network tab, in this case 15Mps.m3u8 was the 4k version. There's lower quality (10/5Mps) ones as well.
  • Right click on the 15Mps.m3u8 line, copy the url.

下载

yt-dlp  https://apd-e884a42ebcb1819c8e185bdd22d6b32d.v.smtcdns.com/mv.music.tc.qq.com/AI85i7JvkmyF4poFqhirDtuEnNIwyQjQKiMDd0TFbAjw/6371017979FE2EAD13F0216756673D8F64FF084085A30F1293F38C7A31471243BB1F9358EDD23833D48FCB2D2AC4B515ZZqqmusic_default/qmmv_0b6biyaaeaaapqaa3pcdpjrfirqaajdaaasa.f9944.m3u8

提取音频

-ss 14s 跳过了MV开头的剧情

ffmpeg -i video_audio.mp4  -map 0:1 -ss 14s   -c:a copy  audio.m4a
使用mediainfo查看音频质量,比特率192kbps,对比其它音乐文件,压缩率挺高的。
mediainfo output.m4a | grep "Bit rate" 
Bit rate mode                            : Constant
Bit rate                                 : 192 kb/s

参考

https://www.reddit.com/r/DataHoarder/comments/1ab8812/how_to_download_blob_embedded_video_on_a_website/

tailscale 使用记录

tailscale 免费最大提供10个节点,足够个人使用。在如今大内网的时代,非常方便组建子网,实现家中和公司互联互通。

添加DERP节点

如果两个节点有一个有公网IP, 或者能打洞(NAT1. NAT2),那么两个节点可以直连,速度很快。否则要通过DERP节点转发,但是tailscale在大陆没有DERP节点,HK的DERP节点都有300+的延迟,完全没法用。 所以只能自己搭建或租赁一个在大陆的私人搭建的节点。

出口节点

出口节点A是流量可以从这个节点出去,比如节点B可以从A查询IP地址时,显示的ip是节点A的公网ip 而不是B的。

转发到出口节点

在B节点上设置流量都从节点A出去 tailscale up --exit-node=

添加转发例外

启动转发后,所有流量都会从出口节点走。如果单机就没事,但是局域网多台电脑需要交互,例如从局域网主机C访问主机B, 由于主机B的流量都从节点A出去了,会导致主机C收不到请求答复。结果是C 无法ping B, C也不能ssh登录B。 所以要添加例外

将来自本地局域网主机C的流量不被转发到出口节点。例如路由器的子网是192.168.10.0/24,主机C是192.168.10.111, 本机ip是192.168.10.100

# ip rule add to <your-local-network> lookup main
ip rule add to 192.168.10.111/32 lookup main

adb remote forward

连接安卓设备后,通过adb连接实现远程桌面的方法

  1. 首先跳板机连接安卓,这里是wifi方式,adb connect 192.168.0.32:5555
  2. 启动隧道转发,ssh -vCN -L5038:localhost:5037 -L27183:localhost:27183 adb@SERVER -p PORT
  3. 在另一台设备上使用scrcpy远程安卓
    user@client:~$ export ADB_SERVER_SOCKET=tcp:localhost:5038
    user@client:~$ scrcpy --force-adb-forward -b2M -m800 --max-fps=15
    

对于不能有线连接设备的情况,通过下面命令开启无线adb

在adb shell中操作

# This line enables adbd to run as tcpip by default
root@device:~$ echo "service.adb.tcp.port=5555" >> /system/build.prop

# This line enables adbd to accept input from keyboard & mouse using scrcpy
root@device:~$ echo "persist.security.adbinput=1" >> /system/build.prop

# This line enables adbd
root@device:~$ echo "persist.service.adb.enable=1" >> /system/build.prop

# After that, just reboot the DEVICE
root@device:~$ reboot

参考

https://psabadac.medium.com/ssh-adb-9d92c676d8c0

吉他学习

最常用的10个和弦

Em
食指放在5弦的第二品上,中指放在4弦的第二品上, 或用中指放在5弦的第二品上,无名指放在4弦的第二品上

D
食指放在3弦的第二品上,中指放在1弦的第二品上,无名指放在2弦的第三品上

C
食指放在2弦的第一品上,中指放在4弦的第二品上,无名指放在5弦的第三品上

G
食指放在5弦的第二品上,中指放在6弦的第三品上,无名指放在1弦的第三品上, 或中指放在5弦的第二品上,无名指放在6弦的第三品上,小指放在1弦的第三品上

Am
食指放在2弦的第一品上,中指放在4弦的第二品上,无名指放在3弦的第二品上

F
食指大横按在一品上,中指放在3弦的第二品上,无名指放在5弦第三品上,小指放在4弦第三品上
F
F

E
食指指放在3弦的第一品上,中指放在5弦第二品上,无名指放在4弦第二品上

Dm
食指放在1弦第一品上,中指放在3弦第二品上,无名指放在2弦第三品上

A
食指放在4弦第二品上,中指放在3弦第二品上,无名指放在2弦第二品上

Bm
食指大横按放在第二品上,中指放在2弦第三品上,无名指放在4弦第四品上,小指放在3弦第四品上
Bm

八个实用的吉他和弦转换技巧

1.压熟要转换的和絃,确保没有杂音 2.找出共通的手指 C-Am 3.练习手指移动的速度,先从练习移动低音的手指开始 C-F 4.相同指型的手指可以一起移动

引用

https://www.zhihu.com/tardis/bd/ans/1797295715