# 2022q1 packet sniffer contributed by < `a12345645` > > [第 14 週測驗題](https://hackmd.io/@sysprog/linux2022-quiz14) > [期末專題列表](https://hackmd.io/@sysprog/linux2022-projects) > [GitHub](https://github.com/a12345645/packet-sniffer) ## Packet MMap :::danger 在列出程式碼之前,應對目標和涉及的背景知識予以解說。 :notes: jserv ::: ``` [setup] socket() -------> creation of the capture socket setsockopt() ---> allocation of the circular buffer (ring) option: PACKET_RX_RING mmap() ---------> mapping of the allocated buffer to the user process [capture] poll() ---------> to wait for incoming packets [shutdown] close() --------> destruction of the capture socket and deallocation of all associated resources. ``` 他會在 kernel space 下建立一個 TX 與 RX 的 ring buffer,然後把收到的 socket buffer (skb) 收到的封包複製到這個 ring buffer,並且使用 mmap 映射到 user space 這樣可以避免把資料從 kernel space 複製到 user space 的負擔。 ### MTU (maximum transmission unit) 一個網卡可以傳出封包的最大長度,當封包的長度超過這個上限他就會被分段 (fragmentation) 分成數個片段 (fragment) 送出去,可能會走不同的路徑抵達再組裝。 <!-- 在路由途中,也可能因為路由器的 MTU 的設置再被分段,一條路徑上的 ``` struct tpacket_req { unsigned int tp_block_size; /* Minimal size of contiguous block */ unsigned int tp_block_nr; /* Number of blocks */ unsigned int tp_frame_size; /* Size of frame */ unsigned int tp_frame_nr; /* Total number of frames */ }; --> ## PACKET_MMAP 設定 ### TPACKET TPACKET_V3 ### 設定 ring buffer 設定擷取封包 ```c setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void *) &req, sizeof(req)) ``` 設定傳送封包 ```c setsockopt(fd, SOL_PACKET, PACKET_TX_RING, (void *) &req, sizeof(req)) ``` * req : tpacket_req 結構,設定 ring buffer #### tpacket_req ```c struct tpacket_req { unsigned int tp_block_size; /* Minimal size of contiguous block */ unsigned int tp_block_nr; /* Number of blocks */ unsigned int tp_frame_size; /* Size of frame */ unsigned int tp_frame_nr; /* Total number of frames */ }; ``` * tp_block_size : ring buffer 中一個 block 的大小。因為是實體的連續記憶體所以每一個 block 的大小必須跟一個 page 大小對齊,可以使用 `__get_free_pages()` 或是 [`getpagesize(2)`](https://man7.org/linux/man-pages/man2/getpagesize.2.html) 來得到一個 page 的大小。 * tp_block_nr : block 的總數。 * tp_frame_size : 一個 fram 不能超過一個 block 的大小,而且為了對齊會使 block 的因數,frame 並不是直接代表連階層 (link-level layer) 的 frame,每一個 frame 前面有一個 `tpacket_hdr` 來描述他。 * tp_frame_nr : frame 的總數,就是 tp_block_size / tp_frame_size * tp_block_nr。 ### tpacket_block_desc 是一個 block 的 header,他會包含 block 的資訊,像是這個 block 包含幾個封包。 這裡用到了一個參數 block_status 表示這個 block 內從 skb 複製過來的封包是否準備好了,與TP_STATUS_USER 做且運算為 1 表示好了。 當取完資料後要設定回初始值 TP_STATUS_KERNEL。 ### tpacket3_hdr 一個 frame 的 header,他會包含 frame 的資訊,像是時間戳等。 並有一個值 tp_next_offset 表示與下一個 frame 的位移,以便尋找下一個。 值 ihl(Internet Header Length ) 表示標頭長度,表式標頭是幾個 32 bits 值的長度(4 bytes),所以要得到標頭的長度需要乘以 4。 值 version 表示 ipv4 與 ipv6 #### 補充 htons(), ntohl(), ntohs(),htons() :::spoiler 網絡字節順序NBO(Network Byte Order): 是 Big-endian 網路上統一的數字表示還保持相容性。 主機字節順序HBO(Host Byte Order): 不同的系統不同的順序 所以在數值轉換的時候需要使用下面函數 ``` htonl()--"Host to Network Long" ntohl()--"Network to Host Long" htons()--"Host to Network Short" ntohs()--"Network to Host Short" ``` https://blog.csdn.net/ai2000ai/article/details/83277815 ::: ### udp ![](https://i.imgur.com/tnHwGgz.png) 要得到 Data 的長度就是 Length 減掉 udphdr 的大小。 ### tcp ![](https://i.imgur.com/qPUQQ0k.png) 參考: https://notfalse.net/26/tcp-seq Sequence Number : 現在封包的序列號,用來確認封包傳送順序,一開始建立連線時雙邊會先交換初始序列號 (Initial Sequence Number) ,是一個隨機的 0 ~ 2^32-1 的值,透過控制位元 (Control Bits) 中的 SYN,讓兩端的 TCP 必須進行 ISN 的交換。 預測下一個序列號,為現在的序列號加封包內容的長度加 1。 在 tcpdump 下抓到的是 Relative Sequence Number 他會追蹤每個 tcp 連線, ![](https://i.imgur.com/TMT4OBY.png) #### TCP 三向交握 (Three Way Handshake) ![](https://i.imgur.com/gLepRfu.png) #### ETH_P_ALL https://man7.org/linux/man-pages/man7/packet.7.html When protocol is set to htons(ETH_P_ALL), then all protocols are received. #### poll() https://man7.org/linux/man-pages/man2/poll.2.html poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O. <!-- MTU 限制傳述輛 1500 做多只能傳 1500 甚麼時候要紀錄封包 公司記錄員工 監控 偵測封包 偵測檔案傳輸 禁止檔案 libpcap packet capture 過去 kernel copy 到一個 memory NAPI 去看 kernel 文件 csapp 第11張 SOCK_RAW SOCK_DGRAM csapp 有講道 link level 清楚 frame 的定義 POLLIN TPACKET TPACKET_V3 搭配 ring buf 不相容 ### 目的 packet sniffer 攔截封包 https tls 不可能每個地方都加密 在 hand shacking不會加密 交握 --> https://hackmd.io/@sysprog/CSAPP/https%3A%2F%2Fhackmd.io%2Fs%2FByPlLNaTG https://github.com/arunppsg/packet-sniffer sudo apt-get install libpcap-dev sudo apt-get install libssl-dev <!-- ```c struct tpacket_block_desc { __u32 version; __u32 offset_to_priv; union tpacket_bd_header_u hdr; }; ``` packet_mreq setting interface to promiscous mode. Promiscous mode passes all traffic to kernel. packet_mreq defined in linux/if_packet.h TPACKET_V3 - It is said that TPACKET_V3 brings the following benefits: - ~15% - 20% reduction in CPU-usage - ~20% increase in packet capture rate - ~2x increase in packet density - Port aggregation analysis - Non static frame size to capture entire packet payload 當 tpacket_block_desc 的 block_status 的 TP_STATUS_USER 設為1 表示已經收到 packet 並準備好了 --> ## quit 4 題目 :::spoiler ```c #include <linux/if.h> #include <linux/if_ether.h> /* The L2 protocols */ #include <linux/if_packet.h> #include <netinet/in.h> #include <signal.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <sys/poll.h> #include <unistd.h> typedef struct _rxring *rxring_t; typedef int (*rx_cb_t)(void *u, const uint8_t *buf, size_t len); struct priv { /* unused */ }; struct _rxring { void *user; rx_cb_t cb; uint8_t *map; size_t map_sz; sig_atomic_t cancel; unsigned int r_idx, nr_blocks, block_sz; int ifindex; int fd; }; #define N_BLOCKS 2049 /* 1. Open the packet socket */ static bool packet_socket(rxring_t rx) { return (rx->fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) >= 0; } /* 2. Set TPACKET_V3 */ static bool set_v3(rxring_t rx) { int val = TPACKET_V3; return !setsockopt(rx->fd, SOL_PACKET, PACKET_VERSION, &val, sizeof(val)); } /* 3. Setup the fd for mmap() ring buffer */ static bool rx_ring(rxring_t rx) { struct tpacket_req3 req = { .tp_block_size = getpagesize() << 2, .tp_block_nr = N_BLOCKS, .tp_frame_size = TPACKET_ALIGNMENT << 7, .tp_frame_nr = req.tp_block_size / req.tp_frame_size * req.tp_block_nr, .tp_retire_blk_tov = 64, .tp_sizeof_priv = sizeof(struct priv), .tp_feature_req_word = 0, }; if (setsockopt(rx->fd, SOL_PACKET, PACKET_RX_RING, (char *) &req, sizeof(req))) return false; rx->map_sz = req.tp_block_size * req.tp_block_nr; rx->nr_blocks = req.tp_block_nr; rx->block_sz = req.tp_block_size; return true; } /* 4. Bind to the ifindex on our sending interface */ static bool bind_if(rxring_t rx, const char *ifname) { if (ifname) { struct ifreq ifr; snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s", ifname); if (ioctl(rx->fd, SIOCGIFINDEX, &ifr)) return false; rx->ifindex = ifr.ifr_ifindex; } else { rx->ifindex = 0; /* interface "any" */ } struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = PF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = rx->ifindex; if (bind(rx->fd, (struct sockaddr *) &sll, sizeof(sll))) return false; return true; } /* 5. finally mmap() the sucker */ static bool map_ring(rxring_t rx) { printf("mapping %zu MiB ring buffer\n", rx->map_sz >> 20); int flags = PROT_READ | PROT_WRITE; rx->map = mmap(NULL, rx->map_sz, flags, MAP_SHARED, rx->fd, 0); return rx->map != MAP_FAILED; } rxring_t rxring_init(const char *ifname, rx_cb_t cb, void *user) { struct _rxring *rx = calloc(1, sizeof(*rx)); if (!rx) goto out; if (!packet_socket(rx)) goto out_free; if (!set_v3(rx)) goto out_close; if (!rx_ring(rx)) goto out_close; if (!bind_if(rx, ifname)) goto out_close; if (!map_ring(rx)) goto out_close; rx->cb = cb; rx->user = user; /* success */ goto out; out_close: close(rx->fd); out_free: free(rx); rx = NULL; out: return rx; } bool rxring_fanout_hash(rxring_t rx, uint16_t id) { int val = PACKET_FANOUT_FLAG_DEFRAG | (PACKET_FANOUT_HASH << 16) | id; return !setsockopt(rx->fd, SOL_PACKET, PACKET_FANOUT, &val, sizeof(val)); } static void do_block(rxring_t rx, struct tpacket_block_desc *desc) { const uint8_t *ptr = (uint8_t *) desc + desc->hdr.bh1.offset_to_first_pkt; unsigned int num_pkts = desc->hdr.bh1.num_pkts; for (unsigned int i = 0; i < num_pkts; i++) { struct tpacket3_hdr *hdr = (struct tpacket3_hdr *) ptr; printf("packet %u/%u %u.%u\n", i, num_pkts, hdr->tp_sec, hdr->tp_nsec); /* packet */ if (rx->cb) (*rx->cb)(rx->user, ptr + hdr->tp_mac, hdr->tp_snaplen); ptr += hdr->tp_next_offset; __sync_synchronize(); } } void rxring_mainloop(rxring_t rx) { struct pollfd pfd = { .fd = rx->fd, .events = POLLIN | POLLERR, .revents = 0, }; while (!rx->cancel) { struct tpacket_block_desc *desc = (struct tpacket_block_desc *) rx->map + rx->r_idx * rx->block_sz; while (!(desc->hdr.bh1.block_status & TP_STATUS_USER)) poll(&pfd, 1, -1); /* walk block */ do_block(rx, desc); desc->hdr.bh1.block_status = TP_STATUS_KERNEL; __sync_synchronize(); rx->r_idx = (rx->r_idx + 1) % rx->nr_blocks; } } void rxring_free(rxring_t rx) { if (!rx) return; munmap(rx->map, rx->map_sz); close(rx->fd); free(rx); } #include <ctype.h> static void hex_dumpf(FILE *f, const uint8_t *tmp, size_t len, size_t llen) { if (!f || 0 == len) return; if (!llen) llen = 0x10; for (size_t line, i, j = 0; j < len; j += line, tmp += line) { line = (j + llen > len) ? len - j : llen; fprintf(f, " | %05zx : ", j); for (i = 0; i < line; i++) fprintf(f, "%c", isprint(tmp[i]) ? tmp[i] : '.'); for (; i < llen; i++) fprintf(f, " "); for (i = 0; i < line; i++) fprintf(f, " %02x", tmp[i]); fprintf(f, "\n"); } fprintf(f, "\n"); } static int cb(void *u, const uint8_t *buf, size_t len) { hex_dumpf(stdout, buf, len, 0); return 1; } int main(int argc, char **argv) { const char *cmd = argv[0]; if (argc < 2) { fprintf(stderr, "Usage:\n\t%s <ifname>\n\n", cmd); return EXIT_FAILURE; } rxring_t rx = rxring_init(argv[1], cb, NULL); if (!rx || !rxring_fanout_hash(rx, 0x1234)) return EXIT_FAILURE; rxring_mainloop(rx); printf("%s: OK\n", cmd); rxring_free(rx); return EXIT_SUCCESS; } ``` RRRR = PACKET_RX_RING FFFF = PROT_READ | PROT_WRITE :::