当前位置: 首页 > news >正文

计算机网站建设与推广做pc端网站资讯

计算机网站建设与推广,做pc端网站资讯,金昌网站建设,福建建设人才与科技发展中心网站1 从诡异开始 最近遇到一个线上问题#xff0c;client 发了一个 udp 请求#xff0c;服务器回了一个响应#xff0c;但诡异的是#xff0c;client 的 log 却看不到对应的处理日志。抓包发现内核发出了一个指示 udp 目的端口不可达的 icmp 报文#xff0c;类似这样的#…1 从诡异开始 最近遇到一个线上问题client 发了一个 udp 请求服务器回了一个响应但诡异的是client 的 log 却看不到对应的处理日志。抓包发现内核发出了一个指示 udp 目的端口不可达的 icmp 报文类似这样的 14:33:36.781627 IP 127.0.0.1 127.0.0.1: ICMP 127.0.0.1 udp port 58988 unreachable, length 42 难道 socket 被人关掉了仔细分析了代码client 发包默认会 connect 到 server特殊情况下会再调用一下 connect 到 0.0.0.0意为取消掉 connect这时就发现取消 connect 之前发的包的回包收不到了。 connect 原意是期望只能收到某目的地址的回包取消之后自然是希望所有的回包都能收到但反而导致了丢包的发生不搞清楚这个原因注定要寝食难安 文中所引用 kernel 代码基于 Linux-2.6.34。 2 connect() 入口当然是从系统调用开始 // net/socket.c SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,int, addrlen) {// 通过fd找socksock sockfd_lookup_light(fd, err, fput_needed);// 拷贝addr到内核空间err move_addr_to_kernel(uservaddr, addrlen, (struct sockaddr *)address);if (err 0)goto out_put;// 调用AF族的connecterr sock-ops-connect(sock, (struct sockaddr *)address, addrlen,sock-file-f_flags); } udp 协议属于 AF_INET 协议族所以调用走到了这里 // net/ipv4/af_inet.c int inet_dgram_connect(struct socket *sock, struct sockaddr * uaddr,int addr_len, int flags) {struct sock *sk sock-sk;if (addr_len sizeof(uaddr-sa_family))return -EINVAL;// 取消connectif (uaddr-sa_family AF_UNSPEC)return sk-sk_prot-disconnect(sk, flags);// connectreturn sk-sk_prot-connect(sk, (struct sockaddr *)uaddr, addr_len); } 这里就根据sa_family的值决定是connect 或者 disconnect可以接着看udp中对应的实现了 // net/ipv4/datagram.c int ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len) {// 查找从源地址 saddr 到目标地址 usin-sin_addr.s_addr 的路由, 填充 rtable 结构体err ip_route_connect(rt, usin-sin_addr.s_addr, saddr,RT_CONN_FLAGS(sk), oif,sk-sk_protocol,inet-inet_sport, usin-sin_port, sk, 1);if (err) {if (err -ENETUNREACH)IP_INC_STATS_BH(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);return err;}// 填充源目的地址if (!inet-inet_saddr)inet-inet_saddr rt-rt_src; /* Update source address */if (!inet-inet_rcv_saddr)inet-inet_rcv_saddr rt-rt_src;inet-inet_daddr rt-rt_dst;inet-inet_dport usin-sin_port;sk-sk_state TCP_ESTABLISHED;inet-inet_id jiffies;sk_dst_set(sk, rt-u.dst);return(0); } 这里原地址有两个一个是 inet_saddr 是发包时用的另一个 inet_rcv_saddr 则是接收时用的一般两者是一致的除了监听 0.0.0.0 这种场景。 可见udp connect 后则是记录了源目的地址大胆猜测一下收包的时候会判断地址不匹配的就不收这也符合我们对 connect 最初的理解。 ICMP_PORT_UNREACH udp 收到一个包的流程比较冗长我们直接看比较关键的部分 // net/ipv4/udp.c int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,int proto) {if (proto IPPROTO_UDP) {/* UDP validates ulen. */if (ulen sizeof(*uh) || pskb_trim_rcsum(skb, ulen))goto short_packet;uh udp_hdr(skb);}// 根据源端口和目标端口查找匹配的套接字sk __udp4_lib_lookup_skb(skb, uh-source, uh-dest, udptable);if (sk ! NULL) { // 如果找到了就进入收包函数int ret udp_queue_rcv_skb(sk, skb);...return 0;}// 安全检查不通过就悄悄丢弃if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))goto drop;/* No socket. Drop packet silently, if checksum is wrong */if (udp_lib_checksum_complete(skb))goto csum_error;// 如果没找到对应的sock并且校验和是ok的就发送icmp不可达的报文icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);drop:UDP_INC_STATS_BH(net, UDP_MIB_INERRORS, proto IPPROTO_UDPLITE);kfree_skb(skb);return 0; } 咦icmp 不可达的报文原来就是这里发出的那什么情况下可能导致找不到对应的 sock 呢 static struct sock *__udp4_lib_lookup(struct net *net, __be32 saddr,__be16 sport, __be32 daddr, __be16 dport,int dif, struct udp_table *udptable) {struct sock *sk, *result;struct hlist_nulls_node *node;unsigned short hnum ntohs(dport);// 首先使用目的端口号dport计算哈希值slot以确定在 UDP 哈希表udptable中的哪个槽位进行查找unsigned int hash2, slot2, slot udp_hashfn(net, hnum, udptable-mask);struct udp_hslot *hslot2, *hslot udptable-hash[slot];int score, badness;rcu_read_lock();// 如果目标槽位hslot中的元素数量超过 10则尝试使用更复杂的哈希hash2来优化查找过程。// 这涉及到根据目的地址和端口号再次计算哈希值并检查另一个槽位hslot2中的元素数量是否更少。// 如果是则优先在那个槽位中查找if (hslot-count 10) {hash2 udp4_portaddr_hash(net, daddr, hnum);slot2 hash2 udptable-mask;hslot2 udptable-hash2[slot2];if (hslot-count hslot2-count)goto begin;result udp4_lib_lookup2(net, saddr, sport,daddr, hnum, dif,hslot2, slot2);if (!result) {// 如果在 hslot2 中没有找到匹配的套接字并且 hslot2 是基于 INADDR_ANY任意地址计算的则再次尝试查找。hash2 udp4_portaddr_hash(net, INADDR_ANY, hnum);slot2 hash2 udptable-mask;hslot2 udptable-hash2[slot2];if (hslot-count hslot2-count)goto begin;result udp4_lib_lookup2(net, saddr, sport,INADDR_ANY, hnum, dif,hslot2, slot2);}rcu_read_unlock();return result;} begin:result NULL;badness -1;// 如果上述优化查找没有成功或者目标槽位中的元素数量不多于 10// 则直接遍历目标槽位hslot中的所有套接字// 对于槽位中的每个套接字使用 compute_score 函数计算一个“分数”该分数基于套接字地址、端口和可能的其他因素如套接字状态sk_nulls_for_each_rcu(sk, node, hslot-head) {score compute_score(sk, net, saddr, hnum, sport,daddr, dport, dif);if (score badness) {result sk;badness score;}}...return result; } __udp4_lib_lookup 中逻辑稍多但简而言之就是用目的地址、端口号从 udp 的 hash 表中快速查找对应的 sock 结构。因此正常来讲udp 查不到 sock 的原因大抵是有人把他从 hash 表中移除了。 接着浅看一下 compute_score: static inline int compute_score(struct sock *sk, struct net *net, __be32 saddr,unsigned short hnum,__be16 sport, __be32 daddr, __be16 dport, int dif) {int score -1;if (net_eq(sock_net(sk), net) udp_sk(sk)-udp_port_hash hnum !ipv6_only_sock(sk)) {struct inet_sock *inet inet_sk(sk);score (sk-sk_family PF_INET ? 1 : 0);if (inet-inet_rcv_saddr) {if (inet-inet_rcv_saddr ! daddr)return -1;score 2;}if (inet-inet_daddr) {if (inet-inet_daddr ! saddr)return -1;score 2;}if (inet-inet_dport) {if (inet-inet_dport ! sport)return -1;score 2;}if (sk-sk_bound_dev_if) {if (sk-sk_bound_dev_if ! dif)return -1;score 2;}}return score; } compute_score 中会判断dport 、daddr 以及 rcv_addr不匹配的就返回 -1 了这进一步证明了我们上面对 connect 原理的推测是正确的。 3 现出原形 int udp_disconnect(struct sock *sk, int flags) {struct inet_sock *inet inet_sk(sk);sk-sk_state TCP_CLOSE;// 重置connect中设置的地址等信息inet-inet_daddr 0;inet-inet_dport 0;sk-sk_bound_dev_if 0;...if (!(sk-sk_userlocks SOCK_BINDPORT_LOCK)) {// unhashsk-sk_prot-unhash(sk);inet-inet_sport 0;}sk_dst_reset(sk);return 0; } unhash 这个函数一看就不对劲 void udp_lib_unhash(struct sock *sk) {if (sk_hashed(sk)) {struct udp_table *udptable sk-sk_prot-h.udp_table;struct udp_hslot *hslot, *hslot2;hslot udp_hashslot(udptable, sock_net(sk),udp_sk(sk)-udp_port_hash);hslot2 udp_hashslot2(udptable, udp_sk(sk)-udp_portaddr_hash);spin_lock_bh(hslot-lock);if (sk_nulls_del_node_init_rcu(sk)) {hslot-count--;inet_sk(sk)-inet_num 0;sock_prot_inuse_add(sock_net(sk), sk-sk_prot, -1);spin_lock(hslot2-lock);hlist_nulls_del_init_rcu(udp_sk(sk)-udp_portaddr_node);hslot2-count--;spin_unlock(hslot2-lock);}spin_unlock_bh(hslot-lock);} } 果然一个是基于端口号的 hash另一个基于 port addr 的 hash 都被取消了引用 4 印证 原理已经搞明白了再自己复现一下印证一番 先搞它一个 server你发啥我回啥 const char* g_server_ip 127.0.0.1; uint16_t g_server_port 6666;int do_server() {int sock socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock 0) {printf(server socket failed: %s\n, strerror(errno));return -1;}uint32_t ip;inet_aton(g_server_ip, (struct in_addr *)ip);struct sockaddr_in addr;memset(addr, 0, sizeof(addr));addr.sin_family AF_INET;addr.sin_addr.s_addr ip;addr.sin_port htons(g_server_port);if (bind(sock, (struct sockaddr *)addr, (socklen_t)sizeof(addr)) 0) {printf(server bind failed: %s\n, strerror(errno));return -1;}char buf[10240];struct sockaddr_in src_addr;socklen_t addrlen sizeof(src_addr);while (1) {ssize_t ret recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr *)src_addr, addrlen);if (ret 0) {if (errno ! EAGAIN) {printf(server recv failed: %s\n, strerror(errno));break;}continue;} buf[ret] 0;size_t ret_s sendto(sock, buf, ret, 0,(struct sockaddr *)src_addr, addrlen);printf(resp:%s %d/%d\n, buf, ret_s, ret);}return 0; } 有 server 必有 client void disconnect(int sock) {uint32_t ip;inet_aton(0.0.0.0, (struct in_addr *)ip);struct sockaddr_in addr;memset(addr, 0, sizeof(addr));if (connect(sock, (struct sockaddr *)addr, (socklen_t)sizeof(addr)) 0) {printf(client connect failed: %s\n, strerror(errno));} }int do_client() {int sock socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);if (sock 0) {printf(client socket failed: %s\n, strerror(errno));return -1;}uint32_t ip;inet_aton(g_server_ip, (struct in_addr *)ip);struct sockaddr_in addr;memset(addr, 0, sizeof(addr));addr.sin_family AF_INET;addr.sin_addr.s_addr ip;addr.sin_port htons(g_server_port);char buf[10240];struct sockaddr_in src_addr;socklen_t addrlen sizeof(src_addr);int i 0;while (1) {int n snprintf(buf, sizeof(buf), echo %d, i);size_t ret_s sendto(sock, buf, n, 0,(struct sockaddr *)addr, sizeof(addr));if (ret_s ! n) {break;}udp_connect(sock);ssize_t ret recvfrom(sock, buf, sizeof(buf), 0,(struct sockaddr *)src_addr, addrlen);if (ret 0) {if (errno ! EAGAIN) {printf(client recv failed: %s\n, strerror(errno));break;}sleep(1);continue;} buf[ret] 0;printf(resp:%s %d/%d\n, buf, ret, n);sleep(1);break;}return 0; }先发一个请求然后 disconnect 一下看看能否收到回包 # server 收到请求并回了响应 [rootcentos udp_connect]# ./a.out -s resp:echo 0 6/6 ^C# client 发出了请求未收到响应阻塞在了recvfrom处 [rootcentos udp_connect]# ./a.out -c ^C# 抓包发现udp不可达的icmp [rootcentos ~]# tcpdump -i any port 6666 or \( icmp and host 127.0.0.1 \) -nn tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes 23:28:23.314272 IP 127.0.0.1.50864 127.0.0.1.6666: UDP, length 6 23:28:23.314372 IP 127.0.0.1.6666 127.0.0.1.50864: UDP, length 6 23:28:23.314381 IP 127.0.0.1 127.0.0.1: ICMP 127.0.0.1 udp port 50864 unreachable, length 42 ^C 3 packets captured 6 packets received by filter 0 packets dropped by kernel [rootcentos ~]# 符合预期 附 最后还是附上测试代码Linux/udp_connect at master · Fireplusplus/Linux · GitHub
http://www.zqtcl.cn/news/838053/

