ip/udp/tcp包 學習
阿新 • • 發佈:2020-09-17
/** * 乙太網 */ class Ethernet { static readonly size = 14; get Destination(): string { return [ this.view.getUint8(0), this.view.getUint8(1), this.view.getUint8(2), this.view.getUint8(3), this.view.getUint8(4), this.view.getUint8(5), ] .map((it) => it.toString(16)) .join(":"); } get Source(): string { return [ this.view.getUint8(6), this.view.getUint8(7), this.view.getUint8(8), this.view.getUint8(9), this.view.getUint8(10), this.view.getUint8(11), ] .map((it) => it.toString(16)) .join(":"); } get Type(): number { return this.view.getUint16(12); } view: DataView; constructor(ethernetBytes: number[]) { this.view = new DataView(Uint8Array.from(ethernetBytes).buffer); } } /** * 解析IP頭 */ class IP { //See also: http://mars.netanya.ac.il/~unesco/cdrom/booklet/HTML/NETWORKING/node020.html static readonly size = 20; /** * ## 版本號 * - 資料報屬於哪個協議版本。 */ get Version(): number { return this.view.getUint8(0) >> 4; } set Version(value: number) { if (value > 0xf) throw new Error("value 不能超過4-bit(15)."); this.view.setUint8(0, (value << 4) | this.HeaderLength); } /** * - 標頭中32位字的數量(DWORD) * - 因為這是4位,所以最大報頭長度為15個字(即60個位元組) * - 標頭至少為20個位元組,但Option(0 or more words)可能會使它更大 */ get HeaderLength(): number { // xor掉version剩下的就是HL return this.view.getUint8(0) ^ (this.Version << 4); } set HeaderLength(value: number) { if (value > 0xf) throw new Error("value 不能超過4-bits."); this.view.setUint8(0, value | (this.Version << 4)); } /** * ## 服務種類 * - 包含一個3位優先順序欄位(今天將被忽略),4個服務位和1個未使用位。 * - 四個服務位可以是: - 1000-最小化延遲 - 0100-最大化吞吐量 - 0010-最大化可靠性 - 0001-最小化金錢成本 */ get TypeOfService(): number { return this.view.getUint8(1); } set TypeOfService(value: number) { if (value > 0xff) throw new Error("value 不能超過8-bits."); this.view.setUint8(1, value); } /** * ## 總長度 * - 資料報的總長度(以位元組為單位)。 * - 我們知道資料從頭開始的位置 * - 我們通過計算“Total Length-Header Length”知道DATA的大小 * - Total Length使用(WORD)儲存,最多存0xFFFF(65535)位元組 * - 但是物理層可能不允許這麼多位元組的資料包大小 * - 因此,IP有時必須對資料包進行分段。 * - 當IP資料報被分段時,每個分段都被視為一個單獨的資料報。 * - 它在最終目的地而不是在路由器處重新組裝! * - 每個片段都有自己的header * - 標識號被複制到每個片段中。See also: MF/DF/FragmentOffset */ get TotalLength(): number { return this.view.getUint16(2); } set TotalLength(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(2, value); } /** * ## 身份證明 * - 唯一標識資料報 * - 通常每次傳送資料報時加1。 * - 資料報的所有片段都包含相同的標識值。 * - 這使目標主機可以確定哪個片段屬於哪個資料報。 */ get Identification(): number { return this.view.getUint16(4); } set Identification(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(4, value); } /** * ## Don't fragment 不要碎片 * - 如果為1,則表示“請勿分段”。 * - 如果IP必須分片資料包並且該位置1,則IP丟棄資料報。 */ get DF(): number { return (this.view.getUint8(6) & (1 << 6)) >> 6; } set DF(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.DF) return; this.view.setUint8(6, this.view.getUint8(6) ^ (1 << 6)); } /** * ## More Fragment 片段標誌 * - 如果該位為0,則表示這是最後一個片段 * - IP標頭的所有其他欄位與第一個資料包相同(校驗和除外) * - 路由器看到3個單獨的資料包。在最終目的地上將資料包傳遞到上層之前重新組裝資料包 * - 片段標誌(MF)0和偏移量(FragmentOffset)0表示資料報未分片 */ get MF(): number { return (this.view.getUint8(6) & (1 << 5)) >> 5; } set MF(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.MF) return; this.view.setUint8(6, this.view.getUint8(6) ^ (1 << 5)); } /** * ## 碎片偏移 * - 片段標誌(MF)0和偏移量(FragmentOffset)0表示資料報未分片 * - 片段偏移量以8位元組(QWORD)為單位進行測量。這是因為片段偏移欄位比總長度欄位短3位(並且2^3為8)。 */ get FragmentOffset(): number { return this.view.getUint16(6) ^ ((this.DF << 14) | (this.MF << 13)); } set FragmentOffset(value: number) { if (value > 0x1fff) throw new Error("value 不能超過13-bits."); // 刪除舊址在設定新值 this.view.setUint16(6, value | (this.DF << 14) | (this.MF << 13)); } /** * ## 生存時間 * - 路由器上限 * - 通常設定為32或64。 * - 每個處理資料報的路由器都會遞減, * - 當TTL達到0時,路由器將丟棄該資料報。 */ get TimeToLive(): number { return this.view.getUint8(8); } set TimeToLive(value: number) { if (value > 0xff) throw new Error("value 不能超過8-bits."); this.view.setUint8(8, value); } /** * ## 協議 * - 告訴IP將資料報傳送到哪裡。 * - 6表示TCP * - 17表示UDP */ get Protocol(): number { return this.view.getUint8(9); } setProtocol(value: number) { if (value > 0xff) throw new Error("value 不能超過8-bits."); this.view.setUint8(9, value); } /** * ## 標頭校驗和 * - 僅覆蓋標題,不覆蓋資料。 */ get HeaderChecksum(): number { return this.view.getUint16(10); } set HeaderChecksum(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(10, value); } updateHeaderChecksum() { this.HeaderChecksum = 0; let offset = 0; const a = []; while (offset < this.view.byteLength) { a.push(this.view.getUint16(offset)); offset += 2; } const v = a.reduce<number>((acc, it) => { acc += it; if (acc > 0xffff) { // 進位 const leftmost = acc >> 16; acc = (acc ^ (leftmost << 16)) + leftmost; } return acc; }, 0); this.HeaderChecksum = ~v; } checkHeaderChecksum(): boolean { let offset = 0; const a = []; while (offset < this.view.byteLength) { a.push(this.view.getUint16(offset)); offset += 2; } const v = a.reduce<number>((acc, it) => { acc += it; if (acc > 0xffff) { // 進位 const leftmost = acc >> 16; acc = (acc ^ (leftmost << 16)) + leftmost; } return acc; }, 0); return 0 === ~v; } /** * 源IP地址 */ get Source(): number { return this.view.getUint32(12); } set Source(value: number) { if (value > 0xffffffff) throw new Error("value 不能超過32-bits."); this.view.setUint32(12, value); } /** * 這只是[Source]的字串形式 */ get SourceString(): string { return [ this.view.getUint8(12), this.view.getUint8(13), this.view.getUint8(14), this.view.getUint8(15), ].join("."); } /** * 以字串的形式設定[Source] * @param value 如: "192.168.1.6" */ set SourceString(value: string) { const a: string[] = value.split("."); if (a.length !== 4) throw new Error(`setSourceString value Error: (${value})`); const inta: number[] = a.map((it) => { const r = parseInt(it, 10); if (r > 0xff) throw new Error(`碼點(${it})不能超過8-bits.`); return r; }); this.view.setUint8(12, inta[0]); this.view.setUint8(13, inta[1]); this.view.setUint8(14, inta[2]); this.view.setUint8(15, inta[3]); } /** * 目的IP地址 */ get Destination(): number { return this.view.getUint32(16); } set Destination(value: number) { if (value > 0xffffffff) throw new Error("value 不能超過32-bits."); this.view.setUint32(16, value); } /** * 這只是[Destination]的字串形式 */ get DestinationString(): string { return [ this.view.getUint8(16), this.view.getUint8(17), this.view.getUint8(18), this.view.getUint8(19), ].join("."); } /** * 以字串的形式設定 * @param value 如: "192.168.1.6" */ setDestinationString(value: string) { const a: string[] = value.split("."); if (a.length !== 4) throw new Error(`setDestinationString value Error: (${value})`); const inta: number[] = a.map((it) => { const r = parseInt(it, 10); if (r > 0xff) throw new Error(`碼點(${it})不能超過8-bits.`); return r; }); this.view.setUint8(16, inta[0]); this.view.setUint8(17, inta[1]); this.view.setUint8(18, inta[2]); this.view.setUint8(19, inta[3]); } view: DataView; constructor(ipBytes: number[]) { this.view = new DataView(Uint8Array.from(ipBytes).buffer); } toString(): string { return ` 0100 .... = Version: ${this.Version} .... 0101 = Header Length: ${this.HeaderLength * 4} bytes (${this.HeaderLength}) Type Of Service: 0x0${this.TypeOfService.toString(16)} Total Length: ${this.TotalLength} Identification: 0x${this.Identification.toString(16)} (${this.Identification}) Df: ${this.DF} MF: ${this.MF} Fragment offset: ${this.FragmentOffset} Time to live: ${this.TimeToLive} Protocol: ${ this.Protocol == 6 ? "TCP" : this.Protocol == 17 ? "UDP" : "Other" } (${this.Protocol}) Header checksum: 0x${this.HeaderChecksum.toString(16)} Source: ${this.SourceString} Destination: ${this.DestinationString} `.trim(); } } /** * ## User Datagram Protocol 使用者資料報協議 * - UDP是一種簡單的無連線協議。它不提供可靠性;它只是將資料傳送到IP層 */ class UDP { static readonly size = 8; get SourcePort(): number { return this.view.getUint16(0); } set SourcePort(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(0, value); } get DestinationPort(): number { return this.view.getUint16(2); } set DestinationPort(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(2, value); } /** * - 長度是報頭和資料的位元組數 * - header是8個位元組 * - IP資料報的最大大小為65535位元組,IP報頭減去20位元組===>剩餘65515位元組用於資料。但是,UDP標頭為8個位元組,剩下65507個位元組用於最大數量的使用者資料。(不確定) */ get Length(): number { return this.view.getUint16(4); } set Length(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(4, value); } get Checksum(): number { return this.view.getUint16(6); } set Checksum(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(5, value); } /** * 計算校驗和 */ updateChecksum() { this.Checksum = 0; const dataSize = this.view.byteLength - UDP.size; const byteLength = 20 + (dataSize % 2 !== 0 ? 0 : dataSize); const pseudoHeader = new DataView(new ArrayBuffer(byteLength)); pseudoHeader.setUint32(0, this.ip.Source); pseudoHeader.setUint32(4, this.ip.Destination); pseudoHeader.setUint16(8, this.ip.Protocol); pseudoHeader.setUint16(10, this.Length); pseudoHeader.setUint16(12, this.SourcePort); pseudoHeader.setUint16(14, this.DestinationPort); pseudoHeader.setUint16(16, this.Length); pseudoHeader.setUint16(18, this.Checksum); if (dataSize % 2 === 0) { for (let i = 0; i < dataSize - 1; i++) { pseudoHeader.setUint8(20 + i, this.view.getUint8(UDP.size + i)); } } let offset = 0; const a = []; while (offset < pseudoHeader.byteLength) { a.push(pseudoHeader.getUint16(offset)); offset += 2; } const v = a.reduce<number>((acc, it) => { acc += it; if (acc > 0xffff) { // 進位 const leftmost = acc >> 16; acc = (acc ^ (leftmost << 16)) + leftmost; } return acc; }, 0); this.Checksum = ~v; } view: DataView; constructor(public readonly ip: IP, udpBytes: number[]) { this.view = new DataView(Uint8Array.from(udpBytes).buffer); } } /** * ## Transmission Control Protocol 傳輸控制協議 * - 不可靠的網路上可靠的端到端位元組流。 */ class TCP { /** * TCP標頭必須至少包含20位元組(5個字)的固定格式資訊以及一個可選部分(該部分始終為4位元組的整數倍) */ static readonly size = 20; /** * * - 埠是用於程序間通訊的邏輯地址。埠在一臺主機內(甚至在一臺程序內)提供多個目的地。 * - 低於256的埠號是“知名”埠,例如:FTP 21,TELNET 23,SMTP 25,HTTP 80,POP3 110。 * - 低於1024的埠號保留用於系統服務。只允許管理員(例如Unix中的root)分配他們 * - 使用者程序可以使用1024到65535(2 ^ 16-1)之間的埠號,而無需任何特殊許可。 * - 可以通過組合[SourceIPAddress.PortNumber,DestinationIPAddress.PortNumber]來標識定義通訊通道端點的套接字對。 * - IP號碼加埠號構成一個TSAP-傳輸服務訪問點(有時也稱為套接字)。 */ get SourcePort(): number { return this.view.getUint16(0); } set SourcePort(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(0, value); } get DestinationPort(): number { return this.view.getUint16(2); } set DestinationPort(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(2, value); } /** * ## 序列號 * - 一個32位數字(DWORD) * - 資料的每個位元組都有編號。 * - TCP段的序列號是該段中第一個資料位元組的ID號 * - 它不需要從1開始! * - 有效序列號的範圍是0到4,294,967,295(0x0000,0000-0xFFFF,FFFF) */ get SequenceNumber(): number { return this.view.getUint32(4); } set SequenceNumber(value: number) { if (value > 0xffffffff) throw new Error("value 不能超過32-bits."); this.view.setUint32(4, value); } /** * ## 確認號 * - 一個32位數字(DWORD) * - 指定傳送者期望的下一個位元組的編號(而不是正確接收的最後一個位元組!) * - 通常將資料從接收方傳送到傳送方 * - 如果此確認段在特定時間內未到達,則傳送方重新傳輸前一個段(計時器) */ get AcknowledgmentNumber(): number { return this.view.getUint32(8); } set AcknowledgmentNumber(value: number) { if (value > 0xffffffff) throw new Error("value 不能超過32-bits."); this.view.setUint32(8, value); } /** * TCP標頭中的標頭長度數(32位)。此資訊是必需的,因為標題有時可能超過4個字。僅4位分配給TCP標頭長度欄位。因此,最長為15個字(60個位元組) */ get TCPHeaderLength(): number { return this.view.getUint8(12) >> 4; } set TCPHeaderLength(value: number) { if (value > 0xf) throw new Error("value 不能超過4-bits."); this.view.setUint8(12, value << 4); } /** * ## Urgent * - 緊急指標有效,與當前序列號的位元組偏移,在當前序列號處可以找到緊急資料(中斷訊息)。當中止rlogin或telnet連線或ftp資料傳輸時,使用緊急模式。 */ get URG(): number { return this.view.getUint8(13) & ((1 << 5) >> 5); } set URG(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.URG) return; this.view.setUint8(13, this.view.getUint8(13) ^ (1 << 5)); } /** * ## Acknowledgment 確認編號有效 * - 如果ACK = 0,則此段中沒有確認 */ get ACK(): number { return this.view.getUint8(13) & ((1 << 4) >> 4); } set ACK(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.ACK) return; this.view.setUint8(13, this.view.getUint8(13) ^ (1 << 4)); } /** * ## Push 接收方應儘快將此資料傳遞給應用程式 * - 接收方被請求在到達時將資料傳遞給應用程式,而不是緩衝它。 */ get PSH(): number { return this.view.getUint8(13) & ((1 << 3) >> 3); } set PSH(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.PSH) return; this.view.setUint8(13, this.view.getUint8(13) ^ (1 << 3)); } /** * ## Reset 重置(關閉)連線。 */ get RST(): number { return this.view.getUint8(13) & ((1 << 2) >> 2); } set RST(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.RST) return; this.view.setUint8(13, this.view.getUint8(13) ^ (1 << 2)); } /** * ## Syn 同步序列號以開始連線 */ get SYN(): number { return this.view.getUint8(13) & ((1 << 1) >> 1); } set SYN(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.RST) return; this.view.setUint8(13, this.view.getUint8(13) ^ (1 << 1)); } /** * ## 傳送方已完成資料傳送 */ get FIN(): number { return this.view.getUint8(13) & 1; } set FIN(value: number) { if (value !== 0 && value !== 1) throw new Error("value 只能為1或者0."); if (value === this.RST) return; this.view.setUint8(13, this.view.getUint8(13) ^ 1); } /** * 此位元組的傳送者願意接受的資料位元組數,從確認欄位中指示的位元組數開始。視窗大小為0表示已收到直到確認編號1為止的位元組,但是此刻接收器無法接受更多資料。稍後,如果接收方準備好接收更多資料,則他傳送具有相同確認號和非零視窗大小的段(如果丟失該段,則傳送方每隔一定的時間探測接收方)。 */ get WindowSize(): number { return this.view.getUint16(14); } set WindowSize(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(14, value); } /** * 整個段的16位校驗和(加上偽IP標頭)。 */ get Checksum(): number { return this.view.getUint16(16); } set Checksum(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(16, value); } get UrgentPointer(): number { return this.view.getUint16(18); } set UrgentPointer(value: number) { if (value > 0xffff) throw new Error("value 不能超過16-bits."); this.view.setUint16(18, value); } view: DataView; constructor(public readonly ip: IP, udpBytes: number[]) { this.view = new DataView(Uint8Array.from(udpBytes).buffer); } } class Data { get data(): Uint8Array { return new Uint8Array(this.view.buffer); } set data(value: Uint8Array) { this.view = new DataView(value.buffer); } view: DataView; constructor(dataBytes: number[]) { this.view = new DataView(Uint8Array.from(dataBytes).buffer); } } class Packet { ethernet: Ethernet; ip: IP; udp?: UDP; tcp?: TCP; data: Data; #_view!: DataView; get view(): DataView { for (let i = 0; i < IP.size; i++) this.#_view.setUint8(Ethernet.size + i, this.ip.view.getUint8(i)); if (this.udp) for (let i = 0; i < UDP.size; i++) this.#_view.setUint8( Ethernet.size + IP.size + i, this.ip.view.getUint8(i) ); return this.#_view; } set view(value: DataView) { this.#_view = value; } constructor(packetBytes: number[]) { this.view = new DataView(Uint8Array.from(packetBytes).buffer); this.ethernet = new Ethernet(packetBytes); this.ip = new IP(packetBytes.slice(Ethernet.size)); let procSize = 0; if (this.ip.Protocol === 6) { this.tcp = new TCP(this.ip, packetBytes.slice(Ethernet.size + IP.size)); procSize = TCP.size; } else if (this.ip.Protocol === 17) { this.udp = new UDP(this.ip, packetBytes.slice(Ethernet.size + IP.size)); procSize = UDP.size; } this.data = new Data(packetBytes.slice(Ethernet.size + IP.size + procSize)); } } const udpData = "70 89 cc ee 84 2c 3c 2c 30 a6 a2 d0 08 00 45 80 00 7b c9 5b 00 00 80 11 00 00 c0 a8 01 06 b7 e8 7f f4 0f a5 1f 40 00 67 c7 76 02 39 3d 03 e3 4c 72 61 dc 91 5f 04 00 00 00 01 01 01 00 00 69 a1 00 00 00 00 00 00 00 00 da d2 8d 74 3e 33 16 50 ea 46 c0 cf 22 9c 93 7d 7e b3 91 89 e0 c5 b9 e4 22 b6 e6 e9 78 d8 0b 28 6e 42 6d f1 d8 44 43 b1 7f 1b f0 a5 aa a7 d6 e9 4a 07 49 e8 a0 88 a0 42 15 e2 a9 da 21 ed da af 03"; const tcpData = "70 89 cc ee 84 2c 3c 2c 30 a6 a2 d0 08 00 45 00 00 68 9b b7 40 00 80 06 00 00 c0 a8 01 06 31 eb 6b da c8 41 0e 96 48 d2 73 52 24 9a 12 8b 50 18 ff ff 5f ce 00 00 54 4c 42 42 30 31 d5 01 18 00 00 7e 0b 84 80 58 0e 03 00 00 00 61 61 61 00 ff ff ff ff 01 00 00 00 00 00 00 54 4c 42 42 30 31 73 03 10 00 00 7f 00 00 00 00 00 00 00 00 00 00 00 00 1e 03 04 00"; const p = new Packet( tcpData .trim() .split(/\s+/) .map((it) => parseInt(it, 16)) );