Skip to content

Commit

Permalink
Add support for Proxy Protocol V2
Browse files Browse the repository at this point in the history
Signed-off-by: f.herbreteau <f.herbreteau@oodrive.com>
  • Loading branch information
fherbreteau committed Aug 31, 2023
1 parent 786fd51 commit 61d4b17
Show file tree
Hide file tree
Showing 8 changed files with 967 additions and 0 deletions.
10 changes: 10 additions & 0 deletions sshd-contrib/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.sshd.contrib.server.session.proxyprotocolv2;

import java.util.Arrays;

import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.contrib.server.session.proxyprotocol.ProxyProtocolAcceptor;
import org.apache.sshd.contrib.server.session.proxyprotocolv2.data.AddressData;
import org.apache.sshd.contrib.server.session.proxyprotocolv2.data.FamilyAndTransport;
import org.apache.sshd.contrib.server.session.proxyprotocolv2.data.VersionAndCommand;
import org.apache.sshd.contrib.server.session.proxyprotocolv2.utils.ProxyUtils;
import org.apache.sshd.server.session.ServerSession;

/**
* A working prototype to support PROXY protocol v2 as described in
* <A HREF="https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt">HAProxy Documentation</A>.
* <p>
* This <code>ServerProxyAcceptor</code> can process PROXY protocol v1 and v2.
* </p>
*
* @author Oodrive - François HERBRETEAU (f.herbreteau@oodrive.com)
*/
public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor {

private static final byte[] PROXY_V2_HEADER
= new byte[] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };

private static final char FIELD_SEPARATOR = ' ';

public ProxyProtocolV2Acceptor() {
super();
}

@Override
public boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception {
int mark = buffer.rpos();
int dataLen = buffer.available();
if (dataLen < PROXY_V2_HEADER.length) {
if (log.isDebugEnabled()) {
log.debug("acceptServerProxyMetadata(session={}) incomplete data - {}/{}", session, dataLen,
PROXY_V2_HEADER.length);
}
return false;
}

byte[] proxyV2Header = new byte[PROXY_V2_HEADER.length];
buffer.getRawBytes(proxyV2Header);

if (!Arrays.equals(PROXY_V2_HEADER, proxyV2Header)) {
buffer.rpos(mark); // Rewind the buffer to allow further reading
return super.acceptServerProxyMetadata(session, buffer);
}
return readProxyV2Header(session, mark, buffer);
}

protected boolean readProxyV2Header(ServerSession session, int markPosition, Buffer buffer) throws Exception {
if (log.isDebugEnabled()) {
int mark = buffer.rpos();
buffer.rpos(markPosition);
log.debug("readProxyV2Header(session={}) processing Proxy Protocol V2 buffer : [{}]", session,
ProxyUtils.toHexString(buffer, mark));
}
StringBuilder proxyPayload = new StringBuilder();
// Read the version and command information
VersionAndCommand versionAndCommand = VersionAndCommand.extractValue(log, session, buffer);
proxyPayload.append(versionAndCommand.name());
// Read the family and transport.
FamilyAndTransport familyAndTransport = FamilyAndTransport.extractValue(log, session, buffer);
proxyPayload.append(FIELD_SEPARATOR).append(familyAndTransport.name());
// Read the data length
int dataLength = ProxyUtils.readUnsignedShort(buffer);
// Unix Socket are not supported by SSHD
if (familyAndTransport.hasSockAddress()) {
log.warn("parseProxyHeader(session={}) unsupported sub-protocol - {} - continue as usual", session,
familyAndTransport);
// Skip socket address data
AddressData.skipUnprocessedData(log, session, buffer, FamilyAndTransport.UNSPEC, dataLength);
return true;
}
// Read the address Data (Host and Port for source and dest)
AddressData data = AddressData.extractAddressData(log, session, buffer, familyAndTransport, dataLength);
proxyPayload.append(FIELD_SEPARATOR).append(data);
// Parse the converted proxy header
return parseProxyHeader(session, proxyPayload.toString(), markPosition, buffer);
}

