# 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
:::