ip - Linux IPv4 協議實現
#include <sys/socket.h>
#include <net/netinet.h>
tcp_socket = socket(PF_INET, SOCK_STREAM, 0);
raw_socket = socket(PF_INET, SOCK_RAW, protocol);
udp_socket = socket(PF_INET, SOCK_DGRAM, protocol);
Linux 實現描述於 RFC791 和 RFC1122
中的 Internet 協議,版本4.
ip
包括遵循 RFC1112
的第二層的多通道廣播技術的實現.它也包括含包過濾器的IP路由器.
程式設計師的介面與 BSD
的套接字(socket)相容.
要獲得關於套接字的更多資訊,參見
socket(7)
建立一個IP套接字是透過以
socket(PF_INET, socket_type, protocol) 方式呼叫
socket(2) 函式來實現的.
有效的套接字型別(socket_type)有:
SOCK_STREAM 用來開啟一個
tcp(7) 套接字,
SOCK_DGRAM
用來開啟一個
udp(7)
套接字,或者是
SOCK_RAW
用來開啟一個
raw(7)
套接字用來直接訪問 IP
協議.
protocol
指的是要接收或者傳送出去的包含在
IP 頭標識(header)中的 IP
協議.
對於TCP套接字而言,唯一的有效
protocol 值是
0 和
IPPROTO_TCP
對於UDP套接字而言,唯一的有效
protocol 值是
0 和
IPPROTO_UDP.
而對於
SOCK_RAW
你可以指定一個在 RFC1700
中定義的有效 IANA IP
協議程式碼來賦值.
當一個程序希望接受新的來訪包或者連線時,它應該使用
bind(2)
繫結一個套接字到一個本地介面地址.
任意給定的本地(地址,埠)對只能繫結一個IP套接字.
當呼叫 bind 時中聲明瞭
INADDR_ANY
時,套接字將會繫結到
所有
本地介面.當在未繫結的套接字上呼叫
listen(2) 或者
connect(2)
時,套接字會自動繫結到一個本地地址設定為
INADDR_ANY
的隨機的空閒埠上.
除非你設定了
S0_REUSEADDR
標識,否則一個已繫結的
TCP
本地套接字地址在關閉後的一段時間內不可用.
使用該標識的時候要小心,因為它會使
TCP 變得不可靠.
一個 IP
套接字地址定義為一個
IP
介面地址和一個埠號的組合.
基本 IP
協議不會提供埠號,它們透過更高層次的協議如
udp(7) 和
tcp(7) 來實現.
對於raw套接字,
sin_port
設定為IP協議.
struct sockaddr_in {
sa_family_t sin_family; /* 地址族: AF_INET */
u_int16_t sin_port; /* 按網路位元組次序的埠 */
struct in_addr sin_addr; /* internet地址 */
};
/* Internet地址. */
struct in_addr {
u_int32_t s_addr; /* 按網路位元組次序的地址 */
};
sin_family 總是設定為
AF_INET.
這是必需的;在 Linux 2.2
中,如果該設定缺失,大多數聯網函式會返回
EINVAL sin_port
包含按網路位元組排序的埠號.埠號在1024以下的稱為
保留埠.
只有那些有效使用者標識為
0 或者
CAP_NET_BIND_SERVICE
有功能的程序才可以
bind(2)
到這些套接字.注意原始的(raw)IPv4協議沒有這樣的埠概念,它們只通過更高的協議如
tcp(7) 和
udp(7) 來實現.
sin_addr 指的是 IP 主機地址.
在
struct in_addr 中的
addr
部分包含按網路位元組序的主機介面地址.
in_addr
應該只能透過使用
inet_aton(3),
inet_addr(3),
inet_makeaddr(3)
庫函式或者直接透過名字解析器(參見
gethostbyname(3)) 來訪問. IPv4
地址分成單點廣播,廣播傳送和多點廣播地址.
單點廣播地址指定了一臺主機的單一介面,廣播地址指
定了在一個網段上的所有主機,
而多點廣播地址則在一個多點傳送組中定址所有主機.
只有當設定了套接字標識
SO_BROADCAST 時,
才能收發資料報到廣播地址.
在當前的實現中,面向連線的套接字只允許使用單點傳送地址.
注意地址和埠總是按照網路位元組序儲存的.
這意味著你需要對分配給埠的號碼呼叫
htons(3).
所有在標準庫中的地址/埠處理函式都是按網路位元組序執行的.
有幾個特殊的地址:
INADDR_LOOPBACK (127.0.0.1)
總是代表經由迴環裝置的本地主機;
INADDR_ANY (0.0.0.0)
表示任何可繫結的地址;
INADDR_BROADCAST (255.255.255.255)
表示任何主機,由於歷史的原因,這與繫結為
INADDR_ANY 有同樣的效果.
IP
支援一些與協議相關的套接字選項,這些選項可以透過
setsockopt(2)
設定,並可以透過
getsockopt(2) 讀取. IP
的套接字選項級別為
SOL_IP
這是一個布林整型標識,當值為0時為假,否則則為真.
- IP_OPTIONS
- 設定或者獲取將由該套接字傳送的每個包的
IP 選項.
該引數是一個指向包含選項和選項長度的儲存緩衝區的指標.
setsockopt(2)
系統呼叫設定與一個套接字相關聯的
IP 選項. IPv4
的最大選項長度為 40
位元組. 參閱 RFC791
獲取可用的選項.
如果一個 SOCK_STREAM
套接字收到的初始連線請求包包含
IP 選項時, IP
選項自動設定為來自初始包的選項,同時反轉路由頭.
在連線建立以後將不允許來訪的包修改選項.
預設情況下是關閉對所有來訪包的源路由選項的,你可以用
accept_source_route sysctl
來啟用.仍然處理其它選項如時間戳(timestamp).
對於資料報套接字而言,IP
選項只能由本地使用者設定.呼叫帶
IP_OPTIONS 的 getsockopt(2)
會把當前用於傳送的
IP
選項放到你提供的緩衝區中.
- IP_PKTINFO
- 傳遞一條包含
pktinfo
結構(該結構提供一些來訪包的相關資訊)的
IP_PKTINFO 輔助資訊.
這個選項只對資料報類的套接字有效.
struct in_pktinfo
{
unsigned int ipi_ifindex; /* 介面索引 */
struct in_addr ipi_spec_dst; /* 路由目的地址 */
struct in_addr ipi_addr; /* 頭標識目的地址 */
};
-
ipi_ifindex
指的是接收包的介面的唯一索引.
ipi_spec_dst
指的是路由表記錄中的目的地址,而
ipi_addr
指的是包頭中的目的地址.
如果給 sendmsg (2)傳遞了
IP_PKTINFO,
那麼外發的包會透過在
ipi_ifindex 中指定的介面
傳送出去,同時把
ipi_spec_dst
設定為目的地址.
- IP_RECVTOS
- 如果打開了這個選項,則
IP_TOS ,
輔助資訊會與來訪包一起傳遞.
它包含一個位元組用來指定包頭中的服務/優先順序欄位的型別.
該位元組為一個布林整型標識.
- IP_RECVTTL
- 當設定了該標識時,
傳送一條帶有用一個位元組表示的接收包生存時間(time
to live)欄位的 IP_RECVTTL
控制資訊.
此選項還不支援
SOCK_STREAM 套接字.
- IP_RECVOPTS
- 用一條 IP_OPTIONS
控制資訊傳遞所有來訪的
IP 選項給使用者.
路由頭標識和其它選項已經為本地主機填好.
此選項還不支援
SOCK_STREAM 套接字.
- IP_RETOPTS
- 等同於 IP_RECVOPTS
但是返回的是帶有時間戳的未處理的原始選項和在這段路由中未填入的路由記錄專案.
- IP_TOS
- 設定或者接收源於該套接字的每個IP包的
Type-Of-Service (TOS
服務型別)欄位.它被用來在網路上區分包的優先順序.
TOS
是單位元組的欄位.定義了一些的標準
TOS 標識: IPTOS_LOWDELAY
用來為互動式通訊最小化延遲時間,
IPTOS_THROUGHPUT
用來最佳化吞吐量,
IPTOS_RELIABILITY
用來作可靠性最佳化,
IPTOS_MINCOST
應該被用作"填充資料",對於這些資料,低速傳輸是無關緊要的.
至多隻能宣告這些 TOS
值中的一個.其它的都是無效的,應當被清除.
預設時,Linux首先發送
IPTOS_LOWDELAY 資料報,
但是確切的做法要看配置的排隊規則而定.
一些高優先順序的層次可能會要求一個有效的使用者標識
0 或者 CAP_NET_ADMIN 能力.
優先順序也可以以於協議無關的方式透過(
SOL_SOCKET, SO_PRIORITY
)套接字選項(參看
socket(7) )來設定.
- IP_TTL
- 設定或者檢索從此套接字發出的包的當前生存時間欄位.
- IP_HDRINCL
- 如果開啟的話,
那麼使用者可在使用者資料前面提供一個
ip 頭. 這隻對 SOCK_RAW
有效.參看 raw(7)
以獲得更多資訊.當激活了該標識之後,其值由
IP_OPTIONS 設定,並且 IP_TOS
被忽略.
- IP_RECVERR
- 允許傳遞擴充套件的可靠的錯誤資訊.
如果在資料報上激活了該標識,
那麼所有產生的錯誤會在每套接字一個的錯誤佇列中排隊等待.
當用戶從套接字操作中收到錯誤時,就可以透過呼叫設定了
MSG_ERRQUEUE 標識的 recvmsg(2)
來接收. 描述錯誤的
sock_extended_err
結構將透過一條型別為
IP_RECVERR , 級別為
SOL_IP的輔助資訊進行傳遞.
這個選項對在未連線的套接字上可靠地處理錯誤很有用.
錯誤佇列的已收到的資料部分包含錯誤包.
- IP
按照下面的方法使用
sock_extended_err 結構: ICMP
包接收的錯誤 ee_origin
設為 SO_EE_ORIGIN_ICMP ,
對於本地產生的錯誤則設為
SO_EE_ORIGIN_LOCAL . ee_type 和 ee_code
設定為 ICMP
頭標識的型別和程式碼欄位.
ee_info 包含用於 EMSGSIZE
時找到的 MTU. ee_data
目前沒有使用.
當錯誤來自於網路時,該套接字上所有IP選項都被啟用
(IP_OPTIONS, IP_TTL,
等.)並且當做控制資訊包含錯誤包中傳遞.引發錯誤的包的有效載荷會以正常資料返回.
- 在 SOCK_STREAM
套接字上, IP_RECVERR
會有細微的語義不同.它並不儲存下次超時的錯誤,而是立即傳遞所有進來的錯誤給使用者.
這對 TCP
連線時間很短的情況很有用,因為它要求快速的錯誤處理.
使用該選項要小心:因為不允許從路由轉移和其它正
常條件下正確地進行恢復,它使得TCP變得不可靠,並且破壞協議的規範.
注意TCP沒有錯誤佇列;
MSG_ERRQUEUE 對於 SOCK_STREAM
套接字是非法的.
因此所有錯誤都會由套接字函式返回,或者只返回
SO_ERROR .
- 對於原始(raw)套接字而言,
IP_RECVERR
允許傳遞所有接收到的ICMP錯誤給應用程式,否則錯誤只在連線的套接字上報告出來.
- 它設定或者檢索一個整型布林標識.
IP_RECVERR
預設設定為off(關閉).
- IP_PMTU_DISCOVER
- 為套接字設定或接收Path
MTU Discovery
setting(路徑MTU發現設定).
當允許時,Linux會在該套接字上執行定
義於RFC1191中的Path MTU
Discovery(路徑MTU發現). don't
段標識會設定在所有外發的資料報上.
系統級別的預設值是這樣的:
SOCK_STREAM 套接字由 ip_no_pmtu_disc
sysctl
控制,而對其它所有的套接字都被都遮蔽掉了,對於非
SOCK_STREAM 套接字而言,
使用者有責任按照MTU的大小對資料分塊並在必要的情況下進行中繼重發.如果設定了該標識
(用 EMSGSIZE
),核心會拒絕比已知路徑MTU更大的包.
Path MTU
discovery(路徑MTU發現)標識 |
含義 |
IP_PMTUDISC_WANT |
對每條路徑進行設定. |
IP_PMTUDISC_DONT |
從不作Path MTU
Discovery(路徑MTU發現). |
IP_PMTUDISC_DO |
總作Path MTU
Discovery(路徑MTU發現). |
當允許 PMTU
(路徑MTU)搜尋時,
核心會自動記錄每個目的主機的path
MTU(路徑MTU).當它使用
connect(2)
連線到一個指定的對端機器時,可以方便地使用
IP_MTU
套接字選項檢索當前已知的
path
MTU(路徑MTU)(比如,在發生了一個
EMSGSIZE
錯誤後).它可能隨著時間的推移而改變.
對於帶有許多目的端的非連線的套接字,一個特定目的端的新到來的
MTU
也可以使用錯誤佇列(參看
IP_RECVERR) 來存取訪問.
新的錯誤會為每次到來的
MTU 的更新排隊等待.
當進行 MTU
搜尋時,來自資料報套接字的初始包可能會被丟棄.
使用 UDP
的應用程式應該知道這個並且考慮
其包的中繼傳送策略.
為了在未連線的套接字上引導路徑
MTU 發現程序,
我們可以用一個大的資料報(頭尺寸超過64K位元組)啟動,
並令其透過更新路徑
MTU 逐步收縮.
為了獲得路徑MTU連線的初始估計,可透過使用
connect(2)
把一個數據報套接字連線到目的地址,並透過呼叫帶
IP_MTU選項的 getsockopt(2)
檢索該MTU.
- IP_MTU
- 檢索當前套接字的當前已知路徑MTU.只有在套接字被連線時才是有效的.返回一個整數.只有作為一個
getsockopt(2) 才有效.
- IP_ROUTER_ALERT
- 給該套接字所有將要轉發的包設定IP路由器警告(IP
RouterAlert option)選項.
只對原始套接字(raw
socket)有效,這對使用者空間的
RSVP後
臺守護程式之類很有用.
分解的包不能被核心轉發,使用者有責任轉發它們.套接字繫結被忽略,
這些包只按協議過濾.
要求獲得一個整型標識.
- IP_MULTICAST_TTL
- 設定或者讀取該套接字的外發多點廣播包的生存時間值.
這對於多點廣播包設定可能的最小TTL很重要.
預設值為1,這意味著多點廣播包不會超出本地網段,
除非使用者程式明確地要求這麼做.引數是一個整數.
- IP_MULTICAST_LOOP
- 設定或讀取一個布林整型引數以決定傳送的多點廣播包是否應該被回送到本地套接字.
- IP_ADD_MEMBERSHIP
- 加入一個多點廣播組.引數為
struct ip_mreqn 結構.
struct ip_mreqn
{
struct in_addr imr_multiaddr; /* IP多點傳送組地址 */
struct in_addr imr_address; /* 本地介面的IP地址 */
int imr_ifindex; /* 介面索引 */
};
-
imr_multiaddr
包含應用程式希望加入或者退出的多點廣播組的地址.
它必須是一個有效的多點廣播地址.
imr_address
指的是系統用來加入多點廣播組的本地介面地址;如果它與
INADDR_ANY
一致,那麼由系統選擇一個合適的介面.
imr_ifindex
指的是要加入/脫離
imr_multiaddr
組的介面索引,或者設為0表示任何介面.
- 由於相容性的緣故,老的
ip_mreq
介面仍然被支援.它與
ip_mreqn
只有一個地方不同,就是沒有包括
imr_ifindex
欄位.這隻在作為一個
setsockopt(2) 時才有效.
- IP_DROP_MEMBERSHIP
- 脫離一個多點廣播組.引數為
ip_mreqn 或者 ip_mreq
結構,這與 IP_ADD_MEMBERSHIP
類似. IP_MULTICAST_IF
為多點廣播套接字設定本地裝置.引數為
ip_mreqn 或者 ip_mreq
結構,它與 IP_ADD_MEMBERSHIP
類似.
- 當傳遞一個無效的套接字選項時,返回
ENOPROTOOPT .
IP協議支援 sysctl
介面配置一些全域性選項.sysctl可透過讀取或者寫入
/proc/sys/net/ipv4/* 檔案或使用
sysctl(2) 介面來存取訪問.
- ip_default_ttl
- 設定外發包的預設生存時間值.此值可以對每個套接字透過
IP_TTL 選項來修改.
- ip_forward
- 以一個布林標識來啟用IP轉發功能.IP轉發也可以按介面來設定
- ip_dynaddr
- 開啟介面地址改變時動態套接字地址和偽裝記錄的重寫.
這對具有變化的IP地址的撥號介面很有
用.0表示不重寫,1開啟其功能,而2則啟用冗餘模式.
- ip_autoconfig
- 無文件
- ip_local_port_range
- 包含兩個整數,定義了預設分配給套接字的本地埠範圍.
分配起始於第一個數而終止於第二個數.
注意這些埠不能與偽裝所使用的埠相沖突(儘管這種情況也可以處理).
同時,隨意的選擇可能會導致一些防火牆包過濾器的問題,它們會誤認為本地埠在使用.
第一個數必須至少>1024,最好是>4096以避免與眾所周知的埠發生衝突,
從而最大可能的減少防火牆問題.
- ip_no_pmtu_disc
- 如果打開了,預設情況下不對TCP套接字執行路徑MTU發現.
如果在路徑上誤配置了防火牆(用來丟棄所有
ICMP包)或者誤配置了介面
(例如,設定了一個兩端MTU不同的端對端連線),路徑MTU發現可能會失敗.
寧願修復路徑上的損壞的路由器,也好過整個地關閉路徑MTU發現,
因為這樣做會導致網路上的高開銷.
- ipfrag_high_thresh, ipfrag_low_thresh
- 如果排隊等待的IP碎片的數目達到
ipfrag_high_thresh ,
佇列被排空為 ipfrag_low_thresh
.
這包含一個表示位元組數的整數.
- ip_always_defrag
- [kernel
2.2.13中的新功能;在早期核心版本中,該功能在編譯時透過
CONFIG_IP_ALWAYS_DEFRAG 選項來控制]
當該布林標識被啟用(不等於0)時,
來訪的碎片(IP包的一部分,這生成於當一些在源端和目的端之間的主機認
定包太大而分割成許多碎片的情況下)將在處理之前重新組合(碎片整理),
即使它們馬上要被轉發也如此.
只在執行著一臺與網路單一連線的防火牆或者透明代理伺服器時才這麼幹;
對於正常的路由器或者主機,
永遠不要開啟它.
否則當碎片在不同連線中透過時碎片的通訊可能會被擾亂.
而且碎片重組也需要花費大量的記憶體和
CPU 時間.
這在配置了偽裝或者透明代理的情況下自動開啟.
- neigh/*
- 參看 arp(7)
所有在
socket(7) 中有描述
的 ioctl 都可應用於ip.
用於配置防火牆應用的ioctl記載在
ipchains 包的
ipfw(7)
的文件中.
用來配置普通裝置引數的ioctl在
netdevice(7) 中有描述.
使用
SO_BROADCAST 選項要小心 -
它在 Linux
中沒有許可權要求.
不小心的廣播很容易導致網路過載.對於新的應用協議而言,最
好是使用多點廣播組來替代廣播.我們不鼓勵使用廣播.
有些其它的BSD套接字實現提供了
IP_RCVDSTADDR 和
IP_RECVIF
套接字選項來獲得目的地址以及接收資料報的介面.Linux有更通用的
IP_PKTINFO 來完成相同任務.
ENOBUFS,EPERM對EACCES等.)
- ENOTCONN
- 操作只定義於連線的套接字,而該套接字卻沒有連線.
- EINVAL
- 傳遞無效的引數.
對於傳送操作,這可以因傳送到一個
blackhole(黑洞)
路由而引發.
- EMSGSIZE
- 資料報大於該路徑上的
MTU,並且它不能被分成碎片.
- EACCES
- 沒有必要許可權的使用者試圖執行一項需要某些許可權的操作.
這包括: 在沒有 SO_BROADCAST
標識設定的情況下發送一個包到廣播地址.
透過一條 禁止的
路由傳送包. 在沒有
CAP_NET_ADMIN
或者有效使用者標識不為0的情況下修改防火牆設定.
在沒有 CAP_NET_BIND_SERVICE
能力或者有效使用者標識不為零0的情況下繫結一個保留埠.
- EADDRINUSE
- 試圖繫結到一個已在使用的地址.
-
ENOMEM 和 ENOBUFS
- 沒有足夠的記憶體可用.
-
ENOPROTOOPT 和 EOPNOTSUPP
- 傳遞無效的套接字選項.
- EPERM
- 使用者沒有許可權設定高優先順序,修改配置或者傳送訊號到請求的程序或組.
- EADDRNOTAVAIL
- 請求一個不存在的介面或者請求的源端地址不是本地的.
- EAGAIN
- 在一個非阻塞的套接字上進行操作會阻塞.
- ESOCKTNOSUPPORT
- 套接字未配置或者請求了一個未知型別的套接字.
- EISCONN
- 在一個已經連線的套接字上呼叫
connect(2).
- EALREADY
- 在一個非阻塞的套接字上的連線操作已經在進行中.
- ECONNABORTED
- 在一次 accept(2)
執行中連線被關閉.
- EPIPE
- 連線意外關閉或者被對端關閉.
- ENOENT
- 在沒有報到達的套接字上呼叫
SIOCGSTAMP .
- EHOSTUNREACH
- 沒有有效路由表記錄匹配目的地址.該錯誤可以被來自遠端路由器的
ICMP訊息或者因為本地路由表的緣故而引發.
- ENODEV
- 網路裝置不可用或者不適於傳送IP.
- ENOPKG
- 核心子系統沒有配置.
- ENOBUFS, ENOMEM
- 沒有足夠的空閒記憶體.
這常常意味著記憶體分配因套接字緩衝區的限制而受限,
而不是因為系統記憶體的緣故,但是這也不是100%正確.
其它錯誤可能由重疊協議族生成;參看
tcp(7),
raw(7),
udp(7) 和
socket(7).
IP_PKTINFO,
IP_MTU,
IP_PMTU_DISCOVER,
IP_PKTINFO,
IP_RECVERR 和
IP_ROUTER_ALERT 是Linux
2.2中的新選項.
struct ip_mreqn 也是新出現在Linux
2.2中的.Linux 2.0只支援
ip_mreq.
sysctl是在Linux 2.2中引入的.
為了與Linux
2.0相容,仍然支援用過時的
socket(PF_INET, SOCK_RAW, protocol)
語法開啟一個
packet(7)
套接字.我們不贊成這麼用,而且應該被
socket(PF_PACKET, SOCK_RAW, protocol)
所代替.主要的區別就是
新的針對一般連結層資訊的
sockaddr_ll
地址結構替換了舊的
sockaddr_pkt 地址結構.
有許多不連貫的錯誤碼.
沒有描述用來配置特定IP介面選項和ARP表的ioctl.
該man頁作者是Andi Kleen.
sendmsg(2),
recvmsg(2),
socket(7),
netlink(7),
tcp(7),
udp(7),
raw(7),
ipfw(7).
RFC791:原始IP規範.
RFC1122:IPv4主機需求.
RFC1812:IPv4路由器需求.
riser <[email protected]>
2001/07/19
http://cmpp.linuxforum.net
本頁面中文版由中文 man
手冊頁計劃提供。
中文 man 手冊頁計劃:
https://github.com/man-pages-zh/manpages-zh