
/bin/bash -c "$(curl -fsSL https://edgeone-document-file-1258344699.cos.ap-guangzhou.myqcloud.com/TOA/install_toa.sh)"

# tarパッケージを解凍しますtar -zxvf CentOS-7.2-x86_64.tar.gz# 解凍後のパッケージディレクトリに入ります。cd CentOS-7.2-x86_64# toaモジュールをロードします。insmod toa.ko# カーネルモジュールディレクトリにコピーします。cp toa.ko /lib/modules/`uname -r`/kernel/net/netfilter/ipvs/toa.ko# システム起動時にtoaモジュールを自動的にロードするように設定します。echo "insmod /lib/modules/`uname -r`/kernel/net/netfilter/ipvs/toa.ko" >> /etc/rc.local
lsmod | grep toa

yum install -y gccyum install -y makeyum install -y kernel-headers kernel-devel
apt-get install -y gccapt-get install -y makeapt-get install -y linux-headers-$(uname -r)
/bin/bash -c "$(curl -fsSL https://edgeone-document-file-1258344699.cos.ap-guangzhou.myqcloud.com/TOA/compile_install_toa.sh)"
# コンパイルディレクトリを作成して移動します。mkdir toa_compile && cd toa_compile# ソースコードのtarパッケージをダウンロードします。curl -o toa.tar.gz https://edgeone-document-file-1258344699.cos.ap-guangzhou.myqcloud.com/TOA/toa.tar.gz# tarパッケージを解凍しますtar -zxvf toa.tar.gz# toa.koファイルをコンパイルします。コンパイルが成功すると、現在のディレクトリにtoa.koファイルが生成されます。make# toaモジュールをロードします。insmod toa.ko# カーネルモジュールディレクトリにコピーします。cp toa.ko /lib/modules/`uname -r`/kernel/net/netfilter/ipvs/toa.ko# システム起動時にtoaモジュールを自動的にロードするように設定します。echo "insmod /lib/modules/`uname -r`/kernel/net/netfilter/ipvs/toa.ko" >> /etc/rc.local
lsmod | grep toa

# Python 2に基づくpython2 -m SimpleHTTPServer 10000# Python 3に基づくpython3 -m http.server 10000
# curlを使用してhttpリクエストを送信します。ここで、ドメイン名は四層プロキシドメイン名であり、10000は四層プロキシ転送ポートです。curl -i "http://a8b7f59fc8d7e6c9.example.com.edgeonedy1.com:10000/"

