QEMU 高性能网络配置

QEMU 的网络配置最简单的是 -net user 模式,从 Guest OS 角度看 Host OS 为其提供了 NAT 和 DHCP 服务。此模式也有两点明显的不足之处:
1. 网络传输性能较差。
2. Host OS 访问 Guest OS 网络服务、Guest OS 互相访问对话网络服务都不方便。

这里推荐使用 -net tap 模式,大概的工作原理是 QEMU 进程会使用 Linux tun 子系统创建一个虚拟的 tap 类型的网络接口,这个虚拟的链路的一端在 Host OS 角度即是一个 Ethernet 类型的网络接口,另一端被 QEMU 连接到其在 Guest OS 中虚拟化的 Ethernet 网络接口上。Tap 类型的接口工作在链路层,链路上承载的协议与常规的 Ethernet 相比无多余的限制。这种情况看上去就像是 Host 与 Guest 都有一个网络接口直连着。

Host OS 还可以创建一个 Bridge,然后将 QEMU 创建的 tap 类型的网口都加入到同一个 Bridge 上面,这样就相当于将多个 Guest 及 Host 的一个网口都连接到同一个交换机上。当然创建多个 Bridge 进行分组管理也可以。

配置实例
Host OS

sudo brctl addbr br0
sudo brctl stp br0 off
sudo ifconfig br0 192.168.5.1/24 up
qemu-system-x86_64 --enable-kvm -net nic,model=virtio -net tap,helper=/usr/lib/qemu/qemu-bridge-helper ...

Guest OS

sudo ifconfig eth0 192.168.5.2/24 up
sudo route add default gw 192.168.5.1

外网配置
Guest OS 中如需连接外部网络,有两种方式:
1. NAT 模式,Host OS 视 Guest OS 所处的 Bridge 是一个内网,由 Host OS 提供 NAT 服务,配置与常规的 Linux NAT 配置一样。
2. Bridge 模式,只需将 Host OS 上连接外网的网口也增加到 Guest OS 所属的 Bridge 中。

Windows virtio 驱动
Guest 中如果安装 Windows 操作系统,需要安装 virtio (net) 驱动,详见:http://www.linux-kvm.org/page/WindowsGuestDrivers/Download_Drivers

Tips: 注意防火墻的配置。

Over!

一种 HTTP 会话劫持的防御方法

HTTP 会话劫持也是 TCP 会话劫持,它是利用了 TCP Seqnum 的可预见性来伪造有效的响应包,并抢先响应客户端同时使得客户端主动丢弃原服务器的响应包。在这个层面想区分哪一个响应包是伪造的是有困难的,但结合实际情况考虑问题也还有一些思路。

我们以劫持程序的设计者角度考虑:
1. 劫持程序不是什么都劫持了,只有符合条件被命中的 HTTP 请求才会劫持的,那也就是说劫持程序需要一个完整或至少包含它需要的信息的请求才可以劫持,我想 Host, QueryString, Referer 这些应该少不了。
2. 劫持程序不希望维护 TCP 上下文进行流重组。因为维护起来成本比较高。
3. 基于上一点来说,劫持程序也就不希望一个 HTTP 请求被拆分在两个或多个包中传输。

从实际检测来看,大多数的 HTTP 请求还是包含在一个独立的 TCP 包中传输的,只有很少的情况被拆分了。我想你一定想到了什么了,对!我们就假设当前在用的劫持程序都没有维护 TCP 传输上下文进行流重组,那么人为对前向链路上的流进行强制拆分,就应该能够逃过被劫持。

实现的方式应该会有很多,我在应用层代理层面实现了一个简单的原型,方式是将前向链路的 Buffer size 强制在了 16 字节。代码托管在 Github => https://github.com/heiher/hev-socks5-proxy/tree/anti-hijack

从抓包来看,HTTP 请求在传输时被拆分了:

packet 0:[syn]
packet 1: GET / HTTP/1.1\r\n
packet 2: Host: xxxx
packet 3: ...

Over!

Forward special packets

主机A与主机B之间建立了一条 IPIP 遂道,两个主机之间生成的TCP协议且源端口是8000的包需要通过遂道传输到对方后,通过对方的网关发送出去。

On Host A

