Add support for TCP_DEFER_ACCEPT and TCP_QUICKACK
Motivation: When using the native transport have support for TCP_DEFER_ACCEPT or / and TCP_QUICKACK can be useful. Modifications: - Add support for TCP_DEFER_ACCEPT and TCP_QUICKACK - Ad unit tests Result: TCP_DEFER_ACCEPT and TCP_QUICKACK are supported now. Conflicts: transport-native-epoll/src/main/java/io/netty/channel/epoll/EpollChannelOption.java
This commit is contained in:
parent
a47c03c3d7
commit
5317c7b648
@ -582,6 +582,14 @@ static void netty_unix_socket_setSoLinger(JNIEnv* env, jclass clazz, jint fd, ji
|
|||||||
netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_LINGER, &solinger, sizeof(solinger));
|
netty_unix_socket_setOption(env, fd, SOL_SOCKET, SO_LINGER, &solinger, sizeof(solinger));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void netty_unix_socket_setTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd, jint optval) {
|
||||||
|
netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void netty_unix_socket_setTcpQuickAck(JNIEnv* env, jclass clazz, jint fd, jint optval) {
|
||||||
|
netty_unix_socket_setOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval));
|
||||||
|
}
|
||||||
|
|
||||||
static jint netty_unix_socket_isKeepAlive(JNIEnv* env, jclass clazz, jint fd) {
|
static jint netty_unix_socket_isKeepAlive(JNIEnv* env, jclass clazz, jint fd) {
|
||||||
int optval;
|
int optval;
|
||||||
if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) {
|
if (netty_unix_socket_getOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval)) == -1) {
|
||||||
@ -641,6 +649,22 @@ static jint netty_unix_socket_getSoError(JNIEnv* env, jclass clazz, jint fd) {
|
|||||||
}
|
}
|
||||||
return optval;
|
return optval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static jint netty_unix_socket_getTcpDeferAccept(JNIEnv* env, jclass clazz, jint fd) {
|
||||||
|
int optval;
|
||||||
|
if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &optval, sizeof(optval)) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return optval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static jint netty_unix_socket_isTcpQuickAck(JNIEnv* env, jclass clazz, jint fd) {
|
||||||
|
int optval;
|
||||||
|
if (netty_unix_socket_getOption(env, fd, IPPROTO_TCP, TCP_QUICKACK, &optval, sizeof(optval)) == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return optval;
|
||||||
|
}
|
||||||
// JNI Registered Methods End
|
// JNI Registered Methods End
|
||||||
|
|
||||||
// JNI Method Registration Table Begin
|
// JNI Method Registration Table Begin
|
||||||
@ -669,13 +693,17 @@ static const JNINativeMethod fixed_method_table[] = {
|
|||||||
{ "setKeepAlive", "(II)V", (void *) netty_unix_socket_setKeepAlive },
|
{ "setKeepAlive", "(II)V", (void *) netty_unix_socket_setKeepAlive },
|
||||||
{ "setTcpCork", "(II)V", (void *) netty_unix_socket_setTcpCork },
|
{ "setTcpCork", "(II)V", (void *) netty_unix_socket_setTcpCork },
|
||||||
{ "setSoLinger", "(II)V", (void *) netty_unix_socket_setSoLinger },
|
{ "setSoLinger", "(II)V", (void *) netty_unix_socket_setSoLinger },
|
||||||
|
{ "setTcpDeferAccept", "(II)V", (void *) netty_unix_socket_setTcpDeferAccept },
|
||||||
|
{ "setTcpQuickAck", "(II)V", (void *) netty_unix_socket_setTcpQuickAck },
|
||||||
{ "isKeepAlive", "(I)I", (void *) netty_unix_socket_isKeepAlive },
|
{ "isKeepAlive", "(I)I", (void *) netty_unix_socket_isKeepAlive },
|
||||||
{ "isTcpNoDelay", "(I)I", (void *) netty_unix_socket_isTcpNoDelay },
|
{ "isTcpNoDelay", "(I)I", (void *) netty_unix_socket_isTcpNoDelay },
|
||||||
{ "getReceiveBufferSize", "(I)I", (void *) netty_unix_socket_getReceiveBufferSize },
|
{ "getReceiveBufferSize", "(I)I", (void *) netty_unix_socket_getReceiveBufferSize },
|
||||||
{ "getSendBufferSize", "(I)I", (void *) netty_unix_socket_getSendBufferSize },
|
{ "getSendBufferSize", "(I)I", (void *) netty_unix_socket_getSendBufferSize },
|
||||||
{ "isTcpCork", "(I)I", (void *) netty_unix_socket_isTcpCork },
|
{ "isTcpCork", "(I)I", (void *) netty_unix_socket_isTcpCork },
|
||||||
{ "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger },
|
{ "getSoLinger", "(I)I", (void *) netty_unix_socket_getSoLinger },
|
||||||
{ "getSoError", "(I)I", (void *) netty_unix_socket_getSoError }
|
{ "getSoError", "(I)I", (void *) netty_unix_socket_getSoError },
|
||||||
|
{ "getTcpDeferAccept", "(I)I", (void *) netty_unix_socket_getTcpDeferAccept },
|
||||||
|
{ "isTcpQuickAck", "(I)I", (void *) netty_unix_socket_isTcpQuickAck }
|
||||||
};
|
};
|
||||||
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);
|
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);
|
||||||
|
|
||||||
|
@ -32,6 +32,8 @@ public final class EpollChannelOption<T> extends ChannelOption<T> {
|
|||||||
public static final ChannelOption<Integer> TCP_USER_TIMEOUT = valueOf("TCP_USER_TIMEOUT");
|
public static final ChannelOption<Integer> TCP_USER_TIMEOUT = valueOf("TCP_USER_TIMEOUT");
|
||||||
public static final ChannelOption<Boolean> IP_FREEBIND = valueOf("IP_FREEBIND");
|
public static final ChannelOption<Boolean> IP_FREEBIND = valueOf("IP_FREEBIND");
|
||||||
public static final ChannelOption<Integer> TCP_FASTOPEN = valueOf("TCP_FASTOPEN");
|
public static final ChannelOption<Integer> TCP_FASTOPEN = valueOf("TCP_FASTOPEN");
|
||||||
|
public static final ChannelOption<Integer> TCP_DEFER_ACCEPT = valueOf("TCP_DEFER_ACCEPT");
|
||||||
|
public static final ChannelOption<Integer> TCP_QUICKACK = valueOf("TCP_QUICKACK");
|
||||||
|
|
||||||
public static final ChannelOption<DomainSocketReadMode> DOMAIN_SOCKET_READ_MODE =
|
public static final ChannelOption<DomainSocketReadMode> DOMAIN_SOCKET_READ_MODE =
|
||||||
valueOf("DOMAIN_SOCKET_READ_MODE");
|
valueOf("DOMAIN_SOCKET_READ_MODE");
|
||||||
|
@ -38,7 +38,8 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<ChannelOption<?>, Object> getOptions() {
|
public Map<ChannelOption<?>, Object> getOptions() {
|
||||||
return getOptions(super.getOptions(), EpollChannelOption.SO_REUSEPORT, EpollChannelOption.IP_FREEBIND);
|
return getOptions(super.getOptions(), EpollChannelOption.SO_REUSEPORT, EpollChannelOption.IP_FREEBIND,
|
||||||
|
EpollChannelOption.TCP_DEFER_ACCEPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -50,6 +51,9 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
if (option == EpollChannelOption.IP_FREEBIND) {
|
if (option == EpollChannelOption.IP_FREEBIND) {
|
||||||
return (T) Boolean.valueOf(isFreeBind());
|
return (T) Boolean.valueOf(isFreeBind());
|
||||||
}
|
}
|
||||||
|
if (option == EpollChannelOption.TCP_DEFER_ACCEPT) {
|
||||||
|
return (T) Integer.valueOf(getTcpDeferAccept());
|
||||||
|
}
|
||||||
return super.getOption(option);
|
return super.getOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +69,8 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
||||||
setTcpMd5Sig(m);
|
setTcpMd5Sig(m);
|
||||||
|
} else if (option == EpollChannelOption.TCP_DEFER_ACCEPT) {
|
||||||
|
setTcpDeferAccept((Integer) value);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -194,4 +200,19 @@ public final class EpollServerSocketChannelConfig extends EpollServerChannelConf
|
|||||||
Native.setIpFreeBind(channel.fd().intValue(), freeBind ? 1: 0);
|
Native.setIpFreeBind(channel.fd().intValue(), freeBind ? 1: 0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code TCP_DEFER_ACCEPT} option on the socket. See {@code man 7 tcp} for more details.
|
||||||
|
*/
|
||||||
|
public EpollServerSocketChannelConfig setTcpDeferAccept(int deferAccept) {
|
||||||
|
channel.fd().setTcpDeferAccept(deferAccept);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a positive value if <a href="http://linux.die.net/man/7/tcp">TCP_DEFER_ACCEPT</a> is enabled.
|
||||||
|
*/
|
||||||
|
public int getTcpDeferAccept() {
|
||||||
|
return channel.fd().getTcpDeferAccept();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
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);
|
EpollChannelOption.TCP_MD5SIG, EpollChannelOption.TCP_QUICKACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@ -99,6 +99,9 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
|
if (option == EpollChannelOption.TCP_USER_TIMEOUT) {
|
||||||
return (T) Integer.valueOf(getTcpUserTimeout());
|
return (T) Integer.valueOf(getTcpUserTimeout());
|
||||||
}
|
}
|
||||||
|
if (option == EpollChannelOption.TCP_QUICKACK) {
|
||||||
|
return (T) Boolean.valueOf(isTcpQuickAck());
|
||||||
|
}
|
||||||
return super.getOption(option);
|
return super.getOption(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,6 +141,8 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
final Map<InetAddress, byte[]> m = (Map<InetAddress, byte[]>) value;
|
||||||
setTcpMd5Sig(m);
|
setTcpMd5Sig(m);
|
||||||
|
} else if (option == EpollChannelOption.TCP_QUICKACK) {
|
||||||
|
setTcpQuickAck((Boolean) value);
|
||||||
} else {
|
} else {
|
||||||
return super.setOption(option, value);
|
return super.setOption(option, value);
|
||||||
}
|
}
|
||||||
@ -323,7 +328,7 @@ 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.
|
* 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.
|
* 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.
|
* Allowing them being read would mean anyone with access to the channel could get them.
|
||||||
@ -333,6 +338,23 @@ public final class EpollSocketChannelConfig extends EpollChannelConfig implement
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@code TCP_QUICKACK} option on the socket. See <a href="http://linux.die.net/man/7/tcp">TCP_QUICKACK</a>
|
||||||
|
* for more details.
|
||||||
|
*/
|
||||||
|
public EpollSocketChannelConfig setTcpQuickAck(boolean quickAck) {
|
||||||
|
channel.fd().setTcpQuickAck(quickAck);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if <a href="http://linux.die.net/man/7/tcp">TCP_QUICKACK</a> is enabled,
|
||||||
|
* {@code false} otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isTcpQuickAck() {
|
||||||
|
return channel.fd().isTcpQuickAck();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAllowHalfClosure() {
|
public boolean isAllowHalfClosure() {
|
||||||
return allowHalfClosure;
|
return allowHalfClosure;
|
||||||
|
@ -263,6 +263,14 @@ public final class Socket extends FileDescriptor {
|
|||||||
return getSoLinger(intValue());
|
return getSoLinger(intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getTcpDeferAccept() {
|
||||||
|
return getTcpDeferAccept(intValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTcpQuickAck() {
|
||||||
|
return isTcpQuickAck(intValue()) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public int getSoError() {
|
public int getSoError() {
|
||||||
return getSoError(intValue());
|
return getSoError(intValue());
|
||||||
}
|
}
|
||||||
@ -291,6 +299,14 @@ public final class Socket extends FileDescriptor {
|
|||||||
setSoLinger(intValue(), soLinger);
|
setSoLinger(intValue(), soLinger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTcpDeferAccept(int deferAccept) {
|
||||||
|
setTcpDeferAccept(intValue(), deferAccept);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTcpQuickAck(boolean quickAck) {
|
||||||
|
setTcpQuickAck(intValue(), quickAck ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Socket{" +
|
return "Socket{" +
|
||||||
@ -357,6 +373,8 @@ public final class Socket extends FileDescriptor {
|
|||||||
private static native int isTcpCork(int fd);
|
private static native int isTcpCork(int fd);
|
||||||
private static native int getSoLinger(int fd);
|
private static native int getSoLinger(int fd);
|
||||||
private static native int getSoError(int fd);
|
private static native int getSoError(int fd);
|
||||||
|
private static native int getTcpDeferAccept(int fd);
|
||||||
|
private static native int isTcpQuickAck(int fd);
|
||||||
|
|
||||||
private static native void setKeepAlive(int fd, int keepAlive);
|
private static native void setKeepAlive(int fd, int keepAlive);
|
||||||
private static native void setReceiveBufferSize(int fd, int receiveBufferSize);
|
private static native void setReceiveBufferSize(int fd, int receiveBufferSize);
|
||||||
@ -364,4 +382,6 @@ public final class Socket extends FileDescriptor {
|
|||||||
private static native void setTcpNoDelay(int fd, int tcpNoDelay);
|
private static native void setTcpNoDelay(int fd, int tcpNoDelay);
|
||||||
private static native void setTcpCork(int fd, int tcpCork);
|
private static native void setTcpCork(int fd, int tcpCork);
|
||||||
private static native void setSoLinger(int fd, int soLinger);
|
private static native void setSoLinger(int fd, int soLinger);
|
||||||
|
private static native void setTcpDeferAccept(int fd, int deferAccept);
|
||||||
|
private static native void setTcpQuickAck(int fd, int quickAck);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 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.ServerBootstrap;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class EpollServerSocketChannelConfigTest {
|
||||||
|
|
||||||
|
private static EventLoopGroup group;
|
||||||
|
private static EpollServerSocketChannel ch;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void before() {
|
||||||
|
group = new EpollEventLoopGroup(1);
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
ch = (EpollServerSocketChannel) bootstrap.group(group)
|
||||||
|
.channel(EpollServerSocketChannel.class)
|
||||||
|
.childHandler(new ChannelInboundHandlerAdapter())
|
||||||
|
.bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void after() {
|
||||||
|
try {
|
||||||
|
ch.close().syncUninterruptibly();
|
||||||
|
} finally {
|
||||||
|
group.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTcpDeferAccept() {
|
||||||
|
ch.config().setTcpDeferAccept(0);
|
||||||
|
assertEquals(0, ch.config().getTcpDeferAccept());
|
||||||
|
ch.config().setTcpDeferAccept(10);
|
||||||
|
// The returned value may be bigger then what we set.
|
||||||
|
// See http://www.spinics.net/lists/netdev/msg117330.html
|
||||||
|
assertTrue(10 <= ch.config().getTcpDeferAccept());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReusePort() {
|
||||||
|
ch.config().setReusePort(false);
|
||||||
|
assertFalse(ch.config().isReusePort());
|
||||||
|
ch.config().setReusePort(true);
|
||||||
|
assertTrue(ch.config().isReusePort());
|
||||||
|
}
|
||||||
|
}
|
@ -47,8 +47,12 @@ public class EpollSocketChannelConfigTest {
|
|||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void after() {
|
public static void after() {
|
||||||
|
try {
|
||||||
|
ch.close().syncUninterruptibly();
|
||||||
|
} finally {
|
||||||
group.shutdownGracefully();
|
group.shutdownGracefully();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private long randLong(long min, long max) {
|
private long randLong(long min, long max) {
|
||||||
return min + nextLong(max - min + 1);
|
return min + nextLong(max - min + 1);
|
||||||
@ -110,4 +114,12 @@ public class EpollSocketChannelConfigTest {
|
|||||||
ch.config().setTcpCork(true);
|
ch.config().setTcpCork(true);
|
||||||
assertTrue(ch.config().isTcpCork());
|
assertTrue(ch.config().isTcpCork());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTcpQickAck() {
|
||||||
|
ch.config().setTcpQuickAck(false);
|
||||||
|
assertFalse(ch.config().isTcpQuickAck());
|
||||||
|
ch.config().setTcpQuickAck(true);
|
||||||
|
assertTrue(ch.config().isTcpQuickAck());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user