mirror of
https://github.com/bluepuma77/traefik-best-practice.git
synced 2025-12-21 21:33:24 +01:00
added docker-traefik-proxyprotocol
This commit is contained in:
23
docker-traefik-proxyprotocol/Dockerfile
Normal file
23
docker-traefik-proxyprotocol/Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Specify the base image
|
||||||
|
FROM node:lts-alpine
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
# Copy the rest of application's source code
|
||||||
|
COPY src/ ./src
|
||||||
|
|
||||||
|
# Copy local certificates
|
||||||
|
COPY *.pem .
|
||||||
|
|
||||||
|
# Expose the port the app runs on
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Command to run your app
|
||||||
|
CMD ["node", "src/index.js"]
|
||||||
87
docker-traefik-proxyprotocol/docker-compose.yml
Normal file
87
docker-traefik-proxyprotocol/docker-compose.yml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
services:
|
||||||
|
traefik:
|
||||||
|
container_name: traefik
|
||||||
|
image: traefik:v3.3
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
- 443:443
|
||||||
|
- 8000:8000
|
||||||
|
- 8001:8001
|
||||||
|
- 8002:8002
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ~/certificates:/certificates
|
||||||
|
- /var/log:/var/log
|
||||||
|
command:
|
||||||
|
- --api.dashboard=true
|
||||||
|
- --api.insecure=true
|
||||||
|
- --log.level=DEBUG
|
||||||
|
#- --log.filepath=/var/log/traefik.log
|
||||||
|
- --accesslog=true
|
||||||
|
#- --accesslog.filepath=/var/log/traefik-access.log
|
||||||
|
- --providers.docker.network=proxy
|
||||||
|
- --providers.docker.exposedByDefault=false
|
||||||
|
- --entrypoints.web.address=:80
|
||||||
|
- --entrypoints.web.http.redirections.entrypoint.to=websecure
|
||||||
|
- --entryPoints.web.http.redirections.entrypoint.scheme=https
|
||||||
|
- --entrypoints.websecure.address=:443
|
||||||
|
- --entrypoints.websecure.asDefault=true
|
||||||
|
- --entrypoints.websecure.http.tls.certresolver=myresolver
|
||||||
|
- --entrypoints.plaintcp0.address=:8000
|
||||||
|
- --entrypoints.plaintcp1.address=:8001
|
||||||
|
- --entrypoints.plaintcp2.address=:8002
|
||||||
|
- --certificatesresolvers.myresolver.acme.email=mail@example.com
|
||||||
|
- --certificatesresolvers.myresolver.acme.tlschallenge=true
|
||||||
|
- --certificatesresolvers.myresolver.acme.storage=/certificates/acme.json
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.mydashboard.rule=Host(`traefik.example.com`)
|
||||||
|
- traefik.http.routers.mydashboard.service=api@internal
|
||||||
|
- traefik.http.routers.mydashboard.middlewares=myauth
|
||||||
|
- traefik.http.middlewares.myauth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/
|
||||||
|
|
||||||
|
whoami:
|
||||||
|
container_name: whoami
|
||||||
|
image: traefik/whoami:v1.10
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.mywhoami.rule=Host(`whoami.example.com`)
|
||||||
|
- traefik.http.services.mywhoami.loadbalancer.server.port=80
|
||||||
|
|
||||||
|
echo:
|
||||||
|
container_name: echo
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
labels:
|
||||||
|
- traefik.enable=true
|
||||||
|
- traefik.http.routers.myecho.rule=Host(`echo.example.com`)
|
||||||
|
- traefik.http.routers.myecho.service=myecho
|
||||||
|
- traefik.http.services.myecho.loadbalancer.server.port=3000
|
||||||
|
|
||||||
|
- traefik.tcp.routers.myecho0.entrypoints=plaintcp0
|
||||||
|
- traefik.tcp.routers.myecho0.rule=HostSNI(`*`)
|
||||||
|
- traefik.tcp.routers.myecho0.service=myecho0
|
||||||
|
- traefik.tcp.services.myecho0.loadbalancer.server.port=3000
|
||||||
|
|
||||||
|
- traefik.tcp.routers.myecho1.entrypoints=plaintcp1
|
||||||
|
- traefik.tcp.routers.myecho1.rule=HostSNI(`*`)
|
||||||
|
- traefik.tcp.routers.myecho1.service=myecho1
|
||||||
|
- traefik.tcp.services.myecho1.loadbalancer.server.port=3000
|
||||||
|
- traefik.tcp.services.myecho1.loadbalancer.proxyprotocol.version=1
|
||||||
|
|
||||||
|
- traefik.tcp.routers.myecho2.entrypoints=plaintcp2
|
||||||
|
- traefik.tcp.routers.myecho2.rule=HostSNI(`*`)
|
||||||
|
- traefik.tcp.routers.myecho2.service=myecho2
|
||||||
|
- traefik.tcp.services.myecho2.loadbalancer.server.port=3000
|
||||||
|
- traefik.tcp.services.myecho2.loadbalancer.proxyprotocol.version=2
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
name: proxy
|
||||||
13
docker-traefik-proxyprotocol/package-lock.json
generated
Normal file
13
docker-traefik-proxyprotocol/package-lock.json
generated
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "node-express-proxyprotocol-tls",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "node-express-proxyprotocol-tls",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "BSD"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
docker-traefik-proxyprotocol/package.json
Normal file
8
docker-traefik-proxyprotocol/package.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "node-express-proxyprotocol-tls",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "BSD",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
}
|
||||||
|
}
|
||||||
0
docker-traefik-proxyprotocol/src/README.md
Normal file
0
docker-traefik-proxyprotocol/src/README.md
Normal file
211
docker-traefik-proxyprotocol/src/index.js
Normal file
211
docker-traefik-proxyprotocol/src/index.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// index.js (ESM)
|
||||||
|
import fs from 'fs';
|
||||||
|
import net from 'net';
|
||||||
|
import tls from 'tls';
|
||||||
|
import { parseProxyProtocol } from './proxyprotocol.js';
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Configuration:
|
||||||
|
const port = Number(process.env.PORT) || 3000;
|
||||||
|
|
||||||
|
// If you have certs for TLS, put them in ./certs/key.pem & ./certs/cert.pem.
|
||||||
|
// If they don't exist, TLS connections will fail to handshake.
|
||||||
|
const keyPath = './private-key.pem';
|
||||||
|
const certPath = './certificate.pem';
|
||||||
|
|
||||||
|
const haveTLSCerts = fs.existsSync(keyPath) && fs.existsSync(certPath);
|
||||||
|
let secureContext = null;
|
||||||
|
if (haveTLSCerts) {
|
||||||
|
secureContext = tls.createSecureContext({
|
||||||
|
key: fs.readFileSync(keyPath),
|
||||||
|
cert: fs.readFileSync(certPath),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Utility to parse an HTTP request from a buffer if it contains \r\n\r\n
|
||||||
|
function parseHttpRequest(buffer) {
|
||||||
|
const raw = buffer.toString('utf8');
|
||||||
|
const headersEnd = raw.indexOf('\r\n\r\n');
|
||||||
|
if (headersEnd === -1) {
|
||||||
|
// No complete HTTP header yet
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Separate the header part from any potential body
|
||||||
|
const headerPart = raw.slice(0, headersEnd);
|
||||||
|
const lines = headerPart.split('\r\n');
|
||||||
|
|
||||||
|
const [requestLine, ...headerLines] = lines;
|
||||||
|
const [method = '', path = '', version = ''] = requestLine.split(' ');
|
||||||
|
|
||||||
|
const headers = {};
|
||||||
|
for (const line of headerLines) {
|
||||||
|
const idx = line.indexOf(':');
|
||||||
|
if (idx > 0) {
|
||||||
|
const key = line.slice(0, idx).trim().toLowerCase();
|
||||||
|
const val = line.slice(idx + 1).trim();
|
||||||
|
headers[key] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
http: {
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
version,
|
||||||
|
},
|
||||||
|
headers,
|
||||||
|
// rawLength is how many bytes total were in the headers portion
|
||||||
|
rawLength: headersEnd + 4, // +4 for "\r\n\r\n"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Construct the JSON echo response
|
||||||
|
function buildHttpResponse(proxyInfo, isTLS, http, headers, socket) {
|
||||||
|
const responseData = {
|
||||||
|
proxyProtocolVersion: proxyInfo?.version || 'none',
|
||||||
|
connectionSourceIp: socket.remoteAddress, // from raw or TLS socket
|
||||||
|
proxyProtocolSourceIp: proxyInfo?.sourceIp || null,
|
||||||
|
isTLS,
|
||||||
|
http,
|
||||||
|
httpHeaders: headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
const json = JSON.stringify(responseData, null, 2);
|
||||||
|
return (
|
||||||
|
'HTTP/1.1 200 OK\r\n' +
|
||||||
|
'Content-Type: application/json\r\n' +
|
||||||
|
`Content-Length: ${Buffer.byteLength(json)}\r\n` +
|
||||||
|
'\r\n' +
|
||||||
|
json
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Handle plain HTTP requests
|
||||||
|
function handlePlainHttp(socket, initialBuffer, proxyInfo) {
|
||||||
|
let buffer = initialBuffer;
|
||||||
|
|
||||||
|
// Immediately check if the initial buffer already contains a full request
|
||||||
|
maybeRespond();
|
||||||
|
|
||||||
|
// If not complete, listen for further data
|
||||||
|
socket.on('data', (chunk) => {
|
||||||
|
buffer = Buffer.concat([buffer, chunk]);
|
||||||
|
maybeRespond();
|
||||||
|
});
|
||||||
|
|
||||||
|
function maybeRespond() {
|
||||||
|
const parsed = parseHttpRequest(buffer);
|
||||||
|
if (parsed) {
|
||||||
|
const { http, headers } = parsed;
|
||||||
|
const response = buildHttpResponse(proxyInfo, false, http, headers, socket);
|
||||||
|
socket.write(response);
|
||||||
|
socket.end();
|
||||||
|
// Once we respond, we don't parse further requests on the same connection
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Handle TLS-wrapped HTTP requests
|
||||||
|
function handleTLSHttp(tlsSocket, initialBuffer, proxyInfo) {
|
||||||
|
let buffer = initialBuffer;
|
||||||
|
|
||||||
|
// Check if the initial buffer (post-handshake) has a full request
|
||||||
|
maybeRespond();
|
||||||
|
|
||||||
|
tlsSocket.on('data', (chunk) => {
|
||||||
|
buffer = Buffer.concat([buffer, chunk]);
|
||||||
|
maybeRespond();
|
||||||
|
});
|
||||||
|
|
||||||
|
function maybeRespond() {
|
||||||
|
const parsed = parseHttpRequest(buffer);
|
||||||
|
if (parsed) {
|
||||||
|
const { http, headers } = parsed;
|
||||||
|
const response = buildHttpResponse(proxyInfo, true, http, headers, tlsSocket);
|
||||||
|
tlsSocket.write(response);
|
||||||
|
tlsSocket.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
// Main server: detect Proxy Protocol, then TLS vs. plain
|
||||||
|
function startServer() {
|
||||||
|
const server = net.createServer((rawSocket) => {
|
||||||
|
let buffer = Buffer.alloc(0);
|
||||||
|
let proxyInfo = null;
|
||||||
|
let protocolDetected = false;
|
||||||
|
|
||||||
|
rawSocket.on('error', (err) => {
|
||||||
|
console.error('Raw socket error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
rawSocket.on('data', (chunk) => {
|
||||||
|
// Accumulate data
|
||||||
|
buffer = Buffer.concat([buffer, chunk]);
|
||||||
|
|
||||||
|
if (!protocolDetected) {
|
||||||
|
// 1) Attempt Proxy Protocol parse
|
||||||
|
const ppResult = parseProxyProtocol(buffer);
|
||||||
|
if (ppResult?.proxy) {
|
||||||
|
proxyInfo = ppResult.proxy;
|
||||||
|
buffer = buffer.slice(ppResult.bytesProcessed);
|
||||||
|
}
|
||||||
|
// If no valid PP, that's fine; we just continue
|
||||||
|
|
||||||
|
// 2) Check if remainder looks like a TLS handshake
|
||||||
|
// Typically: 0x16 (Handshake), 0x03 (TLS major), 0x01..0x03
|
||||||
|
let isTLS = false;
|
||||||
|
if (buffer.length >= 3) {
|
||||||
|
if (buffer[0] === 0x16 && buffer[1] === 0x03) {
|
||||||
|
isTLS = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolDetected = true;
|
||||||
|
|
||||||
|
// Switch to TLS or stay plain
|
||||||
|
if (isTLS && secureContext) {
|
||||||
|
// Wrap raw socket in a TLSSocket
|
||||||
|
const tlsSocket = new tls.TLSSocket(rawSocket, {
|
||||||
|
isServer: true,
|
||||||
|
secureContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Immediately feed any data we've already read to the TLS engine
|
||||||
|
// (internal approach)
|
||||||
|
tlsSocket._handle?.receive(buffer);
|
||||||
|
|
||||||
|
// Remove all listeners from raw socket so we don't double-consume data
|
||||||
|
rawSocket.removeAllListeners('data');
|
||||||
|
|
||||||
|
tlsSocket.on('error', (err) => {
|
||||||
|
console.error('TLS socket error:', err);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Once TLS handshake completes:
|
||||||
|
tlsSocket.on('secure', () => {
|
||||||
|
// Now handle the actual HTTP
|
||||||
|
handleTLSHttp(tlsSocket, Buffer.alloc(0), proxyInfo);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Plain HTTP
|
||||||
|
rawSocket.removeAllListeners('data');
|
||||||
|
handlePlainHttp(rawSocket, buffer, proxyInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If protocol already detected, do nothing here
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Server listening on port ${port}`);
|
||||||
|
console.log(`TLS certs found: ${haveTLSCerts ? 'Yes' : 'No'}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startServer();
|
||||||
143
docker-traefik-proxyprotocol/src/proxyprotocol.js
Normal file
143
docker-traefik-proxyprotocol/src/proxyprotocol.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
// proxyprotocol.js (ESM)
|
||||||
|
|
||||||
|
export function parseProxyProtocol(buffer) {
|
||||||
|
// 1) Try Proxy Protocol v1 (starts with "PROXY ")
|
||||||
|
const text = buffer.toString('ascii', 0, Math.min(buffer.length, 108));
|
||||||
|
if (text.startsWith('PROXY ')) {
|
||||||
|
const lineEnd = text.indexOf('\r\n');
|
||||||
|
if (lineEnd === -1) {
|
||||||
|
return { error: 'Incomplete Proxy Protocol v1 header' };
|
||||||
|
}
|
||||||
|
const line = text.substring(0, lineEnd); // e.g. "PROXY TCP4 1.2.3.4 5.6.7.8 12345 80"
|
||||||
|
const parts = line.split(' ');
|
||||||
|
if (parts.length >= 6) {
|
||||||
|
return {
|
||||||
|
proxy: {
|
||||||
|
version: 'v1',
|
||||||
|
protocol: parts[1], // e.g. "TCP4", "TCP6"
|
||||||
|
sourceIp: parts[2],
|
||||||
|
destinationIp: parts[3],
|
||||||
|
sourcePort: parts[4],
|
||||||
|
destinationPort: parts[5],
|
||||||
|
},
|
||||||
|
bytesProcessed: lineEnd + 2, // +2 for "\r\n"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { error: 'Malformed Proxy Protocol v1 header' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Try Proxy Protocol v2
|
||||||
|
// Signature bytes: 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A
|
||||||
|
if (buffer.length >= 16) {
|
||||||
|
const sig = buffer.slice(0, 12).toString('hex');
|
||||||
|
const v2sig = '0d0a0d0a000d0a515549540a';
|
||||||
|
if (sig === v2sig) {
|
||||||
|
const verCmd = buffer[12];
|
||||||
|
const version = verCmd >> 4; // High 4 bits => version
|
||||||
|
if (version !== 2) {
|
||||||
|
return { error: 'Invalid Proxy Protocol v2 header (version != 2)' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Byte 13 => family & protocol
|
||||||
|
const familyByte = buffer[13];
|
||||||
|
// Byte 14..15 => length of remaining header data
|
||||||
|
const len = buffer.readUInt16BE(14);
|
||||||
|
const totalV2HeaderLen = 16 + len;
|
||||||
|
if (buffer.length < totalV2HeaderLen) {
|
||||||
|
return { error: 'Incomplete Proxy Protocol v2 header' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// The upper 4 bits = address family; the lower 4 bits = transport protocol
|
||||||
|
// 0x1x => AF_INET (IPv4)
|
||||||
|
// 0x2x => AF_INET6 (IPv6)
|
||||||
|
// 0x0x => AF_UNSPEC
|
||||||
|
// plus the lower bits: 1 => STREAM (TCP), 2 => DGRAM (UDP), etc.
|
||||||
|
const addressFamily = (familyByte & 0xf0) >> 4; // top nibble
|
||||||
|
const transportProto = familyByte & 0x0f; // bottom nibble
|
||||||
|
|
||||||
|
let sourceIp = null;
|
||||||
|
let destinationIp = null;
|
||||||
|
let sourcePort = null;
|
||||||
|
let destinationPort = null;
|
||||||
|
|
||||||
|
// The address block starts at offset 16
|
||||||
|
// For IPv4 + TCP/UDP: 4 bytes src IP, 4 bytes dst IP, 2 bytes src port, 2 bytes dst port (total 12)
|
||||||
|
// For IPv6 + TCP/UDP: 16 bytes src IP,16 bytes dst IP, 2 bytes src port, 2 bytes dst port (total 36)
|
||||||
|
let offset = 16;
|
||||||
|
|
||||||
|
if (addressFamily === 0x1) {
|
||||||
|
// IPv4
|
||||||
|
if (len >= 12) {
|
||||||
|
// 4 bytes src IP
|
||||||
|
const srcBuf = buffer.slice(offset, offset + 4);
|
||||||
|
offset += 4;
|
||||||
|
// 4 bytes dst IP
|
||||||
|
const dstBuf = buffer.slice(offset, offset + 4);
|
||||||
|
offset += 4;
|
||||||
|
// 2 bytes src port
|
||||||
|
sourcePort = buffer.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
// 2 bytes dst port
|
||||||
|
destinationPort = buffer.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
sourceIp = srcBuf.join('.');
|
||||||
|
destinationIp = dstBuf.join('.');
|
||||||
|
}
|
||||||
|
} else if (addressFamily === 0x2) {
|
||||||
|
// IPv6
|
||||||
|
if (len >= 36) {
|
||||||
|
// 16 bytes src IP
|
||||||
|
const srcBuf = buffer.slice(offset, offset + 16);
|
||||||
|
offset += 16;
|
||||||
|
// 16 bytes dst IP
|
||||||
|
const dstBuf = buffer.slice(offset, offset + 16);
|
||||||
|
offset += 16;
|
||||||
|
// 2 bytes src port
|
||||||
|
sourcePort = buffer.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
// 2 bytes dst port
|
||||||
|
destinationPort = buffer.readUInt16BE(offset + 2);
|
||||||
|
|
||||||
|
// Convert buffer to IPv6 string
|
||||||
|
sourceIp = bufToIPv6(srcBuf);
|
||||||
|
destinationIp = bufToIPv6(dstBuf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Family was 0x0 (UNSPEC) or 0x4 (AF_UNIX), etc.
|
||||||
|
// We won't parse addresses here.
|
||||||
|
// In that case, the official spec says no address information is carried.
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
proxy: {
|
||||||
|
version: 'v2',
|
||||||
|
sourceIp,
|
||||||
|
destinationIp,
|
||||||
|
sourcePort,
|
||||||
|
destinationPort,
|
||||||
|
family: addressFamily, // 1 => IPv4, 2 => IPv6
|
||||||
|
protocol: transportProto, // 1 => TCP, 2 => UDP, ...
|
||||||
|
},
|
||||||
|
bytesProcessed: totalV2HeaderLen,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none recognized
|
||||||
|
return { error: 'No Proxy Protocol header' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to convert a 16-byte IPv6 buffer into a readable string
|
||||||
|
function bufToIPv6(buf) {
|
||||||
|
// Split into eight 16-bit words
|
||||||
|
const parts = [];
|
||||||
|
for (let i = 0; i < 16; i += 2) {
|
||||||
|
parts.push(buf.readUInt16BE(i).toString(16));
|
||||||
|
}
|
||||||
|
// Then compress the longest run of zeros
|
||||||
|
const ipv6Str = parts.join(':')
|
||||||
|
.replace(/(^|:)0+([0-9a-f])/g, '$1$2') // remove leading zeros in each group
|
||||||
|
.replace(/(:0+)+:/, '::'); // collapse multiple groups of zeros
|
||||||
|
return ipv6Str;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user