TUN_IFACE="tun-b"
HOST_A_IP="10.0.0.3"
HOST_B_IP="10.0.3.2"
TUN_GATEWAY="192.168.4.2"
HOST_GATEWAY="10.0.0.1"
 
sudo iptunnel add ${TUN_IFACE} mode ipip remote ${HOST_B_IP} local ${HOST_A_IP}
sudo ifconfig ${TUN_IFACE} up
sudo route add -host ${TUN_GATEWAY} dev ${TUN_IFACE}
 
sudo iptables -t mangle -A POSTROUTING -p tcp -m tcp --sport 8000 -m mark ! --mark 0x8888 -j TEE --gateway ${TUN_GATEWAY}
sudo iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 8000 -j MARK --set-mark 0x8888
sudo iptables -t mangle -A PREROUTING -m mark --mark 0x8888 -j TEE --gateway ${HOST_GATEWAY}

On Host B

TUN_IFACE="tun-a"
HOST_A_IP="10.0.0.3"
HOST_B_IP="10.0.3.2"
TUN_GATEWAY="192.168.4.1"
HOST_GATEWAY="10.0.3.1"
 
sudo iptunnel add ${TUN_IFACE} mode ipip remote ${HOST_A_IP} local ${HOST_B_IP}
sudo ifconfig ${TUN_IFACE} up
sudo route add -host ${TUN_GATEWAY} dev ${TUN_IFACE}
 
sudo iptables -t mangle -A POSTROUTING -p tcp -m tcp --sport 8000 -m mark ! --mark 0x8888 -j TEE --gateway ${TUN_GATEWAY}
sudo iptables -t mangle -A PREROUTING -p tcp -m tcp --sport 8000 -j MARK --set-mark 0x8888
sudo iptables -t mangle -A PREROUTING -m mark --mark 0x8888 -j TEE --gateway ${HOST_GATEWAY}

Over!

基于 Nginx 的网关级 HTTP 透明代理

出于某些需求在网关级架设 HTTP 透明代理,劫持用户 HTTP 请求,转发或直接进行响应。

iptables 配置
iptables 用于将经过网关的 TCP 80 端口的上行流量转发至网关上的 Nginx 服务。

sudo iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j DNAT \
--to-destination 网关IP:端口

Nginx 演示配置

worker_processes  1;
 
events {
    worker_connections  1024;
}
 
