tencent cloud

Tencent Cloud EdgeOne

SPPプロトコルによるクライアント実IPの伝達

ダウンロード
フォーカスモード
フォントサイズ
最終更新日: 2026-10-15 16:51:02
AI翻訳・品質チェック済み

使用シナリオ

SPP(Simple Proxy Protocol Header、以下SPP)プロトコルは、プロキシサーバーが実際のクライアントIPおよびその他の関連情報をバックエンドサーバーに渡すために使用されるカスタムプロトコルヘッダーフォーマットであり、ログ記録、アクセス制御、CLB、またはトラブルシューティングなどのシナリオに使用されます。SPPプロトコルヘッダーの固定長は38バイトで、Proxy Protocol V2プロトコルよりもシンプルです。
現在ご利用のバックエンド業務サービスがUDPサービスであり、すでにSPPプロトコルに対応している場合、またはよりシンプルな解析方式の利用を希望する場合は、SPPプロトコルを使用してクライアントの実際のIPを渡すことができます。EdgeOneのL4プロキシは、SPPプロトコル標準に基づいて実際のクライアントIPを業務サーバーに渡すことに対応しており、サーバー側でこのプロトコルを独自に解析して実際のクライアントIPとPortを取得できます。
説明:
L4プロキシはエンタープライズ版プランでのみ利用可能です。

EdgeOneによるSPPプロトコル処理フロー

アクセスリクエスト


上図に示すように、SPPプロトコルを使用してクライアントIPとPortを渡す場合、EdgeOneのL4プロキシはクライアントの実際のIPとPortを固定38バイト長で、SPPプロトコルヘッダーフォーマットに従って各ペイロードの前に自動的に追加します。オリジンサーバーでSPPヘッダーフィールドを解析して初めて、クライアントの実際のIPとPortを取得できます。

オリジンサーバーレスポンス


上図に示すように、オリジンサーバーがパケットを返す際には、SPPプロトコルヘッダーを付与してEO L4プロキシに返す必要があります。EO L4プロキシはSPPプロトコルヘッダーを自動的に除去します。
注意:
オリジンサーバーがSPPプロトコルヘッダーを返さない場合、EO L4プロキシがペイロードの業務データを切り詰める原因となります。

操作手順

ステップ1:レイヤー4プロキシ転送ルールの設定

1. EdgeOneコンソールにログインし、左側のメニューバーで「サービス概要」に入り、「Webサイト安全な加速」内で設定が必要なサイトをクリックします。
2. サイト詳細ページで、L4プロキシをクリックします。
3. L4プロキシページで、変更するL4プロキシインスタンスを選択し、設定をクリックします。
4. クライアントの実際のIPを渡す必要があるL4プロキシルールを選択し、編集をクリックします。
5. 対応する業務オリジンサーバーアドレス、オリジンサーバーポートを入力し、転送プロトコルでUDPを選択し、クライアントIPの受け渡しでSimple Proxy Protocolを選択し、保存をクリックします。


ステップ2:オリジンサーバーでSPPフィールドを解析してクライアントの実IPを取得する

SPPプロトコルヘッダーフォーマットとサンプルコードを参照して、オリジンサーバー上でSPPフィールドを解析できます。SPPプロトコルを使用して実際のクライアントIPを転送する場合、サーバー側が取得する業務パケットデータのフォーマットは以下の通りです:

以下のサンプルコードを参照して
、業務データを解析し、実際のクライアント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) // バッファを作成します
n, addr, err := conn.ReadFromUDP(buf) // 接続からデータパケットを読み取ります

if err != nil {
fmt.Println("Error reading from UDP connection:", err)
return
}

// 受信したバイトをNetworkConnection構造体に変換します
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])

// sppヘッダー情報を出力します。これにはmagic、クライアントの実際のipとポート、プロキシのipとポートが含まれます
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)
// 実際の有効なペイロードを出力します
fmt.Printf("\\tdata: %v\\n\\tcount: %v\\n", string(buf[38:n]), n)
} else {
// 実際の有効なペイロードを出力します
fmt.Printf("\\tdata: %v\\n\\tcount: %v\\n", string(buf[0:n]), n)
}

// パケットを返します。注意:SPP 38バイト長をそのまま返す必要があります
response := make([]byte, n)
copy(response, buf[0:n])
_, err = conn.WriteToUDP(response, addr) // データを送信します
if err != nil {
fmt.Println("Write to udp failed, err: ", err)
}
}

func main() {
localAddr, _ := net.ResolveUDPAddr("udp", ":6666") // ローカルアドレスとポートを使用してUDPアドレスを作成します
conn, err := net.ListenUDP("udp", localAddr) // リスナーを作成します
if err != nil {
panic("Failed to listen for UDP connections:" + err.Error())
}

defer conn.Close() // 終了時に接続を閉じます
for {
handleConn(conn) // 受信した接続を処理します
}
}

#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;
}
// 受信したバイトをNetworkConnection構造体に変換します
struct NetworkConnection nc;
nc.magic = ntohs(*(uint16_t *)buf);
if (nc.magic == 0x56EC) { // Magicが0x56ECの場合はSPPヘッダーを表します
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);
// sppヘッダー情報を出力します。これにはmagic、クライアントの実際のipとポート、プロキシのipとポートが含まれます
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);
// 実際の有効なペイロードを出力します
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);
}
// パケットを返します。注意:SPP 38バイト長をそのまま返す必要があります
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);
}
// ローカルアドレスとポートを使用してUDPアドレスを作成します
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);
}
}

ステップ3:テスト検証

サーバーを1台用意してクライアントとして使用し、クライアントリクエストを構築し、ncコマンドでUDPリクエストをシミュレートできます。コマンドの詳細は以下の通りです:
echo "Hello Server" | nc -w 1 -u <IP/DOMAIN> <PORT>
そのうち、IP/Domainはお客様のL4プロキシインスタンスのアクセスIPまたはドメインであり、EdgeOneコンソール内で対応するL4プロキシインスタンス情報を確認できます。Portはお客様がステップ1でこのルールに設定した転送ポートです。

サーバーはリクエストを受信し、クライアントIPアドレスを以下のように解析します:


関連参考

SPPプロトコルヘッダー形式



Magic Number

SPPプロトコル形式では、Magic Numberは16ビットであり、固定値は0x56ECです。主にSPPプロトコルの識別に使用され、SPPプロトコルヘッダーが固定38バイト長であることを定義しています。

Client Address

クライアントがリクエストを開始するIPアドレスで、長さは128ビットです。IPV4クライアントが開始する場合、この値はIPV4を示します。IPV6クライアントが開始する場合、この値はIPV6を示します。

Proxy Address

プロキシサーバーのIPアドレスで、長さは128ビットです。Client Addressと同じ解析方式を使用できます。

Client Port

クライアントがUDPデータパケットを送信するポートで、長さは16ビットです。

Proxy Port

プロキシサーバーがUDPデータパケットを受信するポートで、長さは16ビットです。

payload

ペイロードは、データパケットが運ぶヘッダーに続くデータです。


ヘルプとサポート

この記事はお役に立ちましたか?

フィードバック