Repository: mina-sshd Updated Branches: refs/heads/master 0c4191183 -> 655e7dbd7
[SSHD-656] Support The PROXY protocol See Tony Bussieres <t...@codingtony.com> contribution Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/655e7dbd Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/655e7dbd Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/655e7dbd Branch: refs/heads/master Commit: 655e7dbd7fcb26956e90575bcfb66a0679ccdd48 Parents: 0c41911 Author: Goldstein Lyor <l...@c-b4.com> Authored: Sun Apr 9 16:09:06 2017 +0300 Committer: Goldstein Lyor <l...@c-b4.com> Committed: Sun Apr 9 16:09:06 2017 +0300 ---------------------------------------------------------------------- .../proxyprotocol/ProxyProtocolAcceptor.java | 133 +++++++++++++++++++ 1 file changed, 133 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/655e7dbd/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java b/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java new file mode 100644 index 0000000..29696aa --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java @@ -0,0 +1,133 @@ +/* + * 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:d...@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; + } +}