利用RawSocket篡改UDP源地址

補充交流:

私下交流的時候,寶哥哥提到爲何不考慮TGW或者LVS作爲解決方案?

經過一番調研,無論是TGW還是LVS的DR模式,所謂的session保持功能主要還是基於TCP長連接而言(TGW支持基於客戶端IP的保持功能),其實並不特別適用於這裏的業務場景。

一般後臺Server之間出於性能考慮,往往通過TCP長連接進行通信,而該連接是由全量用戶所共享的。與正常客戶端的Per-Conn-Per-User模式並不一樣,所以繼續基於Connection做session保持就不適用了。

有童鞋對於代碼中的PseudoUdpHeader部分感到疑惑,筆誤?


圖1 IP包頭格式


圖2 UDP包頭格式

這裏重點解釋一下IP包頭和UDP包頭各自Checksum字段的計算方法。IP包頭的Checksum比較簡單,checksum(ip_header),計算時的checksum字段爲0。UDP包頭的Checksum計算方法有點複雜,checksum(pseudo_header+udp_header+data+padding),其中:pseudo_header如下圖所示,data爲實際用戶數據,padding用於非2字節對齊的部分補零,計算時udp_header的checksum字段爲0。


圖3 Pseudo Header

而最終實際發送的IP包格式爲:ip_header+udp_header+data

有了這些背景知識,對於代碼裏pseudo_header相關部分就很容易理解了,無非爲了減少數據拷貝開銷,臨時徵用了ip_header空間,完成udp_checksum的計算後再覆蓋爲真實的ip_header。

————————————————————————————————————————————————

問題描述:

最近在voip項目開發過程中,碰到一種業務場景,接入層通過兩種途徑對外提供訪問入口:

  1. CMLB——外部Server
  2. SSO——移動終端

由於我們的接入層存在session的概念,因此,一旦session在接入節點X建立後,後續請求都必須由節點X進行處理。但是,無論CMLB還是SSO,都屬於無狀態負載均衡分發系統,前後請求並無邏輯關聯。

解決思路:

爲了提高系統的可擴展性,我們將session存儲於公司的ckv系統,接入節點每次接受請求後,首先嚐試從ckv獲取相應session,若無則設置自身爲處理節點,反之則檢查自身是否初始處理節點,若非則將請求轉發至正確節點進行處理。

存在問題:

其實這裏面臨的問題和"Dispatcher-Worker"模式非常類似,假設請求流的走向爲“Client->Dispatcher->Worker”,最終響應流的走向存在兩種選擇:

  1. Worker->Client:節省資源,但可能導致Client校驗異常
  2. Worker->Dispatcher->Client:浪費資源,但確保Client側的通用性

改進手段:

通過RawSocket篡改源地址,Worker僞裝成Dispatcher進行回包。具體實現這裏不展開敘述,可以下載附件源碼,自行測試。

存在風險:

目前我在多臺不同IDC的內網機器測試均表現正常,但不確定會不會哪天網平的兄弟們配置一些安全策略,然後就歇菜了?這一塊如果有經驗的同事還請多多指教!

參考文獻:

http://www.tenouk.com/Module43.html

源碼:

#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
	uint8_t ip_hl:4;
	uint8_t ip_v:4;
	uint8_t ip_tos;
	uint16_t ip_len;
	uint16_t ip_id;
	uint16_t ip_off;
	uint8_t ip_ttl;
	uint8_t ip_p;
	uint16_t ip_sum;
	uint32_t ip_src;
	uint32_t ip_dst;
} __attribute__ ((packed)) IpHeader;

typedef struct
{
	uint16_t source;
	uint16_t dest;
	uint16_t len;
	uint16_t check;
} __attribute__ ((packed)) UdpHeader;

typedef struct
{
	uint32_t src_ip;
	uint32_t dst_ip;
	uint8_t zero;
	uint8_t protocol;
	uint16_t udp_len;
} __attribute__ ((packed)) PseudoUdpHeader;

