Browsed by
作者:fengidri

家用透明代理

家用透明代理

家里用的透明代理, 时间长了之后, 我自己也有些不记得. 这里记录一下.

域名解析

一些域名在国内解析有问题, 但是如果所有的 DNS 解析都代理, 得到的 IP 可能是
国外的, 有一些其它的解决方案. 我这里只把一些指定的域名走了代理.

用的是 dnsmasq 在 etc 下面增加一个文件用于纪录这些域名

server=/slack-msgs.com/127.0.0.1#5353

这一行配置表示, 把域名为 slack-msgs.com 的所有的域名在解析的时候都走 127.0.01 的 5353 端口.

我在 ss-tunnel 在 5353 进行了监听.

这样解决了域名解析的问题.

IP 透明代理

使用 ss-redir 配合 iptables 来实现这个功能.

简单说明一下, iptables 把指定的包转发到 ss-redir 上. 而 ss-redir 可以从系统知道原来这个包是发给谁的.
ss-redir 把数据使用 socks5 得到发送出动. 把收到的数据再返回给内网的请求者.

iptable 先创建一个nat chain 先通过 match-set 来分析目的 ip 的归属, 如果是国内的还有内网的直接 return.
其它的就导给 ss-redir.

其后把 chain 回到 nat 上去.

这里记得对于 socks 服务器地址要路过了别也走了代理就是麻烦了.

bbr

bbr

简单记录一些 bbr 的学习笔记, 这些东西大多是理论性的, 并且平时用到的时候比较少. 容易忘记.

发包, 最基本的方式是什么? 就是发一个, client 确认了我再发下一个, 这个应该是最基本的方式.
但是这个方式慢, 慢在哪里呢? 在等对方回包的过程中, 也是可以发包的啊.

其实发送端可以一直发啊, 不停地发. 这样必然是最快的. 但是出现的问题就是, 网络会被打死, 这个
情况出现过. 所以就有 TCP 公平性的问题.

最慢的方式不行, 一直发也不行. 一直发的问题在于可能会出现网络拥塞.

所以有了拥塞算法. 拥塞算法, 我认为就是做了一件事, 我一直发的话, 什么时候要停下来.
这个值叫做发送窗口 cwnd.

这个值的模型一直都是对于网络容量(BDP)进行估算到 BBR 也没有改变这一点. 也就是网络上可以放多少
包.

所以核心的问题就是测试 BDP 的大小. 应用这个值到发送窗口.

BBR 之前的所有的算法都是当出现了丢包的时候认为网络已经满了. 在出现丢包之前一点点地增加发送窗口.

对于丢包的认识 30 年前应该没有什么问题, 但是现在已经不一样的. 除了 BDP 满了, 还有一些错误包之类这些都会出现丢包.
而 BBR 一个改动就是不再依赖丢包来判断 BDP. 它不再关心丢包这个事情. BBR 采用的方式是分别计算带宽和延迟来得到 BDP.

除这个之外, BBR 还接管了 tcp 的发送的控制权. 解决 tcp 可靠传输/拥塞控制解耦. 使用 pacing 来控制发包的速度.

tex

tex

frame vs lstlisting

在 beamer frame 中使用 litlisting 要增加 fragile

\begin{frame}[fragile]

beamer 全屏一个张图片

\makeatletter
\newlength\beamerleftmargin
\setlength\beamerleftmargin{\Gm@lmargin}
\makeatother

\begin{frame}
\hspace*{-\beamerleftmargin}%
\pgfimage{./iitech2.png}%
\end{frame}

Raw DNS

Raw DNS

最近想用 raw socket 发送 DNS 请求. 遇到了一些问题. 纪录在这里.

sendto

raw socket 最后发送数据的时候, 使用 sendto. sendto 要传入对方的地址, 那么这里可以不写吗? 因为我们已经自己构造了 IP 包. 实际是要写的,
因为 raw socket 是构造的 ip 层以下的内容, 那链路层的信息是怎么生成的呢? 就是通过传给 sendto 的参数. 如果在内网里请求外网的地址, 那么你给 sendto 的地址也是外网的,
那怕是和你 ip 层的地址不同, 也是可以正常送达的, 因为都是可以正常送给路由器的.

UDP checksum

