diff --git a/src/common.hpp b/src/common.hpp
index 5596d2eaadc886a457983cbc3c0fde3f4dafda4f..02f342743909cf04713194d0a7dc0b3e0925b80c 100644
--- a/src/common.hpp
+++ b/src/common.hpp
@@ -5,5 +5,16 @@
 
 extern rlib::logger rlog;
 
+// epoll buffer size.
+constexpr size_t EPOLL_MAX_EVENTS = 32;
+// DGRAM packet usually smaller than 1400B.
+constexpr size_t DGRAM_BUFFER_SIZE = 20480;
+
+// Change a connection on every n seconds,
+//   to reset the GFW deep-packet-inspection process.
+// ( Only if server side is encrypted, so nothing happens
+//   to the real openvpn server.
+constexpr size_t SERVER_ENCRYPT_CONNECTION_TIMEOUT_SECONDS = 60;
+
 #endif
 
diff --git a/src/forwarder.hpp b/src/forwarder.hpp
index 163790b613e0a3bd181164ba527134f812f37219..91ba1e3a3ce7e50ca40f201339253337139d041f 100644
--- a/src/forwarder.hpp
+++ b/src/forwarder.hpp
@@ -12,7 +12,7 @@ struct ConnectionMapping {
     std::unordered_multimap<fd_t, string> server2client;
     static string clientInfoAsKey(string ip, uint16_t port) {
         // Also works for ipv6. We just want to eliminate duplication, rather than make it easy to read. 
-        return ip + "@" + port;
+        return ip + '@' + port;
     }
 };
 
diff --git a/src/protocols/base.hpp b/src/protocols/base.hpp
index 46e8ce8184d6dca092353b4036053dc4b01b3468..9aa17f74c607cf31dce188b9d0911a6b5d8785a5 100644
--- a/src/protocols/base.hpp
+++ b/src/protocols/base.hpp
@@ -6,23 +6,32 @@
 using std::string;
 
 namespace Protocols {
-	class BaseHandler : rlib::noncopyable {
+	// Handler holds the senderId=>nextHopFd mapping.
+	// senderId is "$ip@$port", for example, `fe80:8100::1@1080`. 
+	// Misc protocol may use duplicateSenderId to work on port migration.
+	// Any listener may use removeSenderId to disconnect a sender.
+	// Note: this interface works for both TCP and UDP.
+	struct BaseHandler : rlib::noncopyable {
 		BaseHandler(string outboundConfig) {
 			loadConfig(outboundConfig);
 		}
-		virtual ~BaseOutboundHandler = default;
+		virtual ~BaseHandler = default;
 
 		// Interfaces
-		virtual loadConfig(string config) = 0;
-		virtual handleMessage(string binaryMessage) = 0;
+		virtual void loadConfig(string config) = 0;
+		virtual void handleMessage(string binaryMessage, string senderId) = 0;
+		virtual void duplicateSenderId(string newSenderId, string oldSenderId) = 0;
+		virtual void removeSenderId(string senderId) = 0;
 	};
 
-	class BaseListener : rlib::noncopyable {
+	struct BaseListener : rlib::noncopyable {
 		BaseListener(string inboundConfig) {
 			loadConfig(inboundConfig);
 		}
-		virtual loadConfig(string config) = 0;
-		virtual listenForever(BaseHandler *nextHop) = 0;
+		virtual ~BaseListener = default;
+
+		virtual void loadConfig(string config) = 0;
+		virtual void listenForever(BaseHandler *nextHop) = 0;
 	};
 }
 
diff --git a/src/protocols/misc.hpp b/src/protocols/misc.hpp
index 2f456e4363ede9426aa82cb872f14da0c5e7ad51..ceef4e5fc24d457dba05ef691d0199eef5148b01 100644
--- a/src/protocols/misc.hpp
+++ b/src/protocols/misc.hpp
@@ -16,10 +16,14 @@ namespace Protocols {
 			// listenPort = ar[2].as<uint16_t>();
 			psk = ar[3];
 
+			// listen these ports.
 
 		}
 		virtual listenForever(BaseHandler* nextHop) override {
 
+			// if message arrives:
+			// send to handler. 
+
 		}
 
 	private:
diff --git a/src/protocols/plain.hpp b/src/protocols/plain.hpp
index ba4b955fb30a5e8f69090b30c0a813d0bf4f3a59..c2f6b63df7971102c811f5ff0598e8d4b024f580 100644
--- a/src/protocols/plain.hpp
+++ b/src/protocols/plain.hpp
@@ -4,6 +4,8 @@
 #include <protocols/base.hpp>
 #include <rlib/sys/sio.hpp>
 #include <rlib/string.hpp>
+#include <utils.hpp>
+#include <common.hpp>
 
 namespace Protocols {
 	class PlainInboundListener : public BaseListener {
@@ -12,17 +14,33 @@ namespace Protocols {
 			auto ar = rlib::string(config).split('@'); // Also works for ipv6.
 			if (ar.size() != 3)
 				throw std::invalid_argument("Wrong parameter string for protocol 'plain'. Example:    plain@fe00:1e10:ce95:1@10809");
-			auto listenAddr = ar[1];
-			auto listenPort = ar[2].as<uint16_t>();
+			listenAddr = ar[1];
+			listenPort = ar[2].as<uint16_t>();
 
-			listenFd = rlib::quick_listen(listenAddr, listenPort, true);
 		}
 		virtual listenForever(BaseHandler* nextHop) override {
+			auto listenFd = rlib::quick_listen(listenAddr, listenPort, true);
+			rlib_defer([&] {close(listenFd);});
+
+			auto epollFd = epoll_create1(0);
+			if(epollFd == -1)
+				throw std::runtime_error("Failed to create epoll fd.");
+			epoll_add_fd(epollFd, listenFd);
+
+			epoll_event events[MAX_EVENTS];
+			char buffer[DGRAM_BUFFER_SIZE];
+			// WARN: If you want to modify this program to work for both TCP and UDP, PLEASE use rlib::sockIO::recv instead of fixed buffer.
+			
+			rlog.info("PlainListener listening [{}]:{} ...", listenAddr, listenPort);
+			while (true) {
+				// ...
+			}
 
 		}
 
 	private:
-		fd_t listenFd;
+		string listenAddr;
+		uint16_t listenPort;
 	};
 
 	using PlainOutboundListener = PlainInboundListener;
diff --git a/src/utils.hpp b/src/utils.hpp
index d4c1d1453fdc120410a7d974c9b0814eb123ebcd..97313e10e8da1bc20c916322e05c61be6fa039e6 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -9,6 +9,7 @@
 #include <wepoll.h>
 #endif
 
+
 inline void epoll_add_fd(fd_t epollFd, fd_t fd) {
     epoll_event event {
         .events = EPOLLIN,