基本的には、TCP/UDPセグメントを
わざわざRawソケットを使う必要ありません。
SOCK_DGRAMを使った方が
コードもシンプルになりますし、
そっちの方が簡単です。
しかし時にはUDPセグメントを
Rawソケットから送らなければならない
という場面に遭遇するかもしれません。
Rawソケットから作成した方が、
柔軟性には長けますね。
というわけで自分で一から
UDPセグメントを組み立てて送信します。
データにはHello Worldを入れます。
ソースコードの流れはシンプルです。
まずはRaw Socketを用意します。
そしてインタフェース名から
自分のMACアドレスを取得して、
Ethernet、IP、UDPの各フィールドに
適切な値を入れて送信するだけです。
それではやってみましょう!
UDPをRawソケットで送ってみよう
コードはこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
#include<sys/socket.h> #include<sys/ioctl.h> #include<sys/types.h> #include<sys/wait.h> #include<sys/param.h> #include<linux/if.h> #include<arpa/inet.h> #include<net/ethernet.h> #include<netinet/in.h> #include<netinet/ip.h> #include<netinet/ip6.h> #include<netinet/tcp.h> #include<netinet/udp.h> #include<netinet/ip_icmp.h> #include<netinet/if_ether.h> #include<netinet/in.h> #include<netpacket/packet.h> #include<netdb.h> #include<ctype.h> #include<errno.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sysexits.h> #include<unistd.h> #define FALSE -1 #define TRUE 0 #define BROADCAST_MAC (uint8_t[6]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define SRC_IP "0.0.0.0" #define DST_IP "255.255.255.255" #define SRC_PORT 50000 #define DST_PORT 50001 struct udp_packet{ struct ether_header eh; struct iphdr ip; struct udphdr udp; uint8_t data[256]; }__attribute__((__packed__)); struct pseudo_header{ uint32_t saddr; uint32_t daddr; uint8_t reserved; uint8_t protocol; uint16_t len; }__attribute__((__packed__)); struct pseudo_udp{ struct pseudo_header ip; struct udphdr udp; uint8_t data[256]; }__attribute__((__packed__)); |
読み込むライブラリ、UDPパケット、
チェックサム計算用の疑似ヘッダー
を定義しています。
ブロードキャストのアドレスなども
マクロで定義しています。
DHCPのDiscoverを想定して、
送信元IPアドレスや宛先IPアドレスを
設定してあります。
もしユニキャストしたい場合は、
任意に変更すれば指定した相手に届きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int open_socket(const char *device) { struct sockaddr_ll sll; struct ifreq ifr; int soc; if((soc=socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)))<0){ perror("[-]Failed to open socket"); return FALSE; } strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name)-1); if(ioctl(soc, SIOCGIFINDEX, &ifr)<0){ perror("[-]Failed ioctl(SIOCGIFINDEX)"); close (soc); return FALSE; } sll.sll_family = PF_PACKET; sll.sll_protocol = htons(ETH_P_IP); sll.sll_ifindex = ifr.ifr_ifindex; if(bind(soc, (struct sockaddr *)&sll, sizeof(sll))<0){ perror("[-]Failed bind"); close(soc); return FALSE; } return soc; } |
送信するためにソケットを用意する関数です。
Raw Socketを作成して、
それをインタフェースにBindしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
uint16_t checksum(uint8_t *data, size_t len) { uint32_t sum, c; uint16_t val, *ptr; sum = 0; ptr = (uint16_t *)data; for(c=len; c>=len; c-=2){ sum += (*ptr); if(sum&0x80000000){ sum = (sum & 0xFFFF)+(sum>>16); } ptr++; } if(c==-1){ val=0; memcpy(&val, ptr, sizeof(uint8_t)); sum += val; } while(sum>>16){ sum =(sum & 0xFFFF)+(sum>>16); } if(sum == 0xFFFF){ return sum; }else{ return ~sum; } return sum; } |
IPヘッダとUDPヘッダの
チャックサムを計算する関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
u_char *get_mac(int soc, char *device) { struct ifreq ifr; u_char *mac; memcpy(ifr.ifr_name, device, sizeof(ifr.ifr_name)-1); if(ioctl(soc, SIOCGIFHWADDR, &ifr)<0){ perror("[-]Falied ioctl(SIOCGIFHWADDR)"); return NULL; } mac = (u_char *)ifr.ifr_hwaddr.sa_data; printf("[+]Success got MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", *mac, *(mac+1), *(mac+2), *(mac+3), *(mac+4), *(mac+5)); return mac; } |
指定したインターフェースの
MACアドレスを取得する関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
int send_udp_from_raw(int soc, char *device, const uint8_t *data, size_t size) { uint8_t buf[sizeof(struct udp_packet)]; uint8_t *p; struct udp_packet udp; struct pseudo_udp pse_udp; size_t total; memset(&pse_udp, 0, sizeof(pse_udp)); inet_pton(AF_INET, SRC_IP, &pse_udp.ip.saddr); inet_pton(AF_INET, DST_IP, &pse_udp.ip.daddr); pse_udp.ip.reserved = 0; pse_udp.ip.protocol = 17; pse_udp.ip.len = htons(sizeof(struct udphdr)+size); pse_udp.udp.source = htons(SRC_PORT); pse_udp.udp.dest = htons(DST_PORT); pse_udp.udp.len = htons(sizeof(struct udphdr)+size); pse_udp.udp.check = 0; memset(pse_udp.data, 0, sizeof(pse_udp.data)); memcpy(pse_udp.data, data, size); pse_udp.udp.check = checksum((u_char *)&pse_udp, sizeof(struct pseudo_header)+sizeof(struct pseudo_udp)+size); memset(&udp, 0, sizeof(udp)); memcpy(&udp.udp, &pse_udp.udp, sizeof(struct udphdr)); memcpy(udp.data, pse_udp.data, sizeof(udp.data)); udp.ip.version = 4; udp.ip.ihl = 20/4; udp.ip.tos = 0; udp.ip.tot_len = htons(sizeof(struct iphdr)+sizeof(struct udphdr)+size); udp.ip.id = 0; udp.ip.frag_off = 0; udp.ip.ttl = 64; udp.ip.protocol = IPPROTO_UDP; udp.ip.check = 0; inet_pton(AF_INET, SRC_IP, &udp.ip.saddr); inet_pton(AF_INET, DST_IP, &udp.ip.daddr); udp.ip.check = checksum((uint8_t *)&udp.ip, sizeof(struct iphdr)); memcpy(udp.eh.ether_dhost, BROADCAST_MAC, 6); memcpy(udp.eh.ether_shost, get_mac(soc, device), 6); udp.eh.ether_type = htons(ETHERTYPE_IP); memset(buf, 0, sizeof(buf)); p = buf; memcpy(p, &udp.eh,sizeof(struct ether_header)); p += sizeof(struct ether_header); memcpy(p, &udp.ip, sizeof(struct iphdr)); p += sizeof(struct iphdr); memcpy(p, &udp.udp, sizeof(struct udphdr)); p += sizeof(struct udphdr); memcpy(p, udp.data, size); p += size; total = p-buf; if(send(soc, buf, total, 0)==-1){ perror("[-]Failed to send"); return FALSE; } return size; } |
フレームを組み立てて、
送信まで行う関数です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
int main(int argc, char *argv[]) { int soc; int size; u_char data[] = "Hello World!"; if(argc <= 1){ fprintf(stderr, "[-]Usage: [interface name]\n"); return (EX_USAGE); } if((soc=open_socket(argv[1]))==FALSE){ fprintf(stderr, "[-]raw socket(%s):error\n", argv[1]); return (EX_UNAVAILABLE); }else{ printf("[+]Success creation raw socket: %d\n", soc); } if(send_udp_from_raw(soc, argv[1], data, sizeof(data))!=TRUE){ printf("[+]Success sent UDP packet from raw socket\n%s\n", data); } close (soc); return (EX_OK); } |
main関数です。プログラム実行時に、
インタフェースも一緒に指定します。
あとHello World!は、
ここで文字配列に入れています。
scanfなどで標準入力から読み込むのもありですね。
実行結果はこんな感じになります。

宛先が同一セグメント内への
ブロードキャストなので、
同一セグメントに所属している
他のPCでパケットキャプチャしてます。

しっかり送信されていました!
最後まで読んでいただきありがとうございました。