@Override
protected boolean parseProxyHeader(ServerSession session, String proxyHeader, int markPosition, Buffer buffer)
throws Exception {
String[] proxyFields = GenericUtils.split(proxyHeader, FIELD_SEPARATOR);
// Trim all fields just in case more than one space used
for (int index = 0; index < proxyFields.length; index++) {
String f = proxyFields[index];
proxyFields[index] = GenericUtils.trimToEmpty(f);
}
// Nothing to do for local proxy protocol
if ("LOCAL".equals(proxyFields[0])) {
log.debug("parseProxyHeader(session={}) local proxy check", session);
return true;
}
return super.parseProxyHeader(session, proxyHeader, markPosition, buffer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package org.apache.sshd.contrib.server.session.proxyprotocolv2.data;

import java.io.IOException;
import java.net.InetAddress;

import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.contrib.server.session.proxyprotocolv2.utils.ProxyUtils;
import org.apache.sshd.server.session.ServerSession;
import org.slf4j.Logger;


/**
* Address data structure.
* <p>
* Starting from the 17th byte, addresses are presented in network byte order.
* </p>
* <p>
* The address order is always the same : - source layer 3 address in network byte order - destination layer 3 address
* in network byte order - source layer 4 address if any, in network byte order (port) - destination layer 4 address if
* any, in network byte order (port)
* </p>
* <p>
* The address block may directly be sent from or received into the following union which makes it easy to cast from/to
* the relevant socket native structs depending on the address type :
* </p>
*
* <pre>
* union proxy_addr {
* struct { // for TCP/UDP over IPv4, len = 12
* uint32_t src_addr;
* uint32_t dst_addr;
* uint16_t src_port;
* uint16_t dst_port;
* }ipv4_addr;
* struct{ // for TCP/UDP over IPv6, len = 36
* uint8_t src_addr[16];
* uint8_t dst_addr[16];
* uint16_t src_port;
* uint16_t dst_port;
* }ipv6_addr;
* struct{ // for AF_UNIX sockets, len = 216
* uint8_t src_addr[108];
* uint8_t dst_addr[108];
* }unix_addr;
* };
* </pre>
*
* @author Oodrive - François HERBRETEAU (f.herbreteau@oodrive.com)
*/
public final class AddressData {

private final String srcAddress;
private final String dstAddress;

private final int srcPort;
private final int dstPort;

private AddressData(String srcAddress, String dstAddress, int srcPort, int dstPort) {
this.srcAddress = srcAddress;
this.dstAddress = dstAddress;
this.srcPort = srcPort;
this.dstPort = dstPort;
}

public static AddressData extractAddressData(Logger logger,
ServerSession session,
Buffer buffer,
FamilyAndTransport familyAndTransport,
int dataLength)
throws IOException {
String srcAddress = extractAddresses(buffer, familyAndTransport);
String dstAddress = extractAddresses(buffer, familyAndTransport);
int srcPort = extractPort(buffer, familyAndTransport);
int dstPort = extractPort(buffer, familyAndTransport);
skipUnprocessedData(logger, session, buffer, familyAndTransport, dataLength);
return new AddressData(srcAddress, dstAddress, srcPort, dstPort);
}

public static void skipUnprocessedData(
Logger logger,
ServerSession session,
Buffer buffer,
FamilyAndTransport familyAndTransport,
int dataLength) {
int remaining = dataLength - familyAndTransport.getDataLength();
if (remaining > 0) {
if (logger.isDebugEnabled()) {
logger.debug("extractAddressData({}) skipping additional datas [{}]",
session,
ProxyUtils.toHexString(buffer, buffer.rpos()));
}
// Insure the remaining bytes are available
buffer.ensureAvailable(remaining);
// Skip all extra datas
buffer.rpos(buffer.rpos() + remaining);
}
}

private static String extractAddresses(Buffer buffer, FamilyAndTransport familyAndTransport)
throws IOException {
byte[] datas = new byte[familyAndTransport.getAddressLength()];
buffer.getRawBytes(datas);
if (familyAndTransport.hasInetAddress()) {
return InetAddress.getByAddress(datas).getHostAddress();
}
return "";
}

private static int extractPort(Buffer buffer, FamilyAndTransport familyAndTransport) {
if (familyAndTransport.hasPort()) {
return ProxyUtils.readUnsignedShort(buffer);
}
return 0;
}

@Override
public String toString() {
return String.join(" ", srcAddress, dstAddress, Integer.toString(srcPort), Integer.toString(dstPort));
}
}
Loading

0 comments on commit 61d4b17

Please sign in to comment.