補充交流:
私下交流的時候,寶哥哥提到爲何不考慮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項目開發過程中,碰到一種業務場景,接入層通過兩種途徑對外提供訪問入口:
- CMLB——外部Server
- SSO——移動終端
由於我們的接入層存在session的概念,因此,一旦session在接入節點X建立後,後續請求都必須由節點X進行處理。但是,無論CMLB還是SSO,都屬於無狀態負載均衡分發系統,前後請求並無邏輯關聯。
解決思路:
爲了提高系統的可擴展性,我們將session存儲於公司的ckv系統,接入節點每次接受請求後,首先嚐試從ckv獲取相應session,若無則設置自身爲處理節點,反之則檢查自身是否初始處理節點,若非則將請求轉發至正確節點進行處理。
存在問題:
其實這裏面臨的問題和"Dispatcher-Worker"模式非常類似,假設請求流的走向爲“Client->Dispatcher->Worker”,最終響應流的走向存在兩種選擇:
- Worker->Client:節省資源,但可能導致Client校驗異常
- 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;
}