相关文章:

  • 租服务器发布网站团购网站单页模板
  • 西安网站建设运维凡客精选
  • 权威网站发布平台wordpress 如何安装
  • 没有官方网站怎么做seo优化军事新闻
  • 在招聘网站做销售技巧教育培训平台
  • 网站栏目 英文做网站在哪接单
  • 湖北网络营销网站市场营销策划案
  • 政务信息系统网站建设规范上海网站开发制作公司
  • 网站公众平台建设方案湖南seo优化报价
  • 企业网站制作公司discuz 转wordpress
  • 可信网站是什么意思应用软件开发平台
  • 上海市官方网站福建省中嘉建设工程有限公司网站
  • 备案之后怎样把 放到网站上大连建设网球场价格
  • dkp网站开发今天最新新闻
  • 山东省环保厅官方网站建设项目东莞寮步网站设计
  • 网站开发可能遇到的问题附近电脑培训班位置
  • 如何查看域名以前是做什么网站的网站索引下降如何解决
  • 潜江 网站建设扬中话
  • 网站建设项目方案ppt广州建站模板平台
  • 房产部门成立网站免费seo推广软件
  • python做网站好处百度指数分析报告
  • 网站建设挣钱班级介绍网页制作模板
  • 工作室 网站建设app公司
  • 自己做的网站怎么在百度搜索到网页制作论文3000字
  • 如何网站托管中国跨境电商平台有多少
  • 手机p2p网站做平面设计兼职的网站有哪些
  • 贵金属网站建设唐山网站制作工具
  • 网站入门成都网站制作沈阳
  • 接做网站单子的网站做网站要会那些ps
  • 做盗市相关网站wordpress速度优化简书