tencent cloud

Feedback

Transmitting Client Real IP via SPP Protocol

Last updated: 2024-07-30 16:22:12

    Application scenario

    The SPP (Simple Proxy Protocol Header, hereinafter referred to as SPP) protocol is a custom header format used by proxy servers to transmit real client IP and other related information to backend servers. It is used for logging, access control, CLB, fault troubleshooting, and other scenarios. The SPP header has a fixed length of 38 bytes, making it simpler compared to the Proxy Protocol V2.
    If your current backend business service is a UDP service and either already supports the SPP protocol or you prefer a simpler parsing method, you can use the SPP protocol to transmit the client real IP. EdgeOne's layer 4 proxy supports transmitting the real client IP to the business server based on the SPP protocol standard. You can parse this protocol at the server end to obtain the real client IP and port.

    EdgeOne SPP Protocol Handling Process

    Request Access

    
    As shown in the above diagram, when you use the SPP protocol to transmit the Client IP and Port, the EdgeOne Layer 4 proxy will automatically append the client’s real IP and Port in a fixed 38-byte length, following the SPP header format, before each payload. You need to parse the SPP header field on the origin server to obtain the client’s real IP and Port.

    Origin server response

    
    As illustrated above, when the origin server responds, it must include the SPP header and return it to the EO Layer 4 proxy. The EO Layer 4 proxy will automatically uninstall the SPP header.
    Note:
    If the origin server does not return the SPP header, it will result in the EO Layer 4 proxy truncating the business data in the payload.

    Directions

    Step 1: Configure Layer 4 Proxy Forwarding Rules

    1. Log in into the EdgeOne console, click on Site List in the left sidebar. Subsequently, within the Site List, select the Site you wish to configure.
    2. On the site detail page, click L4 Proxy.
    3. On the L4 Proxy page, select the L4 proxy instance you want to modify, and click Configure.
    4. Select the Layer 4 proxy rule that requires passing the real client IP and click Edit.
    5. Enter the corresponding business origin server address, origin server port, choose UDP for the forwarding protocol, select Simple Proxy Protocol for passing Client IP, and click Save.
    

    Step 2: Parse the SPP field on the origin server to obtain the real client IP

    You can refer to SPP Protocol Header Format and Sample Code to parse the SPP field on the origin server. When using the SPP protocol to transmit the real Client IP, the service packet data format obtained by the server is as follows:
    
    
    You can
    refer to the following sample code to parse the business data and obtain the real Client IP.
    Go
    C
    package main
    
    import (
    "encoding/binary"
    "fmt"
    "net"
    )
    
    type NetworkConnection struct {
    Magic uint16
    ClientAddr net.IP
    ProxyAddr net.IP
    ClientPort uint16
    ProxyPort uint16
    }
    
    func handleConn(conn *net.UDPConn) {
    buf := make([]byte, 1024) // Create a buffer
    n, addr, err := conn.ReadFromUDP(buf) // Read the packet from the connection
    
    if err != nil {
    fmt.Println("Error reading from UDP connection:", err)
    return
    }
    
    // Convert the received bytes to a NetworkConnection struct
    nc := NetworkConnection{
    Magic: binary.BigEndian.Uint16(buf[0:2]),
    ClientAddr: make(net.IP, net.IPv6len),
    ProxyAddr: make(net.IP, net.IPv6len),
    }
    if nc.Magic == 0x56EC {
    copy(nc.ClientAddr, buf[2:18])
    copy(nc.ProxyAddr, buf[18:34])
    nc.ClientPort = binary.BigEndian.Uint16(buf[34:36])
    nc.ProxyPort = binary.BigEndian.Uint16(buf[36:38])
    
    // Print SPP header information, including magic, client's real IP and port, proxy IP and port
    fmt.Printf("Received packet:\\n")
    fmt.Printf("\\tmagic: %x\\n", nc.Magic)
    fmt.Printf("\\tclient address: %s\\n", nc.ClientAddr.String())
    fmt.Printf("\\tproxy address: %s\\n", nc.ProxyAddr.String())
    fmt.Printf("\\tclient port: %d\\n", nc.ClientPort)
    fmt.Printf("\\tproxy port: %d\\n", nc.ProxyPort)
    // Print the real and effective payload
    fmt.Printf("\\tdata: %v\\n\\tcount: %v\\n", string(buf[38:n]), n)
    } else {
    // Print the real and effective payload
    fmt.Printf("\\tdata: %v\\n\\tcount: %v\\n", string(buf[0:n]), n)
    }
    
    // Response package, note: The SPP 38-byte length must be returned unchanged
    response := make([]byte, n)
    copy(response, buf[0:n])
    _, err = conn.WriteToUDP(response, addr) // Send data
    if err != nil {
    fmt.Println("Write to udp failed, err: ", err)
    }
    }
    
    func main() {
    localAddr, _ := net.ResolveUDPAddr("udp", ":6666") // Create a UDP address using the local address and port
    conn, err := net.ListenUDP("udp", localAddr) // Create a listener
    if err != nil {
    panic("Failed to listen for UDP connections:" + err.Error())
    }
    
    defer conn.Close() // Close the connection when done
    for {
    handleConn(conn) // Handle the incoming connection
    }
    }
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    #define BUF_SIZE 1024
    struct NetworkConnection {
    uint16_t magic;
    struct in6_addr clientAddr;
    struct in6_addr proxyAddr;
    uint16_t clientPort;
    uint16_t proxyPort;
    };
    void handleConn(int sockfd) {
    struct sockaddr_in clientAddr;
    socklen_t addrLen = sizeof(clientAddr);
    unsigned char buf[BUF_SIZE];
    ssize_t n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *)&clientAddr, &addrLen);
    if (n < 0) {
    perror("Error reading from UDP connection");
    return;
    }
    // Convert the received bytes to a NetworkConnection struct
    struct NetworkConnection nc;
    nc.magic = ntohs(*(uint16_t *)buf);
    if (nc.magic == 0x56EC) { // Magic value 0x56EC indicates an SPP header
    memcpy(&nc.clientAddr, buf + 2, 16);
    memcpy(&nc.proxyAddr, buf + 18, 16);
    nc.clientPort = ntohs(*(uint16_t *)(buf + 34));
    nc.proxyPort = ntohs(*(uint16_t *)(buf + 36));
    printf("Received packet:\\n");
    printf("\\tmagic: %x\\n", nc.magic);
    char clientIp[INET6_ADDRSTRLEN];
    char proxyIp[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &nc.clientAddr, clientIp, INET6_ADDRSTRLEN);
    inet_ntop(AF_INET6, &nc.proxyAddr, proxyIp, INET6_ADDRSTRLEN);
    // Print SPP header information, including magic, client's real IP and port, proxy IP and port
    printf("\\tclient address: %s\\n", clientIp);
    printf("\\tproxy address: %s\\n", proxyIp);
    printf("\\tclient port: %d\\n", nc.clientPort);
    printf("\\tproxy port: %d\\n", nc.proxyPort);
    // Print the actual payload
    printf("\\tdata: %.*s\\n\\tcount: %zd\\n", (int)(n - 38), buf + 38, n);
    } else {
    printf("\\tdata: %.*s\\n\\tcount: %zd\\n", (int)n, buf, n);
    }
    // Send response packet, note: the SPP 38-byte length must be returned as-is
    sendto(sockfd, buf, n, 0, (struct sockaddr *)&clientAddr, addrLen);
    }
    int main() {
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
    perror("Failed to create socket");
    exit(EXIT_FAILURE);
    }
    // Create a UDP address using the local address and port
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(6666);
    if (bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
    perror("Failed to bind");
    exit(EXIT_FAILURE);
    }
    while (1) {
    handleConn(sockfd);
    }
    }

    Step 3: Testing and Validation

    You can use a server as the client, construct client requests, and use the nc command to simulate UDP requests. The details of the command are as follows:
    echo "Hello Server" | nc -w 1 -u <IP/DOMAIN> <PORT>
    Here, IP/Domain refers to the IP or domain of your fourth-layer proxy instance, which you can view in the EdgeOne console. Port refers to the forward port configured for the rule in Step 1.
    
    The server receives the request and parses the Client IP address as follows:
    

    Related References

    SPP Protocol Header Format

    

    Magic Number

    In the SPP Protocol format, Magic Number is 16 bits with a fixed value of 0x56EC, mainly used to identify the SPP Protocol. It also defines that the SPP protocol header is a fixed length of 38 bytes.

    Client Address

    The client's request IP address is 128 bits long. If initiated by an IPV4 client, the value represents IPV4; if initiated by an IPV6 client, the value represents IPV6.

    Proxy Address

    The proxy server's IP address is 128 bits long and can be parsed in the same way as the Client Address.

    Client Port

    The port for the client to send UDP packets is 16 bits long.

    Proxy Port

    The port for the proxy server to receive UDP packets is 16 bits long.

    payload

    Payload, the data following the header in the data packet.
    
    
    Contact Us

    Contact our sales team or business advisors to help your business.

    Technical Support

    Open a ticket if you're looking for further assistance. Our Ticket is 7x24 avaliable.

    7x24 Phone Support