发现阶段交互过程
PPPoE ( Point to Point Protocol over Ethernet ),基于以太网的点对点协议,包括发现阶段和会话阶段。发现阶段,即 PPPoE Discovery,目的是获取客户端MAC地址并建立连接。发现阶段包括PADI、PADO、PADR、PADS四个阶段。发现阶段结束后,就进入标准PPP会话阶段。发现阶段交互过程如下图所示:

数据帧格式如下图所示:

帧类型域发现阶段都是0x8863,会话阶段都是0x8864;版本和类型在发现阶段和会话阶段都是0x01,代码域不同阶段对应着不同的类型号;会话ID默认为0x0000,在服务器向客户端发送PADS中会指定一个唯一标识会话ID;长度指的是静载荷的长度,单位是字节B。
PADI
PADI ( PPPoE Active Discovery Initiation ),PADI是广播帧,用于寻找可用的服务器,由客户端发送。PADI分组必须包含至少一个服务类型标签 (Service Name Tag,字段值为0x0101),向服务器提出所要求的提供的服务。当客户端在一定时间内没有收到PADO,客户端会重新发送PADI,并加倍等待时间。
目的地址:0xFFFF_FFFF
帧类型域:0x8863
代码域:0x09
会话ID:0x0000
Wireshark接收到的PADI如下图所示:

没有画圈的字节就是静载荷。当接收到PADI帧,就可以根据数据帧格式提取出客户端MAC地址、帧类型、代码ID、载荷帧长
//============================================
// 提取客户端MAC地址、帧类型、代码ID、载荷帧长
//============================================
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) begin
cilent_addr <= 48'b0;
end
else begin
if (cnt == 4'd2 && n_state == PADI) begin
cilent_addr[47:32] <= rx_data_ff[15:0];
end
else if (cnt == 4'd3 && n_state == PADI) begin
cilent_addr[31:0] <= rx_data_ff;
end
else begin
cilent_addr <= cilent_addr;
end
end
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) begin
fram_type <= 16'b0;
code_id <= 8'b0;
rx_fram_len <= 16'b0;
end
else begin
if (cnt == 4'd4) begin
fram_type <= rx_data_ff[31:16];
code_id <= rx_data_ff[7:0];
end
else if (cnt == 4'd5)
rx_fram_len <= rx_data_ff[15:0];
else begin
fram_type <= fram_type;
code_id <= code_id;
rx_fram_len <= rx_fram_len;
end
end
end
//============================================
载荷要用FIFO存起来,因为PADO载荷是由PADI的Host-Uniq Tag和AC_name(服务器名字)组成的
//============================================
// fifo读、写
//============================================
always@(*)
begin
if((cnt_PADO == 5'd3) || (cnt_PADS == 5'd3)) fifo_ren <= 1'b1;
else if(fifo_empty == 1'b1) fifo_ren <= 1'b0;
else fifo_ren <= fifo_ren;
end
always@(*)//头部发送完毕而且fifo计数<=载荷帧长,写
begin //写数据条件一定要严格,因为网络会定时发送无用帧来保持链接,不能把无用帧的载荷存到fifo里面,否则下次读的时候会把无用帧的载荷也读出来加到PADO或者PADS中
if (cnt > 4'd5 && ((c_state == PADI && code_id == `PADI_CODE_ID) || (c_state == PADR && code_id == `PADR_CODE_ID)) && rx_dval_ff == 1'b1 && (fram_type == 16'h8863) && ((cnt_fifo << 2) <= rx_fram_len )) fifo_wren = 1'b1;
else fifo_wren = 1'b0;
end
//============================================
sync_fifo #(
.WIDTH (32),
.ADDR (6)
) U_sync_fifo(
.clk (clk ),
.rst_n (rst_n ),
.din (rx_data_ff ),
.wr_en (fifo_wren ),
.full (fifo_full ),
.dout (fifo_dout ),
.rd_en (fifo_ren ),
.empty (fifo_empty)
);
PADO
PADO ( PPPoE Active Discovery Offer),当服务器接收到PADI帧,服务器就发送PADO帧响应请求。PADO帧也必须包含至少一个服务器名称类型标签(字段值为0x0102),表明可向主机提供的服务种类。PADO载荷是由PADI的Host-Uniq Tag和AC_name(服务器名字)组成。
目的地址:提取的客户端MAC地址
帧类型域:0x8863
代码域:0x07
会话ID:0x0000
Wireshark接收到服务器发送的的PADO如下图所示:

最后圈中的4字节数据不是PADI的Host-Uniq Tag,也不是AC_name,是PADO的服务器名称类型标签,0x000d表示名称字节数量。当提取出PADI或者PADR的帧长时,就可得出PADO和PADS的帧头部
//============================================
// 事先算好帧头
//============================================
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0) begin
fram_head_PADO <= 160'b0;
fram_head_PADS <= 160'b0;
ac_name <= 150'b0;
end
else begin
fram_head_PADO <= {cilent_addr, `SERVER_ADDRE, `FRAM_TYPE_FIND, `EDITION_TYPE, `PADO_CODE_ID, `SESSION_ID, {rx_fram_len + 16'h11}};
fram_head_PADS <= {cilent_addr, `SERVER_ADDRE, `FRAM_TYPE_FIND, `EDITION_TYPE, `PADS_CODE_ID, 16'habcd, rx_fram_len};
ac_name <= `AC_NAME;
end
end
//============================================
PADO帧长+16‘h11是因为还有AC_name和服务器名称类型标签。
PADR
PADR ( PPPoE Active Discovery Request ),客户端在收到的一个或多个PADO中选择一个,然后向选择的服务器发送PADR。PADR也必须包含一个服务名称类型标签。
目的地址:服务器MAC地址
帧类型域:0x8863
代码域:0x19
会话ID:0x0000
Wireshark接收到服务器发送的的PADO如下图所示:

PADR的接收流程与PADI的接收流程一样,提取客户端MAC地址、帧类型、代码ID、载荷帧长,并将载荷存在FIFO中。
PADS
PADS ( PPPoE Active Discovery Session-confirmation ),服务器收到PADR后,组建PADS帧。PADS和PADR的Host-Uniq Tag值相同。
目的地址:客户端MAC地址
帧类型域:0x8863
代码域:0x65
会话ID:唯一标识会话ID的值,不能是0x0000
Wireshark接收到服务器发送的的PADO如下图所示:

Host-Uniq Tag后面的数据并不需要发,是传输时自动加上的。发送PADO和PADS需要计数器进行计数,以此来判别什么时候发帧头,什么时候发载荷,什么时候发AC_name
//============================================
// PADO、PADS、FIFO计数
//============================================
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
cnt_PADO <= 5'b0;
else begin
if ((n_state == PADO) && (cnt_PADO == 5'd6) && (fifo_empty_ff == 1'b1)) cnt_PADO <= cnt_PADO + 1'b1;//fifo空了,输出AC_name
else if ((n_state == PADO) && (cnt_PADO == 5'd6)) cnt_PADO <= 5'd6;//读fifo
else if((n_state == PADO) && (tx_eop == 1'b1)) cnt_PADO <= 5'b0;//发送结束,清零
else if (n_state == PADO) cnt_PADO <= cnt_PADO + 1'b1;//发送帧头
else cnt_PADO <= 5'b0;
end
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
cnt_PADS <= 5'b0;
else begin
if ((n_state == PADS) && (cnt_PADS == 5'd6)) cnt_PADS <= 5'd6;//读fifo
else if((n_state == PADS) && (tx_eop == 1'b1)) cnt_PADS <= 5'b0;//发送结束清零
else if (n_state == PADS) cnt_PADS <= cnt_PADS + 1'b1;//发送帧头
else cnt_PADS <= 5'b0;
end
end
always@(posedge clk or negedge rst_n)
begin
if (rst_n == 1'b0) cnt_fifo <= 4'b0;
else if (cnt >= 4'd5) cnt_fifo <= cnt_fifo + 1'b1;//帧头发送完毕,发送fifo
else cnt_fifo <= 4'b0;
end
//============================================
根据计数值判断发送哪些数据
//============================================
// PADO、PADS发送
//============================================
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) begin
tx_data <= 32'b1;
end
else begin
if (c_state == PADO) begin
case (cnt_PADO)
5'b1: tx_data <= fram_head_PADO[159:128];
5'd2: tx_data <= fram_head_PADO[127:96];
5'd3: tx_data <= fram_head_PADO[95:64];
5'd4: tx_data <= fram_head_PADO[63:32];
5'd5: tx_data <= fram_head_PADO[31:0];
5'd6: tx_data <= fifo_dout_ff;//发送载荷
5'd7: tx_data <= ac_name[159:128];//发送AC_NAME
5'd8: tx_data <= ac_name[127:96];
5'd9: tx_data <= ac_name[95:64];
5'd10: tx_data <= ac_name[63:32];
5'd11: tx_data <= ac_name[31:0];
default: tx_data <= 32'h2;
endcase
end
else if (c_state == PADS) begin
case (cnt_PADS)
5'b1: tx_data <= fram_head_PADS[159:128];
5'd2: tx_data <= fram_head_PADS[127:96];
5'd3: tx_data <= fram_head_PADS[95:64];
5'd4: tx_data <= fram_head_PADS[63:32];
5'd5: tx_data <= fram_head_PADS[31:0];
5'd6: tx_data <= fifo_dout_ff;//发送载荷
default: tx_data <= 32'h3;
endcase
end
else tx_data <= tx_data;
end
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0) begin
tx_mod <= 2'b0 ;
tx_dval <= 1'b0 ;
tx_sop <= 1'b0 ;
tx_eop <= 1'b0 ;
end
else begin
if (c_state == PADO)
case (cnt_PADO)
5'b1: {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b1, 1'b1, 1'b0};
5'd2: {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b1, 1'b0, 1'b0};
5'd11:{tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b01, 1'b1, 1'b0, 1'b1};
5'd12:{tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b0, 1'b0, 1'b0};
default: {tx_mod, tx_dval, tx_sop, tx_eop} <= {tx_mod, tx_dval, tx_sop, tx_eop};
endcase
else if (c_state == PADS) begin
if(cnt_PADS == 5'b1) {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b1, 1'b1, 1'b0};
else if (cnt_PADS == 5'd2) {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b1, 1'b0, 1'b0};
else if (fifo_ren_n == 1'b1) {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b1, 1'b0, 1'b1};
else if (fifo_ren_nn == 1'b1) {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b0, 1'b0, 1'b0};
else {tx_mod, tx_dval, tx_sop, tx_eop} <= {tx_mod, tx_dval, tx_sop, tx_eop};
end
else {tx_mod, tx_dval, tx_sop, tx_eop} <= {2'b0, 1'b0, 1'b0, 1'b0};
end
end
//============================================
发现阶段到此结束,本人做这个完全是为了学习,没有其他用途,工具本身是无害的!