PPPoE protocol discovery phase interaction and FPGA implementation process

Keywords: Verilog

Discovery phase interaction process

PPPoE (point to point protocol over Ethernet) is a point-to-point protocol based on Ethernet, including discovery phase and session phase. The discovery phase, PPPoE Discovery, aims to obtain the client MAC address and establish a connection. The discovery stage includes PADI, PADO, PADR and PADS. After the discovery phase, enter the standard PPP session phase. The interaction process in the discovery phase is shown in the following figure:

Figure 1 interaction process in PPPoE discovery phase

The data frame format is shown in the following figure:

Figure 2 PPPoE data frame format

The frame type domain discovery stage is 0x8863, and the session stage is 0x8864; The version and type are 0x01 in both the discovery phase and the session phase, and different types correspond to different phases of the code field; The default session ID is 0x0000. A unique session ID will be specified when the server sends PADS to the client; Length refers to the length of static load in bytes B.

PADI

PADI (PPPoE active discovery initiative) is a broadcast frame used to find an available server and sent by the client. The PADI group must contain at least one service type tag (Service Name Tag, field value 0x0101) to propose the required services to the server. When the client does not receive PADO within a certain period of time, the client will resend PADI and double the waiting time.
Destination address: 0xFFFF_FFFF
Frame type field: 0x8863
Code field: 0x09
Session ID: 0x0000
The PADI received by Wireshark is shown in the following figure:

Figure 3 PADI data frame received by Wireshark

Bytes without circles are static loads. When the PADI frame is received, the client MAC address, frame type, code ID and load frame length can be extracted according to the data frame format

//============================================
// Extract the client MAC address, frame type, code ID and load frame length
//============================================
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
//============================================

Loads should be stored in FIFO, because PADO loads are generated by PADI's host uniq tag and AC_ Name (server name)

//============================================
//                fifo reading and writing
//============================================
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@(*)//The header is sent and the fifo count < = load frame length, write
begin     //The conditions for writing data must be strict, because the network will regularly send useless frames to maintain the link. The load of useless frames cannot be stored in fifo, otherwise the load of useless frames will be read out and added to PADO or PADS when reading next time
        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): when the server receives PADI frame, the server sends PADO frame response request. The PADO frame must also contain at least one server name type label (the field value is 0x0102), indicating the type of service that can be provided to the host. PADO loads are generated by PADI's host uniq tag and AC_ Name (server name).
Destination address: extracted client MAC address
Frame type field: 0x8863
Code field: 0x07
Session ID: 0x0000
Wireshark receives the PADO sent by the server, as shown in the following figure:

Figure 4 PADO data frame received by Wireshark

The 4-byte data in the last circle is neither PADI's host uniq tag nor AC_name is the server name type label of PADO, and 0x000d represents the number of name bytes. When the frame length of PADI or PADR is extracted, the frame header of PADO and PADS can be obtained

//============================================
//              Calculate the frame header in advance
//============================================
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 frame length + 16 'h11 is due to AC_name and server name type labels.

PADR

PADR (PPPoE active discovery request), the client selects one or more pados received, and then sends PADR to the selected server. PADR must also contain a service name type label.
Destination address: server MAC address
Frame type field: 0x8863
Code field: 0x19
Session ID: 0x0000
Wireshark receives the PADO sent by the server, as shown in the following figure:

Figure 5 PADR data frame received by Wireshark

The receiving process of PADR is the same as that of PADI, which extracts the client MAC address, frame type, code ID and load frame length, and stores the load in FIFO.

PADS

PADS (PPPoE active discovery session confirmation). After receiving PADR, the server sets up PADS frames. The host uniq tag values of PADS and PADR are the same.
Destination address: client MAC address
Frame type field: 0x8863
Code field: 0x65
Session ID: the value that uniquely identifies the session ID, which cannot be 0x0000
Wireshark receives the PADO sent by the server, as shown in the following figure:

Fig. 6 PADS data frame received by Wireshark

The data behind the host uniq tag does not need to be sent, but is automatically added during transmission. To send PADO and PADS, a counter is required to count to determine when to send frame header, load and AC_name

//============================================
//          PADO, PADS, FIFO count
//============================================
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 is empty, output AC_name
        else if ((n_state == PADO) && (cnt_PADO == 5'd6)) cnt_PADO <= 5'd6;//Read fifo
        else if((n_state == PADO) && (tx_eop == 1'b1)) cnt_PADO <= 5'b0;//After sending, reset
        else if (n_state == PADO) cnt_PADO <= cnt_PADO + 1'b1;//Transmit frame header
        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;//Read fifo
        else if((n_state == PADS) && (tx_eop == 1'b1)) cnt_PADS <= 5'b0;//Reset after sending
        else if (n_state == PADS) cnt_PADS <= cnt_PADS + 1'b1;//Transmit frame header
        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;//After sending the frame, send fifo
    else cnt_fifo <= 4'b0;
end
//============================================

Determine which data to send according to the count value

//============================================
//              PADO, PADS send
//============================================
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;//Transmit load
                5'd7: tx_data <= ac_name[159:128];//Send 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;//Transmit load
                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
//============================================

This is the end of the discovery stage. I do this entirely for learning. There is no other use. The tool itself is harmless!

Posted by Alex-B on Thu, 14 Oct 2021 12:30:56 -0700