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

BSD协议栈:UDP发送

BSD实现

在BSD中UDP头部数据结构如下:

/*
 * Udp protocol header.
 * Per RFC 768, September, 1981.
 */
struct udphdr {
	u_short	uh_sport;		/* source port */
	u_short	uh_dport;		/* destination port */
	short	uh_ulen;		/* udp length */
	u_short	uh_sum;			/* udp checksum */
};

IP首部实现可以参考lwip,不过笔者简单讲一讲一个相似的结构体struct ipovly。

struct ipovly是在BSD内部使用的一个覆盖IP首部之上的结构,其实并不是真正的IP首部,这是为了方便计算校验和。

/*
 * Overlay for ip header used by other protocols (tcp, udp).
 */
struct ipovly {
	caddr_t	ih_next, ih_prev;	/* for protocol sequence q's */
	u_char	ih_x1;			/* (unused) */
	u_char	ih_pr;			/* protocol */
	short	ih_len;			/* protocol length */
	struct	in_addr ih_src;		/* source internet address */
	struct	in_addr ih_dst;		/* destination internet address */
};

IP/UDP首部结构体:

/*
 * UDP kernel structures and variables.
 */
struct	udpiphdr {
	struct 	ipovly ui_i;		/* overlaid ip structure */
	struct	udphdr ui_u;		/* udp header */
};
#define	ui_next		ui_i.ih_next
#define	ui_prev		ui_i.ih_prev
#define	ui_x1		ui_i.ih_x1
#define	ui_pr		ui_i.ih_pr
#define	ui_len		ui_i.ih_len
#define	ui_src		ui_i.ih_src
#define	ui_dst		ui_i.ih_dst
#define	ui_sport	ui_u.uh_sport
#define	ui_dport	ui_u.uh_dport
#define	ui_ulen		ui_u.uh_ulen
#define	ui_sum		ui_u.uh_sum

UDP发送

UDP发送实现简单介绍如下:

参数

inp:UDP 套接字在内核中的表示,包含了套接字的所有状态和配置信息,例如IP/UDP首部等信息

m:指向输出mbuf链

addr:一个可选指针,指向sockaddr_in结构中的目的地址

control:一个可选指针,指向sendmsg的控制信息

程序执行

1.如果我们没有使用sendmsg这些带控制信息的API,那么就丢弃control参数。

2.指定了目的地址:判断套接字传递过来的目的地址是不是任意地址(0.0.0.0),如果是,那么该地址无意义,返回错误。如果不是,需要通过in_pcbconnect函数临时连接到该目的地址,不过在连接之前需要暂时提升当前程序优先级并保存本地地址。因为临时连接会改变套接字中的外部地址、端口和本地地址, 此时的套接字信息是不完整的。如果此时有 UDP 数据报到达,协议栈在查找匹配的套接字时,可能因地址信息变化而将数据报交给错误的套接字,导致数据被错误的进程接收。

未指定目的地址:判断是不是已经连接上了(例如调用 connect() 函数实现连接),否则也报错。

connect() 函数的本质也是调用in_pcbconnect函数。

3.关于M_PREPEND和mtod可以看mbuf那一节。程序先申请内存,再设置IP/UDP首部,接下来就是计算校验和。

4.检验与计算校验和,在前文笔者简单介绍过校验和的计算,分为初始化、构建、计算、填写四个步骤,读者可以返回前文查看。

初始化与构建:将IP首部覆盖为ui_next,ui->ui_prev,并覆盖其他参数,这两个参数不会影响校验和结果,因为它们被设置为0。现在IP首部被覆盖为:ui_next,ui_prev,ui_x1,ui_pr,ui_len。其中IP首部中的ui_len、ui_src和ui_dst是真正影响校验和结果的参数。计算和填写就不多赘述了,是通过in_cksum实现的。

正如我们前面所说的,UDP的IPv4是允许不进行校验的,只要把校验和填0。而如果计算出来的结果是0,校验和会填写为0xffff。

5.udp_output会填充IP头部的三个字段:服务类型TOS、全长len、生存时间TTL,而ip_output则会填充其他字段。

6.使用ip_output发送数据报。

