DHCP,代码实现主机动态配置协议IP租用请求和应答
我们在上一节中完成了客户端请求和服务器应答的第一个步骤。客户端发出DHCP_DISCOVER消息,局域网内的所有DHCP服务器发出DHCP_OFFER消息,在该消息中包含一个特殊字段叫Your_IP_Address,这是服务器分配给客户端的IP地址,如下图:
客户端可能会同时受到多个DHCP服务器发送的回应,然后它从中选择一个服务器发送过来的IP地址,并构造一个DHCP_REQUEST发送给对方,在数据包的Options字段中,使用一个Option表示它向服务器请求该IP地址:
然后服务器会向客户端发送DHCP_ACK消息表示确认客户端的租借请求:
完成了上面步骤后,服务器会记录客户端硬件地址与租借地址的对应关系,客户端在租用IP后,想要续租时,依旧会与给定服务器交流,在续租时,它会向绑定服务器发送DHCP_REQUEST消息,在消息中附带续租时常,同时如果允许续租的话,服务器会向客户端发送DHCP_ACK消息,在消息里附带了服务器允许客户端继续租用给定IP的时间。
如果客户端需要离开网络,不再使用给定IP,它会向服务器发送DHCP_RELEASE消息表示放弃当前使用的IP,如此服务器就能回收IP资源,将宝贵的IP分发给其他需要的客户端,DHCP_RELEASE消息中包含了客户端当前使用的IP地址,其基本内容如下:
本节我们就使用代码实现该流程。 由于代码涉及到IP分配,为了不影响运行机器的网络功能,我们在运行协议时使用虚构的mac地址:
//构造一个不存在的mac地址 public byte[] deviceFakeMacAddress() { byte[] fakeMac = new byte[macAddress.length]; for (int i = 0; i < macAddress.length; i++) { fakeMac[i] = (byte) (macAddress[i] + 1); } return fakeMac; }
在DHCPAppliacation中,我们把上节使用到mac地址的地方都改成上面函数,同时我们修改上节对DHCP_OFFER消息的解读,记录下服务器提供的IP:
private boolean readFirstPart(byte[] data) { .... byte[] your_addr = new byte[4]; buffer.position(DHCP_YOUR_IP_ADDRESS_OFFSET); buffer.get(your_addr, 0, your_addr.length); System.out.println("available ip offer by dhcp server is: "); try { //记录下服务器提供的可用ip server_supply_ip = InetAddress.getByAddress(your_addr); System.out.println(server_supply_ip.getHostAddress()); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } .... }
当我们在DHCP_OVER消息中拿到your_client_ip之后,我们就可以向服务器发送一个DHCP_REQUEST请求来租用这个IP:
private byte[] constructDHCPRequestOptions() { byte[] option_msg_type = new byte[OPTION_MSG_TYPE_LENGTH]; ByteBuffer buffer = ByteBuffer.wrap(option_msg_type); buffer.put(DHCP_MSG_TYPE); buffer.put(OPTION_MSG_REQUEST_LENGTH); buffer.put(OPTION_MSG_REQUEST_TYPE); //option 55 Parameter Request List byte[] parameter_request_list = new byte[OPTION_PARAMETER_REQUEST_LENGTH]; buffer = ByteBuffer.wrap(parameter_request_list); buffer.put(OPTION_PARAMETER_REQUEST_LIST); buffer.put(OPTION_PARAMETER_REQUEST_DATA_LENGTH); byte[] option_buffer = new byte[] {OPTIONS_PARAMETER_SUBNET_MASK, OPTIONS_PARAMETER_STATIC_ROUTER, OPTIONS_PARAMETER_ROUTER, OPTIONS_PARAMETER_DOMAIN_NAME_SERVER, OPTIONS_PARAMETER_DOMAIN_NAME, OPTIONS_PARAMETER_DOMAIN_SEARCH,OPTIONS_PARAMETER_PROXY,OPTIONS_PARAMETER_LDPA, OPTIONS_PARAMETER_IP_NAME_SERVER,OPTIONS_PARAMETER_IP_NODE_TYPE}; buffer.put(option_buffer); //option 57 Maximum DHCP Message Size byte[] maximun_dhcp_msg_size = new byte[OPTION_MAXIMUN_DHCP_MESSAGE_SIZE_LENGTH]; buffer = ByteBuffer.wrap(maximun_dhcp_msg_size); buffer.put(OPTION_MAXIMUM_DHCP_MESSAGE_SIZE_TYPE); buffer.put(OPTION_MAXIMUN_DHCP_MESSAGE_SIZE_DATA_LENGTH); buffer.putShort(OPTION_MAXIMUN_DHCP_MESSAGE_SIZE_CONTENT); //add ip request byte[] requested_ip_addr = new byte[OPTION_REQUESTED_IP_TYPE_LENGTH + server_supply_ip.getAddress().length]; buffer = ByteBuffer.wrap(requested_ip_addr); buffer.put(OPTION_REQUESTED_IP_TYPE); buffer.put(OPTION_REQUESTED_IP_LENGTH); buffer.put(server_supply_ip.getAddress()); //option 61 Client identifier byte[] client_identifier = new byte[OPTION_CLIENT_IDENTIFIER_LENGTH]; buffer = ByteBuffer.wrap(client_identifier); buffer.put(OPTION_CLIENT_IDENTIFIER); buffer.put(OPTION_CLIENT_IDENTIFIER_DATA_LENGTH); buffer.put(OPTION_CLIENT_IDENTIFIER_HARDWARE_TYPE); buffer.put(DataLinkLayer.getInstance().deviceFakeMacAddress()); //option 51 ip address lease time byte[] ip_lease_time = new byte[OPTION_IP_LEASE_TIME_LENGTH]; buffer = ByteBuffer.wrap(ip_lease_time); buffer.put(OPTION_IP_LEASE_TIME); buffer.put(OPTION_IP_LEASE_TIME_DATA_LENGTH); buffer.putInt(OPTION_IP_LEASE_TIME_CONTENT); //option 12 Host Name byte[] host_name = new byte[OPTION_HOST_NAME_LENGTH]; buffer = ByteBuffer.wrap(host_name); buffer.put(OPTION_HOST_NAME); buffer.put(OPTION_HOST_NAME_DATA_LENGTH); buffer.put(OPTION_HOST_NAME_CONTENT); //option end byte[] end = new byte[1]; end[0] = OPTION_END; byte[] padding = new byte[13]; dhcp_options_part = new byte[ + option_msg_type.length + parameter_request_list.length + maximun_dhcp_msg_size.length + client_identifier.length + + requested_ip_addr.length + ip_lease_time.length + host_name.length + end.length + pad 4000 ding.length]; buffer = ByteBuffer.wrap(dhcp_options_part); buffer.put(option_msg_type); buffer.put(parameter_request_list); buffer.put(maximun_dhcp_msg_size); buffer.put(client_identifier); buffer.put(ip_lease_time); buffer.put(host_name); buffer.put(end); buffer.put(padding); return buffer.array(); } public void dhcpRequest() { if (this.server_supply_ip == null) { return; } byte[] options = constructDHCPRequestOptions(); byte[] dhcpDiscBuffer = new byte[dhcp_first_part.length + MAGIC_COOKIE.length + options.length]; ByteBuffer buffer = ByteBuffer.wrap(dhcpDiscBuffer); buffer.put(dhcp_first_part); buffer.put(MAGIC_COOKIE); buffer.put(dhcp_options_part); byte[] udpHeader = createUDPHeader(dhcpDiscBuffer); byte[] ipHeader = createIP4Header(udpHeader.length); byte[] dhcpPacket = new byte[ udpHeader.length + ipHeader.length]; buffer = ByteBuffer.wrap(dhcpPacket); buffer.put(ipHeader); buffer.put(udpHeader); //将消息广播出去 ProtocolManager.getInstance().broadcastData(dhcpPacket); }
上面两个函数中,constructDHCPRequestOptions用于构建DHCP_REQUEST数据包的options部分,它与DHCP_DISCOVER唯一的区别在于增加了一个option字段,也就是DHCP_REQUESTED_IP,把要租用的ip地址传递给服务器。接下来的dhcpRequest()与原来实现的dhcpDiscovery差不多,也是生成udp和ip包头后,把数据包发送给服务器。
前面说过,DHCP客户端要维持一个状态机,记录它在协议执行中不同的状态变化,因此我们在代码中也增加了表示当前状态的记录:
private static byte DHCP_MSG_ACK = 5; private final static int DHCP_STATE_DISCOVER = 0; private final static int DHCP_STATE_REQUESTING = 1; private static int dhcp_current_state = DHCP_STATE_DISCOVER;
我们将根据接收到服务器发来的数据包内容改变当前状态:
private void readOptions(byte[] data) { .... switch(type) { case DHCP_MSG_TYPE: //越过长度字段 buff.get(); byte msg_type = buff.get(); if (msg_type == DHCP_MSG_OFFER) { System.out.println("receive DHCP OFFER message from server"); //接收到DHCP_OFFER后,将状态转变为requesting dhcp_current_state = DHCP_STATE_REQUESTING; } //receive ack msg if (msg_type == DHCP_MSG_ACK) { System.out.println("receive DHCP ACK message from server"); } break; .... } .... trigger_action_by_state(); } private void trigger_action_by_state() { switch(dhcp_current_state) { case DHCP_STATE_REQUESTING: dhcpRequest(); break; default: break; } }
上面代码用于读取服务器发来数据包的options字段,根据字段中的内容我们转换状态,一开始程序处于DHCP_STATE_DISCOVER状态,一旦收到服务器发来的回应包中包含DHCP_MSG_OFFER字段时,我们将状态转换为DHCP_STATE_REQUESTING状态。
然后根据状态执行相应动作,在函数trigger_action_by_state中,一旦检测到状态为DHCP_STATE_REQUESTING时,他就构造一个DHCP_REQUEST消息吧发送给服务器,然后等待服务器回发DHCP_ACK数据包,这样我们就走完了ip的租借流程。
当运行上面代码后,使用wireshark抓包情况如下:
我们在代码中构造一个虚假mac地址,DHCP服务器为该mac地址分配了一个可用IP是192.168.2.159,同时代码发送一个DHCP_REQUEST消息,同时服务器正常给我们返回了DHCP_ACK,表示接受了我们对该IP的租用请求。
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
- CentOS 6.5中实现动态主机协议--DHCP
- RHEL5中配置DHCP服务器实现动态主机配置
- DHCP_动态主机配置
- 【转载】C#实现动态分配IP和释放IP (DHCP)
- 实战:RHEL6配置dhcp服务器并绑定主机IP
- java实现具有动态主机IP的域名解析
- 茶叶蛋干货!《超容易的Linux系统管理入门书》(连载十)进行动态主机配置DHCP
- Spring动态对Quartz定时任务的管理,实现动态加载,停止的配置实例代码
- Strom程序的并发机制,配置并行度(代码实现)、动态改变并行度,local or shuffle分组,分组的概念以及分组类型
- spring框架中多数据源创建加载并且实现动态切换的配置实例代码
- DHCP:解析开发板上动态获取ip的2种实现方法详解
- Spring动态对Quartz定时任务的管理,实现动态加载,停止的配置实例代码
- shell脚本实现从master节点批量配置salve节点(主机名有瑕疵,IP映射,ssh服务)
- 检查对端请求连接的IP是否为本机配置的IP的C语言代码
- 一个服务器上面配置多个IP ,实现指定IP的域名请求
- DHCP动态主机协议:
- 实现具有动态主机IP的域名解析
- DHCP 动态主机的简单配置
- 利用cisico模拟器packet traert完成DHCP动态主机协议的实验