TCP/IP詳解V2(一)之協議控制塊


協議控制塊

協議層分成兩種,一種是Internet PCB,另一種是TCP PCB,因為UDP協議是無連接協議,所以不存在專用的連接控制信息。
Internet PCB中包含UDP與TCP所有共用信息:外部與本地IP地址,外部與本地的端口,IP首部原型,該端口使用的IP選型以及一個指向該端點的目的地址的路由表信息。
TCP PCB中包含了TCP為連接維護的信息:兩個方向的序號,窗口大小,重傳次數等的信息。
其中,Internet PCB是一個傳輸層的數據結構,TCP,UDP和原始IP使用它,但是IP,ICMP等的網絡層協議不使用。
整體描述如下:

數據結構

struct inpcb {
	struct	inpcb *inp_next,*inp_prev;    //使用雙向鏈表維護的PCB‘s
	struct	inpcb *inp_head;	    //每個PCB都存在一個指針指向PCB’s雙向鏈表的頭部
	struct	in_addr inp_faddr;	    /* foreign host table entry */    //注意:使用網絡字節序維護這個四元組
	u_short	inp_fport;		    /* foreign port */
	struct	in_addr inp_laddr;	/* local host table entry */
	u_short	inp_lport;		/* local port */
	struct	socket *inp_socket;	    //指向虛擬文件系統中的socket結構
	caddr_t	inp_ppcb;		//如果存在TCP PCB,就指向它,否則為空
	struct	route inp_route;	//指向一條路由選型
	int	inp_flags;		//維護一些標識信息,基本上使用的很少
	struct	ip inp_ip;		//維護一個IP首部備份,只是用其中的兩個成員,TOS和TTL
	struct	mbuf *inp_options;	//維護IP選項
	struct	ip_moptions *inp_moptions;     //維護一個IP多播選項
};

基本操作

創建與銷毀

int
in_pcballoc(so, head)
	struct socket *so;
	struct inpcb *head;
{
	register struct inpcb *inp;

	MALLOC(inp, struct inpcb *, sizeof(*inp), M_PCB, M_WAITOK);    //從內核中分配一個inpcb結構
	if (inp == NULL)    //如果分配失敗,返回錯誤
		return (ENOBUFS);
	bzero((caddr_t)inp, sizeof(*inp));    //將新分配的結構初始化為0,其中IP地址與端口號必須要初始化為0
	inp->inp_head = head;    //指向整個PCB‘s的頭部
	inp->inp_socket = so;    //指向VFS中的socket結構
	insque(inp, head);    //將這個結構加入到隊列中
	so->so_pcb = (caddr_t)inp;    //同時設置socket中的信息指向該PCB
	return (0);
}
int
in_pcbdetach(inp)
	struct inpcb *inp;
{
	struct socket *so = inp->inp_socket;    //從socket中獲取PCB結構,並將socket中的結構置為空

	so->so_pcb = 0;
	sofree(so);    //釋放socket結構
	if (inp->inp_options)    //釋放IP選項
		(void)m_free(inp->inp_options);
	if (inp->inp_route.ro_rt)    //釋放路由記錄
		rtfree(inp->inp_route.ro_rt);
	ip_freemoptions(inp->inp_moptions);    //釋放IP多播選項
	remque(inp);    //從PCB’s的雙向鏈表中移除這個PCB
	FREE(inp, M_PCB);    //徹底的釋放這個PCB
}

in_pcblookup:

  • 功能A:將IP數據報投遞給合適的運輸層Internet PCB
  • 功能B:執行bind時,驗證是否綁定了本地IP與Port
