/** * This class should not be constructed directly instead use HttpProxyBuilder to build and configure * this class * * @see ProxyBuilder * @author jamesdbloom */ public interface Proxy { public static final AttributeKey<Proxy> HTTP_PROXY = AttributeKey.valueOf("HTTP_PROXY"); public static final AttributeKey<LogFilter> LOG_FILTER = AttributeKey.valueOf("PROXY_LOG_FILTER"); public static final AttributeKey<InetSocketAddress> REMOTE_SOCKET = AttributeKey.valueOf("REMOTE_SOCKET"); public static final AttributeKey<InetSocketAddress> HTTP_CONNECT_SOCKET = AttributeKey.valueOf("HTTP_CONNECT_SOCKET"); public void stop(); public boolean isRunning(); }
@Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { LOGGER.debug( String.format("[%s]\n========关闭连接=======", ctx.channel().localAddress().toString())); LOGGER.debug(ctx.channel().remoteAddress().toString()); LOGGER.debug(ctx.channel().attr(AttributeKey.valueOf("haha")).get().toString()); }
static { b = MarkerManager.getMarker("NETWORK_PACKETS", a); c = AttributeKey.valueOf("protocol"); d = new class_nw() { protected NioEventLoopGroup a() { return new NioEventLoopGroup( 0, (new ThreadFactoryBuilder()) .setNameFormat("Netty Client IO #%d") .setDaemon(true) .build()); } // $FF: synthetic method @Override protected Object b() { return this.a(); } }; e = new class_nw() { protected EpollEventLoopGroup a() { return new EpollEventLoopGroup( 0, (new ThreadFactoryBuilder()) .setNameFormat("Netty Epoll Client IO #%d") .setDaemon(true) .build()); } // $FF: synthetic method @Override protected Object b() { return this.a(); } }; f = new class_nw() { protected LocalEventLoopGroup a() { return new LocalEventLoopGroup( 0, (new ThreadFactoryBuilder()) .setNameFormat("Netty Local Client IO #%d") .setDaemon(true) .build()); } // $FF: synthetic method @Override protected Object b() { return this.a(); } }; }
@Sharable public class HttpClientHandler extends ChannelDuplexHandler { public static final AttributeKey<Boolean> HTTP_CLIENT_HANDLER_TRIGGERED_ERROR = AttributeKey.valueOf("__HttpClientHandler_ErrorTriggered"); private final Logger logger = getLogger(getClass()); private final HttpClientResponseHandler httpClientResponseHandler; private final HttpClientRequestHandler httpClientRequestHandler; public HttpClientHandler( HttpClientResponseHandler httpClientResponseHandler, HttpClientRequestHandler httpClientRequestHandler) { this.httpClientResponseHandler = httpClientResponseHandler; this.httpClientRequestHandler = httpClientRequestHandler; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { httpClientResponseHandler.handleResponse(ctx, msg); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { httpClientRequestHandler.handleRequest(ctx, msg, promise); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { httpClientResponseHandler.handleChannelInactive(ctx); super.channelReadComplete(ctx); } @Override public void exceptionCaught(ChannelHandlerContext ctx, final Throwable cause) { logger.trace("Exception on channel " + ctx.channel(), cause); ctx.channel().attr(HTTP_CLIENT_HANDLER_TRIGGERED_ERROR).set(true); HttpRequestContext httpRequestContext = ctx.channel().attr(HttpRequestContext.HTTP_REQUEST_ATTRIBUTE_KEY).get(); if (httpRequestContext != null) { httpRequestContext.getRequestEventBus().triggerEvent(Event.ERROR, httpRequestContext, cause); } } }
@Override public <X> X getAttribute(String key, Class<X> clz) { AttributeKey<X> attributeKey = AttributeKey.valueOf(key); Attribute<X> attribute = channel.attr(attributeKey); return attribute == null ? null : attribute.get(); }
@Override public <X, Y extends X> void setAttribute(String key, Class<X> clz, Y value) { AttributeKey<X> attributeKey = AttributeKey.valueOf(key); channel.attr(attributeKey).set(value); }
/** * AbstractTrafficShapingHandler allows to limit the global bandwidth (see {@link * GlobalTrafficShapingHandler}) or per session bandwidth (see {@link * ChannelTrafficShapingHandler}), as traffic shaping. It allows you to implement an almost real * time monitoring of the bandwidth using the monitors from {@link TrafficCounter} that will call * back every checkInterval the method doAccounting of this handler.<br> * <br> * If you want for any particular reasons to stop the monitoring (accounting) or to change the * read/write limit or the check interval, several methods allow that for you:<br> * * <ul> * <li><tt>configure</tt> allows you to change read or write limits, or the checkInterval * <li><tt>getTrafficCounter</tt> allows you to have access to the TrafficCounter and so to stop * or start the monitoring, to change the checkInterval directly, or to have access to its * values. * </ul> */ public abstract class AbstractTrafficShapingHandler extends ChannelDuplexHandler { /** Default delay between two checks: 1s */ public static final long DEFAULT_CHECK_INTERVAL = 1000; /** Default minimal time to wait */ private static final long MINIMAL_WAIT = 10; /** Traffic Counter */ protected TrafficCounter trafficCounter; /** Limit in B/s to apply to write */ private long writeLimit; /** Limit in B/s to apply to read */ private long readLimit; /** Delay between two performance snapshots */ protected long checkInterval = DEFAULT_CHECK_INTERVAL; // default 1 s private static final AttributeKey<Boolean> READ_SUSPENDED = AttributeKey.valueOf(AbstractTrafficShapingHandler.class.getName() + ".READ_SUSPENDED"); private static final AttributeKey<Runnable> REOPEN_TASK = AttributeKey.valueOf(AbstractTrafficShapingHandler.class.getName() + ".REOPEN_TASK"); /** @param newTrafficCounter the TrafficCounter to set */ void setTrafficCounter(TrafficCounter newTrafficCounter) { trafficCounter = newTrafficCounter; } /** * @param writeLimit 0 or a limit in bytes/s * @param readLimit 0 or a limit in bytes/s * @param checkInterval The delay between two computations of performances for channels or 0 if no * stats are to be computed */ protected AbstractTrafficShapingHandler(long writeLimit, long readLimit, long checkInterval) { this.writeLimit = writeLimit; this.readLimit = readLimit; this.checkInterval = checkInterval; } /** * Constructor using default Check Interval * * @param writeLimit 0 or a limit in bytes/s * @param readLimit 0 or a limit in bytes/s */ protected AbstractTrafficShapingHandler(long writeLimit, long readLimit) { this(writeLimit, readLimit, DEFAULT_CHECK_INTERVAL); } /** Constructor using NO LIMIT and default Check Interval */ protected AbstractTrafficShapingHandler() { this(0, 0, DEFAULT_CHECK_INTERVAL); } /** * Constructor using NO LIMIT * * @param checkInterval The delay between two computations of performances for channels or 0 if no * stats are to be computed */ protected AbstractTrafficShapingHandler(long checkInterval) { this(0, 0, checkInterval); } /** * Change the underlying limitations and check interval. * * @param newWriteLimit The new write limit (in bytes) * @param newReadLimit The new read limit (in bytes) * @param newCheckInterval The new check interval (in milliseconds) */ public void configure(long newWriteLimit, long newReadLimit, long newCheckInterval) { configure(newWriteLimit, newReadLimit); configure(newCheckInterval); } /** * Change the underlying limitations. * * @param newWriteLimit The new write limit (in bytes) * @param newReadLimit The new read limit (in bytes) */ public void configure(long newWriteLimit, long newReadLimit) { writeLimit = newWriteLimit; readLimit = newReadLimit; if (trafficCounter != null) { trafficCounter.resetAccounting(System.currentTimeMillis() + 1); } } /** * Change the check interval. * * @param newCheckInterval The new check interval (in milliseconds) */ public void configure(long newCheckInterval) { checkInterval = newCheckInterval; if (trafficCounter != null) { trafficCounter.configure(checkInterval); } } /** * Called each time the accounting is computed from the TrafficCounters. This method could be used * for instance to implement almost real time accounting. * * @param counter the TrafficCounter that computes its performance */ @SuppressWarnings("unused") protected void doAccounting(TrafficCounter counter) { // NOOP by default } /** Class to implement setReadable at fix time */ private static final class ReopenReadTimerTask implements Runnable { final ChannelHandlerContext ctx; ReopenReadTimerTask(ChannelHandlerContext ctx) { this.ctx = ctx; } @Override public void run() { ctx.attr(READ_SUSPENDED).set(false); ctx.read(); } } /** @return the time that should be necessary to wait to respect limit. Can be negative time */ private static long getTimeToWait(long limit, long bytes, long lastTime, long curtime) { long interval = curtime - lastTime; if (interval <= 0) { // Time is too short, so just lets continue return 0; } return (bytes * 1000 / limit - interval) / 10 * 10; } @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { long size = calculateSize(msg); long curtime = System.currentTimeMillis(); if (trafficCounter != null) { trafficCounter.bytesRecvFlowControl(size); if (readLimit == 0) { // no action ctx.fireChannelRead(msg); return; } // compute the number of ms to wait before reopening the channel long wait = getTimeToWait( readLimit, trafficCounter.currentReadBytes(), trafficCounter.lastTime(), curtime); if (wait >= MINIMAL_WAIT) { // At least 10ms seems a minimal // time in order to // try to limit the traffic if (!isSuspended(ctx)) { ctx.attr(READ_SUSPENDED).set(true); // Create a Runnable to reactive the read if needed. If one was create before it will just // be // reused to limit object creation Attribute<Runnable> attr = ctx.attr(REOPEN_TASK); Runnable reopenTask = attr.get(); if (reopenTask == null) { reopenTask = new ReopenReadTimerTask(ctx); attr.set(reopenTask); } ctx.executor().schedule(reopenTask, wait, TimeUnit.MILLISECONDS); } else { // Create a Runnable to update the next handler in the chain. If one was create before it // will // just be reused to limit object creation Runnable bufferUpdateTask = new Runnable() { @Override public void run() { ctx.fireChannelRead(msg); } }; ctx.executor().schedule(bufferUpdateTask, wait, TimeUnit.MILLISECONDS); return; } } } ctx.fireChannelRead(msg); } @Override public void read(ChannelHandlerContext ctx) { if (!isSuspended(ctx)) { ctx.read(); } } private static boolean isSuspended(ChannelHandlerContext ctx) { Boolean suspended = ctx.attr(READ_SUSPENDED).get(); if (suspended == null || Boolean.FALSE.equals(suspended)) { return false; } return true; } @Override public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception { long curtime = System.currentTimeMillis(); long size = calculateSize(msg); if (size > -1 && trafficCounter != null) { trafficCounter.bytesWriteFlowControl(size); if (writeLimit == 0) { ctx.write(msg, promise); return; } // compute the number of ms to wait before continue with the // channel long wait = getTimeToWait( writeLimit, trafficCounter.currentWrittenBytes(), trafficCounter.lastTime(), curtime); if (wait >= MINIMAL_WAIT) { ctx.executor() .schedule( new Runnable() { @Override public void run() { ctx.write(msg, promise); } }, wait, TimeUnit.MILLISECONDS); return; } } ctx.write(msg, promise); } /** @return the current TrafficCounter (if channel is still connected) */ public TrafficCounter trafficCounter() { return trafficCounter; } @Override public String toString() { return "TrafficShaping with Write Limit: " + writeLimit + " Read Limit: " + readLimit + " and Counter: " + (trafficCounter != null ? trafficCounter.toString() : "none"); } /** * Calculate the size of the given {@link Object}. * * <p>This implementation supports {@link ByteBuf} and {@link ByteBufHolder}. Sub-classes may * override this. * * @param msg the msg for which the size should be calculated * @return size the size of the msg or {@code -1} if unknown. */ protected long calculateSize(Object msg) { if (msg instanceof ByteBuf) { return ((ByteBuf) msg).readableBytes(); } if (msg instanceof ByteBufHolder) { return ((ByteBufHolder) msg).content().readableBytes(); } return -1; } }
@Sharable public class EncoderHandler extends ChannelOutboundHandlerAdapter { private static final byte[] OK = "ok".getBytes(CharsetUtil.UTF_8); public static final AttributeKey<String> ORIGIN = AttributeKey.valueOf("origin"); public static final AttributeKey<String> USER_AGENT = AttributeKey.valueOf("userAgent"); public static final AttributeKey<Boolean> B64 = AttributeKey.valueOf("b64"); public static final AttributeKey<Integer> JSONP_INDEX = AttributeKey.valueOf("jsonpIndex"); public static final AttributeKey<Boolean> WRITE_ONCE = AttributeKey.valueOf("writeOnce"); private final Logger log = LoggerFactory.getLogger(getClass()); private final PacketEncoder encoder; private String version; private Configuration configuration; public EncoderHandler(Configuration configuration, PacketEncoder encoder) throws IOException { this.encoder = encoder; this.configuration = configuration; if (configuration.isAddVersionHeader()) { readVersion(); } } private void readVersion() throws IOException { Enumeration<URL> resources = getClass().getClassLoader().getResources("META-INF/MANIFEST.MF"); while (resources.hasMoreElements()) { try { Manifest manifest = new Manifest(resources.nextElement().openStream()); Attributes attrs = manifest.getMainAttributes(); if (attrs == null) { continue; } String name = attrs.getValue("Bundle-Name"); if (name != null && name.equals("netty-socketio")) { version = name + "/" + attrs.getValue("Bundle-Version"); break; } } catch (IOException E) { // skip it } } } private void write(XHROptionsMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) { HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK); HttpHeaders.addHeader(res, "Set-Cookie", "io=" + msg.getSessionId()); HttpHeaders.addHeader(res, CONNECTION, KEEP_ALIVE); HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_HEADERS, CONTENT_TYPE); addOriginHeaders(ctx.channel(), res); ByteBuf out = encoder.allocateBuffer(ctx.alloc()); sendMessage(msg, ctx.channel(), out, res, promise); } private void write(XHRPostMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) { ByteBuf out = encoder.allocateBuffer(ctx.alloc()); out.writeBytes(OK); sendMessage(msg, ctx.channel(), out, "text/html", promise); } private void sendMessage( HttpMessage msg, Channel channel, ByteBuf out, String type, ChannelPromise promise) { HttpResponse res = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.OK); res.headers() .add(CONTENT_TYPE, type) .add("Set-Cookie", "io=" + msg.getSessionId()) .add(CONNECTION, KEEP_ALIVE); addOriginHeaders(channel, res); HttpHeaders.setContentLength(res, out.readableBytes()); // prevent XSS warnings on IE // https://github.com/LearnBoost/socket.io/pull/1333 String userAgent = channel.attr(EncoderHandler.USER_AGENT).get(); if (userAgent != null && (userAgent.contains(";MSIE") || userAgent.contains("Trident/"))) { res.headers().add("X-XSS-Protection", "0"); } sendMessage(msg, channel, out, res, promise); } private void sendMessage( HttpMessage msg, Channel channel, ByteBuf out, HttpResponse res, ChannelPromise promise) { channel.write(res); if (log.isTraceEnabled()) { log.trace( "Out message: {} - sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId()); } if (out.isReadable()) { channel.write(out); } else { out.release(); } channel .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT, promise) .addListener(ChannelFutureListener.CLOSE); } private void addOriginHeaders(Channel channel, HttpResponse res) { if (version != null) { res.headers().add(HttpHeaders.Names.SERVER, version); } if (configuration.getOrigin() != null) { HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, configuration.getOrigin()); HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE); } else { String origin = channel.attr(ORIGIN).get(); if (origin != null) { HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, origin); HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.TRUE); } else { HttpHeaders.addHeader(res, ACCESS_CONTROL_ALLOW_ORIGIN, "*"); } } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (!(msg instanceof BaseMessage)) { super.write(ctx, msg, promise); return; } if (msg instanceof OutPacketMessage) { OutPacketMessage m = (OutPacketMessage) msg; if (m.getTransport() == Transport.WEBSOCKET) { handleWebsocket((OutPacketMessage) msg, ctx, promise); } if (m.getTransport() == Transport.POLLING) { handleHTTP((OutPacketMessage) msg, ctx, promise); } } else if (msg instanceof XHROptionsMessage) { write((XHROptionsMessage) msg, ctx, promise); } else if (msg instanceof XHRPostMessage) { write((XHRPostMessage) msg, ctx, promise); } } private void handleWebsocket( final OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException { while (true) { Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport()); Packet packet = queue.poll(); if (packet == null) { promise.setSuccess(); break; } final ByteBuf out = encoder.allocateBuffer(ctx.alloc()); encoder.encodePacket(packet, out, ctx.alloc(), true); WebSocketFrame res = new TextWebSocketFrame(out); if (log.isTraceEnabled()) { log.trace( "Out message: {} sessionId: {}", out.toString(CharsetUtil.UTF_8), msg.getSessionId()); } if (out.isReadable()) { ctx.channel().writeAndFlush(res, promise); } else { promise.setSuccess(); out.release(); } for (ByteBuf buf : packet.getAttachments()) { ByteBuf outBuf = encoder.allocateBuffer(ctx.alloc()); outBuf.writeByte(4); outBuf.writeBytes(buf); if (log.isTraceEnabled()) { log.trace( "Out attachment: {} sessionId: {}", ByteBufUtil.hexDump(outBuf), msg.getSessionId()); } ctx.channel().writeAndFlush(new BinaryWebSocketFrame(outBuf)); } } } private void handleHTTP(OutPacketMessage msg, ChannelHandlerContext ctx, ChannelPromise promise) throws IOException { Channel channel = ctx.channel(); Attribute<Boolean> attr = channel.attr(WRITE_ONCE); Queue<Packet> queue = msg.getClientHead().getPacketsQueue(msg.getTransport()); if (!channel.isActive() || queue.isEmpty() || !attr.compareAndSet(null, true)) { promise.setSuccess(); return; } ByteBuf out = encoder.allocateBuffer(ctx.alloc()); Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get(); if (b64 != null && b64) { Integer jsonpIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get(); encoder.encodeJsonP(jsonpIndex, queue, out, ctx.alloc(), 50); String type = "application/javascript"; if (jsonpIndex == null) { type = "text/plain"; } sendMessage(msg, channel, out, type, promise); } else { encoder.encodePackets(queue, out, ctx.alloc(), 50); sendMessage(msg, channel, out, "application/octet-stream", promise); } } }
public class OutboundMessageHandler { private static final long FIVE_MINUTES = TimeUnit.MINUTES.toMillis(5); private static final AttributeKey<Long> lastMessageAttrKey = AttributeKey.valueOf("__last_message"); private final Logger logger = LoggerFactory.getLogger(OutboundMessageHandler.class); private final Map<Long, Boolean> checkedUsers = new HashMap<>(); private final Map<String, Channel> connections; private final Bootstrap outboundBootstrap; private final String channel; private final AuthenticationManager authenticationManager; private final ScheduledExecutorService eventLoopGroup; private ScheduledFuture scheduledFuture; public OutboundMessageHandler( Map<String, Channel> connections, String channel, AuthenticationManager authenticationManager, EventLoopGroup eventLoopGroup) { this.connections = connections; this.channel = channel; this.authenticationManager = authenticationManager; this.eventLoopGroup = eventLoopGroup; this.outboundBootstrap = new Bootstrap(); this.outboundBootstrap.group(eventLoopGroup); if (Epoll.isAvailable()) { this.outboundBootstrap.channel(EpollSocketChannel.class); } else { this.outboundBootstrap.channel(NioSocketChannel.class); } this.outboundBootstrap.option(ChannelOption.SO_KEEPALIVE, true); this.outboundBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); final ChannelHandler stringEncoder = new StringEncoder(CharsetUtil.UTF_8); final ChannelHandler stringDecoder = new StringDecoder(CharsetUtil.UTF_8); final ChannelHandler handler = new Handler(); this.outboundBootstrap.handler( new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast(stringEncoder); pipeline.addLast(stringDecoder); pipeline.addLast(handler); } }); } public void shutdown() { this.scheduledFuture.cancel(false); this.connections.values().forEach(Channel::close); } public void onMessage(Message message) { if (message.getType() == MessageType.MSG && message.get(MessageProperty.ROOM).equals("#main")) { long userId = message.get(MessageProperty.USER_ID); String name = message.get(MessageProperty.NAME); UserCredentials userCredentials = null; if (needsFetchingConnectionData(userId)) { userCredentials = fetchConnectionDataForUser(name, userId); } if (userCredentials != null) { String id = userCredentials.getId(); Channel channel = connections.get(id.toLowerCase()); if (channel == null || !channel.isActive()) { try { channel = createConnection(id, userCredentials.getToken()); } catch (InterruptedException e) { logger.warn("", e); } } if (channel != null) { channel.attr(lastMessageAttrKey).set(System.currentTimeMillis()); channel.writeAndFlush( "PRIVMSG #" + this.channel + " :" + message.get(MessageProperty.TEXT) + "\r\n"); } } } } private boolean needsFetchingConnectionData(long userId) { Boolean tmp = checkedUsers.get(userId); return tmp == null || tmp; } private UserCredentials fetchConnectionDataForUser(String userName, long userId) { UserCredentials userCredentials = null; logger.debug("fetching connection data for user {}", userName); boolean r = false; UserAuthDto auth = authenticationManager.getAuthDataForUser(userId, "twitch.tv"); if (auth != null) { String token = auth.getAuthenticationKey(); String extName = auth.getAuthenticationName(); logger.debug("fetched data for user {} with ext name {}", userName, extName, token); if (token != null && extName != null) { userCredentials = new UserCredentials(extName, token); } r = true; } checkedUsers.put(userId, r); return userCredentials; } private Channel createConnection(String id, String token) throws InterruptedException { Channel channel; logger.debug("creating connection for {}", id); ChannelFuture f = outboundBootstrap.connect("irc.twitch.tv", 6667).sync(); channel = f.channel(); channel.write("TWITCHCLIENT 2\r\n"); channel.write("PASS oauth:" + token + "\r\n"); channel.write("NICK " + id + "\r\n"); channel.write("JOIN #" + channel + "\r\n"); channel.flush(); connections.put(id.toLowerCase(), channel); logger.debug("connection created"); return channel; } public void start() { this.scheduledFuture = eventLoopGroup.scheduleAtFixedRate(new ConnectionCleanupTask(), 10, 5, TimeUnit.MINUTES); } private static class UserCredentials { private final String id; private final String token; private UserCredentials(String id, String token) { this.id = id; this.token = token; } public String getId() { return id; } public String getToken() { return token; } } @Sharable private class Handler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { logger.trace("received message {}", msg); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { if (cause instanceof IOException) { logger.debug("exception", cause); } else { logger.warn("exception", cause); } } } private class ConnectionCleanupTask implements Runnable { @Override public void run() { try { logger.debug("starting cleanup"); for (Map.Entry<String, Channel> entry : connections.entrySet()) { Channel channel = entry.getValue(); Long lastMessage = channel.attr(lastMessageAttrKey).get(); if (System.currentTimeMillis() - lastMessage > FIVE_MINUTES) { channel.disconnect(); connections.remove(entry.getKey()); logger.debug("connection released for {}", entry.getKey()); } else if (!channel.isActive()) { connections.remove(entry.getKey()); logger.debug("connection released for {}", entry.getKey()); } } logger.debug("cleanup complete"); } catch (Exception e) { logger.error("exception while clean up", e); } } } }
/** Netty channel handler for routing inbound requests to the proper RequestHandler. */ @Sharable public class HttpRequestChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> { public static final AttributeKey<PooledServerResponse> ATTR_RESPONSE = AttributeKey.<PooledServerResponse>valueOf("response"); private final HttpServerConfig config; private final ServerMessagePool messagePool; public HttpRequestChannelHandler(final HttpServerConfig config_) { super(); config = config_; messagePool = new ServerMessagePool(config.maxConnections()); } @Override public void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest msg) throws Exception { final RequestHandlerMapping mapping = config.getRequestMapping(msg.getUri()); String relativePath = msg.getUri(); if (mapping != null) { relativePath = relativePath.substring(mapping.path().length()); } // Create request/response final PooledServerRequest request = messagePool.getRequest(); // Handle 503 - sanity check, should be caught in acceptor if (request == null) { sendServerError(ctx, new ServerTooBusyException("Maximum concurrent connections reached")); return; } request.init(ctx.channel(), msg, relativePath); final RequestHandler handler = mapping == null ? null : mapping.handler(request); final PooledServerResponse response = messagePool.getResponse(); response.init(ctx, this, handler, request, config.logger()); if (mapping == null) { // No handler found, 404 response.setStatus(HttpResponseStatus.NOT_FOUND); } // Store in ChannelHandlerContext for future reference ctx.attr(ATTR_RESPONSE).set(response); try { // MJS: Dispatch an error if not found or authorized if (response.getStatus() == HttpResponseStatus.UNAUTHORIZED || response.getStatus() == HttpResponseStatus.NOT_FOUND) { config.errorHandler().onError(request, response, null); } else { handler.onRequest(request, response); } } catch (final Throwable t) { // Catch server errors response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); try { config.errorHandler().onError(request, response, t); } catch (final Throwable t2) { response.write( t.getClass() + " was thrown while processing this request. Additionally, " + t2.getClass() + " was thrown while handling this exception."); } config.logger().error(request, response, t); // Force request to end on exception, async handlers cannot allow // unchecked exceptions and still expect to return data if (!response.isFinished()) { response.finish(); } } finally { // If handler did not request async response, finish request if (!response.isFinished() && !response.isSuspended()) { response.finish(); } } } private void sendServerError(final ChannelHandlerContext ctx, final ServerException cause) throws Exception { if (ctx.channel().isActive()) { final ByteBuf content = Unpooled.buffer(); content.writeBytes( (cause.getStatus().code() + " " + cause.getStatus().reasonPhrase() + " - " + cause.getMessage()) .getBytes()); final FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, cause.getStatus()); response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, content.readableBytes()); response.content().writeBytes(content); ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } } @Override public void channelInactive(final ChannelHandlerContext ctx) { final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get(); if (response != null) { try { if (!response.isFinished()) { response.close(); final RequestHandler handler = response.handler(); if (handler != null) { handler.onAbort(response.request(), response); } } } finally { freeHandlers(ctx); } } } @Override public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable exception) throws Exception { final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).get(); if (response != null) { try { try { if (!response.isFinished()) { response.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR); config.errorHandler().onError(response.request(), response, exception); response.close(); final RequestHandler handler = response.handler(); if (handler != null) { handler.onException(response.request(), response, exception); } } } finally { config.logger().error(response.request(), response, exception); } } finally { freeHandlers(ctx); } } } /** Free any request/response handlers related to the current channel handler context. */ public void freeHandlers(final ChannelHandlerContext ctx) { final PooledServerResponse response = ctx.attr(ATTR_RESPONSE).getAndRemove(); if (response != null) { try { final RequestHandler handler = response.handler(); if (handler != null) { handler.onComplete(response.request(), response); } } finally { response.request().release(); messagePool.makeAvailable(response.request()); response.close(); messagePool.makeAvailable(response); } } } }
/** * Channel handler deals with the switch connection and dispatches messages to the higher orders of * control. * * @author Jason Parraga <*****@*****.**> */ class OFChannelHandler extends SimpleChannelInboundHandler<Iterable<OFMessage>> { private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); public static final AttributeKey<OFChannelInfo> ATTR_CHANNEL_INFO = AttributeKey.valueOf("channelInfo"); private final ChannelPipeline pipeline; private final INewOFConnectionListener newConnectionListener; private final SwitchManagerCounters counters; private Channel channel; private final Timer timer; private volatile OFChannelState state; private OFFactory factory; private OFFeaturesReply featuresReply; private volatile OFConnection connection; private final IDebugCounterService debugCounters; private final List<U32> ofBitmaps; /** * transaction Ids to use during handshake. Since only one thread calls into the OFChannelHandler * we don't need atomic. We will count down */ private long handshakeTransactionIds = 0x00FFFFFFFFL; private volatile long echoSendTime; private volatile long featuresLatency; /** * Default implementation for message handlers in any OFChannelState. * * <p>Individual states must override these if they want a behavior that differs from the default. */ public abstract class OFChannelState { void processOFHello(OFHello m) throws IOException { // we only expect hello in the WAIT_HELLO state illegalMessageReceived(m); } void processOFEchoRequest(OFEchoRequest m) throws IOException { sendEchoReply(m); } void processOFEchoReply(OFEchoReply m) throws IOException { /* Update the latency -- halve it for one-way time */ updateLatency(U64.of((System.currentTimeMillis() - echoSendTime) / 2)); } void processOFError(OFErrorMsg m) { logErrorDisconnect(m); } void processOFExperimenter(OFExperimenter m) { unhandledMessageReceived(m); } void processOFFeaturesReply(OFFeaturesReply m) throws IOException { // we only expect features reply in the WAIT_FEATURES_REPLY state illegalMessageReceived(m); } void processOFPortStatus(OFPortStatus m) { unhandledMessageReceived(m); } private final boolean channelHandshakeComplete; OFChannelState(boolean handshakeComplete) { this.channelHandshakeComplete = handshakeComplete; } void logState() { log.debug( "{} OFConnection Handshake - enter state {}", getConnectionInfoString(), this.getClass().getSimpleName()); } /** * enter this state. Can initialize the handler, send the necessary messages, etc. * * @throws IOException */ void enterState() throws IOException { // Do Nothing } /** * Get a string specifying the switch connection, state, and message received. To be used as * message for SwitchStateException or log messages * * @param h The channel handler (to get switch information_ * @param m The OFMessage that has just been received * @param details A string giving more details about the exact nature of the problem. * @return */ // needs to be protected because enum members are acutally subclasses protected String getSwitchStateMessage(OFMessage m, String details) { return String.format( "Switch: [%s], State: [%s], received: [%s]" + ", details: %s", getConnectionInfoString(), this.toString(), m.getType().toString(), details); } /** * We have an OFMessage we didn't expect given the current state and we want to treat this as an * error. We currently throw an exception that will terminate the connection However, we could * be more forgiving * * @param h the channel handler that received the message * @param m the message * @throws SwitchStateExeption we always through the execption */ // needs to be protected because enum members are acutally subclasses protected void illegalMessageReceived(OFMessage m) { String msg = getSwitchStateMessage(m, "Switch should never send this message in the current state"); throw new SwitchStateException(msg); } /** * We have an OFMessage we didn't expect given the current state and we want to ignore the * message * * @param h the channel handler the received the message * @param m the message */ protected void unhandledMessageReceived(OFMessage m) { counters.unhandledMessage.increment(); if (log.isDebugEnabled()) { String msg = getSwitchStateMessage(m, "Ignoring unexpected message"); log.debug(msg); } } /** * Log an OpenFlow error message from a switch * * @param sw The switch that sent the error * @param error The error message */ protected void logError(OFErrorMsg error) { log.error( "{} from switch {} in state {}", new Object[] {error.toString(), getConnectionInfoString(), this.toString()}); } /** * Log an OpenFlow error message from a switch and disconnect the channel * * @param sw The switch that sent the error * @param error The error message */ protected void logErrorDisconnect(OFErrorMsg error) { logError(error); channel.disconnect(); } /** * Process an OF message received on the channel and update state accordingly. * * <p>The main "event" of the state machine. Process the received message, send follow up * message if required and update state if required. * * <p>Switches on the message type and calls more specific event handlers for each individual OF * message type. If we receive a message that is supposed to be sent from a controller to a * switch we throw a SwitchStateExeption. * * <p>The more specific handlers can also throw SwitchStateExceptions * * @param h The OFChannelHandler that received the message * @param m The message we received. * @throws SwitchStateException * @throws IOException */ void processOFMessage(OFMessage m) throws IOException { // Handle Channel Handshake if (!state.channelHandshakeComplete) { switch (m.getType()) { case HELLO: processOFHello((OFHello) m); break; case ERROR: processOFError((OFErrorMsg) m); break; case FEATURES_REPLY: processOFFeaturesReply((OFFeaturesReply) m); break; case EXPERIMENTER: processOFExperimenter((OFExperimenter) m); break; /* echos can be sent at any time */ case ECHO_REPLY: processOFEchoReply((OFEchoReply) m); break; case ECHO_REQUEST: processOFEchoRequest((OFEchoRequest) m); break; case PORT_STATUS: processOFPortStatus((OFPortStatus) m); break; default: illegalMessageReceived(m); break; } } else { switch (m.getType()) { case ECHO_REPLY: processOFEchoReply((OFEchoReply) m); break; case ECHO_REQUEST: processOFEchoRequest((OFEchoRequest) m); break; // Send to SwitchManager and thus higher orders of control default: sendMessageToConnection(m); break; } } } } /** Initial state before channel is connected. */ class InitState extends OFChannelState { InitState() { super(false); } } /** * We send a HELLO to the switch and wait for a reply. Once we receive the reply we send an * OFFeaturesRequest Next state is WaitFeaturesReplyState */ class WaitHelloState extends OFChannelState { WaitHelloState() { super(false); } @Override void processOFHello(OFHello m) throws IOException { OFVersion theirVersion = m.getVersion(); OFVersion commonVersion = null; /* First, check if there's a version bitmap supplied. WE WILL ALWAYS HAVE a controller-provided version bitmap. */ if (theirVersion.compareTo(OFVersion.OF_13) >= 0 && !m.getElements().isEmpty()) { List<U32> bitmaps = new ArrayList<U32>(); List<OFHelloElem> elements = m.getElements(); /* Grab all bitmaps supplied */ for (OFHelloElem e : elements) { if (e instanceof OFHelloElemVersionbitmap) { bitmaps.addAll(((OFHelloElemVersionbitmap) e).getBitmaps()); } else { log.warn("Unhandled OFHelloElem {}", e); } } /* Lookup highest, common supported OpenFlow version */ commonVersion = computeOFVersionFromBitmap(bitmaps); if (commonVersion == null) { log.error( "Could not negotiate common OpenFlow version for {} with greatest version bitmap algorithm.", channel.remoteAddress()); channel.disconnect(); return; } else { log.info( "Negotiated OpenFlow version of {} for {} with greatest version bitmap algorithm.", commonVersion.toString(), channel.remoteAddress()); factory = OFFactories.getFactory(commonVersion); OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); decoder.setVersion(commonVersion); } } /* If there's not a bitmap present, choose the lower of the two supported versions. */ else if (theirVersion.compareTo(factory.getVersion()) < 0) { log.info( "Negotiated down to switch OpenFlow version of {} for {} using lesser hello header algorithm.", theirVersion.toString(), channel.remoteAddress()); factory = OFFactories.getFactory(theirVersion); OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); decoder.setVersion(theirVersion); } /* else The controller's version is < or = the switch's, so keep original controller factory. */ else if (theirVersion .equals(factory.getVersion())) { log.info( "Negotiated equal OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.remoteAddress()); } else { log.info( "Negotiated down to controller OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.remoteAddress()); } setState(new WaitFeaturesReplyState()); } @Override void enterState() throws IOException { sendHelloMessage(); } } /** * We are waiting for a features reply message. Once we receive it we send capture the features * reply. Next state is CompleteState */ class WaitFeaturesReplyState extends OFChannelState { WaitFeaturesReplyState() { super(false); } @Override void processOFFeaturesReply(OFFeaturesReply m) throws IOException { featuresReply = m; featuresLatency = (System.currentTimeMillis() - featuresLatency) / 2; // Mark handshake as completed setState(new CompleteState()); } @Override void processOFHello(OFHello m) throws IOException { /* * Brocade switches send a second hello after * the controller responds with its hello. This * might be to confirm the protocol version used, * but isn't defined in the OF specification. * * We will ignore such hello messages assuming * the version of the hello is correct according * to the algorithm in the spec. * * TODO Brocade also sets the XID of this second * hello as the same XID the controller used. * Checking for this might help to assure we're * really dealing with the situation we think * we are. */ if (m.getVersion().equals(factory.getVersion())) { log.warn( "Ignoring second hello from {} in state {}. Might be a Brocade.", channel.remoteAddress(), state.toString()); } else { super.processOFHello(m); /* Versions don't match as they should; abort */ } } @Override void processOFPortStatus(OFPortStatus m) { log.warn( "Ignoring PORT_STATUS message from {} during OpenFlow channel establishment. Ports will be explicitly queried in a later state.", channel.remoteAddress()); } @Override void enterState() throws IOException { sendFeaturesRequest(); featuresLatency = System.currentTimeMillis(); } @Override void processOFMessage(OFMessage m) throws IOException { if (m.getType().equals(OFType.PACKET_IN)) { log.warn( "Ignoring PACKET_IN message from {} during OpenFlow channel establishment.", channel.remoteAddress()); } else { super.processOFMessage(m); } } }; /** * This state denotes that the channel handshaking is complete. An OF connection is generated and * passed to the switch manager for handling. */ class CompleteState extends OFChannelState { CompleteState() { super(true); } @Override void enterState() throws IOException { setSwitchHandshakeTimeout(); // Handle non 1.3 connections if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { connection = new OFConnection( featuresReply.getDatapathId(), factory, channel, OFAuxId.MAIN, debugCounters, timer); } // Handle 1.3 connections else { connection = new OFConnection( featuresReply.getDatapathId(), factory, channel, featuresReply.getAuxiliaryId(), debugCounters, timer); // If this is an aux connection, we set a longer echo idle time if (!featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { setAuxChannelIdle(); } } connection.updateLatency(U64.of(featuresLatency)); echoSendTime = 0; // Notify the connection broker notifyConnectionOpened(connection); } }; /** * Creates a handler for interacting with the switch channel * * @param controller the controller * @param newConnectionListener the class that listens for new OF connections (switchManager) * @param pipeline the channel pipeline * @param threadPool the thread pool * @param idleTimer the hash wheeled timer used to send idle messages (echo). passed to * constructor to modify in case of aux connection. * @param debugCounters */ OFChannelHandler( @Nonnull IOFSwitchManager switchManager, @Nonnull INewOFConnectionListener newConnectionListener, @Nonnull ChannelPipeline pipeline, @Nonnull IDebugCounterService debugCounters, @Nonnull Timer timer, @Nonnull List<U32> ofBitmaps, @Nonnull OFFactory defaultFactory) { Preconditions.checkNotNull(switchManager, "switchManager"); Preconditions.checkNotNull(newConnectionListener, "connectionOpenedListener"); Preconditions.checkNotNull(pipeline, "pipeline"); Preconditions.checkNotNull(timer, "timer"); Preconditions.checkNotNull(debugCounters, "debugCounters"); this.pipeline = pipeline; this.debugCounters = debugCounters; this.newConnectionListener = newConnectionListener; this.counters = switchManager.getCounters(); this.state = new InitState(); this.timer = timer; this.ofBitmaps = ofBitmaps; this.factory = defaultFactory; log.debug( "constructor on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); } /** * Determine the highest supported version of OpenFlow in common between both our OFVersion bitmap * and the switch's. * * @param theirs, the version bitmaps of the switch * @return the highest OFVersion in common b/t the two */ private OFVersion computeOFVersionFromBitmap(List<U32> theirs) { Iterator<U32> theirsItr = theirs.iterator(); Iterator<U32> oursItr = ofBitmaps.iterator(); OFVersion version = null; int pos = 0; int size = 32; while (theirsItr.hasNext() && oursItr.hasNext()) { int t = theirsItr.next().getRaw(); int o = oursItr.next().getRaw(); int common = t & o; /* Narrow down the results to the common bits */ for (int i = 0; i < size; i++) { /* Iterate over and locate the 1's */ int tmp = common & (1 << i); /* Select the bit of interest, 0-31 */ if (tmp != 0) { /* Is the version of this bit in common? */ for (OFVersion v : OFVersion.values()) { /* Which version does this bit represent? */ if (v.getWireVersion() == i + (size * pos)) { version = v; } } } } pos++; /* OFVersion position. 1-31 = 1, 32 - 63 = 2, etc. Inc at end so it starts at 0. */ } return version; } /** * Determines if the entire switch handshake is complete (channel+switch). If the channel * handshake is complete the call is forwarded to the connection listener/switch manager to be * handled by the appropriate switch handshake handler. * * @return whether or not complete switch handshake is complete */ public boolean isSwitchHandshakeComplete() { if (this.state.channelHandshakeComplete) { return connection.getListener().isSwitchHandshakeComplete(connection); } else { return false; } } /** Notifies the channel listener that we have a valid baseline connection */ private final void notifyConnectionOpened(OFConnection connection) { this.connection = connection; this.newConnectionListener.connectionOpened(connection, featuresReply); } /** Notifies the channel listener that we our connection has been closed */ private final void notifyConnectionClosed(OFConnection connection) { connection.getListener().connectionClosed(connection); } /** Notifies the channel listener that we have a valid baseline connection */ private final void sendMessageToConnection(OFMessage m) { connection.messageReceived(m); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { log.debug( "channelConnected on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); counters.switchConnected.increment(); channel = ctx.channel(); log.info("New switch connection from {}", channel.remoteAddress()); setState(new WaitHelloState()); } @Override public void channelInactive(ChannelHandlerContext ctx) { // Only handle cleanup connection is even known if (this.connection != null) { // Alert the connection object that the channel has been disconnected this.connection.disconnected(); // Punt the cleanup to the Switch Manager notifyConnectionClosed(this.connection); } log.info("[{}] Disconnected connection", getConnectionInfoString()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof ReadTimeoutException) { if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { log.error( "Disconnecting switch {} due to read timeout on main cxn.", getConnectionInfoString()); ctx.channel().close(); } else { if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { log.error( "Disconnecting switch {} due to read timeout on main cxn.", getConnectionInfoString()); ctx.channel().close(); } else { // We only don't disconnect on aux connections log.warn("Switch {} encountered read timeout on aux cxn.", getConnectionInfoString()); } } // Increment counters counters.switchDisconnectReadTimeout.increment(); } else if (cause instanceof HandshakeTimeoutException) { log.error( "Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}", getConnectionInfoString(), this.state.channelHandshakeComplete); counters.switchDisconnectHandshakeTimeout.increment(); ctx.channel().close(); } else if (cause instanceof ClosedChannelException) { log.debug("Channel for sw {} already closed", getConnectionInfoString()); } else if (cause instanceof IOException) { log.error( "Disconnecting switch {} due to IO Error: {}", getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectIOError.increment(); ctx.channel().close(); } else if (cause instanceof SwitchStateException) { log.error( "Disconnecting switch {} due to switch state error: {}", getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectSwitchStateException.increment(); ctx.channel().close(); } else if (cause instanceof OFAuxException) { log.error( "Disconnecting switch {} due to OF Aux error: {}", getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectSwitchStateException.increment(); ctx.channel().close(); } else if (cause instanceof OFParseError) { log.error( "Disconnecting switch " + getConnectionInfoString() + " due to message parse failure", cause); counters.switchDisconnectParseError.increment(); ctx.channel().close(); } else if (cause instanceof RejectedExecutionException) { log.warn("Could not process message: queue full"); counters.rejectedExecutionException.increment(); } else if (cause instanceof IllegalArgumentException) { log.error("Illegal argument exception with switch {}. {}", getConnectionInfoString(), cause); counters.switchSslConfigurationError.increment(); ctx.channel().close(); } else { log.error( "Error while processing message from switch " + getConnectionInfoString() + "state " + this.state, cause); counters.switchDisconnectOtherException.increment(); ctx.channel().close(); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { log.debug( "channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); OFChannelHandler handler = ctx.pipeline().get(OFChannelHandler.class); handler.sendEchoRequest(); } @Override public void channelRead0(ChannelHandlerContext ctx, Iterable<OFMessage> msgList) throws Exception { for (OFMessage ofm : msgList) { try { // Do the actual packet processing state.processOFMessage(ofm); } catch (Exception ex) { // We are the last handler in the stream, so run the // exception through the channel again by passing in // ctx.getChannel(). ctx.fireExceptionCaught(ex); } } } /** * Sets the channel pipeline's idle (Echo) timeouts to a longer interval. This is specifically for * aux channels. */ private void setAuxChannelIdle() { IdleStateHandler idleHandler = new IdleStateHandler(PipelineIdleReadTimeout.AUX, PipelineIdleWriteTimeout.AUX, 0); pipeline.replace(PipelineHandler.MAIN_IDLE, PipelineHandler.AUX_IDLE, idleHandler); } /** * Sets the channel pipeline's handshake timeout to a more appropriate value for the remaining * part of the switch handshake. */ private void setSwitchHandshakeTimeout() { HandshakeTimeoutHandler handler = new HandshakeTimeoutHandler(this, this.timer, PipelineHandshakeTimeout.SWITCH); pipeline.replace( PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT, handler); } /** * Return a string describing this switch based on the already available information (DPID and/or * remote socket) * * @return */ private String getConnectionInfoString() { String channelString; if (channel == null || channel.remoteAddress() == null) { channelString = "?"; } else { channelString = channel.remoteAddress().toString(); if (channelString.startsWith("/")) channelString = channelString.substring(1); } String dpidString; if (featuresReply == null) { dpidString = "?"; } else { StringBuilder b = new StringBuilder(); b.append(featuresReply.getDatapathId()); if (featuresReply.getVersion().compareTo(OFVersion.OF_13) >= 0) { b.append("(").append(featuresReply.getAuxiliaryId()).append(")"); } dpidString = b.toString(); } return String.format("[%s from %s]", dpidString, channelString); } /** * Update the channels state. Only called from the state machine. * * @param state * @throws IOException */ private void setState(OFChannelState state) throws IOException { this.state = state; state.logState(); state.enterState(); } /** * Send a features request message to the switch using the handshake transactions ids. * * @throws IOException */ private void sendFeaturesRequest() throws IOException { // Send initial Features Request OFFeaturesRequest m = factory.buildFeaturesRequest().setXid(handshakeTransactionIds--).build(); write(m); } /** * Send a hello message to the switch using the handshake transactions ids. * * @throws IOException */ private void sendHelloMessage() throws IOException { // Send initial hello message OFHello.Builder builder = factory.buildHello(); /* Our highest-configured OFVersion does support version bitmaps, so include it */ if (factory.getVersion().compareTo(OFVersion.OF_13) >= 0) { List<OFHelloElem> he = new ArrayList<OFHelloElem>(); he.add(factory.buildHelloElemVersionbitmap().setBitmaps(ofBitmaps).build()); builder.setElements(he); } OFHello m = builder.setXid(handshakeTransactionIds--).build(); write(m); log.debug("Send hello: {}", m); } private void sendEchoRequest() { OFEchoRequest request = factory.buildEchoRequest().setXid(handshakeTransactionIds--).build(); /* Record for latency calculation */ echoSendTime = System.currentTimeMillis(); write(request); } private void sendEchoReply(OFEchoRequest request) { OFEchoReply reply = factory.buildEchoReply().setXid(request.getXid()).setData(request.getData()).build(); write(reply); } private void write(OFMessage m) { channel.writeAndFlush(Collections.singletonList(m)); } OFChannelState getStateForTesting() { return state; } IOFConnectionBackend getConnectionForTesting() { return connection; } ChannelPipeline getPipelineForTesting() { return this.pipeline; } private void updateLatency(U64 latency) { if (connection != null) { connection.updateLatency(latency); } } }
public class Decoder { public static final AttributeKey<InetSocketAddress> INET_ADDRESS_KEY = AttributeKey.valueOf("inet-addr"); public static final AttributeKey<PeerAddress> PEER_ADDRESS_KEY = AttributeKey.valueOf("peer-addr"); private static final Logger LOG = LoggerFactory.getLogger(Decoder.class); private final Queue<Content> contentTypes = new LinkedList<Message.Content>(); // private Message2 result = null; // current state - needs to be deleted if we want to reuse private Message message = null; private boolean headerDone = false; private Signature signature = null; private int neighborSize = -1; private NeighborSet neighborSet = null; private int peerSocketAddressSize = -1; private List<PeerSocketAddress> peerSocketAddresses = null; private int keyCollectionSize = -1; private KeyCollection keyCollection = null; private int mapSize = -1; private DataMap dataMap = null; private Data data = null; private Number640 key = null; private int keyMap640KeysSize = -1; private KeyMap640Keys keyMap640Keys = null; private int keyMapByteSize = -1; private KeyMapByte keyMapByte = null; private int bufferSize = -1; private int bufferTransferred = 0; private DataBuffer buffer = null; private int trackerDataSize = -1; private TrackerData trackerData = null; private Data currentTrackerData = null; private Content lastContent = null; private final SignatureFactory signatureFactory; public Decoder(SignatureFactory signatureFactory) { this.signatureFactory = signatureFactory; } public boolean decode( ChannelHandlerContext ctx, final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) { LOG.debug("Decoding of TomP2P starts now. Readable: {}.", buf.readableBytes()); try { final int readerBefore = buf.readerIndex(); // set the sender of this message for handling timeout final Attribute<InetSocketAddress> attributeInet = ctx.attr(INET_ADDRESS_KEY); attributeInet.set(sender); if (message == null && !headerDone) { headerDone = decodeHeader(buf, recipient, sender); if (headerDone) { // store the sender as an attribute final Attribute<PeerAddress> attributePeerAddress = ctx.attr(PEER_ADDRESS_KEY); attributePeerAddress.set(message.sender()); message.udp(ctx.channel() instanceof DatagramChannel); if (message.isFireAndForget() && message.isUdp()) { TimeoutFactory.removeTimeout(ctx); } } else { return false; } } final boolean donePayload = decodePayload(buf); decodeSignature(buf, readerBefore, donePayload); if (donePayload) { boolean isRelay = message.sender().isRelayed(); if (isRelay && !message.peerSocketAddresses().isEmpty()) { PeerAddress tmpSender = message.sender().changePeerSocketAddresses(message.peerSocketAddresses()); message.sender(tmpSender); } } // see https://github.com/netty/netty/issues/1976 buf.discardSomeReadBytes(); return donePayload; } catch (Exception e) { ctx.fireExceptionCaught(e); e.printStackTrace(); return true; } } public void decodeSignature(final ByteBuf buf, final int readerBefore, final boolean donePayload) throws InvalidKeyException, SignatureException { final int readerAfter = buf.readerIndex(); final int len = readerAfter - readerBefore; if (len > 0) { verifySignature(buf, readerBefore, len, donePayload); } } private void verifySignature( final ByteBuf buf, final int readerBefore, final int len, final boolean donePayload) throws SignatureException, InvalidKeyException { if (!message.isSign()) { return; } // if we read the complete data, we also read the signature // for the verification, we should not use this for the signature final int length = donePayload ? len - signatureFactory.signatureSize() : len; ByteBuffer[] byteBuffers = buf.nioBuffers(readerBefore, length); if (signature == null) { signature = signatureFactory.update(message.publicKey(0), byteBuffers); } else { for (int i = 0; i < byteBuffers.length; i++) { signature.update(byteBuffers[i]); } } if (donePayload) { byte[] signatureReceived = message.receivedSignature().encode(); LOG.debug("Verifying received signature: {}", Arrays.toString(signatureReceived)); if (signature.verify(signatureReceived)) { // set public key only if signature is correct message.setVerified(); LOG.debug("Signature check OK."); } else { LOG.warn("Signature check NOT OK. Message: {}.", message); } } } public boolean decodeHeader( final ByteBuf buf, InetSocketAddress recipient, final InetSocketAddress sender) { if (message == null) { if (buf.readableBytes() < MessageHeaderCodec.HEADER_SIZE) { // we don't have the header yet, we need the full header first // wait for more data return false; } message = MessageHeaderCodec.decodeHeader(buf, recipient, sender); } if (message.sender().isNet4Private() && buf.readableBytes() < MessageHeaderCodec.HEADER_PRIVATE_ADDRESS_SIZE) { return false; } else if (message.sender().isNet4Private() && buf.readableBytes() >= MessageHeaderCodec.HEADER_PRIVATE_ADDRESS_SIZE) { PeerSocketAddress internalPeerSocketAddress = PeerSocketAddress.create(buf, true); message.sender(message.sender().changeInternalPeerSocketAddress(internalPeerSocketAddress)); } // we have set the content types already message.presetContentTypes(true); for (Content content : message.contentTypes()) { if (content == Content.EMPTY) { break; } if (content == Content.PUBLIC_KEY_SIGNATURE) { message.setHintSign(); } contentTypes.offer(content); } LOG.debug("Parsed message {}.", message); return true; } public boolean decodePayload(final ByteBuf buf) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { LOG.debug( "About to pass message {} to {}. Buffer to read: {}.", message, message.senderSocket(), buf.readableBytes()); if (!message.hasContent()) { return true; } // payload comes here int size; PublicKey receivedPublicKey; while (contentTypes.size() > 0) { Content content = contentTypes.peek(); LOG.debug("Parse content: {} in message {}", content, message); switch (content) { case INTEGER: if (buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } message.intValue(buf.readInt()); lastContent = contentTypes.poll(); break; case LONG: if (buf.readableBytes() < Utils.LONG_BYTE_SIZE) { return false; } message.longValue(buf.readLong()); lastContent = contentTypes.poll(); break; case KEY: if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me); message.key(new Number160(me)); lastContent = contentTypes.poll(); break; case BLOOM_FILTER: if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) { return false; } size = buf.getUnsignedShort(buf.readerIndex()); if (buf.readableBytes() < size) { return false; } message.bloomFilter(new SimpleBloomFilter<Number160>(buf)); lastContent = contentTypes.poll(); break; case SET_NEIGHBORS: if (neighborSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) { return false; } if (neighborSize == -1) { neighborSize = buf.readUnsignedByte(); } if (neighborSet == null) { neighborSet = new NeighborSet(-1, new ArrayList<PeerAddress>(neighborSize)); } for (int i = neighborSet.size(); i < neighborSize; i++) { if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) { return false; } int header = buf.getUnsignedShort(buf.readerIndex()); size = PeerAddress.size(header); if (buf.readableBytes() < size) { return false; } PeerAddress pa = new PeerAddress(buf); neighborSet.add(pa); } message.neighborsSet(neighborSet); lastContent = contentTypes.poll(); neighborSize = -1; neighborSet = null; break; case SET_PEER_SOCKET: if (peerSocketAddressSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) { return false; } if (peerSocketAddressSize == -1) { peerSocketAddressSize = buf.readUnsignedByte(); } if (peerSocketAddresses == null) { peerSocketAddresses = new ArrayList<PeerSocketAddress>(peerSocketAddressSize); } for (int i = peerSocketAddresses.size(); i < peerSocketAddressSize; i++) { if (buf.readableBytes() < Utils.BYTE_BYTE_SIZE) { return false; } int header = buf.getUnsignedByte(buf.readerIndex()); boolean isIPv4 = header == 0; size = PeerSocketAddress.size(isIPv4); if (buf.readableBytes() < size + Utils.BYTE_BYTE_SIZE) { return false; } // skip the ipv4/ipv6 header buf.skipBytes(1); peerSocketAddresses.add(PeerSocketAddress.create(buf, isIPv4)); } message.peerSocketAddresses(peerSocketAddresses); lastContent = contentTypes.poll(); peerSocketAddressSize = -1; peerSocketAddresses = null; break; case SET_KEY640: if (keyCollectionSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (keyCollectionSize == -1) { keyCollectionSize = buf.readInt(); } if (keyCollection == null) { keyCollection = new KeyCollection(new ArrayList<Number640>(keyCollectionSize)); } for (int i = keyCollection.size(); i < keyCollectionSize; i++) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me2 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me2); Number160 locationKey = new Number160(me2); buf.readBytes(me2); Number160 domainKey = new Number160(me2); buf.readBytes(me2); Number160 contentKey = new Number160(me2); buf.readBytes(me2); Number160 versionKey = new Number160(me2); keyCollection.add(new Number640(locationKey, domainKey, contentKey, versionKey)); } message.keyCollection(keyCollection); lastContent = contentTypes.poll(); keyCollectionSize = -1; keyCollection = null; break; case MAP_KEY640_DATA: if (mapSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (mapSize == -1) { mapSize = buf.readInt(); } if (dataMap == null) { dataMap = new DataMap(new TreeMap<Number640, Data>()); } if (data != null) { if (!data.decodeBuffer(buf)) { return false; } if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) { return false; } data = null; key = null; } for (int i = dataMap.size(); i < mapSize; i++) { if (key == null) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE) { return false; } byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me3); Number160 locationKey = new Number160(me3); buf.readBytes(me3); Number160 domainKey = new Number160(me3); buf.readBytes(me3); Number160 contentKey = new Number160(me3); buf.readBytes(me3); Number160 versionKey = new Number160(me3); key = new Number640(locationKey, domainKey, contentKey, versionKey); } LOG.debug("Key decoded in message {}, remaining {}", message, buf.readableBytes()); data = Data.decodeHeader(buf, signatureFactory); if (data == null) { return false; } LOG.debug("Header decoded in message {}, remaining {}", message, buf.readableBytes()); dataMap.dataMap().put(key, data); if (!data.decodeBuffer(buf)) { return false; } LOG.debug("Buffer decoded in message {}", message); if (!data.decodeDone(buf, message.publicKey(0), signatureFactory)) { return false; } LOG.debug("Done decoded in message {}", message); // if we have signed the message, set the public key anyway, but only if we indicated so inheritPublicKey(message, data); data = null; key = null; } message.setDataMap(dataMap); lastContent = contentTypes.poll(); mapSize = -1; dataMap = null; break; case MAP_KEY640_KEYS: if (keyMap640KeysSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (keyMap640KeysSize == -1) { keyMap640KeysSize = buf.readInt(); } if (keyMap640Keys == null) { keyMap640Keys = new KeyMap640Keys(new TreeMap<Number640, Collection<Number160>>()); } final int meta = Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE; for (int i = keyMap640Keys.size(); i < keyMap640KeysSize; i++) { if (buf.readableBytes() < meta + Utils.BYTE_BYTE_SIZE) { return false; } size = buf.getUnsignedByte(buf.readerIndex() + meta); if (buf.readableBytes() < meta + Utils.BYTE_BYTE_SIZE + (size * Number160.BYTE_ARRAY_SIZE)) { return false; } byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me3); Number160 locationKey = new Number160(me3); buf.readBytes(me3); Number160 domainKey = new Number160(me3); buf.readBytes(me3); Number160 contentKey = new Number160(me3); buf.readBytes(me3); Number160 versionKey = new Number160(me3); int numBasedOn = buf.readByte(); Set<Number160> value = new HashSet<Number160>(numBasedOn); for (int j = 0; j < numBasedOn; j++) { buf.readBytes(me3); Number160 basedOnKey = new Number160(me3); value.add(basedOnKey); } keyMap640Keys.put(new Number640(locationKey, domainKey, contentKey, versionKey), value); } message.keyMap640Keys(keyMap640Keys); lastContent = contentTypes.poll(); keyMap640KeysSize = -1; keyMap640Keys = null; break; case MAP_KEY640_BYTE: if (keyMapByteSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (keyMapByteSize == -1) { keyMapByteSize = buf.readInt(); } if (keyMapByte == null) { keyMapByte = new KeyMapByte(new HashMap<Number640, Byte>(2 * keyMapByteSize)); } for (int i = keyMapByte.size(); i < keyMapByteSize; i++) { if (buf.readableBytes() < Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + Number160.BYTE_ARRAY_SIZE + 1) { return false; } byte[] me3 = new byte[Number160.BYTE_ARRAY_SIZE]; buf.readBytes(me3); Number160 locationKey = new Number160(me3); buf.readBytes(me3); Number160 domainKey = new Number160(me3); buf.readBytes(me3); Number160 contentKey = new Number160(me3); buf.readBytes(me3); Number160 versionKey = new Number160(me3); byte value = buf.readByte(); keyMapByte.put(new Number640(locationKey, domainKey, contentKey, versionKey), value); } message.keyMapByte(keyMapByte); lastContent = contentTypes.poll(); keyMapByteSize = -1; keyMapByte = null; break; case BYTE_BUFFER: if (bufferSize == -1 && buf.readableBytes() < Utils.INTEGER_BYTE_SIZE) { return false; } if (bufferSize == -1) { bufferSize = buf.readInt(); } if (buffer == null) { buffer = new DataBuffer(); } final int remaining = bufferSize - bufferTransferred; bufferTransferred += buffer.transferFrom(buf, remaining); if (bufferTransferred < bufferSize) { LOG.debug( "Still looking for data. Indicating that its not finished yet. Already Transferred = {}, Size = {}.", bufferTransferred, bufferSize); return false; } message.buffer(new Buffer(buffer.toByteBuf(), bufferSize)); lastContent = contentTypes.poll(); bufferSize = -1; bufferTransferred = 0; buffer = null; break; case SET_TRACKER_DATA: if (trackerDataSize == -1 && buf.readableBytes() < Utils.BYTE_BYTE_SIZE) { return false; } if (trackerDataSize == -1) { trackerDataSize = buf.readUnsignedByte(); } if (trackerData == null) { trackerData = new TrackerData(new HashMap<PeerAddress, Data>(2 * trackerDataSize)); } if (currentTrackerData != null) { if (!currentTrackerData.decodeBuffer(buf)) { return false; } if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) { return false; } currentTrackerData = null; } for (int i = trackerData.size(); i < trackerDataSize; i++) { if (buf.readableBytes() < Utils.SHORT_BYTE_SIZE) { return false; } int header = buf.getUnsignedShort(buf.readerIndex()); size = PeerAddress.size(header); if (buf.readableBytes() < size) { return false; } PeerAddress pa = new PeerAddress(buf); currentTrackerData = Data.decodeHeader(buf, signatureFactory); if (currentTrackerData == null) { return false; } trackerData.peerAddresses().put(pa, currentTrackerData); if (message.isSign()) { currentTrackerData.publicKey(message.publicKey(0)); } if (!currentTrackerData.decodeBuffer(buf)) { return false; } if (!currentTrackerData.decodeDone(buf, message.publicKey(0), signatureFactory)) { return false; } currentTrackerData = null; } message.trackerData(trackerData); lastContent = contentTypes.poll(); trackerDataSize = -1; trackerData = null; break; case PUBLIC_KEY: // fall-through case PUBLIC_KEY_SIGNATURE: receivedPublicKey = signatureFactory.decodePublicKey(buf); if (content == Content.PUBLIC_KEY_SIGNATURE) { if (receivedPublicKey == PeerBuilder.EMPTY_PUBLIC_KEY) { throw new InvalidKeyException("The public key cannot be empty."); } } if (receivedPublicKey == null) { return false; } message.publicKey(receivedPublicKey); lastContent = contentTypes.poll(); break; default: break; } } LOG.debug("Parsed content in message {}", message); if (message.isSign()) { size = signatureFactory.signatureSize(); if (buf.readableBytes() < size) { return false; } SignatureCodec signatureEncode = signatureFactory.signatureCodec(buf); message.receivedSignature(signatureEncode); } return true; } public Message prepareFinish() { Message ret = message; message.setDone(); contentTypes.clear(); message = null; headerDone = false; neighborSize = -1; neighborSet = null; keyCollectionSize = -1; keyCollection = null; mapSize = -1; dataMap = null; data = null; keyMap640KeysSize = -1; keyMap640Keys = null; bufferSize = -1; bufferTransferred = 0; buffer = null; signature = null; return ret; } public Message message() { return message; } public Content lastContent() { return lastContent; } public static void inheritPublicKey(Message message, Data data) { if (message.isSign() && message.publicKey(0) != null && data.hasPublicKey() && (data.publicKey() == null || data.publicKey() == PeerBuilder.EMPTY_PUBLIC_KEY)) { data.publicKey(message.publicKey(0)); } } public void release() { if (message != null) { message.release(); } // release partial data if (data != null) { data.release(); } if (dataMap != null) { for (Data data : dataMap.dataMap().values()) { data.release(); } } if (buffer != null) { buffer.release(); } if (currentTrackerData != null) { currentTrackerData.release(); } if (trackerData != null) { for (Data data : trackerData.peerAddresses().values()) { data.release(); } } } }
public final class SessionManager implements ISessionManager { public static final AttributeKey<Session> SESSION_ATTR = AttributeKey.valueOf("Session.attr"); public static final AttributeKey<Integer> CHANNEL_ID_ATTR = AttributeKey.valueOf("ChannelId.attr"); private final AtomicInteger idGenerator = new AtomicInteger(); private final Map<Integer, BaseSession> sessions = new ConcurrentHashMap<>(); private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); public static boolean isLocked = false; public SessionManager() { SessionManagerAccessor.getInstance().setSessionManager(this); } public boolean add(ChannelHandlerContext channel) { Session session = new Session(channel); // if(PlayerManager.getInstance().getPlayerCountByIpAddress(session.getIpAddress()) > // CometSettings.maxConnectionsPerIpAddress) { // return false; // } this.channelGroup.add(channel.channel()); channel.attr(CHANNEL_ID_ATTR).set(this.idGenerator.incrementAndGet()); return (this.sessions.putIfAbsent(channel.attr(CHANNEL_ID_ATTR).get(), session) == null); } public boolean remove(ChannelHandlerContext channel) { if (this.sessions.containsKey(channel.attr(CHANNEL_ID_ATTR).get())) { this.channelGroup.remove(channel.channel()); this.sessions.remove(channel.attr(CHANNEL_ID_ATTR).get()); return true; } return false; } public boolean disconnectByPlayerId(int id) { if (PlayerManager.getInstance().getSessionIdByPlayerId(id) == -1) { return false; } int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(id); Session session = (Session) sessions.get(sessionId); if (session != null) { session.disconnect(); return true; } return false; } public Session getByPlayerId(int id) { if (PlayerManager.getInstance().getSessionIdByPlayerId(id) != -1) { int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(id); return (Session) sessions.get(sessionId); } return null; } public Set<BaseSession> getByPlayerPermission(String permission) { // TODO: Optimize this Set<BaseSession> sessions = new HashSet<>(); // int rank = // PermissionsManager.getInstance().getPermissions().get(permission).getRank(); // // for (Map.Entry<Integer, BaseSession> session : this.sessions.entrySet()) { // if (session.getValue().getPlayer() != null) { // if (((Session) session.getValue()).getPlayer().getData().getRank() >= rank) { // sessions.add(session.getValue()); // } // } // } return sessions; } public Session getByPlayerUsername(String username) { int playerId = PlayerManager.getInstance().getPlayerIdByUsername(username); if (playerId == -1) return null; int sessionId = PlayerManager.getInstance().getSessionIdByPlayerId(playerId); if (sessionId == -1) return null; if (this.sessions.containsKey(sessionId)) return (Session) this.sessions.get(sessionId); return null; } public int getUsersOnlineCount() { return PlayerManager.getInstance().size(); } public Map<Integer, BaseSession> getSessions() { return this.sessions; } public void broadcast(IMessageComposer msg) { this.getChannelGroup().writeAndFlush(msg); // // for (Session client : sessions.values()) { // client.getChannel().write(msg); // } } public ChannelGroup getChannelGroup() { return channelGroup; } public void broadcastToModerators(IMessageComposer messageComposer) { for (BaseSession session : this.sessions.values()) { if (session.getPlayer() != null && session.getPlayer().getPermissions() != null && session.getPlayer().getPermissions().getRank().modTool()) { session.send(messageComposer); } } } @Override public void parseCommand(String[] message, ChannelHandlerContext ctx) { String password = message[0]; if (password.equals("cometServer")) { String command = message[1]; switch (command) { default: { ctx.channel().writeAndFlush("response||You're connected!"); break; } case "stats": { ctx.channel() .writeAndFlush("response||" + JsonFactory.getInstance().toJson(CometStats.get())); break; } } } else { ctx.disconnect(); } } }
/** * A simple handler that serves incoming HTTP requests to send their respective HTTP responses. It * also implements {@code 'If-Modified-Since'} header to take advantage of browser cache, as * described in <a href="http://tools.ietf.org/html/rfc2616#section-14.25">RFC 2616</a>. * * <p> * * <h3>How Browser Caching Works</h3> * * <p>Web browser caching works with HTTP headers as illustrated by the following sample: * * <ol> * <li>Request #1 returns the content of {@code /file1.txt}. * <li>Contents of {@code /file1.txt} is cached by the browser. * <li>Request #2 for {@code /file1.txt} does return the contents of the file again. Rather, a 304 * Not Modified is returned. This tells the browser to use the contents stored in its cache. * <li>The server knows the file has not been modified because the {@code If-Modified-Since} date * is the same as the file's last modified date. * </ol> * * <p> * * <pre> * Request #1 Headers * =================== * GET /file1.txt HTTP/1.1 * * Response #1 Headers * =================== * HTTP/1.1 200 OK * Date: Tue, 01 Mar 2011 22:44:26 GMT * Last-Modified: Wed, 30 Jun 2010 21:36:48 GMT * Expires: Tue, 01 Mar 2012 22:44:26 GMT * Cache-Control: private, max-age=31536000 * * Request #2 Headers * =================== * GET /file1.txt HTTP/1.1 * If-Modified-Since: Wed, 30 Jun 2010 21:36:48 GMT * * Response #2 Headers * =================== * HTTP/1.1 304 Not Modified * Date: Tue, 01 Mar 2011 22:44:28 GMT * * </pre> */ public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private static final Logger logger = LoggerFactory.getLogger(HttpStaticFileServerHandler.class); public static final String HTTP_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss zzz"; public static final String HTTP_DATE_GMT_TIMEZONE = "GMT"; public static final int HTTP_CACHE_SECONDS = 60; public static final AttributeKey<Object> ATTACHMENT = AttributeKey.valueOf("HttpStaticFileServerHandler.attachment"); public static final String STATIC_MAPPING = SimpleChannelInboundHandler.class.getName() + ".staticMapping"; public static final String SERVICED = SimpleChannelInboundHandler.class.getName() + ".serviced"; private final List<String> paths; private String defaultContentType = "text/html"; public HttpStaticFileServerHandler(List<String> paths) { this.paths = paths; } @Override public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { if (!request.getDecoderResult().isSuccess()) { sendError(ctx, BAD_REQUEST, request); return; } if (request.getMethod() != GET) { sendError(ctx, METHOD_NOT_ALLOWED, request); return; } File file = null; RandomAccessFile raf = null; boolean found = true; for (String p : paths) { String path = p + sanitizeUri(request.getUri()); if (path == null) { path = "/index.html"; } if (path.endsWith("/") || path.endsWith(File.separator)) { path += "index.html"; } if (path.endsWith("/favicon.ico") || path.endsWith(File.separator)) { request.headers().add(SERVICED, "true"); found = false; continue; } file = new File(path); if (file.isHidden() || !file.exists()) { found = false; continue; } // if (file.isDirectory()) { // if (uri.endsWith("/")) { // sendListing(ctx, file); // } else { // sendRedirect(ctx, uri + '/'); // } // return; // } if (!file.isFile()) { found = false; continue; } try { raf = new RandomAccessFile(file, "r"); found = true; break; } catch (FileNotFoundException ignore) { sendError(ctx, NOT_FOUND, request); return; } } if (!found) { sendError(ctx, NOT_FOUND, request); return; } request.headers().add(SERVICED, "true"); // Cache Validation String ifModifiedSince = request.headers().get(IF_MODIFIED_SINCE); if (ifModifiedSince != null && !ifModifiedSince.isEmpty()) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); Date ifModifiedSinceDate = dateFormatter.parse(ifModifiedSince); // Only compare up to the second because the datetime format we send to the client // does not have milliseconds long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getTime() / 1000; long fileLastModifiedSeconds = file.lastModified() / 1000; if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { sendNotModified(ctx); return; } } long fileLength = raf.length(); ctx.pipeline().addBefore(BridgeRuntime.class.getName(), "encoder", new HttpResponseEncoder()); HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); HttpHeaders.setContentLength(response, fileLength); contentType(request, response, file); setDateAndCacheHeaders(response, file); // if (HttpHeaders.isKeepAlive(request)) { // response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE); // } // Write the initial line and the header. ctx.write(response); // Write the content. ChannelFuture sendFileFuture; ChannelFuture lastContentFuture; if (ctx.pipeline().get(SslHandler.class) == null) { sendFileFuture = ctx.write( new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); // Write the end marker. lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); } else { sendFileFuture = ctx.writeAndFlush( new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), ctx.newProgressivePromise()); // HttpChunkedInput will write the end marker (LastHttpContent) for us. lastContentFuture = sendFileFuture; } sendFileFuture.addListener( new ChannelProgressiveFutureListener() { @Override public void operationProgressed( ChannelProgressiveFuture future, long progress, long total) { if (total < 0) { // total unknown logger.trace(future.channel() + " Transfer progress: " + progress); } else { logger.trace(future.channel() + " Transfer progress: " + progress + " / " + total); } } @Override public void operationComplete(ChannelProgressiveFuture future) { logger.trace(future.channel() + " Transfer complete."); } }); // Close the connection when the whole content is written out. lastContentFuture.addListener(ChannelFutureListener.CLOSE); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) { Channel ch = ctx.channel(); // Prevent recursion when the client close the connection during a write operation. In that // scenario the sendError will be invoked, but will fail since the channel has already been // closed // For an unknown reason, if (ch.attr(ATTACHMENT) != null && Error.class.isAssignableFrom(ch.attr(ATTACHMENT).get().getClass())) { return; } Throwable cause = t.getCause(); if (cause instanceof TooLongFrameException) { sendError(ctx, BAD_REQUEST, null); return; } ch.attr(ATTACHMENT).set(new Error()); if (ch.isOpen()) { sendError(ctx, INTERNAL_SERVER_ERROR, null); } if (ctx.channel().isActive()) { sendError(ctx, INTERNAL_SERVER_ERROR, null); } } private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*"); protected String sanitizeUri(String uri) { try { uri = URLDecoder.decode(uri, "UTF-8"); } catch (UnsupportedEncodingException e) { try { uri = URLDecoder.decode(uri, "ISO-8859-1"); } catch (UnsupportedEncodingException e1) { throw new Error(); } } uri = uri.replace('/', File.separatorChar); if (uri.contains(File.separator + ".") || uri.contains("." + File.separator) || uri.startsWith(".") || uri.endsWith(".")) { return null; } int pos = uri.indexOf("?"); if (pos != -1) { uri = uri.substring(0, pos); } return uri; } public static void sendRedirect(ChannelHandlerContext ctx, String newUri) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND); response.headers().set(LOCATION, newUri); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } public void sendError( ChannelHandlerContext ctx, HttpResponseStatus status, FullHttpRequest request) { FullHttpResponse response = new DefaultFullHttpResponse( HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8)); response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8"); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * When file timestamp is the same as what the browser is sending up, send a "304 Not Modified" * * @param ctx Context */ public static void sendNotModified(ChannelHandlerContext ctx) { FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, NOT_MODIFIED); setDateHeader(response); // Close the connection as soon as the error message is sent. ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); } /** * Sets the Date header for the HTTP response * * @param response HTTP response */ public static void setDateHeader(FullHttpResponse response) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); } /** * Sets the Date and Cache headers for the HTTP Response * * @param response HTTP response * @param fileToCache file to extract content type */ public static void setDateAndCacheHeaders(HttpResponse response, File fileToCache) { SimpleDateFormat dateFormatter = new SimpleDateFormat(HTTP_DATE_FORMAT, Locale.US); dateFormatter.setTimeZone(TimeZone.getTimeZone(HTTP_DATE_GMT_TIMEZONE)); // Date header Calendar time = new GregorianCalendar(); response.headers().set(DATE, dateFormatter.format(time.getTime())); // Add cache headers time.add(Calendar.SECOND, HTTP_CACHE_SECONDS); response.headers().set(EXPIRES, dateFormatter.format(time.getTime())); response.headers().set(CACHE_CONTROL, "private, max-age=" + HTTP_CACHE_SECONDS); response .headers() .set(LAST_MODIFIED, dateFormatter.format(new Date(fileToCache.lastModified()))); } protected void contentType(FullHttpRequest request, HttpResponse response, File resource) { String substr; String uri = request.getUri(); int dot = uri.lastIndexOf("."); if (dot < 0) { substr = resource.toString(); dot = substr.lastIndexOf("."); } else { substr = uri; } if (dot > 0) { String ext = substr.substring(dot + 1); int queryString = ext.indexOf("?"); if (queryString > 0) { ext.substring(0, queryString); } String contentType = MimeType.get(ext, defaultContentType); response.headers().add(HttpHeaders.Names.CONTENT_TYPE, contentType); } else { response.headers().add(HttpHeaders.Names.CONTENT_TYPE, defaultContentType); } } }
public class NetworkDispatcher extends SimpleChannelInboundHandler<Packet> implements ChannelOutboundHandler { private static boolean DEBUG_HANDSHAKE = Boolean.parseBoolean(System.getProperty("fml.debugNetworkHandshake", "false")); private static enum ConnectionState { OPENING, AWAITING_HANDSHAKE, HANDSHAKING, HANDSHAKECOMPLETE, FINALIZING, CONNECTED; } private static enum ConnectionType { MODDED, BUKKIT, VANILLA; } public static NetworkDispatcher get(NetworkManager manager) { return manager.channel().attr(FML_DISPATCHER).get(); } public static NetworkDispatcher allocAndSet(NetworkManager manager) { NetworkDispatcher net = new NetworkDispatcher(manager); manager.channel().attr(FML_DISPATCHER).getAndSet(net); return net; } public static NetworkDispatcher allocAndSet( NetworkManager manager, ServerConfigurationManager scm) { NetworkDispatcher net = new NetworkDispatcher(manager, scm); manager.channel().attr(FML_DISPATCHER).getAndSet(net); return net; } public static final AttributeKey<NetworkDispatcher> FML_DISPATCHER = AttributeKey.valueOf("fml:dispatcher"); public static final AttributeKey<Boolean> IS_LOCAL = AttributeKey.valueOf("fml:isLocal"); public static final AttributeKey<GameData.GameDataSnapshot> FML_GAMEDATA_SNAPSHOT = AttributeKey.valueOf("fml:gameDataSnapshot"); public final NetworkManager manager; private final ServerConfigurationManager scm; private EntityPlayerMP player; private ConnectionState state; private ConnectionType connectionType; private final Side side; private final EmbeddedChannel handshakeChannel; private NetHandlerPlayServer serverHandler; private INetHandler netHandler; private Map<String, String> modList; private int overrideLoginDim; public NetworkDispatcher(NetworkManager manager) { super(Packet.class, false); this.manager = manager; this.scm = null; this.side = Side.CLIENT; this.handshakeChannel = new EmbeddedChannel( new HandshakeInjector(this), new ChannelRegistrationHandler(), new FMLHandshakeCodec(), new HandshakeMessageHandler<FMLHandshakeClientState>(FMLHandshakeClientState.class)); this.handshakeChannel.attr(FML_DISPATCHER).set(this); this.handshakeChannel.attr(NetworkRegistry.CHANNEL_SOURCE).set(Side.SERVER); this.handshakeChannel.attr(NetworkRegistry.FML_CHANNEL).set("FML|HS"); this.handshakeChannel.attr(IS_LOCAL).set(manager.isLocalChannel()); if (DEBUG_HANDSHAKE) PacketLoggingHandler.register(manager); } public NetworkDispatcher(NetworkManager manager, ServerConfigurationManager scm) { super(Packet.class, false); this.manager = manager; this.scm = scm; this.side = Side.SERVER; this.handshakeChannel = new EmbeddedChannel( new HandshakeInjector(this), new ChannelRegistrationHandler(), new FMLHandshakeCodec(), new HandshakeMessageHandler<FMLHandshakeServerState>(FMLHandshakeServerState.class)); this.handshakeChannel.attr(FML_DISPATCHER).set(this); this.handshakeChannel.attr(NetworkRegistry.CHANNEL_SOURCE).set(Side.CLIENT); this.handshakeChannel.attr(NetworkRegistry.FML_CHANNEL).set("FML|HS"); this.handshakeChannel.attr(IS_LOCAL).set(manager.isLocalChannel()); if (DEBUG_HANDSHAKE) PacketLoggingHandler.register(manager); } public void serverToClientHandshake(EntityPlayerMP player) { this.player = player; insertIntoChannel(); Boolean fml = this.manager.channel().attr(NetworkRegistry.FML_MARKER).get(); if (fml != null && fml.booleanValue()) { // FML on client, send server hello // TODO: Make this cleaner as it uses netty magic 0.o } else { serverInitiateHandshake(); FMLLog.info("Connection received without FML marker, assuming vanilla."); this.completeServerSideConnection(ConnectionType.VANILLA); } } protected void setModList(Map<String, String> modList) { this.modList = modList; } private void insertIntoChannel() { this.manager.channel().config().setAutoRead(false); // Insert ourselves into the pipeline this.manager.channel().pipeline().addBefore("packet_handler", "fml:packet_handler", this); } public void clientToServerHandshake() { insertIntoChannel(); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { if (this.state != null) { FMLLog.getLogger() .log( Level.INFO, "Opening channel which already seems to have a state set. This is a vanilla connection. Handshake handler will stop now"); return; } FMLLog.getLogger().log(Level.TRACE, "Handshake channel activating"); this.state = ConnectionState.OPENING; // send ourselves as a user event, to kick the pipeline active this.handshakeChannel.pipeline().fireUserEventTriggered(this); this.manager.channel().config().setAutoRead(true); } int serverInitiateHandshake() { // Send mod salutation to the client // This will be ignored by vanilla clients this.state = ConnectionState.AWAITING_HANDSHAKE; // Need to start the handler here, so we can send custompayload packets serverHandler = new NetHandlerPlayServer(scm.getServerInstance(), manager, player) { /** Updates the JList with a new model. */ @Override public void update() { if (NetworkDispatcher.this.state == ConnectionState.FINALIZING) { completeServerSideConnection(ConnectionType.MODDED); } super.update(); } }; this.netHandler = serverHandler; // NULL the play server here - we restore it further on. If not, there are packets sent before // the login player.playerNetServerHandler = null; // manually for the manager into the PLAY state, so we can send packets later this.manager.setConnectionState(EnumConnectionState.PLAY); // Return the dimension the player is in, so it can be pre-sent to the client in the ServerHello // v2 packet // Requires some hackery to the serverconfigmanager and stuff for this to work NBTTagCompound playerNBT = scm.getPlayerNBT(player); if (playerNBT != null) { return playerNBT.getInteger("Dimension"); } else { return 0; } } void clientListenForServerHandshake() { manager.setConnectionState(EnumConnectionState.PLAY); // FMLCommonHandler.instance().waitForPlayClient(); this.netHandler = FMLCommonHandler.instance().getClientPlayHandler(); this.state = ConnectionState.AWAITING_HANDSHAKE; } private void completeClientSideConnection(ConnectionType type) { this.connectionType = type; FMLLog.info( "[%s] Client side %s connection established", Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH)); this.state = ConnectionState.CONNECTED; FMLCommonHandler.instance() .bus() .post( new FMLNetworkEvent.ClientConnectedToServerEvent(manager, this.connectionType.name())); } private synchronized void completeServerSideConnection(ConnectionType type) { this.connectionType = type; FMLLog.info( "[%s] Server side %s connection established", Thread.currentThread().getName(), this.connectionType.name().toLowerCase(Locale.ENGLISH)); this.state = ConnectionState.CONNECTED; FMLCommonHandler.instance() .bus() .post(new FMLNetworkEvent.ServerConnectionFromClientEvent(manager)); if (DEBUG_HANDSHAKE) manager.closeChannel( new ChatComponentText("Handshake Complete review log file for details.")); scm.initializeConnectionToPlayer(manager, player, serverHandler); } @Override protected void channelRead0(ChannelHandlerContext ctx, Packet msg) throws Exception { boolean handled = false; if (msg instanceof C17PacketCustomPayload) { handled = handleServerSideCustomPacket((C17PacketCustomPayload) msg, ctx); } else if (msg instanceof S3FPacketCustomPayload) { handled = handleClientSideCustomPacket((S3FPacketCustomPayload) msg, ctx); } else if (state != ConnectionState.CONNECTED && state != ConnectionState.HANDSHAKECOMPLETE) { handled = handleVanilla(msg); } if (!handled) { ctx.fireChannelRead(msg); } } private boolean handleVanilla(Packet msg) { if (state == ConnectionState.AWAITING_HANDSHAKE && msg instanceof S01PacketJoinGame) { handshakeChannel.pipeline().fireUserEventTriggered(msg); } else { FMLLog.info( "Unexpected packet during modded negotiation - assuming vanilla or keepalives : %s", msg.getClass().getName()); } return false; } public INetHandler getNetHandler() { return netHandler; } /** * The mod list returned by this method is in no way reliable because it is provided by the client * * @return a map that will contain String keys and values listing all mods and their versions */ public Map<String, String> getModList() { return Collections.unmodifiableMap(modList); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof ConnectionType && side == Side.SERVER) { FMLLog.info("Timeout occurred, assuming a vanilla client"); kickVanilla(); } } private void kickVanilla() { kickWithMessage("This is modded. No modded response received. Bye!"); } private void kickWithMessage(String message) { final ChatComponentText chatcomponenttext = new ChatComponentText(message); if (side == Side.CLIENT) { manager.closeChannel(chatcomponenttext); } else { manager.sendPacket( new S40PacketDisconnect(chatcomponenttext), new GenericFutureListener<Future<?>>() { @Override public void operationComplete(Future<?> result) { manager.closeChannel(chatcomponenttext); } }); } manager.channel().config().setAutoRead(false); } private MultiPartCustomPayload multipart = null; private boolean handleClientSideCustomPacket( S3FPacketCustomPayload msg, ChannelHandlerContext context) { String channelName = msg.getChannelName(); if ("FML|MP".equals(channelName)) { try { if (multipart == null) { multipart = new MultiPartCustomPayload(msg.getBufferData()); } else { multipart.processPart(msg.getBufferData()); } } catch (IOException e) { this.kickWithMessage(e.getMessage()); multipart = null; return true; } if (multipart.isComplete()) { msg = multipart; channelName = msg.getChannelName(); multipart = null; } else { return true; // Haven't received all so return till we have. } } if ("FML|HS".equals(channelName) || "REGISTER".equals(channelName) || "UNREGISTER".equals(channelName)) { FMLProxyPacket proxy = new FMLProxyPacket(msg); proxy.setDispatcher(this); handshakeChannel.writeInbound(proxy); // forward any messages into the regular channel for (Object push : handshakeChannel.inboundMessages()) { List<FMLProxyPacket> messageResult = FMLNetworkHandler.forwardHandshake( (FMLMessage.CompleteHandshake) push, this, Side.CLIENT); for (FMLProxyPacket result : messageResult) { result.setTarget(Side.CLIENT); result.payload().resetReaderIndex(); context.fireChannelRead(result); } } handshakeChannel.inboundMessages().clear(); return true; } else if (NetworkRegistry.INSTANCE.hasChannel(channelName, Side.CLIENT)) { FMLProxyPacket proxy = new FMLProxyPacket(msg); proxy.setDispatcher(this); context.fireChannelRead(proxy); return true; } return false; } private boolean handleServerSideCustomPacket( C17PacketCustomPayload msg, ChannelHandlerContext context) { if (state == ConnectionState.AWAITING_HANDSHAKE) { synchronized (this) { // guard from other threads changing the state on us if (state == ConnectionState.AWAITING_HANDSHAKE) { state = ConnectionState.HANDSHAKING; } } } String channelName = msg.getChannelName(); if ("FML|HS".equals(channelName) || "REGISTER".equals(channelName) || "UNREGISTER".equals(channelName)) { FMLProxyPacket proxy = new FMLProxyPacket(msg); proxy.setDispatcher(this); handshakeChannel.writeInbound(proxy); for (Object push : handshakeChannel.inboundMessages()) { List<FMLProxyPacket> messageResult = FMLNetworkHandler.forwardHandshake( (FMLMessage.CompleteHandshake) push, this, Side.SERVER); for (FMLProxyPacket result : messageResult) { result.setTarget(Side.SERVER); result.payload().resetReaderIndex(); context.fireChannelRead(result); } } handshakeChannel.inboundMessages().clear(); return true; } else if (NetworkRegistry.INSTANCE.hasChannel(channelName, Side.SERVER)) { FMLProxyPacket proxy = new FMLProxyPacket(msg); proxy.setDispatcher(this); context.fireChannelRead(proxy); return true; } return false; } public void sendProxy(FMLProxyPacket msg) { manager.sendPacket(msg); } public void rejectHandshake(String result) { kickWithMessage(result); } @Override public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception { ctx.bind(localAddress, promise); } @Override public void connect( ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception { ctx.connect(remoteAddress, localAddress, promise); } @Override public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { if (side == Side.CLIENT) { FMLCommonHandler.instance() .bus() .post(new FMLNetworkEvent.ClientDisconnectionFromServerEvent(manager)); } else { FMLCommonHandler.instance() .bus() .post(new FMLNetworkEvent.ServerDisconnectionFromClientEvent(manager)); } cleanAttributes(ctx); ctx.disconnect(promise); } @Override public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { if (side == Side.CLIENT) { FMLCommonHandler.instance() .bus() .post(new FMLNetworkEvent.ClientDisconnectionFromServerEvent(manager)); } else { FMLCommonHandler.instance() .bus() .post(new FMLNetworkEvent.ServerDisconnectionFromClientEvent(manager)); } cleanAttributes(ctx); ctx.close(promise); } @Override @Deprecated public void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { ctx.deregister(promise); } @Override public void read(ChannelHandlerContext ctx) throws Exception { ctx.read(); } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (msg instanceof FMLProxyPacket) { if (side == Side.CLIENT) { // Client to server large packets are not supported to prevent client being bad. ctx.write(((FMLProxyPacket) msg).toC17Packet(), promise); } else { List<Packet> parts = ((FMLProxyPacket) msg).toS3FPackets(); for (Packet pkt : parts) { ctx.write(pkt, promise); } } } else { ctx.write(msg, promise); } } @Override public void flush(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } public void completeHandshake(Side target) { if (state == ConnectionState.CONNECTED) { FMLLog.severe("Attempt to double complete the network connection!"); throw new FMLNetworkException("Attempt to double complete!"); } if (side == Side.CLIENT) { completeClientSideConnection(ConnectionType.MODDED); } else { this.state = ConnectionState.FINALIZING; // Delay and finalize in the world tick loop. } } public void completeClientHandshake() { state = ConnectionState.HANDSHAKECOMPLETE; } public void abortClientHandshake(String type) { FMLLog.log(Level.INFO, "Aborting client handshake \"%s\"", type); // FMLCommonHandler.instance().waitForPlayClient(); completeClientSideConnection(ConnectionType.valueOf(type)); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // Stop the epic channel closed spam at close if (!(cause instanceof ClosedChannelException)) { FMLLog.log(Level.ERROR, cause, "NetworkDispatcher exception"); } super.exceptionCaught(ctx, cause); } // if we add any attributes, we should force removal of them here so that // they do not hold references to the world and causes it to leak. private void cleanAttributes(ChannelHandlerContext ctx) { ctx.channel().attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).remove(); ctx.channel().attr(NetworkRegistry.NET_HANDLER).remove(); ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).remove(); this.handshakeChannel.attr(FML_DISPATCHER).remove(); this.manager.channel().attr(FML_DISPATCHER).remove(); } public void setOverrideDimension(int overrideDim) { this.overrideLoginDim = overrideDim; FMLLog.fine("Received override dimension %d", overrideDim); } public int getOverrideDimension(S01PacketJoinGame packetIn) { FMLLog.fine("Overriding dimension: using %d", this.overrideLoginDim); return this.overrideLoginDim != 0 ? this.overrideLoginDim : packetIn.getDimension(); } private class MultiPartCustomPayload extends S3FPacketCustomPayload { private String channel; private byte[] data; private PacketBuffer data_buf = null; private int part_count = 0; private int part_expected = 0; private int offset = 0; private MultiPartCustomPayload(PacketBuffer preamble) throws IOException { channel = preamble.readStringFromBuffer(20); part_count = preamble.readUnsignedByte(); int length = preamble.readInt(); if (length <= 0 || length >= FMLProxyPacket.MAX_LENGTH) { throw new IOException( "The received FML MultiPart packet outside of valid length bounds, Max: " + FMLProxyPacket.MAX_LENGTH + ", Received: " + length); } data = new byte[length]; data_buf = new PacketBuffer(Unpooled.wrappedBuffer(data)); } public void processPart(PacketBuffer input) throws IOException { int part = (int) (input.readByte() & 0xFF); if (part != part_expected) { throw new IOException( "Received FML MultiPart packet out of order, Expected " + part_expected + " Got " + part); } int len = input.readableBytes() - 1; input.readBytes(data, offset, len); part_expected++; offset += len; } public boolean isComplete() { return part_expected == part_count; } @Override public String getChannelName() // getChannel { return this.channel; } @Override public PacketBuffer getBufferData() // getData { return this.data_buf; } } }
public class NetworkManager extends SimpleChannelInboundHandler { private static final Logger logger = LogManager.getLogger(); public static final Marker logMarkerNetwork = MarkerManager.getMarker("NETWORK"); public static final Marker logMarkerPackets = MarkerManager.getMarker("NETWORK_PACKETS", logMarkerNetwork); public static final AttributeKey attrKeyConnectionState = AttributeKey.valueOf("protocol"); public static final LazyLoadBase CLIENT_NIO_EVENTLOOP = new LazyLoadBase() { protected NioEventLoopGroup genericLoad() { return new NioEventLoopGroup( 0, (new ThreadFactoryBuilder()) .setNameFormat("Netty Client IO #%d") .setDaemon(true) .build()); } protected Object load() { return this.genericLoad(); } }; public static final LazyLoadBase CLIENT_LOCAL_EVENTLOOP = new LazyLoadBase() { protected LocalEventLoopGroup genericLoad() { return new LocalEventLoopGroup( 0, (new ThreadFactoryBuilder()) .setNameFormat("Netty Local Client IO #%d") .setDaemon(true) .build()); } protected Object load() { return this.genericLoad(); } }; private final EnumPacketDirection direction; /** The queue for packets that require transmission */ private final Queue outboundPacketsQueue = Queues.newConcurrentLinkedQueue(); /** The active channel */ private Channel channel; /** The address of the remote party */ private SocketAddress socketAddress; /** The INetHandler instance responsible for processing received packets */ private INetHandler packetListener; /** A String indicating why the network has shutdown. */ private IChatComponent terminationReason; private boolean isEncrypted; private boolean disconnected; public NetworkManager(EnumPacketDirection packetDirection) { this.direction = packetDirection; } public void channelActive(ChannelHandlerContext p_channelActive_1_) throws Exception { super.channelActive(p_channelActive_1_); this.channel = p_channelActive_1_.channel(); this.socketAddress = this.channel.remoteAddress(); try { this.setConnectionState(EnumConnectionState.HANDSHAKING); } catch (Throwable var3) { logger.fatal(var3); } } /** Sets the new connection state and registers which packets this channel may send and receive */ public void setConnectionState(EnumConnectionState newState) { this.channel.attr(attrKeyConnectionState).set(newState); this.channel.config().setAutoRead(true); logger.debug("Enabled auto read"); } public void channelInactive(ChannelHandlerContext p_channelInactive_1_) { this.closeChannel(new ChatComponentTranslation("disconnect.endOfStream", new Object[0])); } public void exceptionCaught( ChannelHandlerContext p_exceptionCaught_1_, Throwable p_exceptionCaught_2_) { logger.debug("Disconnecting " + this.getRemoteAddress(), p_exceptionCaught_2_); this.closeChannel( new ChatComponentTranslation( "disconnect.genericReason", new Object[] {"Internal Exception: " + p_exceptionCaught_2_})); } protected void channelRead0(ChannelHandlerContext p_channelRead0_1_, Packet p_channelRead0_2_) { ReadPacketEvent event = new ReadPacketEvent(p_channelRead0_2_); XIV.getInstance().getListenerManager().call(event); if (event.isCancelled()) return; p_channelRead0_2_ = event.getPacket(); if (this.channel.isOpen()) { try { p_channelRead0_2_.processPacket(this.packetListener); } catch (ThreadQuickExitException var4) {; } } } /** * Sets the NetHandler for this NetworkManager, no checks are made if this handler is suitable for * the particular connection state (protocol) */ public void setNetHandler(INetHandler handler) { Validate.notNull(handler, "packetListener", new Object[0]); logger.debug("Set listener of {} to {}", new Object[] {this, handler}); this.packetListener = handler; } public void sendPacket(Packet packetIn) { if (this.channel != null && this.channel.isOpen()) { this.flushOutboundQueue(); this.dispatchPacket(packetIn, (GenericFutureListener[]) null); } else { this.outboundPacketsQueue.add( new NetworkManager.InboundHandlerTuplePacketListener( packetIn, (GenericFutureListener[]) null)); } } public void sendPacket( Packet packetIn, GenericFutureListener listener, GenericFutureListener... listeners) { if (this.channel != null && this.channel.isOpen()) { this.flushOutboundQueue(); this.dispatchPacket( packetIn, (GenericFutureListener[]) ArrayUtils.add(listeners, 0, listener)); } else { this.outboundPacketsQueue.add( new NetworkManager.InboundHandlerTuplePacketListener( packetIn, (GenericFutureListener[]) ArrayUtils.add(listeners, 0, listener))); } } /** * Will commit the packet to the channel. If the current thread 'owns' the channel it will write * and flush the packet, otherwise it will add a task for the channel eventloop thread to do that. */ private void dispatchPacket( final Packet inPacket, final GenericFutureListener[] futureListeners) { final EnumConnectionState var3 = EnumConnectionState.getFromPacket(inPacket); final EnumConnectionState var4 = (EnumConnectionState) this.channel.attr(attrKeyConnectionState).get(); if (var4 != var3) { logger.debug("Disabled auto read"); this.channel.config().setAutoRead(false); } if (this.channel.eventLoop().inEventLoop()) { if (var3 != var4) { this.setConnectionState(var3); } ChannelFuture var5 = this.channel.writeAndFlush(inPacket); if (futureListeners != null) { var5.addListeners(futureListeners); } var5.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } else { this.channel .eventLoop() .execute( new Runnable() { public void run() { if (var3 != var4) { NetworkManager.this.setConnectionState(var3); } ChannelFuture var1 = NetworkManager.this.channel.writeAndFlush(inPacket); if (futureListeners != null) { var1.addListeners(futureListeners); } var1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE); } }); } } /** Will iterate through the outboundPacketQueue and dispatch all Packets */ private void flushOutboundQueue() { if (this.channel != null && this.channel.isOpen()) { while (!this.outboundPacketsQueue.isEmpty()) { NetworkManager.InboundHandlerTuplePacketListener var1 = (NetworkManager.InboundHandlerTuplePacketListener) this.outboundPacketsQueue.poll(); this.dispatchPacket(var1.packet, var1.futureListeners); } } } /** Checks timeouts and processes all packets received */ public void processReceivedPackets() { this.flushOutboundQueue(); if (this.packetListener instanceof IUpdatePlayerListBox) { ((IUpdatePlayerListBox) this.packetListener).update(); } this.channel.flush(); } /** Returns the socket address of the remote side. Server-only. */ public SocketAddress getRemoteAddress() { return this.socketAddress; } /** * Closes the channel, the parameter can be used for an exit message (not certain how it gets * sent) */ public void closeChannel(IChatComponent message) { if (this.channel.isOpen()) { this.channel.close().awaitUninterruptibly(); this.terminationReason = message; } } /** * True if this NetworkManager uses a memory connection (single player game). False may imply both * an active TCP connection or simply no active connection at all */ public boolean isLocalChannel() { return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel; } /** * Prepares a clientside NetworkManager: establishes a connection to the address and port supplied * and configures the channel pipeline. Returns the newly created instance. */ public static NetworkManager provideLanClient(InetAddress p_150726_0_, int p_150726_1_) { final NetworkManager var2 = new NetworkManager(EnumPacketDirection.CLIENTBOUND); ((Bootstrap) ((Bootstrap) ((Bootstrap) (new Bootstrap()) .group((EventLoopGroup) CLIENT_NIO_EVENTLOOP.getValue())) .handler( new ChannelInitializer() { protected void initChannel(Channel p_initChannel_1_) { try { p_initChannel_1_ .config() .setOption(ChannelOption.IP_TOS, Integer.valueOf(24)); } catch (ChannelException var4) {; } try { p_initChannel_1_ .config() .setOption(ChannelOption.TCP_NODELAY, Boolean.valueOf(false)); } catch (ChannelException var3) {; } p_initChannel_1_ .pipeline() .addLast("timeout", new ReadTimeoutHandler(20)) .addLast("splitter", new MessageDeserializer2()) .addLast( "decoder", new MessageDeserializer(EnumPacketDirection.CLIENTBOUND)) .addLast("prepender", new MessageSerializer2()) .addLast( "encoder", new MessageSerializer(EnumPacketDirection.SERVERBOUND)) .addLast("packet_handler", var2); } })) .channel(NioSocketChannel.class)) .connect(p_150726_0_, p_150726_1_) .syncUninterruptibly(); return var2; } /** * Prepares a clientside NetworkManager: establishes a connection to the socket supplied and * configures the channel pipeline. Returns the newly created instance. */ public static NetworkManager provideLocalClient(SocketAddress p_150722_0_) { final NetworkManager var1 = new NetworkManager(EnumPacketDirection.CLIENTBOUND); ((Bootstrap) ((Bootstrap) ((Bootstrap) (new Bootstrap()) .group((EventLoopGroup) CLIENT_LOCAL_EVENTLOOP.getValue())) .handler( new ChannelInitializer() { protected void initChannel(Channel p_initChannel_1_) { p_initChannel_1_.pipeline().addLast("packet_handler", var1); } })) .channel(LocalChannel.class)) .connect(p_150722_0_) .syncUninterruptibly(); return var1; } /** * Adds an encoder+decoder to the channel pipeline. The parameter is the secret key used for * encrypted communication */ public void enableEncryption(SecretKey key) { this.isEncrypted = true; this.channel .pipeline() .addBefore( "splitter", "decrypt", new NettyEncryptingDecoder(CryptManager.func_151229_a(2, key))); this.channel .pipeline() .addBefore( "prepender", "encrypt", new NettyEncryptingEncoder(CryptManager.func_151229_a(1, key))); } public boolean func_179292_f() { return this.isEncrypted; } /** Returns true if this NetworkManager has an active channel, false otherwise */ public boolean isChannelOpen() { return this.channel != null && this.channel.isOpen(); } public boolean hasNoChannel() { return this.channel == null; } /** Gets the current handler for processing packets */ public INetHandler getNetHandler() { return this.packetListener; } /** If this channel is closed, returns the exit message, null otherwise. */ public IChatComponent getExitMessage() { return this.terminationReason; } /** Switches the channel to manual reading modus */ public void disableAutoRead() { this.channel.config().setAutoRead(false); } public void setCompressionTreshold(int treshold) { if (treshold >= 0) { if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) { ((NettyCompressionDecoder) this.channel.pipeline().get("decompress")) .setCompressionTreshold(treshold); } else { this.channel .pipeline() .addBefore("decoder", "decompress", new NettyCompressionDecoder(treshold)); } if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) { ((NettyCompressionEncoder) this.channel.pipeline().get("decompress")) .setCompressionTreshold(treshold); } else { this.channel .pipeline() .addBefore("encoder", "compress", new NettyCompressionEncoder(treshold)); } } else { if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) { this.channel.pipeline().remove("decompress"); } if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) { this.channel.pipeline().remove("compress"); } } } public void checkDisconnected() { if (!this.hasNoChannel() && !this.isChannelOpen() && !this.disconnected) { this.disconnected = true; if (this.getExitMessage() != null) { this.getNetHandler().onDisconnect(this.getExitMessage()); } else if (this.getNetHandler() != null) { this.getNetHandler().onDisconnect(new ChatComponentText("Disconnected")); } } } protected void channelRead0(ChannelHandlerContext p_channelRead0_1_, Object p_channelRead0_2_) { this.channelRead0(p_channelRead0_1_, (Packet) p_channelRead0_2_); } static class InboundHandlerTuplePacketListener { private final Packet packet; private final GenericFutureListener[] futureListeners; public InboundHandlerTuplePacketListener( Packet inPacket, GenericFutureListener... inFutureListeners) { this.packet = inPacket; this.futureListeners = inFutureListeners; } } }