Linux soft lockup時遠端除錯的可能性

專注的阿熊發表於2020-12-22

SSH 已經不能指望了,怎麼辦?

想法簡單,不足道,僅僅是個POC ,也希望能有人一起討論:

註冊一個新的四層協議,除了TCP/UDP/ICMP 等熟知協議之外的新協議,這是為了避免每一個資料包都要經過過濾,避免影響效能。

事先分配skb ,避免當事故發生時回送資訊時分配 skb 失敗。

好了,看程式碼,先給出載入核心的程式碼,這個程式碼的大部分都是我從網上抄來的,並不是自己寫的,我只是重組了邏輯:

# include <net/protocol.h>#include <linux/if_ether.h>#include <linux/ip.h>#include <linux/udp.h>

#define IPPROTO_MYPROTO  123#define QUOTA 30

struct sk_buff *eskb[QUOTA];

static int quota = QUOTA - 1;//module_param(quota, int, 0644);//MODULE_PARM_DESC(quota, "soft_lockup");

unsigned short _csum(unsigned short* data, int len){

int pad = 0;

int i = 0;

unsigned short ret = 0;

unsigned int sum = 0;

if (len % 2 != 0)

pad = 1;

len /= 2;

for ( i = 0; i < len; i++) {

sum += data[i];

}

if (pad == 1)

sum += ((unsigned char*)(data + len))[0] ;

sum = (sum & 0xffff) + (sum >> 16);

sum += (sum >> 16);

ret = ~sum;

return ret;}

int myproto_rcv(struct sk_buff *skb){

struct udphdr *uh, *euh;

struct iphdr *iph, *eiph;

struct ethhdr *eh, *ethh;

char esaddr[6];

unsigned char *pos;

if (quota < 0) {

goto end;

}

iph = ip_hdr(skb);

uh = udp_hdr(skb);

eh = (struct ethhdr *)(((unsigned char *)iph) - sizeof(struct ethhdr));

// 出事的時候,直接構造已經分配的 skb

eskb[quota]->ip_summed = CHECKSUM_NONE;

eskb[quota]->protocol = htons(ETH_P_IP);

eskb[quota]->priority = 0;

eskb[quota]->dev = skb->dev;

eskb[quota]->pkt_type = PACKET_OTHERHOST;

skb_reserve(eskb[quota], 1300 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr));

pos = skb_push(eskb[quota], 1300);

strcpy(pos, "abcdefghijk123456789");

pos = skb_push(eskb[quota], sizeof(struct udphdr));

skb_reset_transport_header(eskb[quota]);

euh = (struct udphdr *)pos;

euh->source = uh->dest;

euh->dest = uh->source;

euh->len = htons(1300 + sizeof(struct udphdr));

euh->check = 0;

memcpy(pos - 12, &iph->daddr, 4);

memcpy(pos - 8, &iph->saddr, 4);

((unsigned short *)(pos - 4))[0] = 0x1100;

memcpy(pos - 2, &euh->len, sizeof(euh->len));

euh->check = _csum((unsigned short*)(pos - 12), 12 + 1300 + sizeof(struct udphdr));

pos = skb_push(eskb[quota], sizeof(struct iphdr));

skb_reset_network_header(eskb[quota]);

eiph = (struct iphdr *)pos;

eiph->version = 4;

eiph->ihl = 5;

eiph->tos = 0;

eiph->tot_len = htons(1300 + sizeof(struct udphdr) + sizeof(struct iphdr));

eiph->id = 0x1122;

eiph->frag_off = 0;

eiph->ttl = 64;

eiph->protocol = 0x11;

eiph->check = 0;

eiph->saddr = iph->daddr;

eiph->daddr = iph->saddr;

eiph->check = _csum((unsigned short*)pos, sizeof(struct iphdr));

pos = skb_push(eskb[quota], sizeof(struct ethhdr));

skb_reset_mac_header(eskb[quota]);

ethh = (struct ethhdr *)pos;

memcpy(esaddr, eh->h_dest, 6);

memcpy(ethh->h_dest, eh->h_source, ETH_ALEN);

memcpy(ethh->h_source, eh->h_dest, ETH_ALEN);

ethh->h_proto = htons(ETH_P_IP);

printk("myproto_rcv is called, length:%d  %x %x\n", skb->len, esaddr[2], esaddr[3]);

 

dev_queue_xmit(eskb[quota]);

quota --;

end:

kfree_skb(skb);

return 0;}

int myproto_rcv_err(struct sk_buff *skb, unsigned int err){

printk("myproto_rcv is called:%d\n", err);

kfree_skb(skb);

return 0;}

