【Node.js】socket.remoteAddressだけじゃない!IPアドレス取得のベストプラクティス
socket.remoteAddress
は、Node.jsの net.Socket
オブジェクトのプロパティの一つで、接続してきたリモートクライアントのIPアドレス を文字列として保持しています。
より具体的に言うと、TCP接続やUnixドメインソケット接続が確立された際に、その接続の相手側(クライアント側)のネットワーク層におけるアドレスを示します。
例えば、ウェブブラウザがあなたのNode.jsサーバーにHTTPリクエストを送ってきた場合、そのブラウザが動作しているマシンのIPアドレスが socket.remoteAddress
に格納されます。
このプロパティを利用することで、サーバー側はどのIPアドレスから接続があったのかを知ることができます。これは、以下のような用途で役立ちます。
- デバッグ
ネットワークの問題を調査する際に、接続元のIPアドレスは手がかりとなります。 - 地理的な情報の推定
取得したIPアドレスから、クライアントのだいたいの地理的な位置を推定することができます(ただし、これは追加のAPIやデータベースを利用する必要があります)。 - アクセス制御
特定のIPアドレスからのアクセスを許可したり、拒否したりする処理を実装できます。 - アクセスログの記録
どのクライアントがいつアクセスしてきたのかを記録する際に、IPアドレスは重要な情報となります。
- Unixドメインソケットの場合、
socket.remoteAddress
は通常、未定義(undefined
)または空の文字列となることがあります。 - リバースプロキシ(例:Nginx、ロードバランサー)を経由している場合、
socket.remoteAddress
にはクライアントの直接のIPアドレスではなく、プロキシサーバーのIPアドレスが表示されることがあります。クライアントの実際のIPアドレスを取得したい場合は、通常、HTTPヘッダー(X-Forwarded-For
など)を確認する必要があります。 socket.remoteAddress
は文字列型のIPアドレス(例えば"192.168.1.10"
や"2001:0db8:85a3:0000:0000:8a2e:0370:7334"
のようなIPv6アドレスの場合もあります)を返します。
undefined または空文字列になる場合
- 接続が確立されていないタイミングでのアクセス
ソケット接続が完全に確立される前にsocket.remoteAddress
にアクセスしようとすると、値がまだ設定されていない可能性があります。- トラブルシューティング
connect
イベントや、ソケットが使用可能な状態になったことを示すイベント(例えば、HTTPサーバーのrequest
イベント内など)の中でsocket.remoteAddress
にアクセスするようにしてください。
- トラブルシューティング
- Unixドメインソケット
TCP/IPソケットではなく、Unixドメインソケットを使用している場合、socket.remoteAddress
はundefined
または空の文字列になることが一般的です。Unixドメインソケットはファイルシステム内のパスを介して通信するため、IPアドレスの概念がありません。- トラブルシューティング
使用しているソケットの種類を確認してください。TCP/IP接続であれば、この問題は通常発生しません。
- トラブルシューティング
リバースプロキシを経由した場合のIPアドレス
- 問題
Nginxやロードバランサーなどのリバースプロキシを経由してNode.jsサーバーに接続した場合、socket.remoteAddress
にはクライアントの実際のIPアドレスではなく、プロキシサーバーのIPアドレスが表示されます。- 理由
プロキシサーバーがクライアントからのリクエストを受け付け、それをNode.jsサーバーに転送するため、Node.jsサーバーから見ると接続元はプロキシサーバーになるためです。 - トラブルシューティング
- X-Forwarded-For ヘッダーの確認
多くのリバースプロキシは、クライアントの元のIPアドレスをX-Forwarded-For
というHTTPヘッダーに付加します。このヘッダーの値を確認することで、クライアントの実際のIPアドレスを取得できる場合があります(複数のプロキシを経由している場合は、カンマ区切りで複数のIPアドレスが含まれることがあります)。 - X-Real-IP ヘッダーの確認
一部のプロキシはX-Real-IP
というヘッダーを使用することもあります。 - プロキシの設定
プロキシサーバーの設定によっては、クライアントの情報を他のヘッダーで送信している場合もあります。プロキシの設定ドキュメントを確認してください。 - Node.jsミドルウェアの利用
proxy-addr
などのNode.jsミドルウェアを利用すると、X-Forwarded-For
ヘッダーなどを解析して、信頼できるプロキシを経由した場合のクライアントIPアドレスを正しく取得するのに役立ちます。
- X-Forwarded-For ヘッダーの確認
- 理由
IPv6アドレスの形式
- 問題
クライアントがIPv6アドレスを使用している場合、socket.remoteAddress
はコロン (:
) を含むIPv6形式の文字列になります。IPv4アドレスを期待しているコードでは、この形式の違いによってエラーが発生する可能性があります。- トラブルシューティング
- IPアドレスの形式を考慮した処理
IPアドレスの形式(IPv4またはIPv6)をチェックし、それに応じて処理を分岐させるようにコードを修正します。 - IPv6アドレスの正規化
必要であれば、IPv6アドレスを特定の形式に正規化する処理を追加します。
- IPアドレスの形式を考慮した処理
- トラブルシューティング
セキュリティ上の考慮事項
- 問題
X-Forwarded-For
ヘッダーはクライアントによって改ざんされる可能性があるため、このヘッダーの値をそのまま信頼してアクセス制御などに使用するのは危険です。- トラブルシューティング
- 信頼できるプロキシの特定
どのプロキシサーバーからのX-Forwarded-For
ヘッダーを信頼するかを設定する必要があります。例えば、自社で管理しているプロキシサーバーのIPアドレスを信頼リストに追加するなどします。 - proxy-addr ミドルウェアの利用
proxy-addr
は、信頼できるプロキシのリストを設定することで、改ざんされたX-Forwarded-For
ヘッダーを適切に処理する機能を提供します。
- 信頼できるプロキシの特定
- トラブルシューティング
- 問題
ファイアウォールやネットワークの設定によっては、クライアントのIPアドレスが正しくNode.jsサーバーに伝わらない場合があります。- トラブルシューティング
- ネットワーク構成の確認
ネットワーク管理者と協力して、トラフィックが正しくルーティングされているか、ファイアウォールで必要なポートが開いているかなどを確認します。
- ネットワーク構成の確認
- トラブルシューティング
例1: シンプルなTCPサーバーで接続元のIPアドレスを表示する
この例では、シンプルなTCPサーバーを作成し、クライアントが接続してきた際にそのIPアドレスをコンソールに出力します。
const net = require('net');
const server = net.createServer((socket) => {
const remoteAddress = socket.remoteAddress;
console.log(`クライアント ${remoteAddress} からの接続がありました。`);
socket.on('end', () => {
console.log(`クライアント ${remoteAddress} との接続が閉じられました。`);
});
socket.write('サーバーからのメッセージです。\r\n');
socket.pipe(socket); // エコーバック (受信したデータをそのまま返す)
});
const port = 3000;
server.listen(port, () => {
console.log(`TCPサーバーがポート ${port} で起動しました。`);
});
このコードを実行し、別のターミナルから telnet localhost 3000
などで接続すると、サーバーのコンソールに接続元のIPアドレス(通常は ::1
(IPv6 localhost) または 127.0.0.1
(IPv4 localhost))が表示されます。
例2: HTTPサーバーでリクエスト元のIPアドレスを表示する
この例では、HTTPサーバーを作成し、リクエストを受け取った際に socket.remoteAddress
をログに出力します。
const http = require('http');
const server = http.createServer((req, res) => {
const remoteAddress = req.socket.remoteAddress;
console.log(`リクエストがありました。送信元IP: ${remoteAddress}`);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!\n');
});
const port = 8080;
server.listen(port, () => {
console.log(`HTTPサーバーがポート ${port} で起動しました。`);
});
ウェブブラウザから http://localhost:8080
にアクセスすると、サーバーのコンソールにアクセス元のIPアドレスが表示されます。
例3: リバースプロキシ環境下で X-Forwarded-For
ヘッダーからクライアントIPアドレスを取得する
この例では、HTTPサーバーでリクエストヘッダーの X-Forwarded-For
を確認し、クライアントの実際のIPアドレスを取得しようとします。
const http = require('http');
const server = http.createServer((req, res) => {
let clientIP = req.socket.remoteAddress;
const forwardedFor = req.headers['x-forwarded-for'];
if (forwardedFor) {
// X-Forwarded-For ヘッダーには複数のIPアドレスが含まれる場合がある (経由したプロキシのIPアドレス)
// 通常は一番左のIPアドレスが元のクライアントIP
const ips = forwardedFor.split(',');
clientIP = ips[0].trim();
}
console.log(`リクエストがありました。クライアントIP (推定): ${clientIP}, socket.remoteAddress: ${req.socket.remoteAddress}`);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`Your IP address (estimated): ${clientIP}\n`);
});
const port = 8080;
server.listen(port, () => {
console.log(`HTTPサーバーがポート ${port} で起動しました。`);
});
このコードをNginxなどのリバースプロキシの背後に配置し、ブラウザからアクセスすると、コンソールには socket.remoteAddress
としてプロキシサーバーのIPアドレスが表示され、クライアントの実際のIPアドレスは X-Forwarded-For
ヘッダーから取得されたものが表示されます。
例4: proxy-addr
ミドルウェアを使って信頼できるプロキシ経由のIPアドレスを取得する (Express.js)
Express.jsを使用している場合は、proxy-addr
ミドルウェアを利用して、より安全かつ簡単にクライアントのIPアドレスを取得できます。
まず、proxy-addr
をインストールします。
npm install proxy-addr
そして、以下のようにコードを記述します。
const express = require('express');
const proxyaddr = require('proxy-addr');
const app = express();
const trustedProxies = ['loopback', '10.0.1.111']; // 信頼できるプロキシのIPアドレスや範囲
app.use((req, res, next) => {
const clientIP = proxyaddr(req, trustedProxies);
console.log(`リクエストがありました。クライアントIP (proxy-addr): ${clientIP}, socket.remoteAddress: ${req.socket.remoteAddress}, X-Forwarded-For: ${req.headers['x-forwarded-for']}`);
req.clientIP = clientIP; // リクエストオブジェクトにクライアントIPを保存 (必要に応じて)
next();
});
app.get('/', (req, res) => {
res.send(`Your IP address (via proxy-addr): ${req.clientIP}\n`);
});
const port = 3000;
app.listen(port, () => {
console.log(`Expressサーバーがポート ${port} で起動しました。`);
});
この例では、trustedProxies
配列に信頼できるプロキシサーバーのIPアドレスや特別なキーワード('loopback'
など)を指定します。proxyaddr(req, trustedProxies)
関数は、X-Forwarded-For
ヘッダーなどを解析し、信頼できるプロキシを経由した場合のクライアントIPアドレスを返します。
HTTPヘッダー (X-Forwarded-For, X-Real-IP など) の利用
- 実装例 (HTTPサーバー)
- 欠点
- ヘッダーが存在しない場合や、プロキシが設定されていない場合は利用できません。
- クライアントや悪意のあるユーザーによってヘッダーの値が偽装される可能性があります。信頼できるプロキシからの値であることを確認する必要があります。
- 利点
クライアントが直接Node.jsサーバーに接続していない場合でも、元のIPアドレスを取得できる可能性があります。
const http = require('http');
const server = http.createServer((req, res) => {
const forwardedFor = req.headers['x-forwarded-for'];
const realIP = req.headers['x-real-ip'];
const remoteAddress = req.socket.remoteAddress;
let clientIP = remoteAddress;
if (forwardedFor) {
const ips = forwardedFor.split(',').map(ip => ip.trim());
clientIP = ips[0]; // 通常は一番左が元のクライアントIP
} else if (realIP) {
clientIP = realIP;
}
console.log(`リクエストがありました。remoteAddress: ${remoteAddress}, X-Forwarded-For: ${forwardedFor}, X-Real-IP: ${realIP}, 推定クライアントIP: ${clientIP}`);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`Your IP address (estimated): ${clientIP}\n`);
});
const port = 8080;
server.listen(port, () => {
console.log(`HTTPサーバーがポート ${port} で起動しました。`);
});
専用のNode.jsミドルウェアの利用 (proxy-addr など)
- 欠点
外部ライブラリへの依存が増えます。 - 利点
- ヘッダーの解析を自動化し、複数のプロキシを経由した場合も正しく処理できます。
- 信頼できるプロキシを設定することで、IPアドレスの偽装リスクを軽減できます。
WebSocketの upgrade イベントにおける req.socket.remoteAddress の利用
- 実装例 (ws モジュールを使用)
- 欠点
WebSocket接続が確立した後、ソケットオブジェクト自体からもsocket.remoteAddress
は利用可能です。 - 利点
WebSocket接続確立時のIPアドレスを把握できます。
const WebSocket = require('ws');
const http = require('http');
const server = http.createServer();
const wss = new WebSocket.Server({ server });
wss.on('connection', ws => {
const remoteAddress = ws._socket.remoteAddress; // WebSocketのソケットからアクセス
console.log(`WebSocketクライアント ${remoteAddress} が接続しました。`);
ws.on('message', message => {
console.log(`受信: ${message}`);
ws.send(`サーバーからの応答: ${message}`);
});
ws.on('close', () => {
console.log(`WebSocketクライアント ${remoteAddress} が切断しました。`);
});
});
server.on('upgrade', (req, socket, head) => {
const remoteAddressFromReq = req.socket.remoteAddress;
console.log(`WebSocketアップグレードリクエストがありました。送信元IP (req.socket): ${remoteAddressFromReq}`);
wss.handleUpgrade(req, socket, head, ws => {
wss.emit('connection', ws, req);
});
});
const port = 8080;
server.listen(port, () => {
console.log(`WebSocketサーバーがポート ${port} で起動しました。`);
});
UDPソケットの場合 (dgram モジュール)
- 実装例
- 欠点
TCPとは異なるAPIを使用します。 - 利点
UDP通信における送信元IPアドレスを把握できます。
const dgram = require('dgram');
const server = dgram.createSocket('udp4'); // IPv4を使用
server.on('message', (msg, rinfo) => {
console.log(`サーバーは ${rinfo.address}:${rinfo.port} からメッセージ "${msg}" を受信しました。`);
});
server.on('listening', () => {
const address = server.address();
console.log(`UDPサーバーは ${address.address}:${address.port} でリッスンしています。`);
});
server.bind(41234);
この例では、message
イベントリスナーのコールバック関数に渡される rinfo
オブジェクトの address
プロパティが送信元のIPアドレスを示します。