struct inpcb *
in_pcblookup(head, faddr, fport_arg, laddr, lport_arg, flags)
	struct inpcb *head;
	struct in_addr faddr, laddr;
	u_int fport_arg, lport_arg;
	int flags;
{
	register struct inpcb *inp, *match = 0;
	int matchwild = 3, wildcard;
	u_short fport = fport_arg, lport = lport_arg;

	for (inp = head->inp_next; inp != head; inp = inp->inp_next) {    //從Internet PCB的起始位置開始搜索
    		if (inp->inp_lport != lport)    //如果與數據報想要投遞的端口不匹配,直接開始下一輪的匹配
			continue;
		wildcard = 0;    //將當前的通配匹配數置0
		if (inp->inp_laddr.s_addr != INADDR_ANY) {    //如果PCB中的本地地址不是通配地址,而數據報中的目標地址是一個通配地址,通配數+1
			if (laddr.s_addr == INADDR_ANY)
				wildcard++;
			else if (inp->inp_laddr.s_addr != laddr.s_addr)    //如果PCB中的本地地址與數據報中的目標地址不匹配,直接開始下一輪的循環
				continue;
		} else {
			if (laddr.s_addr != INADDR_ANY)    //如果PCB中是一個本地通配地址,通配數+1
				wildcard++;
		}
		if (inp->inp_faddr.s_addr != INADDR_ANY) {    //與上述代碼相同,判斷PCB中的遠程地址與數據報的源地址之間的通配關系
			if (faddr.s_addr == INADDR_ANY)
				wildcard++;
			else if (inp->inp_faddr.s_addr != faddr.s_addr ||
			    inp->inp_fport != fport)
				continue;
		} else {
			if (faddr.s_addr != INADDR_ANY)
				wildcard++;
		}
		if (wildcard && (flags & INPLOOKUP_WILDCARD) == 0)    //如果這個PCB存在通配匹配但是不允許通配匹配,開啟下一次匹配
			continue;
		if (wildcard < matchwild) {    //記錄目前的匹配,尋找通配匹配數最小的匹配,如果存在為0的通配匹配,退出循環,找到了最合適的匹配
			match = inp;
			matchwild = wildcard;
			if (matchwild == 0)
				break;
		}
	}
	return (match);
}

in_pcbbind:

  • 功能A:為TCP/UDP綁定本地地址與端口,在沒有顯式綁定時會進行隱式綁定
int
in_pcbbind(inp, nam)
	register struct inpcb *inp;
	struct mbuf *nam;
{
	register struct socket *so = inp->inp_socket;    //獲取VFS中描述的socket
	register struct inpcb *head = inp->inp_head;    //獲取整個PCB隊列的首部
	register struct sockaddr_in *sin;
	struct proc *p = curproc;		
	u_short lport = 0;
	int wild = 0, reuseport = (so->so_options & SO_REUSEPORT);
	int error;

	if (in_ifaddr == 0)    //全局變量,判斷接口是否存在
		return (EADDRNOTAVAIL);
	if (inp->inp_lport || inp->inp_laddr.s_addr != INADDR_ANY)    //如果PCB中已經存在本地地址與本地端口,說明整個PCB已經被綁定,重復出錯
		return (EINVAL);
	if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0 &&    //簡而言之,TCP允許通配地址的綁定,UDP不允許
	    ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 ||
	     (so->so_options & SO_ACCEPTCONN) == 0))
		wild = INPLOOKUP_WILDCARD;
	if (nam) {
		sin = mtod(nam, struct sockaddr_in *);    //mbuf中存在的是PCB想要綁定的地址,但是長度不正確,返回錯誤
		if (nam->m_len != sizeof (*sin))
			return (EINVAL);
#ifdef notdef
		/*
		 * We should check the family, but old programs
		 * incorrectly fail to initialize it.
		 */
		if (sin->sin_family != AF_INET)    //檢查協議域類型
			return (EAFNOSUPPORT);
#endif
		lport = sin->sin_port;
		if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {    //檢測綁定的地址是否是一個多播地址
			/*
			 * Treat SO_REUSEADDR as SO_REUSEPORT for multicast;
			 * allow complete duplication of binding if
			 * SO_REUSEPORT is set, or if SO_REUSEADDR is set
			 * and a multicast address is bound on both
			 * new and duplicated sockets.
			 */
			if (so->so_options & SO_REUSEADDR)    //如果是多播地址,置位重用選項
				reuseport = SO_REUSEADDR|SO_REUSEPORT;
		} else if (sin->sin_addr.s_addr != INADDR_ANY) {    //如果本地綁定的不是一個通配地址,判斷這個通配地址是由與一個本地接口對應
			sin->sin_port = 0;		/* yech... */
			if (ifa_ifwithaddr((struct sockaddr *)sin) == 0)
				return (EADDRNOTAVAIL);
		}
		if (lport) {    //如果想要綁定一個本地端口
			struct inpcb *t;

			/* GROSS */
			if (ntohs(lport) < IPPORT_RESERVED &&
			    (error = suser(p->p_ucred, &p->p_acflag)))    //如果綁定<1024的端口,判斷權限是否正常
				return (error);
			t = in_pcblookup(head, zeroin_addr, 0,
			    sin->sin_addr, lport, wild);    //在PCBs中進行尋找是否已經存在相似的PCB
			if (t && (reuseport & t->inp_socket->so_options) == 0)    //如果存在,並且沒有設置REUSE選項,返回錯誤
				return (EADDRINUSE);
		}
		inp->inp_laddr = sin->sin_addr;    //否則的話,設置PCB中本地IP
	}
	if (lport == 0)
		do {
			if (head->inp_lport++ < IPPORT_RESERVED ||                        //在PCB head中以網絡字節序維護着一個下一個使用的端口,找到之后,將這個端口轉換為主機字節序
			    head->inp_lport > IPPORT_USERRESERVED)        //然后調整head中的下一個端口的緩存
				head->inp_lport = IPPORT_RESERVED;
			lport = htons(head->inp_lport);
		} while (in_pcblookup(head,
			    zeroin_addr, 0, inp->inp_laddr, lport, wild));
	inp->inp_lport = lport;    //設置了PCB中的端口,就完事了唄
	return (0);
}

