diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java new file mode 100644 index 000000000..464b85f12 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerBoundEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.proxy; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.ListenerType; +import java.net.InetSocketAddress; + +/** + * This event is fired by the proxy after a listener starts accepting connections. + */ +public final class ListenerBoundEvent { + + private final InetSocketAddress address; + private final ListenerType listenerType; + + public ListenerBoundEvent(InetSocketAddress address, ListenerType listenerType) { + this.address = Preconditions.checkNotNull(address, "address"); + this.listenerType = Preconditions.checkNotNull(listenerType, "listenerType"); + } + + public InetSocketAddress getAddress() { + return address; + } + + public ListenerType getListenerType() { + return listenerType; + } + + @Override + public String toString() { + return "ListenerBoundEvent{" + + "address=" + address + + ", listenerType=" + listenerType + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java new file mode 100644 index 000000000..9cfe28d77 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/proxy/ListenerCloseEvent.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.proxy; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.ListenerType; +import java.net.InetSocketAddress; + +/** + * This event is fired by the proxy before the proxy stops accepting connections. + */ +public final class ListenerCloseEvent { + + private final InetSocketAddress address; + private final ListenerType listenerType; + + public ListenerCloseEvent(InetSocketAddress address, ListenerType listenerType) { + this.address = Preconditions.checkNotNull(address, "address"); + this.listenerType = Preconditions.checkNotNull(listenerType, "listenerType"); + } + + public InetSocketAddress getAddress() { + return address; + } + + public ListenerType getListenerType() { + return listenerType; + } + + @Override + public String toString() { + return "ListenerCloseEvent{" + + "address=" + address + + ", listenerType=" + listenerType + + '}'; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/network/ListenerType.java b/api/src/main/java/com/velocitypowered/api/network/ListenerType.java new file mode 100644 index 000000000..e0c901307 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/network/ListenerType.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.network; + +/** + * Represents each listener type. + */ +public enum ListenerType { + MINECRAFT("Minecraft"), + QUERY("Query"); + + final String name; + + ListenerType(final String name) { + this.name = name; + } + + @Override + public String toString() { + return this.name; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java index 9a402162a..9bf9e78b0 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/ConnectionManager.java @@ -21,6 +21,9 @@ import static org.asynchttpclient.Dsl.asyncHttpClient; import static org.asynchttpclient.Dsl.config; import com.google.common.base.Preconditions; +import com.velocitypowered.api.event.proxy.ListenerBoundEvent; +import com.velocitypowered.api.event.proxy.ListenerCloseEvent; +import com.velocitypowered.api.network.ListenerType; import com.velocitypowered.natives.util.Natives; import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver; @@ -51,7 +54,7 @@ public final class ConnectionManager { private static final WriteBufferWaterMark SERVER_WRITE_MARK = new WriteBufferWaterMark(1 << 20, 1 << 21); private static final Logger LOGGER = LogManager.getLogger(ConnectionManager.class); - private final Map endpoints = new HashMap<>(); + private final Map endpoints = new HashMap<>(); private final TransportType transportType; private final EventLoopGroup bossGroup; private final EventLoopGroup workerGroup; @@ -125,8 +128,12 @@ public final class ConnectionManager { .addListener((ChannelFutureListener) future -> { final Channel channel = future.channel(); if (future.isSuccess()) { - this.endpoints.put(address, channel); + this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT)); LOGGER.info("Listening on {}", channel.localAddress()); + + // Fire the proxy bound event after the socket is bound + server.getEventManager().fireAndForget( + new ListenerBoundEvent(address, ListenerType.MINECRAFT)); } else { LOGGER.error("Can't bind to {}", address, future.cause()); } @@ -150,8 +157,12 @@ public final class ConnectionManager { .addListener((ChannelFutureListener) future -> { final Channel channel = future.channel(); if (future.isSuccess()) { - this.endpoints.put(address, channel); + this.endpoints.put(address, new Endpoint(channel, ListenerType.QUERY)); LOGGER.info("Listening for GS4 query on {}", channel.localAddress()); + + // Fire the proxy bound event after the socket is bound + server.getEventManager().fireAndForget( + new ListenerBoundEvent(address, ListenerType.QUERY)); } else { LOGGER.error("Can't bind to {}", bootstrap.config().localAddress(), future.cause()); } @@ -185,7 +196,14 @@ public final class ConnectionManager { * @param oldBind the endpoint to close */ public void close(InetSocketAddress oldBind) { - Channel serverChannel = endpoints.remove(oldBind); + Endpoint endpoint = endpoints.remove(oldBind); + + // Fire proxy close event to notify plugins of socket close. We block since plugins + // should have a chance to be notified before the server stops accepting connections. + server.getEventManager().fire(new ListenerCloseEvent(oldBind, endpoint.getType())).join(); + + Channel serverChannel = endpoint.getChannel(); + Preconditions.checkState(serverChannel != null, "Endpoint %s not registered", oldBind); LOGGER.info("Closing endpoint {}", serverChannel.localAddress()); serverChannel.close().syncUninterruptibly(); @@ -195,10 +213,17 @@ public final class ConnectionManager { * Closes all endpoints. */ public void shutdown() { - for (final Channel endpoint : this.endpoints.values()) { + for (final Map.Entry entry : this.endpoints.entrySet()) { + final InetSocketAddress address = entry.getKey(); + final Endpoint endpoint = entry.getValue(); + + // Fire proxy close event to notify plugins of socket close. We block since plugins + // should have a chance to be notified before the server stops accepting connections. + server.getEventManager().fire(new ListenerCloseEvent(address, endpoint.getType())).join(); + try { - LOGGER.info("Closing endpoint {}", endpoint.localAddress()); - endpoint.close().sync(); + LOGGER.info("Closing endpoint {}", address); + endpoint.getChannel().close().sync(); } catch (final InterruptedException e) { LOGGER.info("Interrupted whilst closing endpoint", e); Thread.currentThread().interrupt(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java b/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java new file mode 100644 index 000000000..af453e636 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/Endpoint.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.network; + +import com.google.common.base.Preconditions; +import com.velocitypowered.api.network.ListenerType; +import io.netty.channel.Channel; + +/** + * Represents a listener endpoint. + */ +public final class Endpoint { + private final Channel channel; + private final ListenerType type; + + public Endpoint(Channel channel, ListenerType type) { + this.channel = Preconditions.checkNotNull(channel, "channel"); + this.type = Preconditions.checkNotNull(type, "type"); + } + + public Channel getChannel() { + return channel; + } + + public ListenerType getType() { + return type; + } +}