7.如果socket是临时连接上的,那么调用in_pcbdisconnect断连临时连接的socket,同时恢复本地地址并恢复程序优先级,之后程序结束。

其中in_pcbconnect将会耗费程序近三分之一的时间。

int udp_output(inp, m, addr, control)
	register struct inpcb *inp;
	register struct mbuf *m;
	struct mbuf *addr, *control;
{
	register struct udpiphdr *ui;
	register int len = m->m_pkthdr.len;
	struct in_addr laddr;
	int s, error = 0;

	if (control)
		m_freem(control);		/* XXX */

	if (addr) {
		laddr = inp->inp_laddr;
		if (inp->inp_faddr.s_addr != INADDR_ANY) {
			error = EISCONN;
			goto release;
		}
		/*
		 * Must block input while temporarily connected.
		 */
		s = splnet();
		error = in_pcbconnect(inp, addr);
		if (error) {
			splx(s);
			goto release;
		}
	} else {
		if (inp->inp_faddr.s_addr == INADDR_ANY) {
			error = ENOTCONN;
			goto release;
		}
	}
	/*
	 * Calculate data length and get a mbuf
	 * for UDP and IP headers.
	 */
	M_PREPEND(m, sizeof(struct udpiphdr), M_DONTWAIT);
	if (m == 0) {
		error = ENOBUFS;
		goto release;
	}

	/*
	 * Fill in mbuf with extended UDP header
	 * and addresses and length put into network format.
	 */
	ui = mtod(m, struct udpiphdr *);
	ui->ui_next = ui->ui_prev = 0;
	ui->ui_x1 = 0;
	ui->ui_pr = IPPROTO_UDP;
	ui->ui_len = htons((u_short)len + sizeof (struct udphdr));
	ui->ui_src = inp->inp_laddr;
	ui->ui_dst = inp->inp_faddr;
	ui->ui_sport = inp->inp_lport;
	ui->ui_dport = inp->inp_fport;
	ui->ui_ulen = ui->ui_len;

	/*
	 * Stuff checksum and output datagram.
	 */
	ui->ui_sum = 0;
	if (udpcksum) {
	    if ((ui->ui_sum = in_cksum(m, sizeof (struct udpiphdr) + len)) == 0)
		ui->ui_sum = 0xffff;
	}
	((struct ip *)ui)->ip_len = sizeof (struct udpiphdr) + len;
	((struct ip *)ui)->ip_ttl = inp->inp_ip.ip_ttl;	/* XXX */
	((struct ip *)ui)->ip_tos = inp->inp_ip.ip_tos;	/* XXX */
	udpstat.udps_opackets++;
	error = ip_output(m, inp->inp_options, &inp->inp_route,
	    inp->inp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST),
	    inp->inp_moptions);

	if (addr) {
		in_pcbdisconnect(inp);
		inp->inp_laddr = laddr;
		splx(s);
	}
	return (error);

release:
	m_freem(m);
	return (error);
}

http://www.kler.cn/a/551454.html

相关文章:

  • Python中通过Pymysql连接MySQL
  • SoftwareCluster中如何配置VendorSignature
  • 机器学习_14 随机森林知识点总结
  • flink反压详解
  • Android 10.0 移除wifi功能及相关菜单
  • Android中kotlin的Map简单使用方法
  • 【现代深度学习技术】深度学习计算 | GPU
  • STM32 ADC介绍(硬件原理篇)
  • Linux的SSH无法连接(shell request failed on channel 0)
  • Dockerfile 详解:构建自定义镜像
  • AUTO TECH China 2025 广州国际汽车技术展览会:引领汽车科技新潮流
  • 日常问题-pnpm install执行没有node_modules生成
  • OpenHarmony 系统性能优化——默认关闭全局动画
  • DeepSeek教unity------Dotween
  • 网络安全学习笔记之Internet基本知识
  • 开发一个交易所需要哪些技术
  • 算法-栈括号匹配
  • Go语言的游戏开发
  • 视点坐标及鼠标交点坐标的信息显示(七)
  • HBuilderX中uni-app打开页面时,如何用URL传递参数,Query参数传递