in_pcbconnect:

  • 功能A:為TCP/UDP綁定遠程地址與端口
int
in_pcbconnect(inp, nam)
	register struct inpcb *inp;
	struct mbuf *nam;
{
	struct in_ifaddr *ia;
	struct sockaddr_in *ifaddr;
	register struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *);    //從mbuf中獲取遠程地址

	if (nam->m_len != sizeof (*sin))    //判斷地址長度,協議域以及端口是否正確
		return (EINVAL);
	if (sin->sin_family != AF_INET)
		return (EAFNOSUPPORT);
	if (sin->sin_port == 0)
		return (EADDRNOTAVAIL);
	if (in_ifaddr) {
		/*
		 * If the destination address is INADDR_ANY,
		 * use the primary local address.
		 * If the supplied address is INADDR_BROADCAST,
		 * and the primary interface supports broadcast,
		 * choose the broadcast address for that interface.
		 */
#define	satosin(sa)	((struct sockaddr_in *)(sa))
#define sintosa(sin)	((struct sockaddr *)(sin))
#define ifatoia(ifa)	((struct in_ifaddr *)(ifa))
		if (sin->sin_addr.s_addr == INADDR_ANY)    //如果遠程地址是INADDR_ANY,相當於調用進程連接到這個主機上的一個實體
		    sin->sin_addr = IA_SIN(in_ifaddr)->sin_addr;
		else if (sin->sin_addr.s_addr == (u_long)INADDR_BROADCAST &&
		  (in_ifaddr->ia_ifp->if_flags & IFF_BROADCAST))    //如果是多播地址的話,將這個地址轉換為接口合適的IP地址
		    sin->sin_addr = satosin(&in_ifaddr->ia_broadaddr)->sin_addr;
	}
	if (inp->inp_laddr.s_addr == INADDR_ANY) {    //如果沒有指定本地地址
		register struct route *ro;

		ia = (struct in_ifaddr *)0;
		/* 
		 * If route is known or can be allocated now,
		 * our src addr is taken from the i/f, else punt.
		 */
		ro = &inp->inp_route;
		if (ro->ro_rt &&
		    (satosin(&ro->ro_dst)->sin_addr.s_addr !=
			sin->sin_addr.s_addr || 
		    inp->inp_socket->so_options & SO_DONTROUTE)) {    //如果目前的路由的目標地址與PCB的遠程地址不一樣,釋放路由
			RTFREE(ro->ro_rt);
			ro->ro_rt = (struct rtentry *)0;
		}
		if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/        //如果沒有指定SO_DONTROUTE選項,需要重新獲取一條指向遠程地址的路由選項
		    (ro->ro_rt == (struct rtentry *)0 ||
		    ro->ro_rt->rt_ifp == (struct ifnet *)0)) {
			/* No route yet, so try to acquire one */
			ro->ro_dst.sa_family = AF_INET;
			ro->ro_dst.sa_len = sizeof(struct sockaddr_in);
			((struct sockaddr_in *) &ro->ro_dst)->sin_addr =
				sin->sin_addr;
			rtalloc(ro);
		}
		/*
		 * If we found a route, use the address
		 * corresponding to the outgoing interface
		 * unless it is the loopback (in case a route
		 * to our address on another net goes to loopback).
		 */
		if (ro->ro_rt && !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK))    //確定外出的接口。這一部分和協議層相關性不大,不重點關注
			ia = ifatoia(ro->ro_rt->rt_ifa);
		if (ia == 0) {
			u_short fport = sin->sin_port;

			sin->sin_port = 0;
			ia = ifatoia(ifa_ifwithdstaddr(sintosa(sin)));
			if (ia == 0)
				ia = ifatoia(ifa_ifwithnet(sintosa(sin)));
			sin->sin_port = fport;
			if (ia == 0)
				ia = in_ifaddr;
			if (ia == 0)
				return (EADDRNOTAVAIL);
		}
		/*
		 * If the destination address is multicast and an outgoing
		 * interface has been set as a multicast option, use the
		 * address of that interface as our source address.
		 */
		if (IN_MULTICAST(ntohl(sin->sin_addr.s_addr)) &&
		    inp->inp_moptions != NULL) {
			struct ip_moptions *imo;
			struct ifnet *ifp;

			imo = inp->inp_moptions;
			if (imo->imo_multicast_ifp != NULL) {
				ifp = imo->imo_multicast_ifp;
				for (ia = in_ifaddr; ia; ia = ia->ia_next)
					if (ia->ia_ifp == ifp)
						break;
				if (ia == 0)
					return (EADDRNOTAVAIL);
			}
		}
		ifaddr = (struct sockaddr_in *)&ia->ia_addr;
	}
	if (in_pcblookup(inp->inp_head,
	    sin->sin_addr,
	    sin->sin_port,
	    inp->inp_laddr.s_addr ? inp->inp_laddr : ifaddr->sin_addr,
	    inp->inp_lport,
	    0))    //如果在PCB head中找到了相同的四元組,返回錯誤
		return (EADDRINUSE);
	if (inp->inp_laddr.s_addr == INADDR_ANY) {
		if (inp->inp_lport == 0)
			(void)in_pcbbind(inp, (struct mbuf *)0);    //如果本地地址與本地接口沒有設置,就是沒有經過bind的處理,在這塊需要進行處理。比如所UDP協議
		inp->inp_laddr = ifaddr->sin_addr;
	}
	inp->inp_faddr = sin->sin_addr;    //設置完外部地址與外部端口就可以返回了
	inp->inp_fport = sin->sin_port;
	return (0);
}