struct sockaddr_in)を採用してサービスを構築し、リッスンするのはIPv4形式のアドレスです。struct sockaddr_in6)を採用してサービスを構築し、リッスンするのはIPv6形式のアドレスです。#include <sys/socket.h>#include <stdio.h>#include <unistd.h>#include <netinet/in.h>#include <memory.h>#include <arpa/inet.h>int main(int argc, char **argv){int l_sockfd;// サーバーアドレスはv4構造を採用します。struct sockaddr_in serveraddr;// 業務変更点:クライアントアドレスはv6構造を採用する必要があります。struct sockaddr_in6 clientAddr;int server_port = 10000;memset(&serveraddr, 0, sizeof(serveraddr));// ソケットを作成します。l_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (l_sockfd == -1){printf("Failed to create socket.\\n");return -1;}// サーバーアドレス情報を初期化します。memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(server_port);serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);int isReuse = 1;setsockopt(l_sockfd, SOL_SOCKET,SO_REUSEADDR,(const char*)&isReuse,sizeof(isReuse));// ソケットとサーバーアドレス情報を関連付けます。int nRet = bind(l_sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr));if(-1 == nRet){printf("bind error\\n");return -1;}// ソケットをリッスンします。listen(l_sockfd, 5);int clientAddrLen = sizeof(clientAddr);memset(&clientAddr, 0, sizeof(clientAddr));// クライアントからの接続を受け付けます。int linkFd = accept(l_sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);if(-1 == linkFd){printf("accept error\\n");return -1;}// 業務変更点:クライアントのsin6_familyのタイプに基づいて、クライアントがv4アドレスかv6アドレスかを判断します。// AF_INETの場合、クライアントはIPv4であることを示し、クライアントアドレスのポインタをstruct sockaddr_in*に変換して取得します。// AF_INET6の場合、クライアントはIPv6であることを示し、struct sockaddr_in6*を使用して取得します。if (clientAddr.sin6_family == AF_INET) {printf("AF_INET accept getpeername %s : %d successful\\n",inet_ntoa(((struct sockaddr_in*)&clientAddr)->sin_addr),ntohs(((struct sockaddr_in*)&clientAddr)->sin_port));}else if (clientAddr.sin6_family == AF_INET6){char addr_p[128] = {0};inet_ntop(AF_INET6, (void *)&((struct sockaddr_in6*)&clientAddr)->sin6_addr, addr_p, (socklen_t )sizeof(addr_p));printf("AF_INET6 accept getpeername %s : %d successful\\n",addr_p,ntohs(((struct sockaddr_in6*)&clientAddr)->sin6_port));}else{printf("unknow sin_family:%d \\n", clientAddr.sin6_family);}close(l_sockfd);return 0;}
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketAddress;public class ServerDemo {/** IPv4のアドレス構造を採用してサービスを構築する場合、IPV4_HOSTを使用します。 */public static final String IPV4_HOST = "0.0.0.0";/** IPv6のアドレス構造を採用してサービスを構築する場合、IPV6_HOSTを使用します。 */public static final String IPV6_HOST = "::";public static void main(String[] args) {int serverPort = 10000;try (ServerSocket serverSocket = new ServerSocket()) {// アドレス再利用を設定します。serverSocket.setReuseAddress(true);// サーバーアドレスとポートをバインドします。ここではIPv4を使用します。serverSocket.bind(new InetSocketAddress(InetAddress.getByName(IPV4_HOST), serverPort));System.out.println("Server is listening on port " + serverPort);while (true) {// クライアント接続を受け付けます。Socket clientSocket = serverSocket.accept();System.out.println("New client connected: " + clientSocket.getRemoteSocketAddress());// クライアントリクエストを処理します。handleClientRequest(clientSocket);}} catch (IOException e) {System.err.println("Failed to create server socket: " + e.getMessage());}}/*** 処理関数。具体的な業務に応じて実装します。ここでは例として示します。* この関数の役割は、clientの入力をそのままclientに返すことです。*/private static void handleClientRequest(Socket clientSocket) {try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// クライアントから送信されたデータを読み取ります。byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {// 受信したデータをそのままクライアントに返信します。outputStream.write(buffer, 0, bytesRead);}} catch (IOException e) {// クライアントが接続を切断した後System.err.println("Failed to handle client request: " + e.getMessage());} finally {try {clientSocket.close();} catch (IOException e) {System.err.println("Failed to close client socket: " + e.getMessage());}}}}
#include <sys/socket.h>#include <stdio.h>#include <unistd.h>#include <netinet/in.h>#include <memory.h>#include <arpa/inet.h>int main(int argc, char **argv){int l_sockfd;// サーバーアドレスはv6構造を採用します。struct sockaddr_in6 serveraddr;// クライアントアドレスはv6構造を採用します。struct sockaddr_in6 clientAddr;int server_port = 10000;memset(&serveraddr, 0, sizeof(serveraddr));// ソケットを作成します。l_sockfd = socket(AF_INET6, SOCK_STREAM, 0);if (l_sockfd == -1){printf("Failed to create socket.\\n");return -1;}// サーバーアドレス情報を設定します。memset(&serveraddr, 0, sizeof(struct sockaddr_in6));serveraddr.sin6_family = AF_INET6;serveraddr.sin6_port = htons(server_port);serveraddr.sin6_addr = in6addr_any;int isReuse = 1;setsockopt(l_sockfd, SOL_SOCKET,SO_REUSEADDR,(const char*)&isReuse,sizeof(isReuse));// ソケットとサーバーアドレス情報を関連付けます。int nRet = bind(l_sockfd,(struct sockaddr*)&serveraddr, sizeof(serveraddr));if(-1 == nRet){printf("bind error\\n");return -1;}// ソケットをリッスンします。listen(l_sockfd, 5);int clientAddrLen = sizeof(clientAddr);memset(&clientAddr, 0, sizeof(clientAddr));// クライアントからの接続要求を受け付けます。int linkFd = accept(l_sockfd, (struct sockaddr*)&clientAddr, &clientAddrLen);if(-1 == linkFd){printf("accept error\\n");return -1;}// ここで受信したクライアントアドレス情報は、すべてv6構造で保存されます。// なお、クライアントのIPv4アドレスもIPv6アドレスにマッピングされています。例:::ffff:119.29.1.1。char addr_p[128] = {0};inet_ntop(AF_INET6, (void *)&clientAddr.sin6_addr, addr_p, (socklen_t )sizeof(addr_p));printf("accept %s : %d successful\\n", addr_p, ntohs(clientAddr.sin6_port));// 業務変更点:システムマクロ定義IN6_IS_ADDR_V4MAPPEDを使用して、IPv6アドレスがIPv4のマッピングアドレスであるかどうかを判断します(クライアントがIPv4であることを示します)。if(IN6_IS_ADDR_V4MAPPED(&clientAddr.sin6_addr)) {struct sockaddr_in real_v4_sin;memset (&real_v4_sin, 0, sizeof (struct sockaddr_in));real_v4_sin.sin_family = AF_INET;real_v4_sin.sin_port = clientAddr.sin6_port;// 最後の4バイトを読み取ると、それがクライアントの実際のIPv4アドレスになります。memcpy (&real_v4_sin.sin_addr, ((char *)&clientAddr.sin6_addr) + 12, 4);printf("connect %s successful\\n", inet_ntoa(real_v4_sin.sin_addr));}close(l_sockfd);return 0;}
import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.net.InetAddress;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketAddress;public class ServerDemo {/** IPv4のアドレス構造を採用してサービスを構築する場合、IPV4_HOSTを使用します。 */public static final String IPV4_HOST = "0.0.0.0";/** IPv6のアドレス構造を採用してサービスを構築する場合、IPV6_HOSTを使用します。 */public static final String IPV6_HOST = "::";public static void main(String[] args) {int serverPort = 10000;try (ServerSocket serverSocket = new ServerSocket()) {}serverSocket.setReuseAddress(true);// サーバーアドレスとポートをバインドします。ここではIPv4を使用します。serverSocket.bind(new InetSocketAddress(InetAddress.getByName(IPV6_HOST), serverPort));System.out.println("Server is listening on port " + serverPort);while (true) {// クライアント接続を受け付けます。Socket clientSocket = serverSocket.accept();System.out.println("New client connected: " + clientSocket.getRemoteSocketAddress());// クライアントリクエストを処理します。handleClientRequest(clientSocket);}} catch (IOException e) {System.err.println("Failed to create server socket: " + e.getMessage());}}/*** 処理関数。具体的な業務に応じて実装します。ここでは例として示します。* この関数の役割は、clientの入力をそのままclientに返すことです。*/private static void handleClientRequest(Socket clientSocket) {try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {// クライアントから送信されたデータを読み取ります。byte[] buffer = new byte[1024];int bytesRead;while ((bytesRead = inputStream.read(buffer)) != -1) {// 受信したデータをそのままクライアントに返信します。outputStream.write(buffer, 0, bytesRead);}} catch (IOException e) {// クライアントが接続を切断した後System.err.println("Failed to handle client request: " + e.getMessage());} finally {try {clientSocket.close();} catch (IOException e) {System.err.println("Failed to close client socket: " + e.getMessage());}}}}
Server is listening on port 10000New client connected: /127.0.0.1:50680New client connected: /0:0:0:0:0:0:0:1:51124New client connected: /127.0.0.1:51136
cat /proc/net/toa_stats

指標名 | 説明 |
syn_recv_sock_toa | TOA情報を含む接続を受信した数。 |
syn_recv_sock_no_toa | TOA情報を含まない接続を受信した数。 |
getname_toa_ok | getsockoptを呼び出してソースIPの取得に成功するとこのカウントが増加します。また、accept関数を呼び出してクライアントリクエストを受信した場合にもこのカウントが増加します。 |
getname_toa_mismatch | getsockoptを呼び出してソースIPを取得する際、タイプが一致しない場合にこのカウントが増加します。例えば、あるクライアント接続に保存されているソースIPがIPv4であり、IPv6アドレスではない場合にこのカウントが増加します。 |
getname_toa_empty | TOAを含まないクライアントファイルディスクリプタに対してgetsockopt関数を呼び出すと、このカウントが増加します。 |
ip6_address_alloc | TOAカーネルモジュールがTCPデータパケットに保存された送信元IP、送信元ポートを取得する際、情報を保存するための領域を確保します。 |
ip6_address_free | 接続が解放されると、toaカーネルモジュールは以前に送信元IP、送信元ポートを保存するために使用したメモリを解放します。すべての接続が閉じられている場合、全CPUのこのカウントの合計はip6_address_allocのカウントと等しくなるはずです。 |
フィードバック