static const struct net_protocol myproto_protocol = {

.handler = myproto_rcv,

.err_handler = myproto_rcv_err,

.no_policy = 1,

.netns_ok = 1,};

int init_module(void){

int ret = 0, i;

// 事先分配 skb

for (i = 0; i < QUOTA; i++) {

eskb[i] = alloc_skb(1300 + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr), GFP_ATOMIC);

if (eskb[i] == NULL) {

//int j;

//for () {

//}

printk("alloc failed\n");

return -1;

// 註冊 123 協議,它不是 TCP,UDP,ICMP

ret = inet_add_protocol(&myproto_protocol, IPPROTO_MYPROTO);

if (ret) {

printk("failed\n");

return ret;

printk("successful\n");

return 0;}

void cleanup_module(void){

int rc = 0;

inet_del_protocol(&myproto_protocol, IPPROTO_MYPROTO);

//for (i = quota; i >=0; i--) {

//kfree_skb(eskb[i]);

//}

return;}

int init_module(void);void cleanup_module(void);MODULE_LICENSE("GPL");

來來來,看一下客戶端的程式碼。

客戶端需要透過raw 套接字傳送一個 請求 ,跟單網它的傳輸層協議是 123 ,然而回復的卻是一個標準的 UDP 報文,所以客戶端需要在該 UDP 上接收。

程式碼如下:

#include <stdlib.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <sys/socket.h>#include <netinet/in.h>#include <linux/ip.h>#include <linux/udp.h>

#define PCKT_LEN 8192

unsigned short csum(unsigned short *buf, int nwords){

  unsigned long sum;

  for(sum=0; nwords>0; nwords--)

    sum += *buf++;

  sum = (sum >> 16) + (sum &0xffff);

  sum += (sum >> 16);

  return (unsigned short)(~sum);}

int main(int argc, char const *argv[]){

int sd, usd;

struct iphdr *ip;

struct udphdr *udp;

struct sockaddr_in sin, usin, csin;

u_int16_t src_port, dst_port;

u_int32_t src_addr, dst_addr;

int >

const int *val = &one;

int dlen, rlen, clen = sizeof(csin);

char *data;

char buf[1300];

if (argc != 6) {

printf("Usage: %s <source hostname/IP> <source port> <target hostname/IP> <target port>\n", argv[0]);

exit(1);

src_addr = inet_addr(argv[1]);

dst_addr = inet_addr(argv[3]);

src_port = atoi(argv[2]);

dst_port = atoi(argv[4]);

dlen = atoi(argv[5]);

data = malloc(sizeof(struct iphdr) + sizeof(struct udphdr) + dlen);

ip = (struct iphdr *)data;

udp = (struct udphdr *)(data + sizeof(struct iphdr));

sd = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);

if (sd < 0) {

perror("raw error");

exit(2);

if(setsockopt(sd, IPPROTO_IP, IP_HDRINCL, val, sizeof(one)) < 0) {

perror("setsockopt() error");

exit(2);

sin.sin_family = AF_INET;

sin.sin_port = htons(dst_port);

 

sin.sin_addr.s_addr = inet_addr(argv[3]);

ip->ihl = 5;

ip->version = 4;

ip->tos = 16; // low delay

ip->tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + dlen;

ip->id = htons(54321);

ip->ttl = 64; // hops

ip->protocol = 123; // UDP

ip->saddr = src_addr;

ip->daddr = dst_addr;

udp->source = htons(src_port);

udp->dest = htons(dst_port);

udp->len = htons(sizeof(struct udphdr) + dlen);

ip->check = csum((unsigned short *)data, sizeof(struct iphdr) + sizeof(struct udphdr) + dlen);

usd = socket(AF_INET, SOCK_DGRAM, 0);

if (usd < 0) {

perror("usd error");

bzero(&usin, sizeof(usin));

usin.sin_family = AF_INET;

usin.sin_port   = htons(src_port);

usin.sin_addr.s_addr = inet_addr(argv[1]);

if (bind(usd, (struct sockaddr *)&usin, sizeof(usin))) {

perror("bind error");

exit(2);

if (sendto(sd, data, ip->tot_len, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) {

perror("sendto()");

exit(3);

rlen = recvfrom(usd, buf, sizeof(buf), 0, (struct sockaddr*)&csin, &clen);

printf("recv:%s\n", buf);

close(sd);

return 0;}

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69946337/viewspace-2744291/,如需轉載,請註明出處,否則將追究法律責任。

相關文章