in_dispcbconnect

  • 功能A:將UDP插口斷連,把外部端口和外部地址設置為0(INADDR_ANY)。注意:只有當一個PCB被要求重用時才會調用這個函數,比如說UDP的隱式連接斷開時。
int
in_pcbdisconnect(inp)
	struct inpcb *inp;
{

	inp->inp_faddr.s_addr = INADDR_ANY;
	inp->inp_fport = 0;
	if (inp->inp_socket->so_state & SS_NOFDREF)    //如果在VFS中已經沒有索引了,釋放這個PCB結構
		in_pcbdetach(inp);
}

in_setsockaddr和in_setpeeraddr

  • 功能A:獲取本地地址(getsockname)與遠程地址(getpeername)
int
in_setsockaddr(inp, nam)
	register struct inpcb *inp;
	struct mbuf *nam;
{
	register struct sockaddr_in *sin;
	
	nam->m_len = sizeof (*sin);
	sin = mtod(nam, struct sockaddr_in *);
	bzero((caddr_t)sin, sizeof (*sin));
	sin->sin_family = AF_INET;
	sin->sin_len = sizeof(*sin);
	sin->sin_port = inp->inp_lport;
	sin->sin_addr = inp->inp_laddr;
}
int
in_setpeeraddr(inp, nam)
	struct inpcb *inp;
	struct mbuf *nam;
{
	register struct sockaddr_in *sin;
	
	nam->m_len = sizeof (*sin);
	sin = mtod(nam, struct sockaddr_in *);
	bzero((caddr_t)sin, sizeof (*sin));
	sin->sin_family = AF_INET;
	sin->sin_len = sizeof(*sin);
	sin->sin_port = inp->inp_fport;
	sin->sin_addr = inp->inp_faddr;
}

總結

  • 問題1:如何判斷一個端口正在被使用呢?
    只要存在一個PCB,就把該端口作為PCB的本地端口,就是在使用中。“正在使用中”的概念是相對於綁定協議,即TCP與UCP的端口毫無關聯。

  • 問題2:BSD允許進程使用以下兩個選型來修改默認的行為:

    • SO_REUSEADDR:允許使用進程綁定一個正在使用的端口號,但被綁定的IP地址(包括通配地址)必須沒有被綁定到同一個端口。就是對於本地地址與端口的綁定方面,如果設置了這個選項,端口可以一樣,但是IP地址(包括通配地址)不能一樣。
    • SO_REUSEPORT:允許綁定相同的本地IP地址與端口地址,為支持多播使用。
  • 問題3:在外部數據包到達時,UDP如何在協議層確定PCB?
    首先確定本地端口匹配。其次,為了確定匹配數,只考慮本地IP地址與外部IP地址的通配匹配數,數值越低說明匹配度越高。廣播與多播數據包不予討論。

  • 問題4:ICMP報文類型以及處理
    ICMP報文可以從大體上分為:
    A:目的主機不可達
    B:參數問題
    C:重定向(特殊的一項,會對PCB中的路由結構進行操作)
    D:源抑制
    E:超時
    總而言之,UDP上收到的ICMP報文不會傳遞給應用程序,除非在UDP SOCKET上調用connect。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM