Add support for RFC2385 on Linux
Motivation: There are protocols (BGP, SXP), which are typically deployed with TCP MD5 authentication to protect sessions from being hijacked/torn down by third parties. This facility is not available on most operating systems, but is typically present on Linux. Modifications: - add a new EpollChannelOption, which is write-only - teach Epoll(Server)SocketChannel to track which addresses have keys associated - teach Native how to set the MD5 signature keys for a socket Result: Users of the native-epoll transport can set MD5 signature keys and thus leverage RFC-2385 protection on TCP connections.
This commit is contained in:
parent
34de2667c7
commit
30a7701616
@ -1736,3 +1736,51 @@ JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_splice0(JNIEnv* env, j
|
|||||||
JNIEXPORT jlong JNICALL Java_io_netty_channel_epoll_Native_ssizeMax(JNIEnv* env, jclass clazz) {
|
JNIEXPORT jlong JNICALL Java_io_netty_channel_epoll_Native_ssizeMax(JNIEnv* env, jclass clazz) {
|
||||||
return SSIZE_MAX;
|
return SSIZE_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL Java_io_netty_channel_epoll_Native_tcpMd5SigMaxKeyLen(JNIEnv* env, jclass clazz) {
|
||||||
|
struct tcp_md5sig md5sig;
|
||||||
|
|
||||||
|
// Defensive size check
|
||||||
|
if (sizeof(md5sig.tcpm_key) < TCP_MD5SIG_MAXKEYLEN) {
|
||||||
|
return sizeof(md5sig.tcpm_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TCP_MD5SIG_MAXKEYLEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_io_netty_channel_epoll_Native_setTcpMd5Sig0(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jbyteArray key) {
|
||||||
|
struct sockaddr_storage addr;
|
||||||
|
if (init_sockaddr(env, address, scopeId, 0, &addr) == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tcp_md5sig md5sig;
|
||||||
|
memset(&md5sig, 0, sizeof(md5sig));
|
||||||
|
md5sig.tcpm_addr.ss_family = addr.ss_family;
|
||||||
|
|
||||||
|
struct sockaddr_in* ipaddr;
|
||||||
|
struct sockaddr_in6* ip6addr;
|
||||||
|
|
||||||
|
switch (addr.ss_family) {
|
||||||
|
case AF_INET:
|
||||||
|
ipaddr = (struct sockaddr_in*) &addr;
|
||||||
|
memcpy(&((struct sockaddr_in *) &md5sig.tcpm_addr)->sin_addr, &ipaddr->sin_addr, sizeof(ipaddr->sin_addr));
|
||||||
|
break;
|
||||||
|
case AF_INET6:
|
||||||
|
ip6addr = (struct sockaddr_in6*) &addr;
|
||||||
|
memcpy(&((struct sockaddr_in6 *) &md5sig.tcpm_addr)->sin6_addr, &ip6addr->sin6_addr, sizeof(ip6addr->sin6_addr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key != NULL) {
|
||||||
|
md5sig.tcpm_keylen = (*env)->GetArrayLength(env, key);
|
||||||
|
(*env)->GetByteArrayRegion(env, key, 0, md5sig.tcpm_keylen, (void *) &md5sig.tcpm_key);
|
||||||
|
if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setsockopt(fd, IPPROTO_TCP, TCP_MD5SIG, &md5sig, sizeof(md5sig)) < 0) {
|
||||||
|
throwChannelExceptionErrorNo(env, "setsockopt() failed: ", errno);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -129,3 +129,6 @@ jint Java_io_netty_channel_epoll_Native_offsetofEpollData(JNIEnv* env, jclass cl
|
|||||||
|
|
||||||
jlong Java_io_netty_channel_epoll_Native_pipe0(JNIEnv* env, jclass clazz);
|
jlong Java_io_netty_channel_epoll_Native_pipe0(JNIEnv* env, jclass clazz);
|
||||||
jint Java_io_netty_channel_epoll_Native_splice0(JNIEnv* env, jclass clazz, jint fd, jint offIn, jint fdOut, jint offOut, jint len);
|
jint Java_io_netty_channel_epoll_Native_splice0(JNIEnv* env, jclass clazz, jint fd, jint offIn, jint fdOut, jint offOut, jint len);
|
||||||
|
|
||||||
|
jint Java_io_netty_channel_epoll_Native_tcpMd5SigMaxKeyLen(JNIEnv* env, jclass clazz);
|
||||||
|
void Java_io_netty_channel_epoll_Native_setTcpMd5Sig0(JNIEnv* env, jclass clazz, jint fd, jbyteArray address, jint scopeId, jbyteArray key);
|
||||||
|
@ -18,6 +18,9 @@ package io.netty.channel.epoll;
|
|||||||
import io.netty.channel.ChannelOption;
|
import io.netty.channel.ChannelOption;
|
||||||
import io.netty.channel.unix.DomainSocketReadMode;
|
import io.netty.channel.unix.DomainSocketReadMode;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public final class EpollChannelOption<T> extends ChannelOption<T> {
|
public final class EpollChannelOption<T> extends ChannelOption<T> {
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
private static final Class<EpollChannelOption> T = EpollChannelOption.class;
|
private static final Class<EpollChannelOption> T = EpollChannelOption.class;
|
||||||
@ -36,6 +39,8 @@ public final class EpollChannelOption<T> extends ChannelOption<T> {
|
|||||||
public static final ChannelOption<EpollMode> EPOLL_MODE =
|
public static final ChannelOption<EpollMode> EPOLL_MODE =
|
||||||
ChannelOption.valueOf(T, "EPOLL_MODE");
|
ChannelOption.valueOf(T, "EPOLL_MODE");
|
||||||
|
|
||||||
|
public static final ChannelOption<Map<InetAddress, byte[]>> TCP_MD5SIG = valueOf("TCP_MD5SIG");
|
||||||
|
|
||||||
@SuppressWarnings({ "unused", "deprecation" })
|
@SuppressWarnings({ "unused", "deprecation" })
|
||||||
private EpollChannelOption() {
|
private EpollChannelOption() {
|
||||||
super(null);
|
super(null);
|
||||||
|
@ -21,11 +21,13 @@ import io.netty.channel.MessageSizeEstimator;
|
|||||||
import io.netty.channel.RecvByteBufAllocator;
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
import io.netty.util.NetUtil;
|
import io.netty.util.NetUtil;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static io.netty.channel.ChannelOption.SO_BACKLOG;
|
import static io.netty.channel.ChannelOption.SO_BACKLOG;
|
||||||
import static io.netty.channel.ChannelOption.SO_RCVBUF;
|
import static io.netty.channel.ChannelOption.SO_RCVBUF;
|
||||||
import static io.netty.channel.ChannelOption.SO_REUSEADDR;
|
import static io.netty.channel.ChannelOption.SO_REUSEADDR;
|
||||||
|
import static io.netty.channel.epoll.EpollChannelOption.TCP_MD5SIG;;
|
||||||
|
|
||||||
public class EpollServerChannelConfig extends EpollChannelConfig {
|
public class EpollServerChannelConfig extends EpollChannelConfig {
|
||||||
protected final AbstractEpollChannel channel;
|
protected final AbstractEpollChannel channel;
|
||||||
@ -66,6 +68,10 @@ public class EpollServerChannelConfig extends EpollChannelConfig {
|
|||||||
setReuseAddress((Boolean) value);
|
setReuseAddress((Boolean) value);
|
||||||
} else if (option == SO_BACKLOG) {
|
} else if (option == SO_BACKLOG) {
|
||||||
setBacklog((Integer) value);
|
setBacklog((Integer) value);
|
||||||
|
} else if (option == TCP_MD5SIG) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
||||||
|
((EpollServerSocketChannel) channel).setTcpMd5Sig(m);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,12 @@ import io.netty.channel.EventLoop;
|
|||||||
import io.netty.channel.socket.ServerSocketChannel;
|
import io.netty.channel.socket.ServerSocketChannel;
|
||||||
import io.netty.channel.unix.FileDescriptor;
|
import io.netty.channel.unix.FileDescriptor;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ServerSocketChannel} implementation that uses linux EPOLL Edge-Triggered Mode for
|
* {@link ServerSocketChannel} implementation that uses linux EPOLL Edge-Triggered Mode for
|
||||||
@ -31,6 +35,7 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i
|
|||||||
|
|
||||||
private final EpollServerSocketChannelConfig config;
|
private final EpollServerSocketChannelConfig config;
|
||||||
private volatile InetSocketAddress local;
|
private volatile InetSocketAddress local;
|
||||||
|
private volatile Collection<InetAddress> tcpMd5SigAddresses = Collections.emptyList();
|
||||||
|
|
||||||
public EpollServerSocketChannel() {
|
public EpollServerSocketChannel() {
|
||||||
super(Native.socketStreamFd());
|
super(Native.socketStreamFd());
|
||||||
@ -89,4 +94,12 @@ public final class EpollServerSocketChannel extends AbstractEpollServerChannel i
|
|||||||
protected Channel newChildChannel(int fd, byte[] address, int offset, int len) throws Exception {
|
protected Channel newChildChannel(int fd, byte[] address, int offset, int len) throws Exception {
|
||||||
return new EpollSocketChannel(this, fd, Native.address(address, offset, len));
|
return new EpollSocketChannel(this, fd, Native.address(address, offset, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Collection<InetAddress> tcpMd5SigAddresses() {
|
||||||
|
return tcpMd5SigAddresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setTcpMd5Sig(Map<InetAddress, byte[]> keys) {
|
||||||
|
this.tcpMd5SigAddresses = TcpMd5Util.newTcpMd5Sigs(this, tcpMd5SigAddresses, keys);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import io.netty.channel.MessageSizeEstimator;
|
|||||||
import io.netty.channel.RecvByteBufAllocator;
|
import io.netty.channel.RecvByteBufAllocator;
|
||||||
import io.netty.channel.socket.ServerSocketChannelConfig;
|
import io.netty.channel.socket.ServerSocketChannelConfig;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class EpollServerSocketChannelConfig extends EpollServerChannelConfig
|
public final class EpollServerSocketChannelConfig extends EpollServerChannelConfig
|
||||||
@ -60,6 +61,10 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
setReusePort((Boolean) value);
|
setReusePort((Boolean) value);
|
||||||
} else if (option == EpollChannelOption.IP_FREEBIND) {
|
} else if (option == EpollChannelOption.IP_FREEBIND) {
|
||||||
setFreeBind((Boolean) value);
|
setFreeBind((Boolean) value);
|
||||||
|
} else if (option == EpollChannelOption.TCP_MD5SIG) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
||||||
|
setTcpMd5Sig(m);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -145,6 +150,16 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code TCP_MD5SIG} option on the socket. See {@code linux/tcp.h} for more details.
|
||||||
|
* Keys can only be set on, not read to prevent a potential leak, as they are confidential.
|
||||||
|
* Allowing them being read would mean anyone with access to the channel could get them.
|
||||||
|
*/
|
||||||
|
public EpollServerSocketChannelConfig setTcpMd5Sig(Map<InetAddress, byte[]> keys) {
|
||||||
|
((EpollServerSocketChannel) channel).setTcpMd5Sig(keys);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if the SO_REUSEPORT option is set.
|
* Returns {@code true} if the SO_REUSEPORT option is set.
|
||||||
*/
|
*/
|
||||||
|
@ -25,8 +25,12 @@ import io.netty.channel.unix.FileDescriptor;
|
|||||||
import io.netty.util.concurrent.GlobalEventExecutor;
|
import io.netty.util.concurrent.GlobalEventExecutor;
|
||||||
import io.netty.util.internal.OneTimeTask;
|
import io.netty.util.internal.OneTimeTask;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,6 +43,7 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme
|
|||||||
|
|
||||||
private volatile InetSocketAddress local;
|
private volatile InetSocketAddress local;
|
||||||
private volatile InetSocketAddress remote;
|
private volatile InetSocketAddress remote;
|
||||||
|
private volatile Collection<InetAddress> tcpMd5SigAddresses = Collections.emptyList();
|
||||||
|
|
||||||
EpollSocketChannel(Channel parent, int fd, InetSocketAddress remote) {
|
EpollSocketChannel(Channel parent, int fd, InetSocketAddress remote) {
|
||||||
super(parent, fd);
|
super(parent, fd);
|
||||||
@ -47,6 +52,10 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme
|
|||||||
// See https://github.com/netty/netty/issues/2359
|
// See https://github.com/netty/netty/issues/2359
|
||||||
this.remote = remote;
|
this.remote = remote;
|
||||||
local = Native.localAddress(fd);
|
local = Native.localAddress(fd);
|
||||||
|
|
||||||
|
if (parent instanceof EpollServerSocketChannel) {
|
||||||
|
tcpMd5SigAddresses = ((EpollServerSocketChannel) parent).tcpMd5SigAddresses();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpollSocketChannel() {
|
public EpollSocketChannel() {
|
||||||
@ -205,4 +214,8 @@ public final class EpollSocketChannel extends AbstractEpollStreamChannel impleme
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setTcpMd5Sig(Map<InetAddress, byte[]> keys) {
|
||||||
|
this.tcpMd5SigAddresses = TcpMd5Util.newTcpMd5Sigs(this, tcpMd5SigAddresses, keys);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import io.netty.channel.RecvByteBufAllocator;
|
|||||||
import io.netty.channel.socket.SocketChannelConfig;
|
import io.netty.channel.socket.SocketChannelConfig;
|
||||||
import io.netty.util.internal.PlatformDependent;
|
import io.netty.util.internal.PlatformDependent;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static io.netty.channel.ChannelOption.*;
|
import static io.netty.channel.ChannelOption.*;
|
||||||
@ -49,7 +50,8 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
super.getOptions(),
|
super.getOptions(),
|
||||||
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS,
|
SO_RCVBUF, SO_SNDBUF, TCP_NODELAY, SO_KEEPALIVE, SO_REUSEADDR, SO_LINGER, IP_TOS,
|
||||||
ALLOW_HALF_CLOSURE, EpollChannelOption.TCP_CORK, EpollChannelOption.TCP_NOTSENT_LOWAT,
|
ALLOW_HALF_CLOSURE, EpollChannelOption.TCP_CORK, EpollChannelOption.TCP_NOTSENT_LOWAT,
|
||||||
EpollChannelOption.TCP_KEEPCNT, EpollChannelOption.TCP_KEEPIDLE, EpollChannelOption.TCP_KEEPINTVL);
|
EpollChannelOption.TCP_KEEPCNT, EpollChannelOption.TCP_KEEPIDLE, EpollChannelOption.TCP_KEEPINTVL,
|
||||||
|
EpollChannelOption.TCP_MD5SIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -132,6 +134,10 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
setTcpKeepIntvl((Integer) value);
|
setTcpKeepIntvl((Integer) value);
|
||||||
} else if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
|
} else if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
|
||||||
setTcpUserTimeout((Integer) value);
|
setTcpUserTimeout((Integer) value);
|
||||||
|
} else if (option == EpollChannelOption.TCP_MD5SIG) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
||||||
|
setTcpMd5Sig(m);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -317,6 +323,16 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the {@code TCP_MD5SIG} option on the socket. See {@code linux/tcp.h} for more details.
|
||||||
|
* Keys can only be set on, not read to prevent a potential leak, as they are confidential.
|
||||||
|
* Allowing them being read would mean anyone with access to the channel could get them.
|
||||||
|
*/
|
||||||
|
public EpollSocketChannelConfig setTcpMd5Sig(Map<InetAddress, byte[]> keys) {
|
||||||
|
channel.setTcpMd5Sig(keys);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAllowHalfClosure() {
|
public boolean isAllowHalfClosure() {
|
||||||
return allowHalfClosure;
|
return allowHalfClosure;
|
||||||
|
@ -62,6 +62,8 @@ public final class Native {
|
|||||||
public static final int UIO_MAX_IOV = uioMaxIov();
|
public static final int UIO_MAX_IOV = uioMaxIov();
|
||||||
public static final boolean IS_SUPPORTING_SENDMMSG = isSupportingSendmmsg();
|
public static final boolean IS_SUPPORTING_SENDMMSG = isSupportingSendmmsg();
|
||||||
public static final long SSIZE_MAX = ssizeMax();
|
public static final long SSIZE_MAX = ssizeMax();
|
||||||
|
public static final int TCP_MD5SIG_MAXKEYLEN = tcpMd5SigMaxKeyLen();
|
||||||
|
|
||||||
private static final byte[] IPV4_MAPPED_IPV6_PREFIX = {
|
private static final byte[] IPV4_MAPPED_IPV6_PREFIX = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff };
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff };
|
||||||
|
|
||||||
@ -661,6 +663,13 @@ public final class Native {
|
|||||||
|
|
||||||
private static native void tcpInfo0(int fd, int[] array);
|
private static native void tcpInfo0(int fd, int[] array);
|
||||||
|
|
||||||
|
public static void setTcpMd5Sig(int fd, InetAddress address, byte[] key) {
|
||||||
|
final NativeInetAddress a = toNativeInetAddress(address);
|
||||||
|
setTcpMd5Sig0(fd, a.address, a.scopeId, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static native void setTcpMd5Sig0(int fd, byte[] address, int scopeId, byte[] key);
|
||||||
|
|
||||||
private static NativeInetAddress toNativeInetAddress(InetAddress addr) {
|
private static NativeInetAddress toNativeInetAddress(InetAddress addr) {
|
||||||
byte[] bytes = addr.getAddress();
|
byte[] bytes = addr.getAddress();
|
||||||
if (addr instanceof Inet6Address) {
|
if (addr instanceof Inet6Address) {
|
||||||
@ -709,6 +718,8 @@ public final class Native {
|
|||||||
private static native int epollerr();
|
private static native int epollerr();
|
||||||
|
|
||||||
private static native long ssizeMax();
|
private static native long ssizeMax();
|
||||||
|
private static native int tcpMd5SigMaxKeyLen();
|
||||||
|
|
||||||
private Native() {
|
private Native() {
|
||||||
// utility
|
// utility
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project 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 io.netty.channel.epoll;
|
||||||
|
|
||||||
|
import io.netty.util.internal.ObjectUtil;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
final class TcpMd5Util {
|
||||||
|
|
||||||
|
static Collection<InetAddress> newTcpMd5Sigs(AbstractEpollChannel channel, Collection<InetAddress> current,
|
||||||
|
Map<InetAddress, byte[]> newKeys) {
|
||||||
|
ObjectUtil.checkNotNull(channel, "channel");
|
||||||
|
ObjectUtil.checkNotNull(current, "current");
|
||||||
|
ObjectUtil.checkNotNull(newKeys, "newKeys");
|
||||||
|
|
||||||
|
// Validate incoming values
|
||||||
|
for (Entry<InetAddress, byte[]> e : newKeys.entrySet()) {
|
||||||
|
final byte[] key = e.getValue();
|
||||||
|
if (e.getKey() == null) {
|
||||||
|
throw new IllegalArgumentException("newKeys contains an entry with null address: " + newKeys);
|
||||||
|
}
|
||||||
|
if (key == null) {
|
||||||
|
throw new NullPointerException("newKeys[" + e.getKey() + ']');
|
||||||
|
}
|
||||||
|
if (key.length == 0) {
|
||||||
|
throw new IllegalArgumentException("newKeys[" + e.getKey() + "] has an empty key.");
|
||||||
|
}
|
||||||
|
if (key.length > Native.TCP_MD5SIG_MAXKEYLEN) {
|
||||||
|
throw new IllegalArgumentException("newKeys[" + e.getKey() +
|
||||||
|
"] has a key with invalid length; should not exceed the maximum length (" +
|
||||||
|
Native.TCP_MD5SIG_MAXKEYLEN + ')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove mappings not present in the new set.
|
||||||
|
for (InetAddress addr : current) {
|
||||||
|
if (!newKeys.containsKey(addr)) {
|
||||||
|
Native.setTcpMd5Sig(channel.fd().intValue(), addr, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newKeys.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set new mappings and store addresses which we set.
|
||||||
|
final Collection<InetAddress> addresses = new ArrayList<InetAddress>(newKeys.size());
|
||||||
|
for (Entry<InetAddress, byte[]> e : newKeys.entrySet()) {
|
||||||
|
Native.setTcpMd5Sig(channel.fd().intValue(), e.getKey(), e.getValue());
|
||||||
|
addresses.add(e.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TcpMd5Util() {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2015 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project 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 io.netty.channel.epoll;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ConnectTimeoutException;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class EpollSocketTcpMd5Test {
|
||||||
|
private static final byte[] SERVER_KEY = "abc".getBytes(CharsetUtil.US_ASCII);
|
||||||
|
private static final byte[] BAD_KEY = "def".getBytes(CharsetUtil.US_ASCII);
|
||||||
|
private static EventLoopGroup GROUP;
|
||||||
|
private EpollServerSocketChannel server;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() {
|
||||||
|
GROUP = new EpollEventLoopGroup(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClass() {
|
||||||
|
GROUP.shutdownGracefully();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
Bootstrap bootstrap = new Bootstrap();
|
||||||
|
server = (EpollServerSocketChannel) bootstrap.group(GROUP)
|
||||||
|
.channel(EpollServerSocketChannel.class)
|
||||||
|
.handler(new ChannelInboundHandlerAdapter())
|
||||||
|
.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void teardown() {
|
||||||
|
server.close().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerSocketChannelOption() throws Exception {
|
||||||
|
server.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.singletonMap(InetAddress.getLocalHost(),
|
||||||
|
SERVER_KEY));
|
||||||
|
server.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.<InetAddress, byte[]>emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testServerOption() throws Exception {
|
||||||
|
Bootstrap bootstrap = new Bootstrap();
|
||||||
|
EpollServerSocketChannel ch = (EpollServerSocketChannel) bootstrap.group(GROUP)
|
||||||
|
.channel(EpollServerSocketChannel.class)
|
||||||
|
.handler(new ChannelInboundHandlerAdapter())
|
||||||
|
.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
|
||||||
|
|
||||||
|
ch.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.singletonMap(InetAddress.getLocalHost(),
|
||||||
|
SERVER_KEY));
|
||||||
|
ch.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.<InetAddress, byte[]>emptyMap());
|
||||||
|
|
||||||
|
ch.close().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = ConnectTimeoutException.class)
|
||||||
|
public void testKeyMismatch() throws Exception {
|
||||||
|
server.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.singletonMap(InetAddress.getLocalHost(),
|
||||||
|
SERVER_KEY));
|
||||||
|
|
||||||
|
EpollSocketChannel client = (EpollSocketChannel) new Bootstrap().group(GROUP)
|
||||||
|
.channel(EpollSocketChannel.class)
|
||||||
|
.handler(new ChannelInboundHandlerAdapter())
|
||||||
|
.option(EpollChannelOption.TCP_MD5SIG,
|
||||||
|
Collections.singletonMap(InetAddress.getLocalHost(), BAD_KEY))
|
||||||
|
.connect(server.localAddress()).syncUninterruptibly().channel();
|
||||||
|
client.close().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testKeyMatch() throws Exception {
|
||||||
|
server.config().setOption(EpollChannelOption.TCP_MD5SIG, Collections.singletonMap(InetAddress.getLocalHost(),
|
||||||
|
SERVER_KEY));
|
||||||
|
|
||||||
|
EpollSocketChannel client = (EpollSocketChannel) new Bootstrap().group(GROUP)
|
||||||
|
.channel(EpollSocketChannel.class)
|
||||||
|
.handler(new ChannelInboundHandlerAdapter())
|
||||||
|
.option(EpollChannelOption.TCP_MD5SIG,
|
||||||
|
Collections.singletonMap(InetAddress.getLocalHost(), SERVER_KEY))
|
||||||
|
.connect(server.localAddress()).syncUninterruptibly().channel();
|
||||||
|
client.close().syncUninterruptibly();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user