关于UDP接收icmp端口不可达(port unreachable)

关于UDP接收icmp端口不可达(port unreachable)本篇分为3部分1:报文格式2:产生的原因3:linux协议栈如何处理4:应用层如何获取1:报文如下,10.30.13.1往10.30.16.10的80端口发送了一个UDP报文,80端口其实监听的是TCP。服务器回复了一个类型为端口不可达的ICMP,ICMP数据部分就是请求UDPip层及其以上的数据。2:原因首先原因就是接收udp报文…

大家好,又见面了,我是你们的朋友全栈君。

本篇分为3部分

1:报文格式

2:产生的原因

3:linux协议栈如何处理

4:应用层如何获取

1:

报文如下,10.30.13.1往10.30.16.10的80端口发送了一个UDP报文,80端口其实监听的是TCP。

服务器回复了一个类型为端口不可达的ICMP,ICMP数据部分就是请求UDP ip层及其以上的数据。

关于UDP接收icmp端口不可达(port unreachable)

2:原因

   首先原因就是接收udp报文的服务器对应的端口没有开启UDP服务器。注意这里的描述,并不是端口没有开启服务,而是没有开启UDP服务,如果开启了TCP服务,照样也会回port unreachable。

3:Linux内核对UDP处理:

(1):作为服务器接受到一个UDP请求:

首先,做为服务器,当一个报文经过查路由,目的ip是上送本机的时候,经过netfilter 判决后,

调用ip_local_deliver_finish,它根据ip头中的协议类型(TCP/UDP/ICMP/……),调用不同的4层接口函数进行处理。所以之前说了,即使开启了TCP服务,服务器建立的socket的hash和udp超找socket的hash不一致,也会回端口不可达。

关于UDP接收icmp端口不可达(port unreachable)

对于udp而言,handler 是udp_rcv,它直接调用了__udp4_lib_rcv,查找相应的sock,

如果sk不存在if(sk != NULL),就回复icmp destination unreachable(这就是服务器没有对应端口接受UDP的处理流程),函数非常简单

关于UDP接收icmp端口不可达(port unreachable)

    所以作为服务器,收到一个目的端口并未监听的报文,直接回复端口不可达。

那么作为客户端,如何处理服务器回复的 端口不可达 报文呢?

起始当初想法很简单,我认为,不同的协议之间是不会干涉的,即TCP和UDP直接是不会干涉的。

何况这种不伦不类的icmp?后来想错了。

(2)作为客户端收到ICMP端口不可达的回复:

    作为客户端,端口不可达报文进入ip_local_deliver_finish,它调用icmp_rcv函数,进行处理。(其实这也是当初我认为客户端udp不会对端口不可达数据进行相应的原因,因为udp处理流程是udp_rcv)。

    

    实际上icmp_rcv函数最重要的是 它调用了:icmp_pointers[icmph->type].handler(skb);

handler = icmp_unreach

icmp_unreach函数最终的一步,就是它最后一步:

是不是很像ip_local_deliver_finish?

是很像,只是ip_local_deliver_finish中,调用了ipprot->handler,而这里调用了ipprot->err_handler

对于udp,err_handler = udp_err = __udp4_lib_err

在该函数中,只有进入如下的流程,应用程序才会反应:

__udp4_lib_err先根据skb->data中dip和sip,查找socket,skb->data是icmp的负载

故先调用 __udp4_lib_lookup 查找socket,传参时,sip和dip需要反一下。

__udp4_lib_err:

关于UDP接收icmp端口不可达(port unreachable)

先决条件是inet->recverr为非0,或者inet->recverr为0但是udp处于TCP_ESTABLISHED状态。

否则应用程序休想收到该端口不可达的数据,应用程序就等着read超时吧。所以说,为了获取udp端口不可达的情况

有2种方法:

(1):

int val = 1;

setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));

(2):

对udp进行connect操作,并且将sendto改成send

4:

udp获知端口不可达的源程序(方法1:设置Socket选项;方法2:对UDP进行Connect)

注意,阻塞情况下,recvfrom会阻塞,即使收到端口不可达消息,也会阻塞。但是经过 方法1 和 方法2后,recvfrom会返回,返回值是-1,然后 判断errno是否是ECONNREFUSED来判断是否收到端口不可达消息。

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
unsigned char revc_buf[1024];
 