static uint16_t CalcChecksum(void *data, size_t len)
{
	uint16_t *p = (uint16_t *)data;
	size_t left = len;
	uint32_t sum = 0;
	while (left > 1) {
		sum += *p++;
		left -= sizeof(uint16_t);
	}
	if (left == 1) {
		sum += *(uint8_t *)p;
	}
	sum = (sum >> 16) + (sum & 0xFFFF);
	sum += (sum >> 16);
	return ~sum;
}

int SendFakeUdp(const void *msg, size_t len, const char *src_ip, uint16_t src_port, const char *dst_ip, uint16_t dst_port)
{
	if (!msg || len <= 0 || !src_ip || !dst_ip) {
		return -1;
	}

	static int fd = -1;
	if (fd == -1) {
		if ((fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) {
			return -2;
		}
		int on = 1;
		if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) == -1) {
			close(fd);
			fd = -1;
			return -3;
		}
	}

	static char buf[65536];
	IpHeader *ip_header = (IpHeader *)buf;
	UdpHeader *udp_header = (UdpHeader *)(ip_header + 1);
	char *data = (char *)(udp_header + 1);
	PseudoUdpHeader *pseudo_header = (PseudoUdpHeader *)((char *)udp_header - sizeof(PseudoUdpHeader));
	if (sizeof(*ip_header) + sizeof(*udp_header) + len + len % 2 > sizeof(buf)) {
		return -4;
	}

	uint32_t src_ip_v = 0;
	if (inet_pton(AF_INET, src_ip, &src_ip_v) <= 0) {
		return -5;
	}
	uint32_t dst_ip_v = 0;
	if (inet_pton(AF_INET, dst_ip, &dst_ip_v) <= 0) {
		return -6;
	}

	uint16_t udp_len = sizeof(*udp_header) + len;
	uint16_t total_len = sizeof(*ip_header) + sizeof(*udp_header) + len;

	pseudo_header->src_ip = src_ip_v;
	pseudo_header->dst_ip = dst_ip_v;
	pseudo_header->zero = 0;
	pseudo_header->protocol = IPPROTO_UDP;
	pseudo_header->udp_len = htons(udp_len);

	udp_header->source = htons(src_port);
	udp_header->dest = htons(dst_port);
	udp_header->len = htons(sizeof(*udp_header) + len);
	udp_header->check = 0;

	memcpy(data, msg, len);

	size_t udp_check_len = sizeof(*pseudo_header) + sizeof(*udp_header) + len;
	if (len % 2 != 0) {
		udp_check_len += 1;
		*(data + len) = 0;
	}
	udp_header->check = CalcChecksum(pseudo_header, udp_check_len);

	ip_header->ip_hl = sizeof(*ip_header) / sizeof (uint32_t);
	ip_header->ip_v = 4;
	ip_header->ip_tos = 0;
	ip_header->ip_len = htons(total_len);
	ip_header->ip_id = htons(0); //爲0,協議棧自動設置
	ip_header->ip_off = htons(0);
	ip_header->ip_ttl = 255;
	ip_header->ip_p = IPPROTO_UDP;
	ip_header->ip_src = src_ip_v;
	ip_header->ip_dst = dst_ip_v;
	ip_header->ip_sum = 0;

	//協議棧總是自動計算與填充
	//ip_header->ip_sum = CalcChecksum(ip_header, sizeof(*ip_header));

	struct sockaddr_in dst_addr;
	memset(&dst_addr, 0, sizeof(dst_addr));
	dst_addr.sin_family = AF_INET;
	dst_addr.sin_addr.s_addr = dst_ip_v;
	dst_addr.sin_port = htons(dst_port);
	if (sendto(fd, buf, total_len, 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)) != total_len)  {
		return -7;
	}

	return 0;
}

int main(int argc, char *argv[])
{
	if (argc != 6) {
		printf("Usage: %s msg src_ip src_port dst_ip dst_port\n", argv[0]);
		return -1;
	}
	int ret = SendFakeUdp(argv[1], strlen(argv[1]), argv[2], strtoul(argv[3], NULL, 0), argv[4], strtoul(argv[5], NULL, 0));
	if (ret != 0) {
		printf("SendFakeUdp fail. ret=%d\n", ret);
		return -1;
	}
	printf("SendFakeUdp succ.\n");
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章