dhcp协议学习

作者:
淡白
创建时间:
2024-08-04 15:29:00
dhcp 协议

摘要:DHCP(动态主机配置协议)是用于自动向网络设备分配IP地址及其他网络配置的重要协议。它通过客户端与服务器间的交互流程简化了设备接入网络的配置过程。该协议的基本交互步骤包括DHCP Discover、Offer、Request及Acknowledge,允许设备快速获得所需的IP地址和配置信息。此外,DHCP还支持配置其他网络参数,如默认网关、DNS服务器及子网掩码等。文章中还提供了一个简单的DHCP服务器实现示例,展现了如何在实际应用中应用该协议。欲了解更深入的信息,文末附有相关链接供进一步阅读。

协议概述

DHCP(动态主机配置协议)是一种网络协议,用于自动向网络设备分配IP地址及其他相关网络配置。DHCP通过客户端与服务器之间的交互来简化网络设备的配置过程,使设备能够快速接入网络。

客户端服务端交互流程

sequenceDiagram
    participant C as DHCP Client
    participant S as DHCP Server

    C->>S: DHCP Discover (广播)
    S->>C: DHCP Offer
    C->>S: DHCP Request
    S->>C: DHCP Acknowledge

在这个流程中,DHCP客户端和服务器的交互步骤如下:

  1. DHCP Discover:

    • 客户端发送发现消息,寻找可用的DHCP服务器。这个消息是广播形式的,因为客户端在此时尚不知道网络中存在的DHCP服务器。
    • 消息中包含客户端的MAC地址和请求的IP地址(如果有的话)。
  2. DHCP Offer:

    • 服务器接收到发现消息后,发送一个提供消息,告知客户端可以使用的IP地址及其他网络配置(如子网掩码、默认网关和DNS服务器)。
    • 提供消息是单播或广播的,取决于服务器的配置。
  3. DHCP Request:

    • 客户端收到多个提供消息后,选择其中一个,并向该DHCP服务器发送请求,确认使用该IP地址。
    • 此请求消息也是广播的,以通知其他DHCP服务器该IP地址已被分配。
  4. DHCP Acknowledge:

    • 服务器确认请求,并向客户端发送确认消息,完成IP地址分配。此时,客户端正式获得所请求的IP地址。

扩展阶段(可选)

在某些情况下,DHCP交互流程可能会包括以下步骤:

  1. DHCP Nak:

    • 如果DHCP服务器无法满足客户端的IP地址请求(例如,所请求的IP地址已被分配),服务器会发送一个DHCP Nak(否定确认)消息,以告知客户端请求失败。
    • 客户端在收到Nak消息后,会重新开始DORA流程,发送新的Discover消息。
  2. DHCP Release:

    • 当客户端不再需要分配的IP地址时(例如,设备关闭或断开网络),客户端可以发送一个DHCP Release消息给DHCP服务器,以释放该IP地址。
    • 这样,服务器可以将该IP地址重新分配给其他设备使用。

DHCP租约

其他配置项

DHCP不仅可以用于动态分配IP地址,还可以配置其他网络参数,包括路由、MTU(最大传输单元)以及其他一些网络设置。下面是DHCP可以配置的一些常见选项:

1. 路由(默认网关)

2. MTU(最大传输单元)

3. DNS服务器

4. 子网掩码

5. 主机名称

6. NTP(网络时间协议)服务器

7. 其他网络参数

DHCP协议还支持许多其他选项,具体包括:

简易的dhcp服务器实现

package main

import (
	"fmt"
	"github.com/insomniacslk/dhcp/dhcpv4"
	"github.com/insomniacslk/dhcp/dhcpv4/server4"
	"log"
	"net"
	"test/allocators/bitmap"
	"time"
)

type Record struct {
	IP       net.IP
	expires  int
	hostname string
}

