基于 FPGA 的主机 IP 自动配置方案设计

目录

前言

1.DHCP协议

[1.1、 DHCP 报文格式](#1.1、 DHCP 报文格式)

[1.2、 DHCP Options(选项字段)](#1.2、 DHCP Options(选项字段))

[1.3DHCP 的核心:DORA 交互流程](#1.3DHCP 的核心:DORA 交互流程)

[(1). Discover(发现阶段)](#(1). Discover(发现阶段))

[(2). Offer(提供阶段)](#(2). Offer(提供阶段))

[(3). Request(请求阶段)](#(3). Request(请求阶段))

[(4). Acknowledge(确认阶段)](#(4). Acknowledge(确认阶段))

[1.4、 租约维护(Lease)](#1.4、 租约维护(Lease))

[2. 基于 FPGA 的主机 IP 自动配置方案设计](#2. 基于 FPGA 的主机 IP 自动配置方案设计)

[2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic)](#2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic))

代码如下:

Discover和Request的代码:

[offer 和 Acknowledge阶段的代码:](#offer 和 Acknowledge阶段的代码:)

[核心区别:siaddr vs Server Identifier (Option 54)](#核心区别:siaddr vs Server Identifier (Option 54))

[在Verilog 代码中如何实现](#在Verilog 代码中如何实现)

建议配置表

代码调试

调试成功现象:

调试经验:

[2.2 方案二:IPv4 Link-Local (最简单的纯硬件方案)](#2.2 方案二:IPv4 Link-Local (最简单的纯硬件方案))

[2.2.1. 核心原理:RFC 3927 标准](#2.2.1. 核心原理:RFC 3927 标准)

[2.2.2. FPGA 端的实现步骤](#2.2.2. FPGA 端的实现步骤)

[第一步:固化 IP 和 MAC 地址](#第一步:固化 IP 和 MAC 地址)

[第二步:必须实现 ARP 响应(关键!)](#第二步:必须实现 ARP 响应(关键!))

[第三步:简单的 UDP 监听/发送](#第三步:简单的 UDP 监听/发送)

[3. 为什么大家都说它有"潜规则"?](#3. 为什么大家都说它有“潜规则”?)

拓展1:DHCP请求和确认阶段的必要性

[1. 协议的"盲目性" (Standardization)](#1. 协议的“盲目性” (Standardization))

[2. 为什么物理上"一根线"也可能有多个服务器?](#2. 为什么物理上“一根线”也可能有多个服务器?)

[3. 在 Request 报文中,电脑是如何"挑人"的?](#3. 在 Request 报文中,电脑是如何“挑人”的?)

[对你 FPGA 实现的启示](#对你 FPGA 实现的启示)

总结

[拓展二:电脑端的行为:双重机制并行(DHCP + Link-Local)](#拓展二:电脑端的行为:双重机制并行(DHCP + Link-Local))

[1. 抓包证据分析](#1. 抓包证据分析)

[2. 为什么电脑会"坚持不懈"?](#2. 为什么电脑会“坚持不懈”?)

拓展三:UDP的端口选取

[1. 端口的三大分类](#1. 端口的三大分类)

[2. 为什么你的 DHCP 必须用 67 和 68?](#2. 为什么你的 DHCP 必须用 67 和 68?)

[3. FPGA 开发中的自由度](#3. FPGA 开发中的自由度)

[💡 调试总结](#💡 调试总结)


前言

针对科研探测设备与上位机直连的通信需求,本文提出了基于 FPGA 的两种主机 IP 自动配置方案:DHCP 协议与 IPv4 链路本地地址(Link-Local Address)。该设计有效规避了传统静态 IP 手动配置的局限性,显著提升了系统部署的灵活性与自动化水平。

1.DHCP协议

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)是应用层协议,旨在为网络中的主机自动分配 IP 地址、子网掩码、默认网关以及 DNS 服务器。其在以太网帧的位置如下:

其中各层协议的组成如下:

1.1、 DHCP 报文格式

在 FPGA 中手写 DHCP,你需要关注的是这个巨大的 UDP 载荷(通常固定为 300 字节 以上)。

字段名称 长度 (Bytes) 描述 FPGA 实现要点
Message Type (op) 1 1=Request, 2=Reply 作为 Server,你发送时该位恒为 0x02
Hardware Type 1 以太网通常为 0x01 恒定值
Hardware Length 1 MAC 地址长度,恒为 0x06 恒定值
Hops 1 跳数,通常为 0x00 恒定值
Transaction ID (xid) 4 关键:随机传输 ID 你发出的 Offer 必须拷贝 Discover 里的这个 ID
Seconds 2 客户端开始请求后的秒数 通常设为 0x0000
Flags 2 广播标志位 0x8000 表示要求服务端广播回复
Client IP (ciaddr) 4 客户端当前 IP 初始请求时为 0.0.0.0
Your IP (yiaddr) 4 分配给电脑的 IP 这是你告诉电脑"给你这个 IP"的地方
Server IP (siaddr) 4 DHCP 服务器 IP 填入你给 FPGA 设定的 IP
Client MAC (chaddr) 16 客户端 MAC 地址 前 6 字节填入电脑的 MAC,后 10 字节补 0
Magic Cookie 4 固定为 0x63, 0x82, 0x53, 0x63 协议识别标志,不可写错
Options 变长 协议扩展选项 见下表

1.2、 DHCP Options(选项字段)

Options 是 DHCP 的精华,采用 Type-Length-Value (TLV) 格式。在 FPGA 中,你需要手动拼接这些字节。

常见的 Options:

  • Option 53 (Message Type) :长度 1 字节。1 代表 Discover,2 代表 Offer,3 代表 Request,5 代表 Ack。

  • Option 51 (Lease Time) :长度 4 字节。告知电脑 IP 能用多久(例如 3600 秒)。

  • Option 1 (Subnet Mask) :长度 4 字节。子网掩码(如 255.255.255.0)。

  • Option 54 (Server Identifier):长度 4 字节。服务器自己的 IP 地址。

  • Option 255 (End) :长度 1 字节。固定为 0xFF,代表 Options 结束。

1.3DHCP 的核心:DORA 交互流程

DHCP 建立在 UDP 之上,服务端监听 67 端口,客户端监听 68 端口。其核心交互被称为 DORA 流程。

(1). Discover(发现阶段)

  • 客户端 服务端(全网广播)。

  • 电脑(客户端)插上网线后,不知道谁是 DHCP 服务器,于是发送一个目的 IP 为 255.255.255.255 的广播包,寻找网络中的服务器。

字段层级 关键字段 值 (Hex/描述) 硬件处理要点
MAC 层 目的 MAC FF:FF:FF:FF:FF:FF 广播帧。
IP 层 源 / 目的 IP 0.0.0.0 / 255.255.255.255 此时电脑还没 IP。
UDP 层 源 / 目的端口 68 / 67 电脑客户端到服务端。
DHCP 层 op 0x01 代表这是一个请求 (Request)。
xid 0x12345678 (随机) FPGA 必须存下这个 ID,后续回包要用。
chaddr 电脑的 MAC 地址 FPGA 必须存下这个 MAC,作为回复的目标。
Option 53 0x35 0x01 0x01 消息类型:Discover。

(2). Offer(提供阶段)

  • 服务端 客户端(单播或广播)。

  • DHCP 服务器收到请求后,从自己的地址池中选出一个未分配的 IP(例如 192.168.1.100),并携带租期、掩码等信息回发给客户端。

字段层级 关键字段 值 (Hex/描述) 硬件处理要点
MAC 层 目的 MAC 电脑的 MAC 如果设置了广播位,也可以发 FF...FF
IP 层 源 / 目的 IP FPGA IP / 255.255.255.255 此时电脑还没 IP,只能发广播或根据 MAC 寻找。
DHCP 层 op 0x02 代表这是一个回复 (Reply)。
xid 同 Discover 的 xid 匹配事务,否则电脑会丢弃。
yiaddr 192.168.1.100 这是你打算分给电脑的 IP
Option 53 0x35 0x01 0x02 消息类型:Offer。
Option 1 0x01 0x04 FF FF FF 00 告诉电脑子网掩码是 255.255.255.0

(3). Request(请求阶段)

  • 客户端 服务端(全网广播)。

  • 电脑可能收到多个服务器的 Offer,它会选择其中一个,并再次广播:"我接受了来自服务器 A 分配的 192.168.1.100,其他的 Offer 我拒绝了。"

  • 注意:这里依然用广播,是为了告诉其他服务器它们可以收回之前提供的 IP。

字段层级 关键字段 值 (Hex/描述) 硬件处理要点
MAC 层 目的 MAC FF:FF:FF:FF:FF:FF 全网广播。
DHCP 层 xid 同上 事务 ID 保持不变。
Option 53 0x35 0x01 0x03 消息类型:Request。
Option 50 192.168.1.100 电脑明确说:"我要刚才那个 IP"。
Option 54 FPGA 的 IP 地址 核心! 这是电脑选中的服务器 ID。如果不是你的 IP,说明它选了别人。

对这部分的理解在拓展1。

(4). Acknowledge(确认阶段)

  • 服务端 客户端(单播或广播)。

  • 服务器正式确认该 IP 租借给客户端,并告知最后的配置细节。

字段层级 关键字段 值 (Hex/描述) 硬件处理要点
IP 层 源 / 目的 IP FPGA IP / 255.255.255.255 最后的广播确认。
DHCP 层 op 0x02 回复。
yiaddr 192.168.1.100 再次确认分配的 IP。
Option 53 0x35 0x01 0x05 消息类型:ACK。
Option 51 0x33 0x04 0x00 0x00 0x8C A0 租期:例如设置为 36000 秒。

1.4、 租约维护(Lease)

DHCP 分配的 IP 不是永久的,而是"租"给电脑的。

  • T1 定时器 :当租期达到 50% 时,电脑会向 FPGA 发送单播 Request,尝试续约。

  • T2 定时器 :当租期达到 87.5% 时,电脑会发送广播 Request 寻找任何可用的服务器。

对 FPGA 的简化建议:在你的 VLF 信号处理应用中,为了简化逻辑,你可以给出一个极长的租期(例如 7 天),甚至忽略 T1/T2 的续约逻辑。只要网线没断,电脑通常不会主动放弃 IP。

2. 基于 FPGA 的主机 IP 自动配置方案设计

在科研探测设备与上位机直连的场景下,为了摆脱手动配置静态 IP 的繁琐过程,本系统设计了硬件协议栈级的自动寻址逻辑。

2.1 方案一:嵌入式 DHCP 服务器硬件逻辑实现 (DHCP Server Logic)

该方案通过在 FPGA 内部构建一个轻量级的 应用层 DHCP 服务器状态机,主动为主机分配指定网段的 IP 地址。交互过程如1.3小结所示。对应的 DHCP Server 逻辑如下:

  1. UDP 解析 :只要收到目标端口为 67 的 UDP 包。

  2. 状态机跳转

    • 收到 Discover 发送 Offer(把你的固定 IP 段扔过去)。

    • 收到 Request 发送 Ack

  3. 报文拼接技巧

    由于大部分报文字节是固定的,你可以把一个完整的 Offer 包(约 342 字节,包含 Ethernet/IP/UDP 头)存入 FPGA 的 ROM 中。

    发送时,只需实时动态替换以下几个关键点:

    • 以太网帧的目的 MAC。

    • DHCP 报文里的 xid(Transaction ID)。

    • 重新计算 IP 和 UDP 的 Checksum(校验和)。

代码如下:

Discover和Request的代码:
复制代码
`default_nettype none
module dhcp_server_request(
    input   wire            clk,
    input   wire            rst_n,
    // UDP RX Interface
    input   wire            udp_rx_busy,
    input   wire    [15:0]  udp_r_dst_port,
    input   wire    [7:0]   udp_r_data,
    input   wire            udp_r_data_valid,
    // User Interface
    output  reg     [31:0]  out_xid,
    output  reg     [47:0]  out_chaddr,
    output  reg             out_is_discover,
    output  reg             out_is_request,
    output  reg             out_valid
);

localparam S_IDLE    = 4'h1;
localparam S_PAYLOAD = 4'h2;
localparam S_OPTIONS = 4'h4;
localparam S_DONE    = 4'h8;

reg [3:0] state, next_state;
reg [15:0] cnt;

// 1. 状态寄存器 (Sequential)
always @(posedge clk) begin
	if (!rst_n) state <= S_IDLE;
	else        state <= next_state;
end

// 2. 下一状态逻辑 (Combinational)
always @(*) begin
	case (state)
		S_IDLE:    next_state = (udp_rx_busy && udp_r_dst_port == 16'd67) ? S_PAYLOAD : S_IDLE;//不需要判断op,因为FPGA作为服务端
		S_PAYLOAD: next_state = (udp_r_data_valid && cnt == 16'd239) ? S_OPTIONS : S_PAYLOAD;
		S_OPTIONS: next_state = (udp_r_data_valid && udp_r_data == 8'h35) ? S_DONE : 
								(~udp_rx_busy ? S_IDLE : S_OPTIONS);
		S_DONE:    next_state = S_IDLE;
		default:   next_state = S_IDLE;
	endcase
end

// 3. 输出逻辑与寄存器更新 (Sequential)
always @(posedge clk) begin
	case (state)
		S_IDLE: begin
			cnt <= 16'd0;
			out_valid <= 1'b0;
		end
		S_PAYLOAD: begin
			if (udp_r_data_valid) begin
				cnt <= cnt + 1'b1;
				// 提取 xid (Offset 4-7)
				if (cnt == 16'd4) out_xid[31:24] <= udp_r_data;
				if (cnt == 16'd5) out_xid[23:16] <= udp_r_data;
				if (cnt == 16'd6) out_xid[15:8]  <= udp_r_data;
				if (cnt == 16'd7) out_xid[7:0]   <= udp_r_data;
				// 提取 Client MAC (Offset 28-33)
				if (cnt >= 16'd28 && cnt <= 16'd33) out_chaddr <= {out_chaddr[39:0], udp_r_data};
			end
		end
		S_OPTIONS: begin
			if (udp_r_data_valid) begin
				// 记录 Option 53 之后的一个字节内容
				out_is_discover <= (udp_r_data == 8'h01);
				out_is_request  <= (udp_r_data == 8'h03);
			end
		end
		S_DONE: begin
			out_valid <= 1'b1; // 触发发送模块脉冲
		end
	endcase
end
endmodule

需要记录xid、电脑的MAC地址以及判断请求的类型即可。

offer 和 Acknowledge阶段的代码:
复制代码
`default_nettype none
module dhcp_server_reply(
    input   wire            clk,
    input   wire            rst_n,
    // UDP TX Interface
    output  reg             udp_tx_req,
    input   wire            udp_tx_busy,
    output  wire    [15:0]  udp_t_data_len,
    input   wire            udp_t_data_ready,
    output  reg             udp_t_data_valid,
    output  reg     [7:0]   udp_t_data,
    // Control Interface
    input   wire    [31:0]  in_xid,
    input   wire    [47:0]  in_chaddr,
    input   wire            in_is_discover,
    input   wire            in_is_request,
    input   wire            start
);

    localparam S_IDLE  = 4'h1;
    localparam S_START = 4'h2;
    localparam S_SEND  = 4'h4;
    localparam S_WAIT  = 4'h8;

    reg [3:0] state, next_state;
    reg [15:0] cnt;
    assign udp_t_data_len = 16'd300; // 固定包长度

    // 1. 状态寄存器
    always @(posedge clk) begin
        if (!rst_n) state <= S_IDLE;
        else        state <= next_state;
    end

    // 2. 下一状态逻辑
    always @(*) begin
        case (state)
            S_IDLE:  next_state = start ? S_START : S_IDLE;
            S_START: next_state = udp_tx_busy ? S_SEND : S_START;
            S_SEND:  next_state = (udp_t_data_ready && cnt == udp_t_data_len - 1) ? S_WAIT : S_SEND;
            S_WAIT:  next_state = ~udp_tx_busy ? S_IDLE : S_WAIT;
            default: next_state = S_IDLE;
        endcase
    end

    // 3. 输出逻辑
    always @(posedge clk) begin
        case (state)
            S_IDLE: begin
                udp_tx_req <= 1'b0;
                udp_t_data_valid <= 1'b0;
                cnt <= 16'd0;
            end
            S_START: begin
                udp_tx_req <= 1'b1;
            end
            S_SEND: begin
                udp_tx_req <= 1'b0;
                if (udp_t_data_ready) begin
                    udp_t_data_valid <= 1'b1;
                    cnt <= cnt + 1'b1;
                    
                    // DHCP 字节拼接逻辑
                    case (cnt)
                        16'd0:   udp_t_data <= 8'h02; // op: Reply
                        16'd1:   udp_t_data <= 8'h01; // htype: Ethernet
                        16'd2:   udp_t_data <= 8'h06; // hlen
                        // Transaction ID
                        16'd4:   udp_t_data <= in_xid[31:24];
                        16'd5:   udp_t_data <= in_xid[23:16];
                        16'd6:   udp_t_data <= in_xid[15:8];
                        16'd7:   udp_t_data <= in_xid[7:0];
                        // Your IP (yiaddr): 10.0.0.203
                        16'd16:  udp_t_data <= 8'd10;
                        16'd17:  udp_t_data <= 8'd0;
                        16'd18:  udp_t_data <= 8'd0;
                        16'd19:  udp_t_data <= 8'd203;
                        // Client MAC (chaddr)
                        16'd28:  udp_t_data <= in_chaddr[47:40];
                        16'd29:  udp_t_data <= in_chaddr[39:32];
                        16'd30:  udp_t_data <= in_chaddr[31:24];
                        16'd31:  udp_t_data <= in_chaddr[23:16];
                        16'd32:  udp_t_data <= in_chaddr[15:8];
                        16'd33:  udp_t_data <= in_chaddr[7:0];
                        // Magic Cookie
                        16'd236: udp_t_data <= 8'h63;
                        16'd237: udp_t_data <= 8'h82;
                        16'd238: udp_t_data <= 8'h53;
                        16'd239: udp_t_data <= 8'h63;
                        // Option 53: Offer(2) or ACK(5)
                        16'd240: udp_t_data <= 8'h35;
                        16'd241: udp_t_data <= 8'h01;
                        16'd242: udp_t_data <= in_is_discover ? 8'h02 : 8'h05;
                        // End Option
                        16'd243: udp_t_data <= 8'hFF;
                        default: udp_t_data <= 8'h00;
                    endcase
                end
            end
            S_WAIT: udp_t_data_valid <= 1'b0;
        endcase
    end
endmodule

提供和确认阶段需要向电脑提供应答op,硬件类型,硬件长度,xid(用的是电脑提供的),yiaddr(给电脑配置的IP地址),CHAddr(电脑的MAC地址),Magic Cookie(固定字节),Option 53: Offer(2) or ACK(5)。

其他默认为0即可。对应的解释如下:

在 FPGA DHCP Server 实现中,siaddr(下一个服务器 IP 地址)完全可以"省略" (即填充为 0.0.0.0),也可以设置为 FPGA 自己的 IP 地址。

对于直连电脑并分配 IP 的简单场景,这个字段通常是不需要的。

核心区别:siaddr vs Server Identifier (Option 54)

在 DHCP 协议中,这两个 IP 地址字段的功能完全不同,你只需要保证 Option 54 正确即可:

  • siaddr (Next Server IP Address) :位于 DHCP 固定首部。它仅在客户端需要进行网络启动(如 PXE 启动)并从另一台服务器(如 TFTP 服务器)获取启动文件时才使用。如果你的 FPGA 只是给电脑分个 IP,将其设为 32'h0000_0000 是最标准、最省事的做法。

  • Option 54 (Server Identifier) :位于 DHCP 选项区(Options)。这个字段必须填写 FPGA 自己的 IP 地址。它是电脑用来识别到底是哪台服务器给它发了 Offer 的关键标志。

在Verilog 代码中如何实现

参考你提供的代码片段:

  • yiaddr (Your IP Address) :你已经实现在了偏移 16'd1616'd19 的位置,这是分给电脑的 IP。

  • siaddr (Next Server IP Address) :它紧随其后,对应计数器 cnt16'd2016'd23 的位置。

  • 目前的实现 :由于你的代码中 default 分支将数据设为了 8'h00,这意味着 siaddr 已经被你默认"省略"为了 0.0.0.0。这对你目前的 VLF 信号探测项目来说是完全正确的。

建议配置表

如果你想让报文看起来更"专业"一点,可以参考下表进行填充:

字段 对应 cnt (Hex/Dec) 建议填充值 作用
yiaddr 16'd16 ~ 16'd19 10.0.0.203 给电脑用的地址
siaddr 16'd20 ~ 16'd23 0.0.0.0 可省略(填零)
Option 54 16'd243 附近(需计算) FPGA 自己的 IP 告诉电脑"我是谁"

温馨提示 :除非你打算让这台电脑通过网线从你的 FPGA 里"远程启动"一个操作系统(这对于 VLF 系统来说确实有点炫技了),否则没必要折腾 siaddr

代码调试

在 Windows、Linux 或 macOS 系统中,可以通过命令行或图形界面强制触发 DHCP 客户端重新发送请求。这在调试 FPGA 的 DHCP Server 逻辑时非常有用,因为你不需要反复拔插网线。

我使用的是 Windows 环境,可以使用以下命令:

  • 重新获取 IP(发送 DHCP Discover)ipconfig /renew 这是最关键的一步。执行该命令后,Windows 会立即发送 DHCP Discover 广播包。如果FPGA 逻辑正确且硬件通路已通,就能在 Wireshark 中看到响应过程。

调试成功现象:

依次执行四个交互过程,并获取里面的IP。

调试经验:

(1)状态机需要正确,idx,交互阶段的类型需要判断正确;

(2)UDP的源端口和目的端口是固定的需要注意。

调试过程的记录见资源"DHCP调试过程.doc"

IPv4 Link-Local(链路本地地址) ,通常被称为 Auto-IPAPIPA (Automatic Private IP Addressing)。在 Windows 系统中,当你看到网卡显示"未识别的网络"并分配了一个 169.254.x.x 的 IP 时,就是它在起作用。

对于 FPGA 开发者来说,这是实现"零协议成本"通信的最快路径。以下是该方案的深层运作机制和实现细节:

2.2.1. 核心原理:RFC 3927 标准

当一个网络设备(比如你的电脑)配置为"自动获取 IP",但物理链路接通后一段时间内没有收到任何 DHCP Offer 报文时,它会认为网络中没有 DHCP 服务器。为了不让通信完全中断,它会根据 RFC 3927 协议从 169.254.1.0 到 169.254.254.255 的地址池中随机挑选一个 IP。

  • 冲突检测 :电脑选定 IP 后会发出一个 ARP Probe(探测),如果没有收到回应,说明这个 IP 没人用,它就正式启用该 IP。

  • 子网掩码:固定为 255.255.0.0。

2.2.2. FPGA 端的实现步骤

在 FPGA 中,你不需要写任何复杂的自动协商逻辑,只需要"伪装"成一个已经在该网段内的静态设备。

第一步:固化 IP 和 MAC 地址

在你的 Verilog 代码(UDP/IP 协议栈模块)中,直接硬编码以下参数:

  • FPGA IP : 169.254.1.10 (建议选一个简单的,避免冲突)。

  • FPGA MAC : 00:0A:35:00:01:02 (自定义,确保唯一即可)。

  • Subnet Mask : 255.255.0.0

第二步:必须实现 ARP 响应(关键!)

这是很多同学实验失败的原因。即使你给 FPGA 设了 IP,电脑在发送 UDP 数据包之前,依然会发一个 ARP 请求问:"谁是 169.254.1.10?"

  • 逻辑实现 :当 FPGA 收到以太网帧类型为 0x0806 (ARP) 且目标 IP 匹配时,立即构造一个 ARP Reply 包,把 FPGA 的 MAC 地址告诉电脑。

  • 只有 ARP 通了,电脑的网卡驱动才会把上层 UDP 数据包通过物理层发出来。

第三步:简单的 UDP 监听/发送

实现一个最基础的 UDP 模块,监听特定端口(如 8080),并处理来自电脑的数据流。

3. 为什么大家都说它有"潜规则"?

虽然它实现起来最简单,但有几个工程上的坑需要你提前知道:

现象 原因 影响
启动慢 Windows 默认会花 30-60 秒 疯狂寻找 DHCP Server。 插上网线后,你需要盯着任务栏,直到那个"小圆圈"变成"黄色感叹号"或"未识别的网络",通信才真正开启。
IP 不固定 电脑端的 IP 是随机生成的(例如这次是 .12.34,下次是 .56.78)。 如果你的 FPGA 需要主动给电脑发数据,你不能在代码里写死电脑的 IP。
路由受限 该地址无法通过路由器进入外网。 仅限于点对点(直连)或交换机组网。

该方法的确会自动配置一个IP地址,但需要将FPGA的地址也设置为169.254.x.x才能通信。

拓展1:DHCP请求和确认阶段的必要性

这是一个非常敏锐的观察!在你的 FPGA 直连电脑(点对点)场景下,物理上确实只有一台"服务器",但 DHCP 协议作为一种通用标准,它的设计初衷是处理复杂的局域网环境

要理解为什么要"多此一举"地广播,我们可以从以下三个维度来看:

1. 协议的"盲目性" (Standardization)

你的电脑(作为 DHCP 客户端)在发送 Request 报文时,它的操作系统协议栈(Windows/Linux)并不知道网线的另一头是一个简单的 FPGA,还是一个连接着数十台服务器的交换机。

  • 统一流程:为了保证在任何网络环境下都能正常工作,电脑必须严格遵守 RFC 标准。标准规定 Request 必须是广播,这样可以确保:

    • 被选中的服务器:知道客户接受了它的 Offer。

    • 落选的服务器:知道客户拒绝了它的 Offer,从而立即释放掉那个被占用的临时 IP。

2. 为什么物理上"一根线"也可能有多个服务器?

虽然你现在是直连,但在实际工程应用中,这根线可能接入了:

  • 物理交换机:交换机上可能挂载了多个开启了 DHCP 服务的设备。

  • 虚拟网桥/虚拟机:如果你的电脑开启了 VMware 或 Hyper-V,电脑内部可能会运行多个虚拟 DHCP 服务,它们都会通过这根网线争抢分配 IP。

  • 多径冗余:在工业或深空探测的地面站网络中,为了可靠性,通常会有主、备两台 DHCP 服务器同时运行。

3. 在 Request 报文中,电脑是如何"挑人"的?

既然是广播,大家都能收到。那么 FPGA 怎么知道电脑是选了自己,还是选了别人呢?

这取决于 DHCP Request 报文内部的一个关键字段:Option 54 (Server Identifier)

  • 如果电脑接受了 FPGA 的 Offer,它会在 Option 54 填入 FPGA 的 IP 地址

  • FPGA 的逻辑处理

    1. 收到广播的 Request。

    2. 检查 Option 54。

    3. 如果是自己的 IP发送 Ack(确认)。

    4. 如果不是自己的 IP 放弃,回到 IDLE(说明电脑选了别人)。


对你 FPGA 实现的启示

在纯 Verilog 实现时,这个机制其实简化了你的设计,而不是增加了难度:

  1. 无需关心目标 MAC :由于电脑发出的 Request 是广播包(目的 MAC 为 FF:FF:FF:FF:FF:FF),你的 FPGA 逻辑层不需要识别 MAC 地址就能直接抓到这个包进行解析。

  2. "懒人"实现技巧 :在你的点对点实验中,既然确定只有 FPGA 一个服务器,你可以甚至不去校验 Option 54。只要收到 Request 包,就默认电脑选的是你,直接回发 Ack 即可。

  3. 冲突规避:如果未来你的设备要接入复杂的实验室网络,记得在状态机里加上对 Option 54 的校验,防止 FPGA 错误地截获了电脑给实验室路由器的请求。


总结

广播 Request 就像是在一个喧闹的房间里大喊:"A 先生,我用你给我的那个号码了!" 这样 A 先生(FPGA)可以确认,而原本也想给你号码的 B 先生、C 先生听到后就会默默地把号码收回去给别人用。

电脑在自动配置了 169.254.x.x 地址后,仍然会持续发送 DHCP 请求

从Wireshark 抓包图中可以清楚地观察到这一行为:

1. 抓包证据分析

  • 现状确认 :你的电脑已经由于 DHCP 超时而启用了 Auto-IP (APIPA) 机制,给自己分配了 169.254.8.89 这个地址(见 下图 中的 ipconfig 结果)。
  • 持续请求 :在抓包记录的第 27 行,你可以看到一条 DHCP Discover 报文,其源 IP 依然为 0.0.0.0。这证明了即使电脑有了临时 IP,它仍然在不断"寻找"真正的 DHCP 服务器。

2. 为什么电脑会"坚持不懈"?

  • 临时性与局限性169.254.x.x 只是一个链路本地地址,它只能在直连的小范围内通信,无法访问外网或跨网段。

  • 后台重试机制 :操作系统的网络服务会保持 DHCP 客户端后台运行。它会定期发送广播(通常是每隔几秒或几十秒),一旦你的 FPGA 响应了请求并成功分配了 IP(如你代码中设置的 10.0.0.203),电脑会立刻丢弃 169.254.x.x 地址并切换到正式 IP。

拓展三:UDP的端口选取

在普通应用开发中,UDP端口号选择可以有很大的自由度;但在实现标准协议(如 DHCP)时,端口是绝对不能随意取的。

端口号的范围是。根据国际标准(IANA),它们被划分为三个"阶级",了解这些规则对你调试 FPGA 协议栈至关重要。

1. 端口的三大分类

范围 类别 说明 常见例子
知名端口 (Well-known) 预留给标准服务,不可随意占用。 DHCP (67/68)、DNS (53)、HTTP (80)
注册端口 (Registered) 用于用户自定义的应用或特定软件。 你之前工程用的 8001
动态端口 (Dynamic) 操作系统随机分配给客户端临时使用的。 浏览器访问网页时分配的随机端口

2. 为什么你的 DHCP 必须用 67 和 68?

这就好比收音机:如果广播电台在 90.0MHz 发射信号,你的收音机必须调到 90.0MHz 才能听到。

  • 服务端(FPGA)必须监听 67 端口 :电脑发出的 Discover 报文是广播包,它会敲响网络中所有设备 67 号端口的大门。如果你的 FPGA 没在这个端口"守着",包就丢了。

  • 客户端(电脑)必须监听 68 端口 :电脑在发包时告诉网络,"我在 68 号端口等回信"。因此你的 FPGA 发送 Offer 时,目的端口必须填 68

遇到的问题:

在你之前的抓包 中,端口变成了 0 。在网络协议中,端口 0 是一个保留值,通常代表"无效"或"未指定"。电脑的防火墙和协议栈一看到端口是 0,会认为这是一个非法数据包直接过滤掉。


3. FPGA 开发中的自由度

虽然标准协议很死板,但在后续回传 VLF 探测数据 时,你是完全自由的:

  • 自定义协议:你可以像之前的工程一样,定义源端口为 8001,目的端口为电脑上的上位机监听端口(比如 8002)。

  • 避坑指南 :虽然可以选 之间的任何数字,但尽量避开 3389 (远程桌面)、443 (HTTPS) 等常用端口,防止和电脑上的其他软件"撞车"。

💡 调试总结

对于 DHCP Server 调试,请死死记住这一组配对:

  • FPGA 发包时: 源端口 = 67 ,目的端口 = 68

  • FPGA 收包时: 只处理目的端口为 67 的 UDP 包。

只要这一组数字填对了,Wireshark 就会立刻把你的包从绿色的"UDP"识别成蓝色的"DHCP"。

以上就是本次笔记的内容。

相关推荐
迎风打盹儿2 小时前
FPGA实现AGC自动增益控制:原理详解与Verilog实战
fpga·vivado·verilog hdl·agc·数字自动增益控制
pe7er11 天前
macOS 应用无法打开(权限问题)解决方案
macos·mac
上海云盾王帅14 天前
从底层守护:深度解析四层协议(TCP/UDP)的DDoS防护之道
tcp/ip·udp·ddos
科技块儿14 天前
开发者需要为网站或应用集成IP归属地显示功能,如何选择可靠的数据源?
服务器·网络·数据库·tcp/ip·edge·ip
budingxiaomoli15 天前
TCP协议和UDP协议
网络·网络协议·udp
czxyvX15 天前
018-Linux-Socket编程-UDP
linux·udp
百锦再16 天前
Java的TCP和UDP实现详解
java·spring boot·tcp/ip·struts·spring cloud·udp·kafka
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧17 天前
JSP, MVC, El, JSTL, MAC
java·开发语言·mvc·mac·jsp
独泪了无痕17 天前
Mac Homebrew 安装 MySQL 指南
数据库·mysql·mac