int main()
{
	int fd,ret,recv_len,size=1024;
	struct sockaddr_in server_addr,addr;
	int val = 1;
	server_addr.sin_family = AF_INET;
	server_addr.sin_addr.s_addr = inet_addr("192.168.2.254");
	server_addr.sin_port = htons(77);
	
	fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if(fd < 0)
	{	
		perror("socket fail ");
		return -1;
	}
	
	printf("socket sucess\n");
 
        //方法1
	#if 1
	setsockopt(fd, IPPROTO_IP, IP_RECVERR , &val,sizeof(int));
	if(sendto(fd, "nihao", strlen("nihao"), 0, (const struct sockaddr *)&(server_addr), sizeof(struct sockaddr_in))<0)
	{
		perror("sendto fail ");
		return -1;
	}
	printf("sendto sucess\n");
	recv_len = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
	if (recv_len == -1)
	{
		if (errno == ECONNREFUSED)
		{
			printf("Recv port unreachable\n");
		}
	}
	//方法2
	#elif 0
	ret = connect(fd, (const struct sockaddr *) &(server_addr), sizeof (struct sockaddr_in));
	if(ret < 0)
	{
		printf("connect fail\n");
		return -1;
	}
	
	ret = send(fd, "ni hao", strlen("nihao"),0);
	if(ret < 0)
	{
		printf("write fail\n");
		return -1;
	}
	
	ret = recvfrom(fd, revc_buf, sizeof(revc_buf), 0, (struct sockaddr *)&addr, (int *)&size);
	if (ret == -1) {
		if (errno == ECONNREFUSED)
		{
			printf("Recv port unreachable\n");
		}
	}
 
	#endif
	close(fd);
	
	return 0;
}

如果对你有用,请打赏一元哦:http://www.mrpre.com/

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157911.html原文链接:https://javaforall.net

赞 (0)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • CMS收集器和G1收集器的区别「建议收藏」

    CMS收集器和G1收集器的区别「建议收藏」目录CMS收集器和G1收集器的区别区别一:使用范围不一样区别二:STW的时间区别三:垃圾碎片区别四:垃圾回收的过程不一样对于CMS收集器和G1收集器的不同,目前简单写了一下4点,有不足的地方后面再不断的更新修改。CMS收集器和G1收集器的区别 区别一:使用范围不一样  CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集…

    2022年6月3日
  • linux的rwx权限,linux权限管理:rwx

    linux的rwx权限,linux权限管理:rwx标签:rwx权限管理简介:r,w,x对文件及目录进行权限管理,从而达到文件及目录管理。1、rwx对于文件而言:r:可读,可以使用类似cat等命令查看文件内容;w:可写,可以编辑或删除此文件;x:可执行,exacutable,可以命令提示符下当作命令提交给内核运行;2、rwx对于目录而言:r:可以对此目录执行ls以列出内部的所有文件;w:可以在此目录创建文件;x:可以使用cd切换进此目录,也可以使用…

    2022年6月10日
  • 调用网站第三方接口实现短信发邮件「建议收藏」

    调用网站第三方接口实现短信发邮件「建议收藏」一,电子邮件的使用在项目开发中,经常会用到通过程序发送电子邮件,例如:注册用户邮件激活,通过邮件找回密码,发送报表等。二,通过PHP程序来操作电子邮件几种通过PHP发送电子邮件的方式1)通过mail()函数发送邮件2)使用fsockopen方式连接smtp服务器发送3)使用phpmailer邮件类发送。个人推荐使用phpmailer邮件类发送,phpmailer比较方便而且功能强大…

    2022年6月3日
  • Python的缩进规则「建议收藏」

    Python中的缩进(Indentation)决定了代码的作用域范围。这一点和传统的c/c++有很大的不同(传统的c/c++使用花括号花括号{}符决定作用域的范围;python使用缩进空格来表示作用域的范围,相同缩进行的代码是处于同一范围)。每行代码中开头的空格数(whitespace)用于计算该行代码的缩进级别(Indentationlevel),注意一个Tab会被替换为1~8个Space(具体的空格数量,不同的编译器有不同的数量),缩进级别为0表示无缩进空格。在一个源文件不建议同时使用空格和制表缩

    2022年4月10日
  • NAT网关之SNAT进阶使用(一)SNAT POOL

    NAT网关之SNAT进阶使用(一)SNAT POOL

    2021年5月25日
  • 因果图案例_生活中的因果例子

    因果图案例_生活中的因果例子案例:交通一卡通自动充值软件系统要求:系统只接收50或100元纸币,一次只能使用一张纸币,一次充值金额只能为50元或100元。 若输入50元纸币,并选择充值50元,完成充值后退卡,提示充值成功; 若输入50元纸币,并选择充值100元,提示输入金额不足,并退回50元; 若输入100元纸币,并选择充值50元,完成充值后退卡,提示充值成功,找零50元; 若输入100元纸币,并选择充值10…

    2022年8月14日

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号