UDP 的 checksum 的计算是居然是另外构造了一个结构体进行计算的, 这让我很意外. 以前也没有特别关注过这个问题.

struct pseudo_header
{
    u_int32_t source_address;
    u_int32_t dest_address;
    u_int8_t placeholder;
    u_int8_t protocol;
    u_int16_t udp_length;
};

这个结构体之后再加上 UDP 的报头还有 payload 进行checksum 计算.

checksum offload

这个是占用了我最多时间的, 因为我先构造了一个 dns 报文, 使用正常的 UDP socket 进行发送了. 发现 UDP 的 checksum 是一个不正确的值, 我以为自己计算有问题, 就直接使用了这个值.
但是我的报文怎么也发送不成功. 在这个上面浪费了大量的时候, 比较我构造的包与使用 UDP socket 包的区别.

最后, 看着 wireshark 提示的 “UDP checksum offload” 知道了. 这里, UDP socket 在 checksum 的时候, 实际没有进行 checksum 计算, 而是利用了网卡的 offload 功能, 让网卡进行计算的.
我得到的包的时候, 还没有进入网卡, 所以我看到 checksum 的值并不正确.

代码

 github 

启动 U 盘

启动 U 盘

dd

这个对于 iso 文件是有要求的, 一般 linux 发行版本的 iso 都是可以支持.
但是 win 的 iso 是不行的. 当然有一些主板是支持的, 这个看运气了.

分区

创建分区, 过程中, 发现 mac os 会检查分区表里的文件系统类型, 只有支持的
才会识别. 并且要与实际的文件系统一样. 注意的 ntfs 不是 67, 68 而是应该用 7.
这个类型也包括了 exfat.

而如果有多个分区, win 如果发现第一个分区不能识别那就会直接不能识别后面的
分区了, 所以数据分区要放在第一个.

还有一个问题是 mac os 下不能写 ntfs 文件系统, 没有专门去了解. 只是
大概是不能写的.

所以对于 U 盘的分区结构就是:

  • exfat 格式, 用于保存数据, 占了主要的大小. 这个格式可以支持大于 4g 文件.
  • ext4 这个用来保存 grub 的数据.

使用 fat32 安装 grub, 不能成功启动. 改用 exfat 测试一下. 分区结构进行调整到 一个独立的分区, 同时打开 boot.
同时发布设置 boot 也会影响 grub 的加载.

iso

加载 iso 是另一个思路, 目前来看 grub 不能直接加载 iso. 但是 syslinux
提供了一个 memdisk 模块, 可以帮助 grub 来完成这个工作.

memdisk 在 grub 下面也有一个同名的, 所以在使用的时候要注意使用路径.

   set root='(hd0,msdos1)'
   linux16 /boot/memdisk iso raw
   initrd16 /boot/Win8PE.iso
   boot

这个的速度是一个问题, 我测试了 win7 的 iso, 时间等了很久我中断了.
再找了一个 win7 pe 测试了一下, 可以成功引导.

直接引导

我一开始的时候, 以为不能直接引导 win7 的 iso 文件里的内容. 后来我测试了一下,
是可以直接引导的. 但是我在 mac os 下解开 iso 文件的时候出现了问题,
mac os 说一个 license 文件不能复制, 不知道是什么原因.

    insmod part_msdos
    insmod ntfs
    insmod ntldr
    set root='(hd0,msdos1)'
    ntldr ($root)/bootmgr
    boot

结论

基于最终引导到了 windows boot manager 最后都会出现错误. 没有搞清楚, 出现问题的在哪里.
目前唯一可以使用的 linux 下制作 window 引导 U 盘的方法只有 syslinux memdisk 引导 pe 再使用原生系统盘.

SSH/HTTPS 认证

SSH/HTTPS 认证

最近讨论了有关加密相关的一些知识点, 简单整理记录一下.

非对称密钥的认证原理

我们知道, 可以使用公钥进行加密, 再用密钥进行解密, 这是非对称加密的一个重要的特点.

那么对于认证来说, 核心的是什么呢? 所谓认证本质就是确认对方的身份, 在基于非对称密钥的认证过程里
就是确认对方是不是私钥的拥有者. 所以我想一个基本的流程是这样的:

  • 基于公钥加密一断数据, 发送给对方
  • 对方解开加密数据
  • 对方展示原文, 如果原文与实际的原文是一样的, 那么就是验证了对方是私钥的所有者, 即完成了身份论证.

