今回はDHCPのdiscoverパケットを送信してみました。
実行環境にはVirtual Boxで構築したネットワークを使用しています

※DHCPパケット送信できるかという勉強目的なので、実際はDHCPクライアントに既にIPアドレスが割り振られていいます(192.168.198.3)
ということでコードの方を見ていきます。
最終的にはDHCPを実装するコードを完成させようかと思っています。まず手始めに今回は、クライアント側がDHCPのdiscoverをブロードキャストするだけのコードです。
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 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
#include<stdio.h> #include<stdlib.h> #include<locale.h> #include<string.h> #include<errno.h> #include<sys/time.h> #include<sys/ioctl.h> #include<sys/socket.h> #include<sys/types.h> #include<fcntl.h> #include<getopt.h> #include<netdb.h> #include<netinet/in.h> #include<net/if.h> #include<arpa/inet.h> #include<unistd.h> #include<time.h> #include<linux/if_ether.h> #include<features.h> #define FALSE -1 #define TRUE 0 #define MAX_DHCP_CHADDR_LEN 16 #define MAX_DHCP_SNAME_LEN 64 #define MAX_DHCP_FILE_LEN 128 #define MAX_DHCP_OPTIONS_LEN 312 struct dhcp_packet{ uint8_t op; uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; struct in_addr ciaddr; struct in_addr yiaddr; struct in_addr siaddr; struct in_addr giaddr; u_char chaddr[MAX_DHCP_CHADDR_LEN]; char sname[MAX_DHCP_SNAME_LEN]; char file[MAX_DHCP_FILE_LEN]; char options[MAX_DHCP_OPTIONS_LEN]; }__attribute__((__packed__)); /*Message Operation Code*/ #define BOOTREQUEST 1 #define BOOTREPLY 2 /*DHCP Option Code*/ #define DHCP_OPTION_MESSAGE_TYPE 53 #define DHCP_OPTION_SUBNET_MASK 1 #define DHCP_OPTION_ROUTER 3 #define DHCP_OPTION_DNS_SERVER 6 #define DHCP_OPTION_HOST_NAME 12 #define DHCP_OPTION_DOMAIN_NAME 15 #define DHCP_OPTION_BROADCAST_ADDRESS 28 #define DHCP_OPTION_REQUESTED_ADDRESS 50 #define DHCP_OPTION_LEASE_TIME 51 #define DHCP_OPTION_PARAMETER_REQUEST 55 #define DHCP_OPTION_RENEWAL_TIME 58 #define DHCP_OPTION_REBINDING_TIME 59 #define DHCP_OPTION_TFTP_SERVER_NAME 66 #define DHCP_OPTION_BOOT_FILE_NAME 67 #define DHCP_OPTION_RELAY_AGENT_INFORMATION 82 /*Option_53 = Message Type*/ #define DHCP_DISCOVER 1 #define DHCP_OFFER 2 #define DHCP_REQUEST 3 #define DHCP_DECLINE 4 #define DHCP_ACK 5 #define DHCP_NAK 6 #define DHCP_RELEASE 7 #define DHCP_INFORM 8 #define DHCP_FORCERENEW 9 #define DHCP_INFINITE_TIME 0xFFFFFFFF #define DHCP_BROADCAST_FLAG 32768 //0b10000000 /*UDP Port Number*/ #define DHCP_SERVER_PORT 67 #define DHCP_CLIENT_PORT 68 /*DHCP_htype & DHCP_hle */ #define HTYPE_ETHER 1 #define HTYPE_ETHER_LEN 6 u_char *get_hardware_addr(int soc, char *device); int open_dhcp_socket(char *device); int send_dhcp_discover(int soc, u_char *client_mac); |
まずはDHCPのフォーマットに従い、DHCPパケットの構造体を定義します。そしてマクロに 、DHCPパケットの各フィールドに対応したパラメータを定義します。ついでに、使用するポート番号なども書きます。
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 |
int open_dhcp_socket(char *device) { struct sockaddr_in client_sin; struct ifreq ifr; int soc; int flag; memset(&client_sin, 0, sizeof(struct sockaddr_in)); client_sin.sin_family = AF_INET; client_sin.sin_port = htons(DHCP_CLIENT_PORT); client_sin.sin_addr.s_addr = INADDR_ANY; if((soc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))<0){ perror("[-]Failed create socket \n"); return FALSE; } flag = 1; if(setsockopt(soc, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag))<0){ perror("[-]Could not set reuse address option on DHCP socket \n"); return FALSE; } if(setsockopt(soc, SOL_SOCKET, SO_BROADCAST, (char *)&flag, sizeof(flag))<0){ printf("[-]Could not set broadcast option on DHCP socket\n"); return FALSE; } strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name)-1); if(setsockopt(soc, SOL_SOCKET, SO_BINDTODEVICE, device, sizeof(device))<0){ perror("[-]Could not bind socket to device \n"); return FALSE; } if(bind(soc, (struct sockaddr *)&client_sin, sizeof(client_sin))<0){ perror("[-]Failed to bind \n"); return FALSE; } return soc; } |
ソケットを作成しないとパケット送信などが出来ないので、まずはソケットを作成します。DHCPはUDP上で使うので、UDPソケットを作ります。このときにソケットのオプションをいじります。DHCPはUDPを使ったブロードキャストをするので、setsockopt関数でSO_BROADCASTを有効にします。あとは今後のこと(DHCPの本格的実装)を考えて、setsockopt関数でSO_REUSEADDRを指定し、ソケットの再利用を可能にしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
u_char *get_hardware_addr(int soc, char *device) { struct ifreq ifreq; u_char *hwaddr; memset(&ifreq, 0, sizeof(struct ifreq)); strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name)-1); if(ioctl(soc, SIOCGIFHWADDR, &ifreq)<0){ printf("[-]Failed ioctl(SIOCGIFHWADDR)\n"); return NULL; } if(!(hwaddr=(u_char *)malloc(sizeof(u_char)*HTYPE_ETHER_LEN))){ printf("[-]Falied malloc(hwaddr)\n"); } memcpy(hwaddr, ifreq.ifr_hwaddr.sa_data, sizeof(u_char)*6); printf("[+]Got hardware_address: %02x:%02x:%02x:%02x:%02x:%02x\n", *hwaddr, *(hwaddr+1), *(hwaddr+2), *(hwaddr+3), *(hwaddr+4), *(hwaddr+5)); return hwaddr; } |
これはクライアントのMACアドレスを取得するための関数です。DHCPのDiscoverパケットを送信するとき、chaddrフィールドにクライアントのMACアドレスを入れます。なので、ioctl(SIOCGIFHWADDR)で送信するインタフェースの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 |
int send_dhcp_discover(int soc, u_char *client_mac) { struct dhcp_packet dhcp; struct sockaddr_in server_sin; int bytes; int requested_address = 0; memset(&dhcp, 0, sizeof(struct dhcp_packet)); dhcp.op = BOOTREQUEST; dhcp.htype = HTYPE_ETHER; dhcp.hlen = HTYPE_ETHER_LEN; dhcp.hops = 0; srand(time(NULL)); dhcp.xid = htonl(random()); dhcp.secs = 0xFF; dhcp.flags = htons(DHCP_BROADCAST_FLAG); memcpy(dhcp.chaddr, client_mac, HTYPE_ETHER_LEN); /*Magic Cookie*/ dhcp.options[0] = '\x63'; dhcp.options[1] = '\x82'; dhcp.options[2] = '\x53'; dhcp.options[3] = '\x63'; dhcp.options[4] = DHCP_OPTION_MESSAGE_TYPE; dhcp.options[5] = '\x01'; dhcp.options[6] = DHCP_DISCOVER; dhcp.options[7] = DHCP_OPTION_REQUESTED_ADDRESS; dhcp.options[8] = '\x04'; memcpy(&dhcp.options[9], &requested_address, sizeof(requested_address)); memset(&server_sin, 0, sizeof(struct sockaddr_in)); server_sin.sin_family = AF_INET; server_sin.sin_port = htons(DHCP_SERVER_PORT); server_sin.sin_addr.s_addr = INADDR_BROADCAST; if(bytes=sendto(soc, &dhcp, sizeof(struct dhcp_packet), 0, (struct sockaddr *)&server_sin, sizeof(server_sin))<0){ perror("[-]Failed to send DHCP Discover packet\n"); return FALSE; } return TRUE; } |
DHCPのDiscoverを組み立てて、送信まで行う関数です。最初に定義したDHCPフォーマットの各フィールドに値を入れていきます。
op:クライアントからサーバに送るので、BOOTREQUESTを入れます。
htype:ハードウェアはethernetなので、値1を定義したHTYPE_ETHERを入れます。
hlen:ethernetアドレスは6バイトなので、値6を定義したHTYPE_ETHER_LENを入れます。
hops:リレーエージェントが何回経由したかを示すフィールドなので、クライアント側送信時点では0をセットします。
xid:トランザクションIDのことで、DiscoverからAckまで、DHCP一連の流れで一貫したランダム値が使われます。srandで現時刻を種として、ランダム値を生成し、それをxidにセットします。
secs:DHCPサーバによって払い出されたIPアドレスのリース残り期間を示しますが、Discoverの時点では0xFFをセットします。
flags:2Bytes(16Bits)の中の最初の1ビット目に1をセットし、残りのビットには0をセットします。
ciaddr, yiaddr, siaddr, giaddr:Discover送信時点では0が入ります。
chaddr:クライアントのMACアドレスが入ります。
Magic Cookieという0x63, 0x82, 0x53, 0x63の4バイト数字が必要です。実はDHCPプロトコルは、その前身であるBOOTP(Bootstrap Protocol)とフォーマットがほとんど同じです。なのでこのMagic Cookieで「このパケットは、BOOTPではなく、DHCPですよ」と識別しています。
あとはオプションを指定します。ここまででは「まだこのDHCPパケットはDiscoverですよ」という識別が出来ないので、オプションのMasic Cookieの次にMessage Typeとして、DHCP_DISCOVERを指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
int main(int argc, char *argv[]) { int dhcp_soc; u_char *mac; char *device; struct dhcp_packet dhcp; if(argc!=2){ fprintf(stdout, "[-]Usage: [interface name]\n"); return -1; } device = argv[1]; if((dhcp_soc = open_dhcp_socket(device))!=FALSE) printf("[+]Openeded dhcp_socket\n"); mac=get_hardware_addr(dhcp_soc, device); if((send_dhcp_discover(dhcp_soc, mac))==TRUE) printf("[+]Sent DHCP Discover\n"); close (dhcp_soc); return 0; } |
main関数です。実行時に使用するインタフェース名を指定してます。
流れとしては、まずソケットを作成して、その次にそのソケットを使って、インタフェースのMACアドレスを取得します。そしてDHCPのDiscoverパケットを送信する関数に渡します。
実行結果は以下の感じになります。

ブロードキャストなので、DHCPサーバとDHCPクライアント(ゲストOS)以外のPC(ホストOS)でパケットキャプチャしました。

送信成功です!
最後まで読んでいただきありがとうございました。