http {
    include       mime.types;
    default_type  application/octet-stream;
 
    sendfile        on;
    keepalive_timeout  65;
 
    server {
        listen       8000;
        server_name  localhost;
 
        resolver 8.8.8.8;
 
        location /test {
            proxy_pass http://hev.cc/sftp/files/;
            proxy_buffers 256 4k;
            proxy_max_temp_file_size 0k;
        }
 
        location / {
            # 包含关键词 '计算机' 重定向至 /test
            rewrite ^.*计算机.*$ /test last;
 
            # 通用透明代理
            proxy_pass $scheme://$host$request_uri;
            proxy_set_header Host $http_host;
            proxy_buffers 256 4k;
            proxy_max_temp_file_size 0k;
        }
    }
 
    # 匹配 *.baidu.com 域名
    server {
        listen       8000;
        server_name  *.baidu.com;
 
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

Over!

重新实现了 Socsk5

可能你会想实现那么多的 Socsk5 能吃吗?其实我只是想研究一下 I/O 模型,拿它来练习一下,如果效果不错也可以拿来用用。本次的实现没有使用 GLib,或许我真的想再也不用 GLib 啦?应该是这样的吧,至少我已经开始在实现我需要的东西。HevLib 就是我的一个初级替代品吧,目前仅仅包含最基本的数据结构、EventSource、EventLoop 等等。更多的数据结构与算法还有类型和对象系统就在以后有需要的时候再实现,一步一步来吧。

重写的 Socks5 Proxy 就是使用了 HevLib 的事件循环机制(Epoll,优先级排序),目前是单线程异步事件驱动的,感觉异步事件驱动实现起来还是有点麻烦的,和内核的进程调度一样,要上下文切换,实际来说 Session 中已经存储了上下文了,要做说也就是流程控制,我是简单的使用了一个状态变量控制的(可能不是太好,有好的方法可以告诉我哦)。

另外,在 DNS 解析上遇到一点麻烦、gethostbyname 虽然工作的没问题,显然这个阻塞的方法并不适合我现在的模型,又看了一下 getaddrinfo_a 和 c-ares 这个异步 DNS 查询库,结果发现想套到当前的事件循环机制中都有一些麻烦。后来就直接基于 UDP 实现了一个简单的异步的标准A记录查询器,简单又好用。

现在服务已经在 VPS 上部署了,RAM 的使用由原来的 300+M 直接下降到 80+M,功能还和原来一样的。;)

代码仓库:https://github.com/heiher/hev-socks5-proxy

Over!

在 iOS 上使用 Socks5 代理

创建 PAC 脚本

function FindProxyForURL(url, host)
{
    if (isInNet(host, "192.168.1.0", "255.255.255.0"))
      return "DIRECT";
    return "SOCKS xxx.xxx.xxx.xxx:xxxx";
}

将此脚本放入一个 Web Server 的服务目录中。

应用代理服务
1. 打开设置 -> Wifi -> 选择当前使用的 Wifi。
2. 将 HTTP 代理置于 Auto 模式,输入上面文件的 URL。

Over!

SSH using a SOCKS or HTTP proxy

If you follow planet debian, you may already know about the ProxyCommand directive in $HOME/.ssh/config. It allows OpenSSH to connect to a remote host through a given command.

One setup that I use a lot is to have connections be established through a SOCKS proxy. Until today, I was using connect, a small tool written by Shun-ichi Gotô. The typical setup I used is:

    Host *.mydomain.com
    ProxyCommand connect -S socksserver:1080 %h %p

I also use jump hosts occasionally, with a setup like this:

    Host somehost.mydomain.com
    ProxyCommand ssh otherhost.mydomain.com nc -w1 %h %p

And today I discovered that netcat-openbsd does support connexions through a proxy, either SOCKS or HTTP. Why keep using two different tools when you can use one? 😉 So I changed my setup to:

    Host *.mydomain.com
    ProxyCommand nc -xsocksserver:1080 -w1 %h %p

The default is to use SOCKS 5, add -X4 for SOCKS 4 and -Xconnect for HTTP CONNECT proxies. Note that it doesn’t support choosing which end does the name resolutions like connect does with the -R option.

From: http://glandium.org/blog/?p=223

Over!

修复 ModemManager 中华为 CDMA 设备的信号获取

在 ModemManager 0.6 及之前版本中,华为 CDMA 设备的信号获取依赖于 MMGenericCdma 基类实现的 MMModemCdma 接口的 get_signal_quality 方法。在此方法的实现中通过 AT+CSQ 或 AT+CSQ? 查询设备的 CDMA 1X 信号强度。但是华为的设备返回的 +CSQ 数据格式与代码中的格式并不相符,导致返回的信号数据并不能使用,换言之什么也没做,NetworkManager 看到的就是无信号!

目前 CDMA 设备是有两类信号要获取的,分别是 CDMA 1X 和 EVDO,而当前的 org.freedesktop.ModemManager.Modem.Cdma D-Bus 接口中只有一个方法 GetSignalQuality 且只能返回一个信号值。

在插件中的 MMModemHuaweiCdma 子类里实现了对非主动上报的 CDMA 1X (^RSSILVL) 和 EVDO (^HRSSILVL) 信号进行了更新,也仅此一处涉及到了 EVDO 信号的获取。

基于上述的情况,产生了这样的一种修复方案:在 MMModemHuaweiCdma 中重新实现 MMModemCdma 接口类的 get_signal_quality 方法。在此方法中根据当前的 CDMA 1X 和 EVDO 网络注册情况获取信号,当 EVDO 注册状态为 Unknown 时通过 AT^CSQLVL 获取 CDMA 1X 信号并更新,而当 EVDO 注册状态为非 Unknown 时通过 AT^HDRCSQLVL 获取 EVDO 信号并更新。

https://github.com/heiher/ModemManager/commit/4711bfc79abafd04d678c4c95976e9e7d561d2c4

在基类 MMGenericCdma 中信号变更还会触发信号 org.freedesktop.ModemManager.Modem.Cdma.SignalQuality,与上述规则相同,当 EVDO 注册时只返回 EVDO,反之返回 CDMA 1X。

https://github.com/heiher/ModemManager/commit/2a13ed070c2762058b4150c7069967df1bd4e6de

Over!