三、以太網接口數據輸出
當網絡層協議調用接口ifnet結構體中的if_output時,開始輸出。所有以太網設備的if_output都指向ether_output函數,該函數封裝以太網的頭部,並將數據輸入到接口的發送隊列。
(1)驗證接口狀態:主要是接口狀態的校驗,判斷接口是否啓用。
/******************************************************/
if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != (IFF_UP|IFF_RUNNING))
senderr(ENETDOWN);
ifp->if_lastchange = time;
/*****************************************************/
(2)輸出路由處理:包括主機路由和網關路由的處理,避免ARP氾濫處理
/*****************************************************/
if (rt = rt0) {
if ((rt->rt_flags & RTF_UP) == 0) {
if (rt0 = rt = rtalloc1(dst, 1))
rt->rt_refcnt--;
else
senderr(EHOSTUNREACH);
}
if (rt->rt_flags & RTF_GATEWAY) {
if (rt->rt_gwroute == 0)
goto lookup;
if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) {
rtfree(rt); rt = rt0;
lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1);
if ((rt = rt->rt_gwroute) == 0)
senderr(EHOSTUNREACH);
}
}
if (rt->rt_flags & RTF_REJECT)
if (rt->rt_rmx.rmx_expire == 0 ||
time.tv_sec < rt->rt_rmx.rmx_expire)
senderr(rt == rt0 ? EHOSTDOWN : EHOSTUNREACH);
}
/*****************************************************/
rt0 上層協議(IP ip_output)找到的路由信息,該值也可能爲空,此時將跳過路由驗證部分直接進入分組處理。
如果輸入的路由無效,參考路由表,獲取一條有效的路由,rt0 = rt = rtalloc1(dst, 1)。
如果下一跳是網關,找到一條到網關的路由。
當路由的結果是RTF_REJECT,表示對方不準備響應一個ARP,此時丟棄改分組。
(3)分組的按不同的協議處理:
IP分組處理:
/***************************************************/
case AF_INET:
if (!arpresolve(ac, rt, m, dst, edst))
return (0); /* if not yet resolved */
/* If broadcasting on a simplex interface, loopback a copy */
if ((m->m_flags & M_BCAST) && (ifp->if_flags & IFF_SIMPLEX))
mcopy = m_copy(m, 0, (int)M_COPYALL);
off = m->m_pkthdr.len - m->m_len;
type = ETHERTYPE_IP;
break;
/***************************************************/
如果dst_safamily地址族是AF_INET,表示該分組攜帶IP協議數據。
首先,通過arpresolv函數查找IP地址到以太網地址的對應。如果ARP緩存中存在地址對應,返回1,否則,該IP分組將被ARP程序接管,直道ARP知道獲取到具體的物理地址後,通過in_arpintr調用ether_output重新發送分組。ARP具體的實現,後面會有詳細介紹。
如果數據是廣播分組且接口是單向的,需要複製一份分組,並在後面將分組輸入到接口的接收隊列。
顯式輸出分組處理:
/********************************************************/
case AF_UNSPEC:
eh = (struct ether_header *)dst->sa_data;
bcopy((caddr_t)eh->ether_dhost, (caddr_t)edst, sizeof (edst));
type = eh->ether_type;
break;
/*********************************************************/
地址族是AF_UNSPEC,表示某些顯式的分組輸出,由調用者顯式的指定目標的物理地址,類別字段爲接口首部的地址類別。
除了以上的兩種類型的分組外,還有ISO和AF_NS類別的分組,在這裏不詳細介紹。
如果之前有拷貝分組,在對不同類型的分組處理完後調用
if (mcopy)
(void) looutput(ifp, mcopy, dst, rt);
將分組輸入到接口輸入隊列,具體的實現在loop接口代碼分析時詳細解析。
(4)幀構造:構造以太網的數據幀。
/***************************************************/
/*
* Add local net header. If no space in first mbuf,
* allocate another.
*/
M_PREPEND(m, sizeof (struct ether_header), M_DONTWAIT);
if (m == 0)
senderr(ENOBUFS);
eh = mtod(m, struct ether_header *);
type = htons((u_short)type);
bcopy((caddr_t)&type,(caddr_t)&eh->ether_type,
sizeof(eh->ether_type));
bcopy((caddr_t)edst, (caddr_t)eh->ether_dhost, sizeof (edst));
bcopy((caddr_t)ac->ac_enaddr, (caddr_t)eh->ether_shost,
sizeof(eh->ether_shost));
/******************************************************/
構造幀主要的工作是構造以太網的幀首部。
(5)分組入接口輸出隊列
/******************************************************/
s = splimp();
/*
* Queue message on interface, and start output if interface
* not yet active.
*/
if (IF_QFULL(&ifp->if_snd)) {
IF_DROP(&ifp->if_snd);
splx(s);
senderr(ENOBUFS);
}
IF_ENQUEUE(&ifp->if_snd, m);
if ((ifp->if_flags & IFF_OACTIVE) == 0)
(*ifp->if_start)(ifp);
splx(s);
ifp->if_obytes += len + sizeof (struct ether_header);
if (m->m_flags & M_MCAST)
ifp->if_omcasts++;
return (error);
/******************************************************/
最後部分是將分組入輸出隊列,如果隊列滿了就丟棄該分組,並返回沒有緩存的錯誤碼。
入隊後,判斷接口是否激活,如果未激活,需要調用if_start實例啓動接口的輸出。
函數的末尾更新接口統計信息並返回。
if_start指向具體設備的驅動函數,如lestart函數。
lestart首先檢查接口是後工作,如果不工作直接返回。
/*********************************************/
tmd = &le->sc_r2->ler2_tmd[le->sc_tmd];
do {
if (tmd->tmd1 & LE_OWN) {
le->sc_xown2++;
return (0);
}
IF_DEQUEUE(&le->sc_if.if_snd, m);
if (m == 0)
return (0);
len = leput(le->sc_r2->ler2_tbuf[le->sc_tmd], m);
#if NBPFILTER > 0
/*
* If bpf is listening on this interface, let it
* see the packet before we commit it to the wire.
*/
if (ifp->if_bpf)
bpf_tap(ifp->if_bpf, le->sc_r2->ler2_tbuf[le->sc_tmd],
len);
#endif
tmd->tmd3 = 0;
tmd->tmd2 = -len;
tmd->tmd1 = LE_OWN | LE_STP | LE_ENP;
if (++le->sc_tmd == LETBUF) {
le->sc_tmd = 0;
tmd = le->sc_r2->ler2_tmd;
} else
tmd++;
} while (++le->sc_txcnt < LETBUF);
le->sc_if.if_flags |= IFF_OACTIVE;
return (0);
/*********************************************/
數據退隊:一旦開始數據輸出,函數會循環的從輸出隊列中退隊並寫入硬件緩存中。
bpf輸出:如果接口註冊了bpf,此時也會調用bpf_tap扔出數據。
輸出結束:當輸出隊列爲空或者接口發送的數據包等於接口的最大輸出緩存時,會跳出循環,並將接口置爲輸出激活狀態。
到此,完成了一個數據分組輸出的全過程。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/mythfish/archive/2008/10/26/3152370.aspx