这样的一个过程, 为什么一定要基于非对称加密呢? 如果是对称的, 可不可以呢? 由于这个密钥只有一个, 在验证的双方都有一样的密钥, 再进行上面的
过程也是可以验证对方的身份, 但是这个时候密钥的传递就成了最复杂的事情了. 这个时候, 非对称加密的优势就体现出来了,
所以非对称加密优势一方面在于加解密的安全性上, 另一方面在于公钥传递的可靠与便利上.

SSH 认证流程

SSH 的论证第一步就是把公钥放到服务器上.

之后当连接进来的时候, 客户端会带有自己的公钥, 服务器上可能有多个公钥. 服务器进行检查, 这个客户的公钥在不在服务器上.
如果存在, 那么再进行下面的过程.

在上面那个原型上, 实际的 SSH 认证还多了一步.

先假定如下的标记:

  • Ac 客户端公钥
  • Bc 客户端密钥
  • As 服务器公钥
  • Bs 服务器密钥

  • 会话密钥(session key)生成
    • 客户端请求连接服务器,服务器将 As 发送给客户端。
    • 服务器生成会话ID(session id),设为 p,发送给客户端。
    • 客户端生成会话密钥(session key),设为 q,并计算 r = p xor q。
    • 客户端将 r 用 As 进行加密,结果发送给服务器。
    • 服务器用 Bs 进行解密,获得 r。
    • 服务器进行 r xor p 的运算,获得 q。
    • 至此服务器和客户端都知道了会话密钥q,以后的传输都将被 q 加密。
  • 认证
    • 服务器生成随机数 x,并用 Ac 加密后生成结果 S(x),发送给客户端
    • 客户端使用 Bc 解密 S(x) 得到 x
    • 客户端计算 q + x 的 md5 值 n(q+x),q为上一步得到的会话密钥
    • 服务器计算 q + x 的 md5 值 m(q+x)
    • 客户端将 n(q+x) 发送给服务器
    • 服务器比较 m(q+x) 和 n(q+x),两者相同则认证成功

SSH 登录的场景下有一个是可信的, 就是服务器是可信的. 所以一开始, 服务给了客户发送了公钥.
之后基于公钥, 加密了一个对称的密钥. 之后的交流都是基于对称的密钥了.
第二步的认证过程就是我们上面所说的认证的原理了. 关键在于客户端在返回 x 的时候并不是
直接返回的, 而使用了之前的得到对称加密的密钥.

SSH 的登录场景之下, 关键的点在于服务器的可信任. 所以一开始就基于服务器的公钥计算出来了会话密钥. 之后的所有的
信息都被加密了.

证书

在 https 过程中被广泛使用的证书, 我梳理了一下, 证书里的核心的信息就是: 域名, 服务器的公钥. 其它的 hash
值, 以及加密的算法, 都是为了保证域名与服务器的公钥的关系的可靠性.

https

https 我学习了一下, 感觉步骤比较多, 我提练了一下核心的目的. https 应该也有两个目的, 一是验证服务器的身份,
二是保证传输的安全性. 但是我感觉, 第二个目的是比较简单的. 如何要保证安全性, 大可以由客户端提供公钥, 再进行协商加密.
我所希望了解到的就是, 在 https 的握手过程中, 是如何验证服务器的身份的.

并且我还发现了一些奇怪的现象, 为什么要三个随机数呢? 我的理解, 有最后一个随机数应该就可以了. 我找到如下的解释.

"不管是客户端还是服务器,都需要随机数,这样生成的密钥才不会每次都一样。由于SSL协议中证书是静态的,
因此十分有必要引入一种随机因素来保证协商出来的密钥的随机性。

对于RSA密钥交换算法来说,pre-master-key本身就是一个随机数,再加上hello消息中的随机,
三个随机数通过一个密钥导出器最终导出一个对称密钥。