var recordsV4 = make(map[string]*Record)
var allocator, _ = bitmap.NewIPv4Allocator(net.ParseIP("172.20.0.2"), net.ParseIP("172.20.0.254"))

func handler(conn net.PacketConn, peer net.Addr, req *dhcpv4.DHCPv4) {
	res, err := dhcpv4.NewReplyFromRequest(req)
	if err != nil {
		log.Printf("MainHandler4: failed to build reply: %v", err)
		return
	}
	switch mt := req.MessageType(); mt {
	case dhcpv4.MessageTypeDiscover:
		res.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeOffer))
	case dhcpv4.MessageTypeRequest:
		res.UpdateOption(dhcpv4.OptMessageType(dhcpv4.MessageTypeAck))
	default:
		log.Printf("plugins/server: Unhandled message type: %v", mt)
		return
	}
	//autoconfigure
	{
		if res.MessageType() != dhcpv4.MessageTypeOffer || !res.YourIPAddr.IsUnspecified() {
			goto autoconfigureEnd
		}
		_, ok := req.AutoConfigure()
		if ok {
			res.UpdateOption(dhcpv4.OptAutoConfigure(dhcpv4.DoNotAutoConfigure))
		}
	}
autoconfigureEnd:
	//DNS
	{
		if req.IsOptionRequested(dhcpv4.OptionDomainNameServer) {
			res.Options.Update(dhcpv4.OptDNS(net.ParseIP("172.20.0.1"), net.ParseIP("114.114.114.114")))
		}
	}
	//IP router Mask
	{
		record, ok := recordsV4[req.ClientHWAddr.String()]
		hostname := req.HostName()
		if !ok {
			var ip net.IPNet
			ip, err = allocator.Allocate(net.IPNet{})
			if err != nil {
				return
			}
			rec := Record{
				IP:       ip.IP.To4(),
				expires:  int(time.Now().Add(time.Hour * 12).Unix()),
				hostname: hostname,
			}
			recordsV4[req.ClientHWAddr.String()] = &rec
			record = &rec
		} else {
			expiry := time.Unix(int64(record.expires), 0)
			if expiry.Before(time.Now().Add(time.Hour * 12)) {
				record.expires = int(time.Now().Add(time.Hour * 12).Round(time.Second).Unix())
				record.hostname = hostname
				recordsV4[req.ClientHWAddr.String()] = record
			}
		}

		res.Options.Update(dhcpv4.OptRouter(net.ParseIP("172.20.0.1")))
		res.Options.Update(dhcpv4.OptSubnetMask(net.IPv4Mask(255, 255, 255, 0)))
		res.YourIPAddr = record.IP
		res.Options.Update(dhcpv4.OptIPAddressLeaseTime((time.Hour * 12).Round(time.Second)))

	}
	//ServerId
	{
		serverID := net.ParseIP("172.20.0.1")
		if req.OpCode == dhcpv4.OpcodeBootRequest {
			if req.ServerIPAddr != nil &&
				!req.ServerIPAddr.Equal(net.IPv4zero) &&
				!req.ServerIPAddr.Equal(serverID.To4()) {
				return
			}
			res.ServerIPAddr = make(net.IP, net.IPv4len)
			copy(res.ServerIPAddr[:], serverID.To4())
			res.UpdateOption(dhcpv4.OptServerIdentifier(serverID.To4()))
		}
	}
	//回复
	if res != nil {
		if _, err = conn.WriteTo(res.ToBytes(), peer); err != nil {
			log.Printf("MainHandler4: WriteTo failed: %v", err)
		}
	} else {
		log.Print("MainHandler4: dropping request because response is nil")
	}
}

func main() {
	lAddr := net.UDPAddr{
		IP:   net.ParseIP("0.0.0.0"),
		Port: 67,
	}
	server, err := server4.NewServer("ens19", &lAddr, handler)
	if err != nil {
		fmt.Println(err)
	}
	err = server.Serve()
	if err != nil {
		return
	}
}

相关链接