/*
* 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.server.session.proxyprotocol;
import java.net.InetSocketAddress;
import java.util.Arrays;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.ValidateUtils;
import org.apache.sshd.common.util.buffer.Buffer;
import org.apache.sshd.common.util.buffer.BufferUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.server.session.AbstractServerSession;
import org.apache.sshd.server.session.ServerProxyAcceptor;
import org.apache.sshd.server.session.ServerSession;
/**
* A working prototype to support PROXY protocol as described in
* <A HREF="http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">HAProxy Documentation</A>.
* @see <A HREF="https://gist.github.com/codingtony/a8684c9ffa08ad56899f94d3b6c2a040">Tony Bussieres's</A> contribution
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ProxyProtocolAcceptor extends AbstractLoggingBean implements ServerProxyAcceptor {
// 108 bytes is the largest buffer needed for the PROXY protocol, but we are a bit more lenient
public static final int MAX_PROXY_HEADER_LENGTH = Byte.MAX_VALUE;
public static final String PROX_PROTOCOL_PREFIX = "PROXY";
private static final byte[] PROXY_HEADER = new byte[] {0x50, 0x52, 0x4F, 0x58, 0x59, 0x20};
public ProxyProtocolAcceptor() {
super();
}
@Override
public boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception {
int mark = buffer.rpos();
int dataLen = buffer.available();
if (dataLen < PROXY_HEADER.length) {
if (log.isDebugEnabled()) {
log.debug("acceptServerProxyMetadata(session={}) incomplete data - {}/{}", session, dataLen, PROXY_HEADER.length);
}
return false;
}
byte[] proxyHeader = new byte[PROXY_HEADER.length];
buffer.getRawBytes(proxyHeader);
buffer.rpos(mark); // Rewind the buffer
if (!Arrays.equals(PROXY_HEADER, proxyHeader)) {
if (log.isDebugEnabled()) {
log.debug("acceptServerProxyMetadata(session={}) mismatched protocol header: expected={}, actual={}",
session, BufferUtils.toHex(':', PROXY_HEADER), BufferUtils.toHex(':', proxyHeader));
}
return true;
}
StringBuilder proxyPayload = new StringBuilder(MAX_PROXY_HEADER_LENGTH);
while ((proxyPayload.length() < MAX_PROXY_HEADER_LENGTH) && (buffer.available() > 0)) {
char ch = (char) buffer.getUByte();
if (ch != '\n') {
proxyPayload.append(ch);
continue;
}
// remove trailing CR if found
int ppLen = proxyPayload.length();
if ((ppLen > 0) && (proxyPayload.charAt(ppLen - 1) == '\r')) {
proxyPayload.setLength(ppLen - 1);
}
return parseProxyHeader(session, proxyPayload.toString(), mark, buffer);
}
// Could not see LF before MAX_PROXY_HEADER_LENGTH expired
buffer.rpos(mark); // Rewind the buffer
return false;
}
protected boolean parseProxyHeader(ServerSession session, String proxyHeader, int markPosition, Buffer buffer) throws Exception {
if (log.isDebugEnabled()) {
log.debug("parseProxyHeader(session={}) parsing header='{}'", session, proxyHeader);
}
String[] proxyFields = GenericUtils.split(proxyHeader, ' ');
// 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);
}
String proxyProtocolPrefix = proxyFields[0];
ValidateUtils.checkTrue(PROX_PROTOCOL_PREFIX.equalsIgnoreCase(proxyProtocolPrefix), "Mismatched protocol prefix: %s", proxyProtocolPrefix);
String protocolVersion = proxyFields[1];
if ("TCP4".equalsIgnoreCase(protocolVersion) || "TCP6".equalsIgnoreCase(protocolVersion)) {
String layer3SrcAddress = proxyFields[2];
String layer3DstAddress = proxyFields[3];
String layer3SrcPort = proxyFields[4];
String layer3DstPort = proxyFields[5];
if (log.isDebugEnabled()) {
log.debug("parseProxyHeader(session={}) using {}:{} -> {}:{} proxy",
session, layer3SrcAddress, layer3SrcPort, layer3DstAddress, layer3DstPort);
}
if (session instanceof AbstractServerSession) {
// Set the client address in the session from the proxy payload
InetSocketAddress clientAddress = new InetSocketAddress(layer3SrcAddress, Integer.parseInt(layer3SrcPort));
((AbstractServerSession) session).setClientAddress(clientAddress);
}
} else {
log.warn("parseProxyHeader(session={}) unsuppored sub-protocol - {} - continue as usual", session, protocolVersion);
}
return true;
}
}