pre master的存在在于SSL协议不信任每个主机都能产生完全随机的随机数,如果随机数不随机,
那么pre master secret就有可能被猜出来,那么仅适用pre master secret作为密钥就不合适了,
因此必须引入新的随机因素,那么客户端和服务器加上pre master secret三个随机数一同生成的密钥就不容易被猜出了,
一个伪随机可能完全不随机,可是是三个伪随机就十分接近随机了,每增加一个自由度,随机性增加的可不是一。"

再查看了一些资料之后发现, https 这个体系还真是比较复杂的, dh 与 rsa 两种加密方式的认证是不一样的.
rsa 是在最后一步,服务器得到了客户发送的主随机数, 把握手过程的信息进行 hash 再加密之后传递给客户进行验证, 从而完成
对于服务器的认证. 而 dh 由于加密方便的不同, 在服务器向客户传递证书的时候就已经使用了私钥进行了数据加密并传递给
客户, 客户同时得到了证书与服务器的加密数据.

参考

 https://razeen.me/post/ssl-handshake-detail.html 
 http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html 

hugepage

hugepage

故事开始于很久之前, 我发现了一个问题. 我大概同时 shmget 了 12 个 4g 的内存块, 在程序退出的时候,
发现 exit 了很久之后, 父进程才收到了退出事件. 后来发现主要由于这些内存块的释放导致的. 都卡在 shmdt 操作上了.
大概会卡住 1-3s 的样子. 当时研究了一断时间, 也查了很多资源没有找到相关的信息. 这个事情也就只能先放下了.

今天在查看 intel cpu 的那个 bug 的相关文档的时候, 这些 bug 所在的层面也就是内存层面的, 看到了 page
table 的相关的信息, 突然间想到了上面的那个问题. 我猜想是不是由于大内存的 page table 太大的关系,
shmdt 必然会做的事情就是对于当前在使用的内存解开映射, 而这必然要做的就是想去查 page table 当前在使用哪些内存了.

我做了一些测试, 使用 shmget 申请了大概 8g 的内存(当前机器的 shmmax 就是 8g). 然后两次 memset 整个内存空间.
分别纪录了 shmget, shmat, shmdt 所消耗的时候.

使用 hugepage

没有使用 hugepage

  • 如果 shmget 的申请的内存大于 shmmax, 会返回 Invalid argument.
  • 如果 HugePages_Total 的值是 0, 或者总的内存量小于申请的内存量, 在 shmat 的时候会失败.

这个机器上的 huagepage size 是 2M. 看到了在使用 huage page 的情况下, 第一次调用内存的情况下可以提升 40% 的性能.
而对于 shmdt 的优化就是巨大的了. 大概从 700ms 降到了 2ms.

tty 编程

tty 编程

经常使用 tty, 但是对于 tty 工作原理了解的真的是不足的.

  • ctrl-c 是怎么实现的, 谁把收到的按键变成了 signal, signal 发给谁了.
  • pty 的 master, slave 有什么区别
  • 标准输出与错误输出与 pty 的关系
  • ssh 协议加密之外做了什么, ssh 协议是怎么实现, 远程操作的
  • tty 的工作模式

以上这些问题现代的程序员了解的好像并不多. 因为这些都是基础的原理, 并且已经有大量的工具. 我们只是用就是了.

ctrl-c 是怎么变成 signal 的, 这个是一个根本性的问题, 也是 tty(pty) 所做的一个重要的工作.
我们知道, 我们可以使用 kill -int 发送这个信号. 但是 ctrl-c 是这个操作下, 是由谁送的信息呢? ctrl-c 到底是
一个什么特殊的操作呢? 其实 ctrl-c 就是一组输入, 键盘在收到用户的这个操作的时候, 产生的 ^C 输入, 这个输入
传递到了 pty 这个设备. 这个设备在检测到了 pty, 就会在内核里产生信号给下在使用的程序组.

我们可能知道 pty, 但是真的不是很了解这个pty. pty 创建之后会有两个 fd, 分别是 masterfd, slavefd.
masterfd 一般由父进程控制, 子进程使用 slavefd. 比如打开了个终端了, 终端这个程序会占用 masterfd, 终端程序会启动 bash,
bash 的标准输入, 标准输出, 错误输出其实都是这个 dup 自这个 fd. 或者说, 其实都是指向了打开的 pty.
这里的感觉, pty 和管道之类的很相似. 从 masterfd 写入, slavefd 可以读取, 从 slavefd 写入, masterfd 可以读取到.
但是 pty 还有一些特别的属性. 如窗口大小, 之类的.

