【问题标题】:why raw udp packet does not follow routing rules为什么原始 udp 数据包不遵循路由规则
【发布时间】:2022-03-20 04:44:57
【问题描述】:

我正在尝试在 linux 系统 (ArchLinux) 上用 C 语言伪造原始 IP 和 UDP。为此,我使用了以下代码

char *src_ip = "192.168.10.1";
char *dst_ip = "192.168.30.3";
short src_port = 22221;
short dst_port = 11111;

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;
};

unsigned short csum(unsigned short *ptr,int nbytes)
{
   register long sum; 
   unsigned short oddbyte;
   register short answer;

   sum=0;
   while(nbytes>1) {
           sum+=*ptr++;
           nbytes-=2;
   }
   if(nbytes==1) {
           oddbyte=0;
           *((u_char*)&oddbyte)=*(u_char*)ptr;
           sum+=oddbyte;
   }

   sum = (sum>>16)+(sum & 0xffff);
   sum = sum + (sum>>16);
   answer=(short)~sum;

   return(answer);
}

void fill_ip(struct iphdr *iph, int lendata /*including udp header*/)
{  
   iph->ihl = 5;
   iph->version = 4;
   iph->tos = 0;
   iph->tot_len = sizeof (struct iphdr) + lendata;     //sizeof (struct udphdr) + strlen(data);
   iph->id = htonl (54321);        //Id of this packet
   iph->frag_off = 0;
   iph->ttl = 255; 
   iph->protocol = IPPROTO_UDP;
   iph->check = 0;         //Set to 0 before     calculating checksum
   iph->saddr = inet_addr (src_ip);        //Spoof the source ip address
   iph->daddr = inet_addr (dst_ip);
   iph->check = csum ((unsigned short *) iph, iph->tot_len);
}

void fill_udp(struct udphdr *udph, int lendata)
{
  udph->source = htons (src_port);
  udph->dest = htons (dst_port);
  udph->len = htons(8 + lendata);       //tcp header size

  /* CSUM */
  struct pseudo_header psh;
  psh.source_address = inet_addr(src_ip);
  psh.dest_address = inet_addr (dst_ip);
  psh.placeholder = 0;
  psh.protocol = IPPROTO_UDP;
  psh.udp_length = htons(sizeof(struct udphdr) + lendata );
  int psize = sizeof(struct pseudo_header) + sizeof(struct udphdr) + lendata;
  char *pseudogram = malloc(psize);
  memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
  memcpy(pseudogram + sizeof(struct pseudo_header) , udph , sizeof(struct udphdr) +     lendata);
  udph->check = csum( (unsigned short*) pseudogram , psize);
}

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


  char datagram[4096]={0};
  int s;

  s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);
  assert(s != -1);

  //link to interface
  const char *opt;
  opt = "ens6";
  const int len = strnlen(opt, IFNAMSIZ);
  if (len == IFNAMSIZ) {
    fprintf(stderr, "Too long iface name");
    return 1;
  }
  assert(setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, opt, len) == 0);


  // IP header
  struct iphdr *iph = (struct iphdr *) datagram;

  //UDP header
  struct udphdr *udph = (struct udphdr *) (datagram + sizeof (struct ip));

  // data
  char *data = datagram + sizeof(struct iphdr) + sizeof(struct udphdr);
  data[0] = 'a';

  fill_ip(iph,sizeof(struct udphdr) + 1/*len of data*/);
  fill_udp(udph,1);

  struct sockaddr_in sin;
  memset(&sin,0,sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(dst_port);
  sin.sin_addr.s_addr = inet_addr (dst_ip);

  sendto (s, datagram, iph->tot_len , 0, (struct sockaddr *) &sin, sizeof (sin));

  return 0;
}

此代码创建一个从 192.168.10.1 到 192.168.30.3 的 UDP 数据包,源端口为 22221,目标端口为 11111。

然后,我添加一个路由规则并从主表中删除默认路由:

sudo ip rule add to 192.168.30.3 dport 11111 proto 17 table 2001
sudo ip route add default via 192.168.10.2 src 192.168.10.1 table 2001
sudo ip route del default

(主表中定义了192.168.10.2的路由)

这最终给出了以下规则:

# ip rule
0:      from all lookup local
32765:  from all to 192.168.30.3 dport 11111 lookup 2001 proto 17
32766:  from all lookup main
32767:  from all lookup default

还有以下路线:

# ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1 
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1 
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1 
broadcast 192.168.10.0 dev ens6 proto kernel scope link src 192.168.10.1 
local 192.168.10.1 dev ens6 proto kernel scope host src 192.168.10.1 
broadcast 192.168.10.255 dev ens6 proto kernel scope link src 192.168.10.1 
# ip route show table 2001
default via 192.168.10.2 dev ens6 src 192.168.10.1 
192.168.10.0/24 dev ens6 proto kernel scope link src 192.168.10.1
# ip route show table main
192.168.10.0/24 dev ens6 proto kernel scope link src 192.168.10.1 

我希望在 192.168.10.2 和 192.168.30.3 上看到带有wireshark 的原始数据包,但什么也没有。你知道为什么吗?也许我的原始套接字不遵循我定义的路由规则?请注意,如果我不触摸计算机上的路由规则,代码将按预期工作。

【问题讨论】:

  • 你说得完全正确。你的里程会因操作系统而异(听起来你在 Linux 上)......但所有的赌注都在于期望通过“原始套接字”发送的手工制作的数据包遵守所有标准的 TCP/IP 级别行为(像“路线”)。看这里:squidarth.com/networking/systems/rc/2018/05/28/…
  • 确实我在 Linux 上(我刚刚编辑了我的消息以包含此信息)。
  • 据我了解,对于 Linux 上的原始套接字,只有目标地址用于路由。确实sudo ip rule add to 192.168.30.3 table 2001 有效,而sudo ip rule add to 192.168.30.3 dport 11111 proto 17 table 2001sudo ip rule add from 192.168.10.1 to 192.168.30.3 table 2001 无效

标签: c routes raw-sockets


【解决方案1】:

TL;DR:您需要bind() 套接字来接口 IP 地址。

原始套接字确实遵循路由规则,您的setsockopt SO_BINDTODEVICE 是正确的举措,这在某些情况下适用,例如 L3 (TUN) 隧道,但是对于真正的以太网设备,您(也)需要bind()到它的接口 IP 地址。

内核不会检查您在sendto buf 中传递的 IP 标头,但会检查套接字绑定到的地址/设备。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-04
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 2016-10-23
    • 2022-01-20
    • 2021-07-05
    相关资源
    最近更新 更多