masterfd 对于 tty 的拥有的权限也更大一些.
回到刚刚的问题, 我们现在知道了, 所有的输入都要经过 pty 这个设备, pty 在工作模式有很多, 一般情况下的经典模式下收到了 ^C
就会生成 int 信号, 并发送给使用 slavefd 的进程. 如果设置成 raw 模式, 那么就不会进行转换了.
后端的进程会直接收到了 ^C 这个字符串.

我们了解一下 ssh 登录的一个基本流程, 我们可以很好的理解, ssh 的工作原理还有 tty 的一些特性.
这里所说的流程, 其实并不是 ssh 特有的, ssh 的特点在于加密. 我们这里不谈这个.

我们打开一个终端, 这里会创建一个 pty, 我们在 bash 下启动了 ssh, ssh 就是一个占用了, 输入输出的普通程序.
他会通过网络连接到远程的机器的 sshd 服务. 这个服务会在运程机器上开启一个 pty, 并启动一个新的 bash 使用 pty.
我们在这边所有输入给本地 ssh 的输入, 都会被传发给远程的 sshd, sshd 再通过远程机器的 pty 写入到远程的 bash.

启动的过程中, 本地的 ssh 会设置本地 pty 的属性, 不对于 ^C 这样的字符进行传换, 而是直接收到 ^C, 然后转发给远程的 pty.

terminal ==> pty ==> ssh ---------- sshd => pty ==> bash

ssh 到 sshd 之类的协议还固定了, 比如窗口大小调整之类的事情, 来保持本地的 pty 与 远程的 pty 的窗口大小一样.

以上大概就是一个 ssh 的工作流程. 我们可以说, ssh + sshd 其实就是给了 bash 一个工作的环境, 然后通过网络,
把 bash 的输出给传递到本地, 再把输入传递进来.

了解了这些, 我们是不是就可以发现一些, 不容易的事情就变得容易了, 比如 ssh 的代理.

terminal ==> pty ==> ssh ---------- sshd => pty ==> kg
                                                       \
                                                         pty  ==> ssh ---------- sshd => pty ==> bash

页眉

页眉

定义页眉, 页脚

\usepackage{fancyhdr}
\pagestyle{fancy} % 使用 fancy 代替latex 的原生的默认的格式


\lhead{}                                            % left header
\chead{}                                            % center header
\rhead{\bfseries The performance of new graduates}  % right header
\lfoot{From: K. Grant}                              % left footer
\cfoot{To: Dean A. Smith}                           % center footer
\rfoot{\thepage}                                    % right footer

\thepage

可以得到当前的页码.

可以单独指定当前页的页眉样式

\thispagestyle %如 \thispagestyle{empty}

\renewcommand{\headrulewidth}{0.4pt} % 指定页眉线的大小
\renewcommand{\footrulewidth}{0.4pt} % 指定页脚线的大小

sysctl net all/default/eth

sysctl net all/default/eth

解释

直接说明, 后面再详细解释.

在 /proc/sys/net/ipv4/conf 下面有多个目录, 这些目录除 all, default 之外对应名个网卡设备.

各自的意义如下:

  • default: 配置的默认值. 新激活的网卡复制这个配置.
  • *: 各自己网卡的配置.
  • all: 全局的配置.

对于网卡的这些配置, 并不是直接读取的. 比如 arp_ignore 是取的 all, 与自己设备的 max.
all 这个全局配置的意义就在这里.

文件 include/linux/inetdevice.h 的如下内容, 展示了各个 net 设备的各个参数, 实际是如何提取的.
有的是直接提取, 有的是 AND, 有的是 OR, 有的是 MAX.

#define IN_DEV_FORWARD(in_dev)		IN_DEV_CONF_GET((in_dev), FORWARDING)
#define IN_DEV_MFORWARD(in_dev)		IN_DEV_ANDCONF((in_dev), MC_FORWARDING)
#define IN_DEV_RPFILTER(in_dev)		IN_DEV_MAXCONF((in_dev), RP_FILTER)
#define IN_DEV_SRC_VMARK(in_dev)    	IN_DEV_ORCONF((in_dev), SRC_VMARK)
#define IN_DEV_SOURCE_ROUTE(in_dev)	IN_DEV_ANDCONF((in_dev), \
						       ACCEPT_SOURCE_ROUTE)
#define IN_DEV_ACCEPT_LOCAL(in_dev)	IN_DEV_ORCONF((in_dev), ACCEPT_LOCAL)
#define IN_DEV_BOOTP_RELAY(in_dev)	IN_DEV_ANDCONF((in_dev), BOOTP_RELAY)

#define IN_DEV_LOG_MARTIANS(in_dev)	IN_DEV_ORCONF((in_dev), LOG_MARTIANS)
#define IN_DEV_PROXY_ARP(in_dev)	IN_DEV_ORCONF((in_dev), PROXY_ARP)
#define IN_DEV_PROXY_ARP_PVLAN(in_dev)	IN_DEV_CONF_GET(in_dev, PROXY_ARP_PVLAN)
#define IN_DEV_SHARED_MEDIA(in_dev)	IN_DEV_ORCONF((in_dev), SHARED_MEDIA)
#define IN_DEV_TX_REDIRECTS(in_dev)	IN_DEV_ORCONF((in_dev), SEND_REDIRECTS)
#define IN_DEV_SEC_REDIRECTS(in_dev)	IN_DEV_ORCONF((in_dev), \
						      SECURE_REDIRECTS)
#define IN_DEV_IDTAG(in_dev)		IN_DEV_CONF_GET(in_dev, TAG)
#define IN_DEV_MEDIUM_ID(in_dev)	IN_DEV_CONF_GET(in_dev, MEDIUM_ID)
#define IN_DEV_PROMOTE_SECONDARIES(in_dev) \
					IN_DEV_ORCONF((in_dev), \
						      PROMOTE_SECONDARIES)
#define IN_DEV_ROUTE_LOCALNET(in_dev)	IN_DEV_ORCONF(in_dev, ROUTE_LOCALNET)
#define IN_DEV_NET_ROUTE_LOCALNET(in_dev, net)	\
	IN_DEV_NET_ORCONF(in_dev, net, ROUTE_LOCALNET)

#define IN_DEV_RX_REDIRECTS(in_dev) \
	((IN_DEV_FORWARD(in_dev) && \
	  IN_DEV_ANDCONF((in_dev), ACCEPT_REDIRECTS)) \
	 || (!IN_DEV_FORWARD(in_dev) && \
	  IN_DEV_ORCONF((in_dev), ACCEPT_REDIRECTS)))

#define IN_DEV_IGNORE_ROUTES_WITH_LINKDOWN(in_dev) \
	IN_DEV_CONF_GET((in_dev), IGNORE_ROUTES_WITH_LINKDOWN)

#define IN_DEV_ARPFILTER(in_dev)	IN_DEV_ORCONF((in_dev), ARPFILTER)
#define IN_DEV_ARP_ACCEPT(in_dev)	IN_DEV_ORCONF((in_dev), ARP_ACCEPT)
#define IN_DEV_ARP_ANNOUNCE(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_ANNOUNCE)
#define IN_DEV_ARP_IGNORE(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_IGNORE)
#define IN_DEV_ARP_NOTIFY(in_dev)	IN_DEV_MAXCONF((in_dev), ARP_NOTIFY)

代码追踪

如下的代码, 主要表现了新的设备创建的时候是如何 copy default 的值的.

static struct in_device *inetdev_init(struct net_device *dev)
{
	struct in_device *in_dev;
	int err = -ENOMEM;

	ASSERT_RTNL();

	in_dev = kzalloc(sizeof(*in_dev), GFP_KERNEL);
	if (!in_dev)
		goto out;
	memcpy(&in_dev->cnf, dev_net(dev)->ipv4.devconf_dflt,
			sizeof(in_dev->cnf));                         // 复制 conf default
	in_dev->cnf.sysctl = NULL;
	in_dev->dev = dev;

    .......

	err = devinet_sysctl_register(in_dev);
	if (err) {
		in_dev->dead = 1;
		in_dev_put(in_dev);
		in_dev = NULL;
		goto out;
	}

    .......
}