You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by mo...@apache.org on 2023/06/29 23:44:21 UTC

[trafficserver] branch master updated: Untangle some classes in iocore (#9838)

This is an automated email from the ASF dual-hosted git repository.

mochen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new ee92c0846a Untangle some classes in iocore (#9838)
ee92c0846a is described below

commit ee92c0846a8974be414a4f77955be6bb3365026c
Author: Mo Chen <mo...@apache.org>
AuthorDate: Thu Jun 29 18:44:15 2023 -0500

    Untangle some classes in iocore (#9838)
    
    - Isolate the following classes into their own files
      - NetEvent
      - NetHandler
      - NetVCOptions
      - PollCont
      - AcceptOptions
    - Fix some includes other files to compile cleanly
---
 iocore/dns/P_DNSConnection.h             |   1 +
 iocore/eventsystem/I_ProxyAllocator.h    |   1 +
 iocore/eventsystem/I_Thread.h            |   7 +-
 iocore/net/AcceptOptions.cc              |  49 +++
 iocore/net/AcceptOptions.h               |  98 +++++
 iocore/net/CMakeLists.txt                |   5 +
 iocore/net/EventIO.cc                    | 228 +++++++++++
 iocore/net/EventIO.h                     | 126 +++++++
 iocore/net/I_NetProcessor.h              |  71 +---
 iocore/net/I_NetVConnection.h            | 352 +----------------
 iocore/net/I_UDPNet.h                    |   1 +
 iocore/net/Makefile.am                   |  34 +-
 iocore/net/NetEvent.h                    |   2 +
 iocore/net/{UnixNet.cc => NetHandler.cc} | 274 ++++----------
 iocore/net/NetHandler.h                  | 235 ++++++++++++
 iocore/net/NetVCOptions.cc               |  71 ++++
 iocore/net/NetVCOptions.h                | 322 ++++++++++++++++
 iocore/net/P_NetAccept.h                 |   7 +-
 iocore/net/P_UDPNet.h                    |   2 +-
 iocore/net/P_UnixNet.h                   | 614 +-----------------------------
 iocore/net/P_UnixPollDescriptor.h        |  42 ++-
 iocore/net/PollCont.cc                   |  95 +++++
 iocore/net/PollCont.h                    |  43 +++
 iocore/net/UnixNet.cc                    | 627 ++-----------------------------
 iocore/net/UnixNetProcessor.cc           |  23 --
 iocore/net/UnixUDPNet.cc                 |   1 +
 iocore/net/test_I_UDPNet.cc              |   1 +
 proxy/http/HttpDebugNames.cc             |   1 +
 src/traffic_server/InkIOCoreAPI.cc       |   1 +
 29 files changed, 1442 insertions(+), 1892 deletions(-)

diff --git a/iocore/dns/P_DNSConnection.h b/iocore/dns/P_DNSConnection.h
index 2a1e599f1a..ed372eb9c2 100644
--- a/iocore/dns/P_DNSConnection.h
+++ b/iocore/dns/P_DNSConnection.h
@@ -32,6 +32,7 @@
 
 #include "I_EventSystem.h"
 #include "I_DNSProcessor.h"
+#include "EventIO.h"
 
 //
 // Connection
diff --git a/iocore/eventsystem/I_ProxyAllocator.h b/iocore/eventsystem/I_ProxyAllocator.h
index a5320de744..851de14624 100644
--- a/iocore/eventsystem/I_ProxyAllocator.h
+++ b/iocore/eventsystem/I_ProxyAllocator.h
@@ -34,6 +34,7 @@
 #include <utility>
 
 #include "tscore/ink_platform.h"
+#include "tscore/Allocator.h"
 
 class EThread;
 
diff --git a/iocore/eventsystem/I_Thread.h b/iocore/eventsystem/I_Thread.h
index 43acbde76b..30ea6e77d4 100644
--- a/iocore/eventsystem/I_Thread.h
+++ b/iocore/eventsystem/I_Thread.h
@@ -58,15 +58,12 @@
 
 #pragma once
 
-#if !defined(_I_EventSystem_h) && !defined(_P_EventSystem_h)
-#error "include I_EventSystem.h or P_EventSystem.h"
-#endif
-
 #include <functional>
 
+#include "I_ProxyAllocator.h"
+#include "tscore/Ptr.h"
 #include "tscore/ink_platform.h"
 #include "tscore/ink_thread.h"
-#include "I_ProxyAllocator.h"
 
 class ProxyMutex;
 
diff --git a/iocore/net/AcceptOptions.cc b/iocore/net/AcceptOptions.cc
new file mode 100644
index 0000000000..e1fc4bebaa
--- /dev/null
+++ b/iocore/net/AcceptOptions.cc
@@ -0,0 +1,49 @@
+/** @file
+
+  Asynchronous networking API
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ */
+
+#include "AcceptOptions.h"
+#include "I_Net.h"
+
+AcceptOptions &
+AcceptOptions::reset()
+{
+  local_port = 0;
+  local_ip.invalidate();
+  accept_threads        = -1;
+  ip_family             = AF_INET;
+  etype                 = ET_NET;
+  localhost_only        = false;
+  frequent_accept       = true;
+  recv_bufsize          = 0;
+  send_bufsize          = 0;
+  sockopt_flags         = 0;
+  packet_mark           = 0;
+  packet_tos            = 0;
+  packet_notsent_lowat  = 0;
+  tfo_queue_length      = 0;
+  f_inbound_transparent = false;
+  f_mptcp               = false;
+  f_proxy_protocol      = false;
+  return *this;
+}
diff --git a/iocore/net/AcceptOptions.h b/iocore/net/AcceptOptions.h
new file mode 100644
index 0000000000..22f9bc960d
--- /dev/null
+++ b/iocore/net/AcceptOptions.h
@@ -0,0 +1,98 @@
+/** @file
+
+  This file implements an I/O Processor for network I/O
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ */
+
+#pragma once
+
+#include "tscore/ink_inet.h"
+#include "I_Event.h"
+
+struct AcceptOptions {
+  using self = AcceptOptions; ///< Self reference type.
+
+  /// Port on which to listen.
+  /// 0 => don't care, which is useful if the socket is already bound.
+  int local_port;
+  /// Local address to bind for accept.
+  /// If not set -> any address.
+  IpAddr local_ip;
+  /// IP address family.
+  /// @note Ignored if an explicit incoming address is set in the
+  /// the configuration (@c local_ip). If neither is set IPv4 is used.
+  int ip_family;
+  /// Should we use accept threads? If so, how many?
+  int accept_threads;
+  /// Event type to generate on accept.
+  EventType etype;
+  /** If @c true, the continuation is called back with
+      @c NET_EVENT_ACCEPT_SUCCEED
+      or @c NET_EVENT_ACCEPT_FAILED on success and failure resp.
+  */
+
+  bool localhost_only;
+  /// Are frequent accepts expected?
+  /// Default: @c false.
+  bool frequent_accept;
+
+  /// Socket receive buffer size.
+  /// 0 => OS default.
+  int recv_bufsize;
+  /// Socket transmit buffer size.
+  /// 0 => OS default.
+  int send_bufsize;
+  /// defer accept for @c sockopt.
+  /// 0 => OS default.
+  int defer_accept;
+  /// Socket options for @c sockopt.
+  /// 0 => do not set options.
+  uint32_t sockopt_flags;
+  uint32_t packet_mark;
+  uint32_t packet_tos;
+  uint32_t packet_notsent_lowat;
+
+  int tfo_queue_length;
+
+  /** Transparency on client (user agent) connection.
+      @internal This is irrelevant at a socket level (since inbound
+      transparency must be set up when the listen socket is created)
+      but it's critical that the connection handling logic knows
+      whether the inbound (client / user agent) connection is
+      transparent.
+  */
+  bool f_inbound_transparent;
+
+  /** MPTCP enabled on listener.
+      @internal For logging and metrics purposes to know whether the
+      listener enabled MPTCP or not.
+  */
+  bool f_mptcp;
+
+  /// Proxy Protocol enabled
+  bool f_proxy_protocol;
+
+  /// Default constructor.
+  /// Instance is constructed with default values.
+  AcceptOptions() { this->reset(); }
+  /// Reset all values to defaults.
+  self &reset();
+};
diff --git a/iocore/net/CMakeLists.txt b/iocore/net/CMakeLists.txt
index 7aed806c98..618f2e49d4 100644
--- a/iocore/net/CMakeLists.txt
+++ b/iocore/net/CMakeLists.txt
@@ -17,14 +17,19 @@
 
 
 add_library(inknet STATIC
+        AcceptOptions.cc
         ALPNSupport.cc
         BIO_fastopen.cc
         BoringSSLUtils.cc
         Connection.cc
+        EventIO.cc
         Inline.cc
         YamlSNIConfig.cc
         Net.cc
+        NetHandler.cc
+        NetVCOptions.cc
         NetVConnection.cc
+        PollCont.cc
         ProxyProtocol.cc
         Socks.cc
         SSLCertLookup.cc
diff --git a/iocore/net/EventIO.cc b/iocore/net/EventIO.cc
new file mode 100644
index 0000000000..7c4b7dafe3
--- /dev/null
+++ b/iocore/net/EventIO.cc
@@ -0,0 +1,228 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "EventIO.h"
+#include "tscore/ink_assert.h"
+#include "P_Net.h"
+#include "P_UnixNetProcessor.h"
+#include "P_UnixNetVConnection.h"
+#include "P_NetAccept.h"
+#include "P_DNSConnection.h"
+#include "P_UnixUDPConnection.h"
+#include "P_UnixPollDescriptor.h"
+
+int
+EventIO::start(EventLoop l, DNSConnection *vc, int events)
+{
+  type        = EVENTIO_DNS_CONNECTION;
+  data.dnscon = vc;
+  return start_common(l, vc->fd, events);
+}
+int
+EventIO::start(EventLoop l, NetAccept *vc, int events)
+{
+  type    = EVENTIO_NETACCEPT;
+  data.na = vc;
+  return start_common(l, vc->server.fd, events);
+}
+int
+EventIO::start(EventLoop l, NetEvent *ne, int events)
+{
+  type    = EVENTIO_READWRITE_VC;
+  data.ne = ne;
+  return start_common(l, ne->get_fd(), events);
+}
+
+int
+EventIO::start(EventLoop l, UnixUDPConnection *vc, int events)
+{
+  type    = EVENTIO_UDP_CONNECTION;
+  data.uc = vc;
+  return start_common(l, vc->fd, events);
+}
+
+int
+EventIO::start(EventLoop l, int afd, NetEvent *ne, int e)
+{
+  data.ne = ne;
+  return start_common(l, afd, e);
+}
+
+int
+EventIO::start_common(EventLoop l, int afd, int e)
+{
+  if (!this->syscall) {
+    return 0;
+  }
+
+  fd         = afd;
+  event_loop = l;
+#if TS_USE_EPOLL
+  struct epoll_event ev;
+  memset(&ev, 0, sizeof(ev));
+  ev.events   = e | EPOLLEXCLUSIVE;
+  ev.data.ptr = this;
+#ifndef USE_EDGE_TRIGGER
+  events = e;
+#endif
+  return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+#endif
+#if TS_USE_KQUEUE
+  events = e;
+  struct kevent ev[2];
+  int n = 0;
+  if (e & EVENTIO_READ) {
+    EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+  }
+  if (e & EVENTIO_WRITE) {
+    EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+  }
+  return kevent(l->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
+#endif
+}
+
+int
+EventIO::modify(int e)
+{
+  if (!this->syscall) {
+    return 0;
+  }
+
+  ink_assert(event_loop);
+#if TS_USE_EPOLL && !defined(USE_EDGE_TRIGGER)
+  struct epoll_event ev;
+  memset(&ev, 0, sizeof(ev));
+  int new_events = events, old_events = events;
+  if (e < 0)
+    new_events &= ~(-e);
+  else
+    new_events |= e;
+  events      = new_events;
+  ev.events   = new_events;
+  ev.data.ptr = this;
+  if (!new_events)
+    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, &ev);
+  else if (!old_events)
+    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
+  else
+    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_MOD, fd, &ev);
+#endif
+#if TS_USE_KQUEUE && !defined(USE_EDGE_TRIGGER)
+  int n = 0;
+  struct kevent ev[2];
+  int ee = events;
+  if (e < 0) {
+    ee &= ~(-e);
+    if ((-e) & EVENTIO_READ)
+      EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, this);
+    if ((-e) & EVENTIO_WRITE)
+      EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, this);
+  } else {
+    ee |= e;
+    if (e & EVENTIO_READ)
+      EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+    if (e & EVENTIO_WRITE)
+      EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+  }
+  events = ee;
+  if (n)
+    return kevent(event_loop->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
+  else
+    return 0;
+#endif
+  (void)e; // ATS_UNUSED
+  return 0;
+}
+
+int
+EventIO::refresh(int e)
+{
+  if (!this->syscall) {
+    return 0;
+  }
+
+  ink_assert(event_loop);
+#if TS_USE_KQUEUE && defined(USE_EDGE_TRIGGER)
+  e = e & events;
+  struct kevent ev[2];
+  int n = 0;
+  if (e & EVENTIO_READ) {
+    EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+  }
+  if (e & EVENTIO_WRITE) {
+    EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
+  }
+  if (n) {
+    return kevent(event_loop->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
+  } else {
+    return 0;
+  }
+#endif
+  (void)e; // ATS_UNUSED
+  return 0;
+}
+
+int
+EventIO::stop()
+{
+  if (!this->syscall) {
+    return 0;
+  }
+  if (event_loop) {
+    int retval = 0;
+#if TS_USE_EPOLL
+    struct epoll_event ev;
+    memset(&ev, 0, sizeof(struct epoll_event));
+    ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
+    retval    = epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, &ev);
+#endif
+    event_loop = nullptr;
+    return retval;
+  }
+  return 0;
+}
+
+int
+EventIO::close()
+{
+  if (!this->syscall) {
+    return 0;
+  }
+
+  stop();
+  switch (type) {
+  default:
+    ink_assert(!"case");
+  // fallthrough
+  case EVENTIO_DNS_CONNECTION:
+    return data.dnscon->close();
+    break;
+  case EVENTIO_NETACCEPT:
+    return data.na->server.close();
+    break;
+  case EVENTIO_READWRITE_VC:
+    return data.ne->close();
+    break;
+  }
+  return -1;
+}
diff --git a/iocore/net/EventIO.h b/iocore/net/EventIO.h
new file mode 100644
index 0000000000..c513691970
--- /dev/null
+++ b/iocore/net/EventIO.h
@@ -0,0 +1,126 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+#include "P_UnixPollDescriptor.h"
+
+#define USE_EDGE_TRIGGER_EPOLL  1
+#define USE_EDGE_TRIGGER_KQUEUE 1
+#define USE_EDGE_TRIGGER_PORT   1
+
+#define EVENTIO_NETACCEPT      1
+#define EVENTIO_READWRITE_VC   2
+#define EVENTIO_DNS_CONNECTION 3
+#define EVENTIO_UDP_CONNECTION 4
+#define EVENTIO_ASYNC_SIGNAL   5
+#define EVENTIO_IO_URING       6
+
+#if TS_USE_EPOLL
+#ifndef EPOLLEXCLUSIVE
+#define EPOLLEXCLUSIVE 0
+#endif
+#ifdef USE_EDGE_TRIGGER_EPOLL
+#define USE_EDGE_TRIGGER 1
+#define EVENTIO_READ     (EPOLLIN | EPOLLET)
+#define EVENTIO_WRITE    (EPOLLOUT | EPOLLET)
+#else
+#define EVENTIO_READ  EPOLLIN
+#define EVENTIO_WRITE EPOLLOUT
+#endif
+#define EVENTIO_ERROR (EPOLLERR | EPOLLPRI | EPOLLHUP)
+#endif
+#if TS_USE_KQUEUE
+#ifdef USE_EDGE_TRIGGER_KQUEUE
+#define USE_EDGE_TRIGGER    1
+#define INK_EV_EDGE_TRIGGER EV_CLEAR
+#else
+#define INK_EV_EDGE_TRIGGER 0
+#endif
+#define EVENTIO_READ  INK_EVP_IN
+#define EVENTIO_WRITE INK_EVP_OUT
+#define EVENTIO_ERROR (0x010 | 0x002 | 0x020) // ERR PRI HUP
+#endif
+
+struct PollDescriptor;
+using EventLoop = PollDescriptor *;
+
+class NetEvent;
+class UnixUDPConnection;
+class DiskHandler;
+struct DNSConnection;
+struct NetAccept;
+
+/// Unified API for setting and clearing kernel and epoll events.
+struct EventIO {
+  int fd = -1; ///< file descriptor, often a system port
+#if TS_USE_KQUEUE || TS_USE_EPOLL && !defined(USE_EDGE_TRIGGER)
+  int events = 0; ///< a bit mask of enabled events
+#endif
+  EventLoop event_loop = nullptr; ///< the assigned event loop
+  bool syscall         = true;    ///< if false, disable all functionality (for QUIC)
+  int type             = 0;       ///< class identifier of union data.
+  union {
+    void *untyped;
+    NetEvent *ne;
+    DNSConnection *dnscon;
+    NetAccept *na;
+    UnixUDPConnection *uc;
+    DiskHandler *dh;
+  } data; ///< a kind of continuation
+
+  /** The start methods all logically Setup a class to be called
+     when a file descriptor is available for read or write.
+     The type of the classes vary.  Generally the file descriptor
+     is pulled from the class, but there is one option that lets
+     the file descriptor be expressed directly.
+     @param l the event loop
+     @param events a mask of flags (for details `man epoll_ctl`)
+     @return int the number of events created, -1 is error
+   */
+  int start(EventLoop l, DNSConnection *vc, int events);
+  int start(EventLoop l, NetAccept *vc, int events);
+  int start(EventLoop l, NetEvent *ne, int events);
+  int start(EventLoop l, UnixUDPConnection *vc, int events);
+  int start(EventLoop l, int fd, NetEvent *ne, int events);
+  int start_common(EventLoop l, int fd, int events);
+
+  /** Alter the events that will trigger the continuation, for level triggered I/O.
+     @param events add with positive mask(+EVENTIO_READ), or remove with negative mask (-EVENTIO_READ)
+     @return int the number of events created, -1 is error
+   */
+  int modify(int events);
+
+  /** Refresh the existing events (i.e. KQUEUE EV_CLEAR), for edge triggered I/O
+     @param events mask of events
+     @return int the number of events created, -1 is error
+   */
+  int refresh(int events);
+
+  /// Remove the kernel or epoll event. Returns 0 on success.
+  int stop();
+
+  /// Remove the epoll event and close the connection. Returns 0 on success.
+  int close();
+
+  EventIO() { data.untyped = nullptr; }
+};
diff --git a/iocore/net/I_NetProcessor.h b/iocore/net/I_NetProcessor.h
index b58038358c..0ac2c0e790 100644
--- a/iocore/net/I_NetProcessor.h
+++ b/iocore/net/I_NetProcessor.h
@@ -27,6 +27,7 @@
 #include "I_EventSystem.h"
 #include "I_Socks.h"
 #include "I_NetVConnection.h"
+#include "AcceptOptions.h"
 struct socks_conf_struct;
 #define NET_CONNECT_TIMEOUT 30
 
@@ -43,75 +44,7 @@ class NetProcessor : public Processor
 public:
   /** Options for @c accept.
    */
-  struct AcceptOptions {
-    using self = AcceptOptions; ///< Self reference type.
-
-    /// Port on which to listen.
-    /// 0 => don't care, which is useful if the socket is already bound.
-    int local_port;
-    /// Local address to bind for accept.
-    /// If not set -> any address.
-    IpAddr local_ip;
-    /// IP address family.
-    /// @note Ignored if an explicit incoming address is set in the
-    /// the configuration (@c local_ip). If neither is set IPv4 is used.
-    int ip_family;
-    /// Should we use accept threads? If so, how many?
-    int accept_threads;
-    /// Event type to generate on accept.
-    EventType etype;
-    /** If @c true, the continuation is called back with
-        @c NET_EVENT_ACCEPT_SUCCEED
-        or @c NET_EVENT_ACCEPT_FAILED on success and failure resp.
-    */
-
-    bool localhost_only;
-    /// Are frequent accepts expected?
-    /// Default: @c false.
-    bool frequent_accept;
-
-    /// Socket receive buffer size.
-    /// 0 => OS default.
-    int recv_bufsize;
-    /// Socket transmit buffer size.
-    /// 0 => OS default.
-    int send_bufsize;
-    /// defer accept for @c sockopt.
-    /// 0 => OS default.
-    int defer_accept;
-    /// Socket options for @c sockopt.
-    /// 0 => do not set options.
-    uint32_t sockopt_flags;
-    uint32_t packet_mark;
-    uint32_t packet_tos;
-    uint32_t packet_notsent_lowat;
-
-    int tfo_queue_length;
-
-    /** Transparency on client (user agent) connection.
-        @internal This is irrelevant at a socket level (since inbound
-        transparency must be set up when the listen socket is created)
-        but it's critical that the connection handling logic knows
-        whether the inbound (client / user agent) connection is
-        transparent.
-    */
-    bool f_inbound_transparent;
-
-    /** MPTCP enabled on listener.
-        @internal For logging and metrics purposes to know whether the
-        listener enabled MPTCP or not.
-    */
-    bool f_mptcp;
-
-    /// Proxy Protocol enabled
-    bool f_proxy_protocol;
-
-    /// Default constructor.
-    /// Instance is constructed with default values.
-    AcceptOptions() { this->reset(); }
-    /// Reset all values to defaults.
-    self &reset();
-  };
+  using AcceptOptions = ::AcceptOptions;
 
   /**
     Accept connections on a port.
diff --git a/iocore/net/I_NetVConnection.h b/iocore/net/I_NetVConnection.h
index 9a5d811746..f16b5e84d1 100644
--- a/iocore/net/I_NetVConnection.h
+++ b/iocore/net/I_NetVConnection.h
@@ -23,6 +23,7 @@
  */
 #pragma once
 
+#include "NetVCOptions.h"
 #include "ProxyProtocol.h"
 #include "I_Net.h"
 
@@ -53,357 +54,6 @@ typedef enum {
   NET_VCONNECTION_OUT, // ATS <--> Server, Server-Side
 } NetVConnectionContext_t;
 
-/** Holds client options for NetVConnection.
-
-    This class holds various options a user can specify for
-    NetVConnection. Various clients need many slightly different
-    features. This is an attempt to prevent out of control growth of
-    the connection method signatures. Only options of interest need to
-    be explicitly set -- the rest get sensible default values.
-
-    @note Binding addresses is a bit complex. It is not currently
-    possible to bind indiscriminately across protocols, which means
-    any connection must commit to IPv4 or IPv6. For this reason the
-    connection logic will look at the address family of @a local_addr
-    even if @a addr_binding is @c ANY_ADDR and bind to any address in
-    that protocol. If it's not an IP protocol, IPv4 will be used.
-*/
-struct NetVCOptions {
-  using self = NetVCOptions; ///< Self reference type.
-
-  /// Values for valid IP protocols.
-  enum ip_protocol_t {
-    USE_TCP, ///< TCP protocol.
-    USE_UDP  ///< UDP protocol.
-  };
-
-  /// IP (TCP or UDP) protocol to use on socket.
-  ip_protocol_t ip_proto;
-
-  /** IP address family.
-
-      This is used for inbound connections only if @c local_ip is not
-      set, which is sometimes more convenient for the client. This
-      defaults to @c AF_INET so if the client sets neither this nor @c
-      local_ip then IPv4 is used.
-
-      For outbound connections this is ignored and the family of the
-      remote address used.
-
-      @note This is (inconsistently) called "domain" and "protocol" in
-      other places. "family" is used here because that's what the
-      standard IP data structures use.
-
-  */
-  uint16_t ip_family;
-
-  /** The set of ways in which the local address should be bound.
-
-      The protocol is set by the contents of @a local_addr regardless
-      of this value. @c ANY_ADDR will override only the address.
-
-      @note The difference between @c INTF_ADDR and @c FOREIGN_ADDR is
-      whether transparency is enabled on the socket. It is the
-      client's responsibility to set this correctly based on whether
-      the address in @a local_addr is associated with an interface on
-      the local system ( @c INTF_ADDR ) or is owned by a foreign
-      system ( @c FOREIGN_ADDR ).  A binding style of @c ANY_ADDR
-      causes the value in @a local_addr to be ignored.
-
-      The IP address and port are separate because most clients treat
-      these independently. For the same reason @c IpAddr is used
-      to be clear that it contains no port data.
-
-      @see local_addr
-      @see addr_binding
-   */
-  enum addr_bind_style {
-    ANY_ADDR,    ///< Bind to any available local address (don't care, default).
-    INTF_ADDR,   ///< Bind to interface address in @a local_addr.
-    FOREIGN_ADDR ///< Bind to foreign address in @a local_addr.
-  };
-
-  /** Local address for the connection.
-
-      For outbound connections this must have the same family as the
-      remote address (which is not stored in this structure). For
-      inbound connections the family of this value overrides @a
-      ip_family if set.
-
-      @note Ignored if @a addr_binding is @c ANY_ADDR.
-      @see addr_binding
-      @see ip_family
-  */
-  IpAddr local_ip;
-
-  /** Local port for connection.
-      Set to 0 for "don't care" (default).
-  */
-  uint16_t local_port;
-
-  /// How to bind the local address.
-  /// @note Default is @c ANY_ADDR.
-  addr_bind_style addr_binding;
-
-  /// Make the socket blocking on I/O (default: @c false)
-  bool f_blocking;
-  /// Make socket block on connect (default: @c false)
-  bool f_blocking_connect;
-
-  // Use TCP Fast Open on this socket. The connect(2) call will be omitted.
-  bool f_tcp_fastopen = false;
-
-  bool tls_upstream = false;
-
-  /// Control use of SOCKS.
-  /// Set to @c NO_SOCKS to disable use of SOCKS. Otherwise SOCKS is
-  /// used if available.
-  unsigned char socks_support;
-  /// Version of SOCKS to use.
-  unsigned char socks_version;
-
-  int socket_recv_bufsize;
-  int socket_send_bufsize;
-
-  /// Configuration options for sockets.
-  /// @note These are not identical to internal socket options but
-  /// specifically defined for configuration. These are mask values
-  /// and so must be powers of 2.
-  uint32_t sockopt_flags;
-  /// Value for TCP no delay for @c sockopt_flags.
-  static uint32_t const SOCK_OPT_NO_DELAY = 1;
-  /// Value for keep alive for @c sockopt_flags.
-  static uint32_t const SOCK_OPT_KEEP_ALIVE = 2;
-  /// Value for linger on for @c sockopt_flags
-  static uint32_t const SOCK_OPT_LINGER_ON = 4;
-  /// Value for TCP Fast open @c sockopt_flags
-  static uint32_t const SOCK_OPT_TCP_FAST_OPEN = 8;
-  /// Value for SO_MARK @c sockopt_flags
-  static uint32_t const SOCK_OPT_PACKET_MARK = 16;
-  /// Value for IP_TOS @c sockopt_flags
-  static uint32_t const SOCK_OPT_PACKET_TOS = 32;
-  /// Value for TCP_NOTSENT_LOWAT @c sockopt_flags
-  static uint32_t const SOCK_OPT_TCP_NOTSENT_LOWAT = 64;
-
-  uint32_t packet_mark;
-  uint32_t packet_tos;
-  uint32_t packet_notsent_lowat;
-
-  EventType etype;
-
-  /** ALPN protocol-lists. The format is OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
-      https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
-   */
-  std::string_view alpn_protos;
-  /** Server name to use for SNI data on an outbound connection.
-   */
-  ats_scoped_str sni_servername;
-  /** FQDN used to connect to the origin.  May be different
-   * than sni_servername if pristine host headers are used
-   */
-  ats_scoped_str ssl_servername;
-
-  /** Server host name from client's request to use for SNI data on an outbound connection.
-   */
-  ats_scoped_str sni_hostname;
-
-  /** Outbound sni policy which overrides proxy.ssl.client.sni_policy
-   */
-  ats_scoped_str outbound_sni_policy;
-
-  /**
-   * Client certificate to use in response to OS's certificate request
-   */
-  ats_scoped_str ssl_client_cert_name;
-  /*
-   * File containing private key matching certificate
-   */
-  const char *ssl_client_private_key_name = nullptr;
-  /*
-   * File containing CA certs for verifying origin's cert
-   */
-  const char *ssl_client_ca_cert_name = nullptr;
-  /*
-   * Directory containing CA certs for verifying origin's cert
-   */
-  const char *ssl_client_ca_cert_path = nullptr;
-
-  unsigned char alpn_protocols_array[MAX_ALPN_STRING];
-  int alpn_protocols_array_size = 0;
-
-  /**
-   * Set to DISABLED, PERFMISSIVE, or ENFORCED
-   * Controls how the server certificate verification is handled
-   */
-  YamlSNIConfig::Policy verifyServerPolicy = YamlSNIConfig::Policy::DISABLED;
-
-  /**
-   * Bit mask of which features of the server certificate should be checked
-   * Currently SIGNATURE and NAME
-   */
-  YamlSNIConfig::Property verifyServerProperties = YamlSNIConfig::Property::NONE;
-
-  /// Reset all values to defaults.
-  void reset();
-
-  void set_sock_param(int _recv_bufsize, int _send_bufsize, unsigned long _opt_flags, unsigned long _packet_mark = 0,
-                      unsigned long _packet_tos = 0, unsigned long _packet_notsent_lowat = 0);
-
-  NetVCOptions() { reset(); }
-  ~NetVCOptions() {}
-
-  /** Set the SNI server name.
-      A local copy is made of @a name.
-  */
-  self &
-  set_sni_servername(const char *name, size_t len)
-  {
-    IpEndpoint ip;
-
-    // Literal IPv4 and IPv6 addresses are not permitted in "HostName".(rfc6066#section-3)
-    if (name && len && ats_ip_pton(std::string_view(name, len), &ip) != 0) {
-      sni_servername = ats_strndup(name, len);
-    } else {
-      sni_servername = nullptr;
-    }
-    return *this;
-  }
-
-  self &
-  set_ssl_client_cert_name(const char *name)
-  {
-    if (name) {
-      ssl_client_cert_name = ats_strdup(name);
-    } else {
-      ssl_client_cert_name = nullptr;
-    }
-    return *this;
-  }
-
-  self &
-  set_ssl_servername(const char *name)
-  {
-    if (name) {
-      ssl_servername = ats_strdup(name);
-    } else {
-      ssl_servername = nullptr;
-    }
-    return *this;
-  }
-
-  self &
-  set_sni_hostname(const char *name, size_t len)
-  {
-    IpEndpoint ip;
-
-    // Literal IPv4 and IPv6 addresses are not permitted in "HostName".(rfc6066#section-3)
-    if (name && len && ats_ip_pton(std::string_view(name, len), &ip) != 0) {
-      sni_hostname = ats_strndup(name, len);
-    } else {
-      sni_hostname = nullptr;
-    }
-    return *this;
-  }
-
-  self &
-  operator=(self const &that)
-  {
-    if (&that != this) {
-      /*
-       * It is odd but necessary to null the scoped string pointer here
-       * and then explicitly call release on them in the string assignments
-       * below.
-       * We a memcpy from that to this.  This will put that's string pointers into
-       * this's memory.  Therefore we must first explicitly null out
-       * this's original version of the string.  The release after the
-       * memcpy removes the extra reference to that's copy of the string
-       * Removing the release will eventually cause a double free crash
-       */
-      sni_servername       = nullptr; // release any current name.
-      ssl_servername       = nullptr;
-      sni_hostname         = nullptr;
-      ssl_client_cert_name = nullptr;
-      memcpy(static_cast<void *>(this), &that, sizeof(self));
-      if (that.sni_servername) {
-        sni_servername.release(); // otherwise we'll free the source string.
-        this->sni_servername = ats_strdup(that.sni_servername);
-      }
-      if (that.ssl_servername) {
-        ssl_servername.release(); // otherwise we'll free the source string.
-        this->ssl_servername = ats_strdup(that.ssl_servername);
-      }
-      if (that.sni_hostname) {
-        sni_hostname.release(); // otherwise we'll free the source string.
-        this->sni_hostname = ats_strdup(that.sni_hostname);
-      }
-      if (that.ssl_client_cert_name) {
-        this->ssl_client_cert_name.release(); // otherwise we'll free the source string.
-        this->ssl_client_cert_name = ats_strdup(that.ssl_client_cert_name);
-      }
-    }
-    return *this;
-  }
-
-  std::string_view get_family_string() const;
-
-  std::string_view get_proto_string() const;
-
-  /// @name Debugging
-  //@{
-  /// Convert @a s to its string equivalent.
-  static const char *toString(addr_bind_style s);
-  //@}
-
-  // noncopyable
-  NetVCOptions(const NetVCOptions &) = delete;
-};
-
-inline void
-NetVCOptions::reset()
-{
-  ip_proto  = USE_TCP;
-  ip_family = AF_INET;
-  local_ip.invalidate();
-  local_port         = 0;
-  addr_binding       = ANY_ADDR;
-  f_blocking         = false;
-  f_blocking_connect = false;
-  socks_support      = NORMAL_SOCKS;
-  socks_version      = SOCKS_DEFAULT_VERSION;
-  socket_recv_bufsize =
-#if defined(RECV_BUF_SIZE)
-    RECV_BUF_SIZE;
-#else
-    0;
-#endif
-  socket_send_bufsize  = 0;
-  sockopt_flags        = 0;
-  packet_mark          = 0;
-  packet_tos           = 0;
-  packet_notsent_lowat = 0;
-
-  etype = ET_NET;
-
-  sni_servername              = nullptr;
-  ssl_servername              = nullptr;
-  sni_hostname                = nullptr;
-  ssl_client_cert_name        = nullptr;
-  ssl_client_private_key_name = nullptr;
-  outbound_sni_policy         = nullptr;
-}
-
-inline void
-NetVCOptions::set_sock_param(int _recv_bufsize, int _send_bufsize, unsigned long _opt_flags, unsigned long _packet_mark,
-                             unsigned long _packet_tos, unsigned long _packet_notsent_lowat)
-{
-  socket_recv_bufsize  = _recv_bufsize;
-  socket_send_bufsize  = _send_bufsize;
-  sockopt_flags        = _opt_flags;
-  packet_mark          = _packet_mark;
-  packet_tos           = _packet_tos;
-  packet_notsent_lowat = _packet_notsent_lowat;
-}
-
 /**
   A VConnection for a network socket. Abstraction for a net connection.
   Similar to a socket descriptor VConnections are IO handles to
diff --git a/iocore/net/I_UDPNet.h b/iocore/net/I_UDPNet.h
index a08d01a943..fd536323a6 100644
--- a/iocore/net/I_UDPNet.h
+++ b/iocore/net/I_UDPNet.h
@@ -34,6 +34,7 @@
 #include "tscore/I_Version.h"
 #include "I_EventSystem.h"
 #include "tscore/ink_inet.h"
+#include "NetVCOptions.h"
 
 /**
    UDP service
diff --git a/iocore/net/Makefile.am b/iocore/net/Makefile.am
index 7bfa944007..f926fc65c3 100644
--- a/iocore/net/Makefile.am
+++ b/iocore/net/Makefile.am
@@ -136,12 +136,16 @@ test_libinknet_LDADD += \
 endif
 
 libinknet_a_SOURCES = \
+	AcceptOptions.cc \
+	AcceptOptions.h \
 	ALPNSupport.cc \
 	BIO_fastopen.cc \
 	BIO_fastopen.h \
 	BoringSSLUtils.cc \
 	BoringSSLUtils.h \
 	Connection.cc \
+	EventIO.h \
+	EventIO.cc \
 	I_Net.h \
 	I_NetProcessor.h \
 	I_NetVConnection.h \
@@ -154,6 +158,10 @@ libinknet_a_SOURCES = \
 	YamlSNIConfig.h \
 	YamlSNIConfig.cc \
 	Net.cc \
+	NetHandler.h \
+	NetHandler.cc \
+	NetVCOptions.h \
+	NetVCOptions.cc \
 	NetVConnection.cc \
 	P_ALPNSupport.h \
 	P_SNIActionPerformer.h \
@@ -186,6 +194,8 @@ libinknet_a_SOURCES = \
 	P_UnixNetVConnection.h \
 	P_UnixPollDescriptor.h \
 	P_UnixUDPConnection.h \
+	PollCont.h \
+	PollCont.cc \
 	ProxyProtocol.h \
 	ProxyProtocol.cc \
 	Socks.cc \
@@ -229,18 +239,18 @@ libinknet_a_SOURCES = \
 
 if ENABLE_QUIC
 libinknet_a_SOURCES += \
-  P_QUICClosedConCollector.h \
-  P_QUICPacketHandler.h \
-  P_QUICNetProcessor.h \
-  P_QUICNetVConnection.h \
-  P_QUICNextProtocolAccept.h \
-  QUICClosedConCollector.cc \
-  QUICMultiCertConfigLoader.cc \
-  QUICNet.cc \
-  QUICNetProcessor_quiche.cc \
-  QUICNetVConnection_quiche.cc \
-  QUICNextProtocolAccept_quiche.cc \
-  QUICPacketHandler_quiche.cc
+	P_QUICClosedConCollector.h \
+	P_QUICPacketHandler.h \
+	P_QUICNetProcessor.h \
+	P_QUICNetVConnection.h \
+	P_QUICNextProtocolAccept.h \
+	QUICClosedConCollector.cc \
+	QUICMultiCertConfigLoader.cc \
+	QUICNet.cc \
+	QUICNetProcessor_quiche.cc \
+	QUICNetVConnection_quiche.cc \
+	QUICNextProtocolAccept_quiche.cc \
+	QUICPacketHandler_quiche.cc
 endif
 
 if BUILD_TESTS
diff --git a/iocore/net/NetEvent.h b/iocore/net/NetEvent.h
index c535374a42..552d9878e0 100644
--- a/iocore/net/NetEvent.h
+++ b/iocore/net/NetEvent.h
@@ -25,7 +25,9 @@
 
 #include <atomic>
 
+#include "EventIO.h"
 #include "I_EventSystem.h"
+#include "P_UnixNetState.h"
 
 class NetHandler;
 
diff --git a/iocore/net/UnixNet.cc b/iocore/net/NetHandler.cc
similarity index 68%
copy from iocore/net/UnixNet.cc
copy to iocore/net/NetHandler.cc
index 8ce096b9c6..330463374d 100644
--- a/iocore/net/UnixNet.cc
+++ b/iocore/net/NetHandler.cc
@@ -21,243 +21,91 @@
   limitations under the License.
  */
 
-#include "P_Net.h"
-#include "I_AIO.h"
-#include "tscore/ink_hrtime.h"
+#include "NetHandler.h"
 
 #if TS_USE_LINUX_IO_URING
 #include "I_IO_URING.h"
 #endif
 
-using namespace std::literals;
-
-ink_hrtime last_throttle_warning;
-ink_hrtime last_shedding_warning;
-int net_connections_throttle;
-bool net_memory_throttle = false;
-int fds_throttle;
-int fds_limit = 8000;
-ink_hrtime last_transient_accept_error;
+#include "P_DNSConnection.h"
+#include "P_Net.h"
+#include "P_UnixNet.h"
+#include "P_UnixNetProcessor.h"
+#include "PollCont.h"
 
-NetHandler::Config NetHandler::global_config;
-std::bitset<std::numeric_limits<unsigned int>::digits> NetHandler::active_thread_types;
-const std::bitset<NetHandler::CONFIG_ITEM_COUNT> NetHandler::config_value_affects_per_thread_value{0x3};
+using namespace std::literals;
 
-extern "C" void fd_reify(struct ev_loop *);
+// NetHandler method definitions
 
-// INKqa10496
-// One Inactivity cop runs on each thread once every second and
-// loops through the list of NetEvents and calls the timeouts
-class InactivityCop : public Continuation
+NetHandler::NetHandler() : Continuation(nullptr)
 {
-public:
-  explicit InactivityCop(Ptr<ProxyMutex> &m) : Continuation(m.get()) { SET_HANDLER(&InactivityCop::check_inactivity); }
-  int
-  check_inactivity(int event, Event *e)
-  {
-    (void)event;
-    ink_hrtime now = Thread::get_hrtime();
-    NetHandler &nh = *get_NetHandler(this_ethread());
-
-    Debug("inactivity_cop_check", "Checking inactivity on Thread-ID #%d", this_ethread()->id);
-    // The rest NetEvents in cop_list which are not triggered between InactivityCop runs.
-    // Use pop() to catch any closes caused by callbacks.
-    while (NetEvent *ne = nh.cop_list.pop()) {
-      // If we cannot get the lock don't stop just keep cleaning
-      MUTEX_TRY_LOCK(lock, ne->get_mutex(), this_ethread());
-      if (!lock.is_locked()) {
-        NET_INCREMENT_DYN_STAT(inactivity_cop_lock_acquire_failure_stat);
-        continue;
-      }
-
-      if (ne->closed) {
-        nh.free_netevent(ne);
-        continue;
-      }
-
-      if (ne->default_inactivity_timeout_in == -1) {
-        // If no context-specific default inactivity timeout has been set by an
-        // override plugin, then use the global default.
-        Debug("inactivity_cop", "vc: %p setting the global default inactivity timeout of %d, next_inactivity_timeout_at: %" PRId64,
-              ne, nh.config.default_inactivity_timeout, ne->next_inactivity_timeout_at);
-        ne->set_default_inactivity_timeout(HRTIME_SECONDS(nh.config.default_inactivity_timeout));
-      }
+  SET_HANDLER(&NetHandler::mainNetEvent);
+}
 
-      // set a default inactivity timeout if one is not set
-      // The event `EVENT_INACTIVITY_TIMEOUT` only be triggered if a read
-      // or write I/O operation was set by `do_io_read()` or `do_io_write()`.
-      if (ne->next_inactivity_timeout_at == 0 && ne->default_inactivity_timeout_in > 0 && (ne->read.enabled || ne->write.enabled)) {
-        Debug("inactivity_cop", "vc: %p inactivity timeout not set, setting a default of %d", ne,
-              nh.config.default_inactivity_timeout);
-        ne->use_default_inactivity_timeout = true;
-        ne->next_inactivity_timeout_at     = Thread::get_hrtime() + ne->default_inactivity_timeout_in;
-        ne->inactivity_timeout_in          = 0;
-        NET_INCREMENT_DYN_STAT(default_inactivity_timeout_applied_stat);
-      }
+int
+NetHandler::startIO(NetEvent *ne)
+{
+  ink_assert(this->mutex->thread_holding == this_ethread());
+  ink_assert(ne->get_thread() == this_ethread());
+  int res = 0;
 
-      if (ne->next_inactivity_timeout_at && ne->next_inactivity_timeout_at < now) {
-        if (ne->is_default_inactivity_timeout()) {
-          // track the connections that timed out due to default inactivity
-          Debug("inactivity_cop", "vc: %p timed out due to default inactivity timeout", ne);
-          NET_INCREMENT_DYN_STAT(default_inactivity_timeout_count_stat);
-        }
-        if (nh.keep_alive_queue.in(ne)) {
-          // only stat if the connection is in keep-alive, there can be other inactivity timeouts
-          ink_hrtime diff = (now - (ne->next_inactivity_timeout_at - ne->inactivity_timeout_in)) / HRTIME_SECOND;
-          NET_SUM_DYN_STAT(keep_alive_queue_timeout_total_stat, diff);
-          NET_INCREMENT_DYN_STAT(keep_alive_queue_timeout_count_stat);
-        }
-        Debug("inactivity_cop_verbose", "ne: %p now: %" PRId64 " timeout at: %" PRId64 " timeout in: %" PRId64, ne,
-              ink_hrtime_to_sec(now), ne->next_inactivity_timeout_at, ne->inactivity_timeout_in);
-        ne->callback(VC_EVENT_INACTIVITY_TIMEOUT, e);
-      } else if (ne->next_activity_timeout_at && ne->next_activity_timeout_at < now) {
-        Debug("inactivity_cop_verbose", "active ne: %p now: %" PRId64 " timeout at: %" PRId64 " timeout in: %" PRId64, ne,
-              ink_hrtime_to_sec(now), ne->next_activity_timeout_at, ne->active_timeout_in);
-        ne->callback(VC_EVENT_ACTIVE_TIMEOUT, e);
-      }
-    }
-    // The cop_list is empty now.
-    // Let's reload the cop_list from open_list again.
-    forl_LL(NetEvent, ne, nh.open_list)
-    {
-      if (ne->get_thread() == this_ethread()) {
-        nh.cop_list.push(ne);
-      }
+  PollDescriptor *pd = get_PollDescriptor(this->thread);
+  if (ne->ep.start(pd, ne, EVENTIO_READ | EVENTIO_WRITE) < 0) {
+    res = errno;
+    // EEXIST should be ok, though it should have been cleared before we got back here
+    if (errno != EEXIST) {
+      Debug("iocore_net", "NetHandler::startIO : failed on EventIO::start, errno = [%d](%s)", errno, strerror(errno));
+      return -res;
     }
-    // NetHandler will remove NetEvent from cop_list if it is triggered.
-    // As the NetHandler runs, the number of NetEvents in the cop_list is decreasing.
-    // NetHandler runs 100 times maximum between InactivityCop runs.
-    // Therefore we don't have to check all the NetEvents as much as open_list.
-
-    // Cleanup the active and keep-alive queues periodically
-    nh.manage_active_queue(nullptr, true); // close any connections over the active timeout
-    nh.manage_keep_alive_queue();
-
-    return 0;
   }
-};
 
-PollCont::PollCont(Ptr<ProxyMutex> &m, int pt)
-  : Continuation(m.get()), net_handler(nullptr), nextPollDescriptor(nullptr), poll_timeout(pt)
-{
-  pollDescriptor = new PollDescriptor();
-  SET_HANDLER(&PollCont::pollEvent);
+  if (ne->read.triggered == 1) {
+    read_ready_list.enqueue(ne);
+  }
+  ne->nh = this;
+  return res;
 }
 
-PollCont::PollCont(Ptr<ProxyMutex> &m, NetHandler *nh, int pt)
-  : Continuation(m.get()), net_handler(nh), nextPollDescriptor(nullptr), poll_timeout(pt)
+void
+NetHandler::stopIO(NetEvent *ne)
 {
-  pollDescriptor = new PollDescriptor();
-  SET_HANDLER(&PollCont::pollEvent);
-}
+  ink_release_assert(ne->nh == this);
 
-PollCont::~PollCont()
-{
-  delete pollDescriptor;
-  if (nextPollDescriptor != nullptr) {
-    delete nextPollDescriptor;
+  ne->ep.stop();
+
+  read_ready_list.remove(ne);
+  write_ready_list.remove(ne);
+  if (ne->read.in_enabled_list) {
+    read_enable_list.remove(ne);
+    ne->read.in_enabled_list = 0;
+  }
+  if (ne->write.in_enabled_list) {
+    write_enable_list.remove(ne);
+    ne->write.in_enabled_list = 0;
   }
-}
 
-//
-// PollCont continuation which does the epoll_wait
-// and stores the resultant events in ePoll_Triggered_Events
-//
-int
-PollCont::pollEvent(int, Event *)
-{
-  this->do_poll(-1);
-  return EVENT_CONT;
+  ne->nh = nullptr;
 }
 
 void
-PollCont::do_poll(ink_hrtime timeout)
+NetHandler::startCop(NetEvent *ne)
 {
-  if (likely(net_handler)) {
-    /* checking to see whether there are connections on the ready_queue (either read or write) that need processing [ebalsa] */
-    if (likely(!net_handler->read_ready_list.empty() || !net_handler->write_ready_list.empty() ||
-               !net_handler->read_enable_list.empty() || !net_handler->write_enable_list.empty())) {
-      NetDebug("iocore_net_poll", "rrq: %d, wrq: %d, rel: %d, wel: %d", net_handler->read_ready_list.empty(),
-               net_handler->write_ready_list.empty(), net_handler->read_enable_list.empty(),
-               net_handler->write_enable_list.empty());
-      poll_timeout = 0; // poll immediately returns -- we have triggered stuff to process right now
-    } else if (timeout >= 0) {
-      poll_timeout = ink_hrtime_to_msec(timeout);
-    } else {
-      poll_timeout = net_config_poll_timeout;
-    }
-  }
-// wait for fd's to trigger, or don't wait if timeout is 0
-#if TS_USE_EPOLL
-  pollDescriptor->result =
-    epoll_wait(pollDescriptor->epoll_fd, pollDescriptor->ePoll_Triggered_Events, POLL_DESCRIPTOR_SIZE, poll_timeout);
-  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] epoll_fd: %d, timeout: %d, results: %d", pollDescriptor->epoll_fd,
-           poll_timeout, pollDescriptor->result);
-#elif TS_USE_KQUEUE
-  struct timespec tv;
-  tv.tv_sec  = poll_timeout / 1000;
-  tv.tv_nsec = 1000000 * (poll_timeout % 1000);
-  pollDescriptor->result =
-    kevent(pollDescriptor->kqueue_fd, nullptr, 0, pollDescriptor->kq_Triggered_Events, POLL_DESCRIPTOR_SIZE, &tv);
-  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] kqueue_fd: %d, timeout: %d, results: %d", pollDescriptor->kqueue_fd,
-           poll_timeout, pollDescriptor->result);
-#endif
-}
+  ink_assert(this->mutex->thread_holding == this_ethread());
+  ink_release_assert(ne->nh == this);
+  ink_assert(!open_list.in(ne));
 
-static void
-net_signal_hook_callback(EThread *thread)
-{
-#if HAVE_EVENTFD
-  uint64_t counter;
-  ATS_UNUSED_RETURN(read(thread->evfd, &counter, sizeof(uint64_t)));
-#else
-  char dummy[1024];
-  ATS_UNUSED_RETURN(read(thread->evpipe[0], &dummy[0], 1024));
-#endif
+  open_list.enqueue(ne);
 }
 
 void
-initialize_thread_for_net(EThread *thread)
+NetHandler::stopCop(NetEvent *ne)
 {
-  NetHandler *nh = get_NetHandler(thread);
-
-  new (reinterpret_cast<ink_dummy_for_new *>(nh)) NetHandler();
-  new (reinterpret_cast<ink_dummy_for_new *>(get_PollCont(thread))) PollCont(thread->mutex, nh);
-  nh->mutex  = new_ProxyMutex();
-  nh->thread = thread;
-
-  PollCont *pc       = get_PollCont(thread);
-  PollDescriptor *pd = pc->pollDescriptor;
-
-  InactivityCop *inactivityCop = new InactivityCop(get_NetHandler(thread)->mutex);
-  int cop_freq                 = 1;
-
-  REC_ReadConfigInteger(cop_freq, "proxy.config.net.inactivity_check_frequency");
-  memcpy(&nh->config, &NetHandler::global_config, sizeof(NetHandler::global_config));
-  nh->configure_per_thread_values();
-  thread->schedule_every(inactivityCop, HRTIME_SECONDS(cop_freq));
-
-  thread->set_tail_handler(nh);
-  thread->ep = static_cast<EventIO *>(ats_malloc(sizeof(EventIO)));
-  new (thread->ep) EventIO();
-  thread->ep->type = EVENTIO_ASYNC_SIGNAL;
-#if HAVE_EVENTFD
-  thread->ep->start(pd, thread->evfd, nullptr, EVENTIO_READ);
-#else
-  thread->ep->start(pd, thread->evpipe[0], nullptr, EVENTIO_READ);
-#endif
-
-#if TS_USE_LINUX_IO_URING
-  nh->uring_evio.type = EVENTIO_IO_URING;
-  nh->uring_evio.start(pd, IOUringContext::local_context()->register_eventfd(), nullptr, EVENTIO_READ);
-#endif
-}
-
-// NetHandler method definitions
+  ink_release_assert(ne->nh == this);
 
-NetHandler::NetHandler() : Continuation(nullptr)
-{
-  SET_HANDLER(&NetHandler::mainNetEvent);
+  open_list.remove(ne);
+  cop_list.remove(ne);
+  remove_from_keep_alive_queue(ne);
+  remove_from_active_queue(ne);
 }
 
 int
@@ -458,6 +306,18 @@ NetHandler::mainNetEvent(int event, Event *e)
   }
 }
 
+static void
+net_signal_hook_callback(EThread *thread)
+{
+#if HAVE_EVENTFD
+  uint64_t counter;
+  ATS_UNUSED_RETURN(read(thread->evfd, &counter, sizeof(uint64_t)));
+#else
+  char dummy[1024];
+  ATS_UNUSED_RETURN(read(thread->evpipe[0], &dummy[0], 1024));
+#endif
+}
+
 int
 NetHandler::waitForActivity(ink_hrtime timeout)
 {
diff --git a/iocore/net/NetHandler.h b/iocore/net/NetHandler.h
new file mode 100644
index 0000000000..b733fced2b
--- /dev/null
+++ b/iocore/net/NetHandler.h
@@ -0,0 +1,235 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include "I_Continuation.h"
+#include "I_EThread.h"
+#include "NetEvent.h"
+
+//
+// NetHandler
+//
+// A NetHandler handles the Network IO operations.  It maintains
+// lists of operations at multiples of it's periodicity.
+//
+
+/**
+  NetHandler is the processor of NetEvent for the Net sub-system. The NetHandler
+  is the core component of the Net sub-system. Once started, it is responsible
+  for polling socket fds and perform the I/O tasks in NetEvent.
+
+  The NetHandler is executed periodically to perform read/write tasks for
+  NetVConnection. The NetHandler::mainNetEvent() should be viewed as a part of
+  EThread::execute() loop. This is the reason that Net System is a sub-system.
+
+  By get_NetHandler(this_ethread()), you can get the NetHandler object that
+  runs inside the current EThread and then @c startIO / @c stopIO which
+  assign/release a NetEvent to/from NetHandler. Before you call these functions,
+  holding the mutex of this NetHandler is required.
+
+  The NetVConnection provides a set of do_io functions through which you can
+  specify continuations to be called back by its NetHandler. These function
+  calls do not block. Instead they return an VIO object and schedule the
+  callback to the continuation passed in when there are I/O events occurred.
+
+  Multi-thread scheduler:
+
+  The NetHandler should be viewed as multi-threaded schedulers which process
+  NetEvents from their queues. If vc wants to be managed by NetHandler, the vc
+  should be derived from NetEvent. The vc can be made of NetProcessor
+  (allocate_vc) either by directly adding a NetEvent to the queue
+  (NetHandler::startIO), or more conveniently, calling a method service call
+  (NetProcessor::connect_re) which synthesizes the NetEvent and places it in the
+  queue.
+
+  Callback event codes:
+
+  These event codes for do_io_read and reenable(read VIO) task:
+    VC_EVENT_READ_READY, VC_EVENT_READ_COMPLETE,
+    VC_EVENT_EOS, VC_EVENT_ERROR
+
+  These event codes for do_io_write and reenable(write VIO) task:
+    VC_EVENT_WRITE_READY, VC_EVENT_WRITE_COMPLETE
+    VC_EVENT_ERROR
+
+  There is no event and callback for do_io_shutdown / do_io_close task.
+
+  NetVConnection allocation policy:
+
+  VCs are allocated by the NetProcessor and deallocated by NetHandler.
+  A state machine may access the returned, non-recurring NetEvent / VIO until
+  it is closed by do_io_close. For recurring NetEvent, the NetEvent may be
+  accessed until it is closed. Once the NetEvent is closed, it's the
+  NetHandler's responsibility to deallocate it.
+
+  Before assign to NetHandler or after release from NetHandler, it's the
+  NetEvent's responsibility to deallocate itself.
+
+ */
+class NetHandler : public Continuation, public EThread::LoopTailHandler
+{
+  using self_type = NetHandler; ///< Self reference type.
+public:
+  // @a thread and @a trigger_event are redundant - you can get the former from
+  // the latter. If we don't get rid of @a trigger_event we should remove @a
+  // thread.
+  EThread *thread      = nullptr;
+  Event *trigger_event = nullptr;
+  QueM(NetEvent, NetState, read, ready_link) read_ready_list;
+  QueM(NetEvent, NetState, write, ready_link) write_ready_list;
+  Que(NetEvent, open_link) open_list;
+  DList(NetEvent, cop_link) cop_list;
+  ASLLM(NetEvent, NetState, read, enable_link) read_enable_list;
+  ASLLM(NetEvent, NetState, write, enable_link) write_enable_list;
+  Que(NetEvent, keep_alive_queue_link) keep_alive_queue;
+  uint32_t keep_alive_queue_size = 0;
+  Que(NetEvent, active_queue_link) active_queue;
+  uint32_t active_queue_size = 0;
+
+#ifdef TS_USE_LINUX_IO_URING
+  EventIO uring_evio;
+#endif
+
+  /// configuration settings for managing the active and keep-alive queues
+  struct Config {
+    uint32_t max_connections_in                 = 0;
+    uint32_t max_requests_in                    = 0;
+    uint32_t inactive_threshold_in              = 0;
+    uint32_t transaction_no_activity_timeout_in = 0;
+    uint32_t keep_alive_no_activity_timeout_in  = 0;
+    uint32_t default_inactivity_timeout         = 0;
+
+    /** Return the address of the first value in this struct.
+
+        Doing updates is much easier if we treat this config struct as an array.
+        Making it a method means the knowledge of which member is the first one
+        is localized to this struct, not scattered about.
+     */
+    uint32_t &
+    operator[](int n)
+    {
+      return *(&max_connections_in + n);
+    }
+  };
+  /** Static global config, set and updated per process.
+
+      This is updated asynchronously and then events are sent to the NetHandler
+     instances per thread to copy to the per thread config at a convenient time.
+     Because these are updated independently from the command line, the update
+     events just copy a single value from the global to the local. This
+     mechanism relies on members being identical types.
+  */
+  static Config global_config;
+  Config config; ///< Per thread copy of the @c global_config
+  // Active and keep alive queue values that depend on other configuration
+  // values. These are never updated directly, they are computed from other
+  // config values.
+  uint32_t max_connections_per_thread_in = 0;
+  uint32_t max_requests_per_thread_in    = 0;
+  /// Number of configuration items in @c Config.
+  static constexpr int CONFIG_ITEM_COUNT = sizeof(Config) / sizeof(uint32_t);
+  /// Which members of @c Config the per thread values depend on.
+  /// If one of these is updated, the per thread values must also be updated.
+  static const std::bitset<CONFIG_ITEM_COUNT> config_value_affects_per_thread_value;
+  /// Set of thread types in which nethandlers are active.
+  /// This enables signaling the correct instances when the configuration is
+  /// updated. Event type threads that use @c NetHandler must set the
+  /// corresponding bit.
+  static std::bitset<std::numeric_limits<unsigned int>::digits> active_thread_types;
+
+  int mainNetEvent(int event, Event *data);
+  int waitForActivity(ink_hrtime timeout) override;
+  void process_enabled_list();
+  void process_ready_list();
+  void manage_keep_alive_queue();
+  bool manage_active_queue(NetEvent *ne, bool ignore_queue_size);
+  void add_to_keep_alive_queue(NetEvent *ne);
+  void remove_from_keep_alive_queue(NetEvent *ne);
+  bool add_to_active_queue(NetEvent *ne);
+  void remove_from_active_queue(NetEvent *ne);
+
+  /// Per process initialization logic.
+  static void init_for_process();
+  /// Update configuration values that are per thread and depend on other
+  /// configuration values.
+  void configure_per_thread_values();
+
+  /**
+    Start to handle read & write event on a NetEvent.
+    Initial the socket fd of ne for polling system.
+    Only be called when holding the mutex of this NetHandler.
+
+    @param ne NetEvent to be managed by this NetHandler.
+    @return 0 on success, ne->nh set to this NetHandler.
+            -ERRNO on failure.
+   */
+  int startIO(NetEvent *ne);
+  /**
+    Stop to handle read & write event on a NetEvent.
+    Remove the socket fd of ne from polling system.
+    Only be called when holding the mutex of this NetHandler and must call
+    stopCop(ne) first.
+
+    @param ne NetEvent to be released.
+    @return ne->nh set to nullptr.
+   */
+  void stopIO(NetEvent *ne);
+
+  /**
+    Start to handle active timeout and inactivity timeout on a NetEvent.
+    Put the ne into open_list. All NetEvents in the open_list is checked for
+    timeout by InactivityCop. Only be called when holding the mutex of this
+    NetHandler and must call startIO(ne) first.
+
+    @param ne NetEvent to be managed by InactivityCop
+   */
+  void startCop(NetEvent *ne);
+  /**
+    Stop to handle active timeout and inactivity on a NetEvent.
+    Remove the ne from open_list and cop_list.
+    Also remove the ne from keep_alive_queue and active_queue if its context is
+    IN. Only be called when holding the mutex of this NetHandler.
+
+    @param ne NetEvent to be released.
+   */
+  void stopCop(NetEvent *ne);
+
+  // Signal the epoll_wait to terminate.
+  void signalActivity() override;
+
+  /**
+    Release a ne and free it.
+
+    @param ne NetEvent to be detached.
+   */
+  void free_netevent(NetEvent *ne);
+
+  NetHandler();
+
+private:
+  void _close_ne(NetEvent *ne, ink_hrtime now, int &handle_event, int &closed, int &total_idle_time, int &total_idle_count);
+
+  /// Static method used as the callback for runtime configuration updates.
+  static int update_nethandler_config(const char *name, RecDataT, RecData data, void *);
+};
diff --git a/iocore/net/NetVCOptions.cc b/iocore/net/NetVCOptions.cc
new file mode 100644
index 0000000000..9e14b870bb
--- /dev/null
+++ b/iocore/net/NetVCOptions.cc
@@ -0,0 +1,71 @@
+/**@file
+
+   A brief file description
+
+ @section license License
+
+   Licensed to the Apache Software
+   Foundation(ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "NetVCOptions.h"
+#include "I_Net.h"
+#include "I_Socks.h"
+
+void
+NetVCOptions::reset()
+{
+  ip_proto  = USE_TCP;
+  ip_family = AF_INET;
+  local_ip.invalidate();
+  local_port    = 0;
+  addr_binding  = ANY_ADDR;
+  socks_support = NORMAL_SOCKS;
+  socks_version = SOCKS_DEFAULT_VERSION;
+  socket_recv_bufsize =
+#if defined(RECV_BUF_SIZE)
+    RECV_BUF_SIZE;
+#else
+    0;
+#endif
+  socket_send_bufsize  = 0;
+  sockopt_flags        = 0;
+  packet_mark          = 0;
+  packet_tos           = 0;
+  packet_notsent_lowat = 0;
+
+  etype = ET_NET;
+
+  sni_servername              = nullptr;
+  ssl_servername              = nullptr;
+  sni_hostname                = nullptr;
+  ssl_client_cert_name        = nullptr;
+  ssl_client_private_key_name = nullptr;
+  outbound_sni_policy         = nullptr;
+}
+
+void
+NetVCOptions::set_sock_param(int _recv_bufsize, int _send_bufsize, unsigned long _opt_flags, unsigned long _packet_mark,
+                             unsigned long _packet_tos, unsigned long _packet_notsent_lowat)
+{
+  socket_recv_bufsize  = _recv_bufsize;
+  socket_send_bufsize  = _send_bufsize;
+  sockopt_flags        = _opt_flags;
+  packet_mark          = _packet_mark;
+  packet_tos           = _packet_tos;
+  packet_notsent_lowat = _packet_notsent_lowat;
+}
diff --git a/iocore/net/NetVCOptions.h b/iocore/net/NetVCOptions.h
new file mode 100644
index 0000000000..9b8c9af5b7
--- /dev/null
+++ b/iocore/net/NetVCOptions.h
@@ -0,0 +1,322 @@
+/** @file
+
+  NetVConnection options class
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+ */
+#pragma once
+#include "tscore/ink_inet.h"
+#include "I_EventSystem.h"
+#include "I_Event.h"
+#include "YamlSNIConfig.h"
+
+#include <cstdint>
+
+struct NetVCOptions {
+  using self = NetVCOptions; ///< Self reference type.
+
+  /// Values for valid IP protocols.
+  enum ip_protocol_t {
+    USE_TCP, ///< TCP protocol.
+    USE_UDP  ///< UDP protocol.
+  };
+
+  /// IP (TCP or UDP) protocol to use on socket.
+  ip_protocol_t ip_proto;
+
+  /** IP address family.
+
+      This is used for inbound connections only if @c local_ip is not
+      set, which is sometimes more convenient for the client. This
+      defaults to @c AF_INET so if the client sets neither this nor @c
+      local_ip then IPv4 is used.
+
+      For outbound connections this is ignored and the family of the
+      remote address used.
+
+      @note This is (inconsistently) called "domain" and "protocol" in
+      other places. "family" is used here because that's what the
+      standard IP data structures use.
+
+  */
+  uint16_t ip_family;
+
+  /** The set of ways in which the local address should be bound.
+
+      The protocol is set by the contents of @a local_addr regardless
+      of this value. @c ANY_ADDR will override only the address.
+
+      @note The difference between @c INTF_ADDR and @c FOREIGN_ADDR is
+      whether transparency is enabled on the socket. It is the
+      client's responsibility to set this correctly based on whether
+      the address in @a local_addr is associated with an interface on
+      the local system ( @c INTF_ADDR ) or is owned by a foreign
+      system ( @c FOREIGN_ADDR ).  A binding style of @c ANY_ADDR
+      causes the value in @a local_addr to be ignored.
+
+      The IP address and port are separate because most clients treat
+      these independently. For the same reason @c IpAddr is used
+      to be clear that it contains no port data.
+
+      @see local_addr
+      @see addr_binding
+   */
+  enum addr_bind_style {
+    ANY_ADDR,    ///< Bind to any available local address (don't care, default).
+    INTF_ADDR,   ///< Bind to interface address in @a local_addr.
+    FOREIGN_ADDR ///< Bind to foreign address in @a local_addr.
+  };
+
+  /** Local address for the connection.
+
+      For outbound connections this must have the same family as the
+      remote address (which is not stored in this structure). For
+      inbound connections the family of this value overrides @a
+      ip_family if set.
+
+      @note Ignored if @a addr_binding is @c ANY_ADDR.
+      @see addr_binding
+      @see ip_family
+  */
+  IpAddr local_ip;
+
+  /** Local port for connection.
+      Set to 0 for "don't care" (default).
+  */
+  uint16_t local_port;
+
+  /// How to bind the local address.
+  /// @note Default is @c ANY_ADDR.
+  addr_bind_style addr_binding;
+
+  /// Make the socket blocking on I/O (default: @c false)
+  // TODO: make this const.  We don't use blocking
+  bool f_blocking = false;
+  /// Make socket block on connect (default: @c false)
+  // TODO: make this const.  We don't use blocking
+  bool f_blocking_connect = false;
+
+  // Use TCP Fast Open on this socket. The connect(2) call will be omitted.
+  bool f_tcp_fastopen = false;
+
+  /// Control use of SOCKS.
+  /// Set to @c NO_SOCKS to disable use of SOCKS. Otherwise SOCKS is
+  /// used if available.
+  unsigned char socks_support;
+  /// Version of SOCKS to use.
+  unsigned char socks_version;
+
+  int socket_recv_bufsize;
+  int socket_send_bufsize;
+
+  /// Configuration options for sockets.
+  /// @note These are not identical to internal socket options but
+  /// specifically defined for configuration. These are mask values
+  /// and so must be powers of 2.
+  uint32_t sockopt_flags;
+  /// Value for TCP no delay for @c sockopt_flags.
+  static uint32_t const SOCK_OPT_NO_DELAY = 1;
+  /// Value for keep alive for @c sockopt_flags.
+  static uint32_t const SOCK_OPT_KEEP_ALIVE = 2;
+  /// Value for linger on for @c sockopt_flags
+  static uint32_t const SOCK_OPT_LINGER_ON = 4;
+  /// Value for TCP Fast open @c sockopt_flags
+  static uint32_t const SOCK_OPT_TCP_FAST_OPEN = 8;
+  /// Value for SO_MARK @c sockopt_flags
+  static uint32_t const SOCK_OPT_PACKET_MARK = 16;
+  /// Value for IP_TOS @c sockopt_flags
+  static uint32_t const SOCK_OPT_PACKET_TOS = 32;
+  /// Value for TCP_NOTSENT_LOWAT @c sockopt_flags
+  static uint32_t const SOCK_OPT_TCP_NOTSENT_LOWAT = 64;
+
+  uint32_t packet_mark;
+  uint32_t packet_tos;
+  uint32_t packet_notsent_lowat;
+
+  EventType etype;
+
+  /** ALPN protocol-lists. The format is OpenSSL protocol-lists format (vector of 8-bit length-prefixed, byte strings)
+      https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_alpn_protos.html
+   */
+  std::string_view alpn_protos;
+  /** Server name to use for SNI data on an outbound connection.
+   */
+  ats_scoped_str sni_servername;
+  /** FQDN used to connect to the origin.  May be different
+   * than sni_servername if pristine host headers are used
+   */
+  ats_scoped_str ssl_servername;
+
+  /** Server host name from client's request to use for SNI data on an outbound connection.
+   */
+  ats_scoped_str sni_hostname;
+
+  /** Outbound sni policy which overrides proxy.ssl.client.sni_policy
+   */
+  ats_scoped_str outbound_sni_policy;
+
+  /**
+   * Client certificate to use in response to OS's certificate request
+   */
+  ats_scoped_str ssl_client_cert_name;
+  /*
+   * File containing private key matching certificate
+   */
+  const char *ssl_client_private_key_name = nullptr;
+  /*
+   * File containing CA certs for verifying origin's cert
+   */
+  const char *ssl_client_ca_cert_name = nullptr;
+  /*
+   * Directory containing CA certs for verifying origin's cert
+   */
+  const char *ssl_client_ca_cert_path = nullptr;
+
+  bool tls_upstream = false;
+
+  unsigned char alpn_protocols_array[MAX_ALPN_STRING];
+  int alpn_protocols_array_size = 0;
+
+  /**
+   * Set to DISABLED, PERFMISSIVE, or ENFORCED
+   * Controls how the server certificate verification is handled
+   */
+  YamlSNIConfig::Policy verifyServerPolicy = YamlSNIConfig::Policy::DISABLED;
+
+  /**
+   * Bit mask of which features of the server certificate should be checked
+   * Currently SIGNATURE and NAME
+   */
+  YamlSNIConfig::Property verifyServerProperties = YamlSNIConfig::Property::NONE;
+
+  /// Reset all values to defaults.
+  void reset();
+
+  void set_sock_param(int _recv_bufsize, int _send_bufsize, unsigned long _opt_flags, unsigned long _packet_mark = 0,
+                      unsigned long _packet_tos = 0, unsigned long _packet_notsent_lowat = 0);
+
+  NetVCOptions() { reset(); }
+  ~NetVCOptions() {}
+
+  /** Set the SNI server name.
+      A local copy is made of @a name.
+  */
+  self &
+  set_sni_servername(const char *name, size_t len)
+  {
+    IpEndpoint ip;
+
+    // Literal IPv4 and IPv6 addresses are not permitted in "HostName".(rfc6066#section-3)
+    if (name && len && ats_ip_pton(std::string_view(name, len), &ip) != 0) {
+      sni_servername = ats_strndup(name, len);
+    } else {
+      sni_servername = nullptr;
+    }
+    return *this;
+  }
+
+  self &
+  set_ssl_client_cert_name(const char *name)
+  {
+    if (name) {
+      ssl_client_cert_name = ats_strdup(name);
+    } else {
+      ssl_client_cert_name = nullptr;
+    }
+    return *this;
+  }
+
+  self &
+  set_ssl_servername(const char *name)
+  {
+    if (name) {
+      ssl_servername = ats_strdup(name);
+    } else {
+      ssl_servername = nullptr;
+    }
+    return *this;
+  }
+
+  self &
+  set_sni_hostname(const char *name, size_t len)
+  {
+    IpEndpoint ip;
+
+    // Literal IPv4 and IPv6 addresses are not permitted in "HostName".(rfc6066#section-3)
+    if (name && len && ats_ip_pton(std::string_view(name, len), &ip) != 0) {
+      sni_hostname = ats_strndup(name, len);
+    } else {
+      sni_hostname = nullptr;
+    }
+    return *this;
+  }
+
+  self &
+  operator=(self const &that)
+  {
+    if (&that != this) {
+      /*
+       * It is odd but necessary to null the scoped string pointer here
+       * and then explicitly call release on them in the string assignments
+       * below.
+       * We a memcpy from that to this.  This will put that's string pointers into
+       * this's memory.  Therefore we must first explicitly null out
+       * this's original version of the string.  The release after the
+       * memcpy removes the extra reference to that's copy of the string
+       * Removing the release will eventually cause a double free crash
+       */
+      sni_servername       = nullptr; // release any current name.
+      ssl_servername       = nullptr;
+      sni_hostname         = nullptr;
+      ssl_client_cert_name = nullptr;
+      memcpy(static_cast<void *>(this), &that, sizeof(self));
+      if (that.sni_servername) {
+        sni_servername.release(); // otherwise we'll free the source string.
+        this->sni_servername = ats_strdup(that.sni_servername);
+      }
+      if (that.ssl_servername) {
+        ssl_servername.release(); // otherwise we'll free the source string.
+        this->ssl_servername = ats_strdup(that.ssl_servername);
+      }
+      if (that.sni_hostname) {
+        sni_hostname.release(); // otherwise we'll free the source string.
+        this->sni_hostname = ats_strdup(that.sni_hostname);
+      }
+      if (that.ssl_client_cert_name) {
+        this->ssl_client_cert_name.release(); // otherwise we'll free the source string.
+        this->ssl_client_cert_name = ats_strdup(that.ssl_client_cert_name);
+      }
+    }
+    return *this;
+  }
+
+  std::string_view get_family_string() const;
+
+  std::string_view get_proto_string() const;
+
+  /// @name Debugging
+  //@{
+  /// Convert @a s to its string equivalent.
+  static const char *toString(addr_bind_style s);
+  //@}
+
+  // noncopyable
+  NetVCOptions(const NetVCOptions &) = delete;
+};
diff --git a/iocore/net/P_NetAccept.h b/iocore/net/P_NetAccept.h
index e30394d4b2..e116affe51 100644
--- a/iocore/net/P_NetAccept.h
+++ b/iocore/net/P_NetAccept.h
@@ -38,11 +38,14 @@
  ****************************************************************************/
 #pragma once
 
+#include "EventIO.h"
+#include "I_NetProcessor.h"
 #include <vector>
 #include "tscore/ink_platform.h"
 #include "P_Connection.h"
 
 struct NetAccept;
+struct HttpProxyPort;
 class Event;
 class SSLNextProtocolAccept;
 //
@@ -50,7 +53,7 @@ class SSLNextProtocolAccept;
 //   Accepts as many connections as possible, returning the number accepted
 //   or -1 to stop accepting.
 //
-typedef int(AcceptFunction)(NetAccept *na, void *e, bool blockable);
+using AcceptFunction    = int(NetAccept *, void *, bool);
 using AcceptFunctionPtr = AcceptFunction *;
 AcceptFunction net_accept;
 
@@ -91,7 +94,7 @@ struct NetAccept : public Continuation {
   EventIO ep;
 
   HttpProxyPort *proxyPort = nullptr;
-  NetProcessor::AcceptOptions opt;
+  AcceptOptions opt;
 
   virtual NetProcessor *getNetProcessor() const;
 
diff --git a/iocore/net/P_UDPNet.h b/iocore/net/P_UDPNet.h
index d1d862b840..796e07a642 100644
--- a/iocore/net/P_UDPNet.h
+++ b/iocore/net/P_UDPNet.h
@@ -32,6 +32,7 @@
 
 #include "tscore/ink_platform.h"
 #include "I_UDPNet.h"
+#include "PollCont.h"
 
 // added by YTS Team, yamsat
 static inline PollCont *get_UDPPollCont(EThread *);
@@ -335,7 +336,6 @@ public:
   UDPNetHandler(bool enable_gso);
 };
 
-struct PollCont;
 static inline PollCont *
 get_UDPPollCont(EThread *t)
 {
diff --git a/iocore/net/P_UnixNet.h b/iocore/net/P_UnixNet.h
index 06abfed6ed..dc1a8d3086 100644
--- a/iocore/net/P_UnixNet.h
+++ b/iocore/net/P_UnixNet.h
@@ -27,119 +27,20 @@
 
 #include "tscore/ink_platform.h"
 
-#define USE_EDGE_TRIGGER_EPOLL  1
-#define USE_EDGE_TRIGGER_KQUEUE 1
-#define USE_EDGE_TRIGGER_PORT   1
-
-#define EVENTIO_NETACCEPT      1
-#define EVENTIO_READWRITE_VC   2
-#define EVENTIO_DNS_CONNECTION 3
-#define EVENTIO_UDP_CONNECTION 4
-#define EVENTIO_ASYNC_SIGNAL   5
-#define EVENTIO_IO_URING       6
-
-#if TS_USE_EPOLL
-#ifndef EPOLLEXCLUSIVE
-#define EPOLLEXCLUSIVE 0
-#endif
-#ifdef USE_EDGE_TRIGGER_EPOLL
-#define USE_EDGE_TRIGGER 1
-#define EVENTIO_READ     (EPOLLIN | EPOLLET)
-#define EVENTIO_WRITE    (EPOLLOUT | EPOLLET)
-#else
-#define EVENTIO_READ  EPOLLIN
-#define EVENTIO_WRITE EPOLLOUT
-#endif
-#define EVENTIO_ERROR (EPOLLERR | EPOLLPRI | EPOLLHUP)
-#endif
-
-#if TS_USE_KQUEUE
-#ifdef USE_EDGE_TRIGGER_KQUEUE
-#define USE_EDGE_TRIGGER    1
-#define INK_EV_EDGE_TRIGGER EV_CLEAR
-#else
-#define INK_EV_EDGE_TRIGGER 0
-#endif
-#define EVENTIO_READ  INK_EVP_IN
-#define EVENTIO_WRITE INK_EVP_OUT
-#define EVENTIO_ERROR (0x010 | 0x002 | 0x020) // ERR PRI HUP
-#endif
-
-struct PollDescriptor;
-using EventLoop = PollDescriptor *;
-
-class NetEvent;
-class UnixUDPConnection;
-class DiskHandler;
-struct DNSConnection;
-struct NetAccept;
-
-/// Unified API for setting and clearing kernel and epoll events.
-struct EventIO {
-  int fd = -1; ///< file descriptor, often a system port
-#if TS_USE_KQUEUE || TS_USE_EPOLL && !defined(USE_EDGE_TRIGGER)
-  int events = 0; ///< a bit mask of enabled events
-#endif
-  EventLoop event_loop = nullptr; ///< the assigned event loop
-  bool syscall         = true;    ///< if false, disable all functionality (for QUIC)
-  int type             = 0;       ///< class identifier of union data.
-  union {
-    void *untyped;
-    NetEvent *ne;
-    DNSConnection *dnscon;
-    NetAccept *na;
-    UnixUDPConnection *uc;
-    DiskHandler *dh;
-  } data; ///< a kind of continuation
-
-  /** The start methods all logically Setup a class to be called
-     when a file descriptor is available for read or write.
-     The type of the classes vary.  Generally the file descriptor
-     is pulled from the class, but there is one option that lets
-     the file descriptor be expressed directly.
-     @param l the event loop
-     @param events a mask of flags (for details `man epoll_ctl`)
-     @return int the number of events created, -1 is error
-   */
-  int start(EventLoop l, DNSConnection *vc, int events);
-  int start(EventLoop l, NetAccept *vc, int events);
-  int start(EventLoop l, NetEvent *ne, int events);
-  int start(EventLoop l, UnixUDPConnection *vc, int events);
-  int start(EventLoop l, int fd, NetEvent *ne, int events);
-  int start_common(EventLoop l, int fd, int events);
-
-  /** Alter the events that will trigger the continuation, for level triggered I/O.
-     @param events add with positive mask(+EVENTIO_READ), or remove with negative mask (-EVENTIO_READ)
-     @return int the number of events created, -1 is error
-   */
-  int modify(int events);
-
-  /** Refresh the existing events (i.e. KQUEUE EV_CLEAR), for edge triggered I/O
-     @param events mask of events
-     @return int the number of events created, -1 is error
-   */
-  int refresh(int events);
-
-  /// Remove the kernel or epoll event. Returns 0 on success.
-  int stop();
-
-  /// Remove the epoll event and close the connection. Returns 0 on success.
-  int close();
-
-  EventIO() { data.untyped = nullptr; }
-};
-
+#include "PollCont.h"
+#include "EventIO.h"
+#include "NetHandler.h"
 #include "P_Net.h"
+#include "P_NetAccept.h"
 #include "P_UnixNetProcessor.h"
 #include "P_UnixNetVConnection.h"
-#include "P_NetAccept.h"
-#include "P_DNSConnection.h"
-#include "P_UnixUDPConnection.h"
 #include "P_UnixPollDescriptor.h"
 #include <limits>
 
-class NetEvent;
-class NetHandler;
+NetHandler *get_NetHandler(EThread *t);
+PollCont *get_PollCont(EThread *t);
+PollDescriptor *get_PollDescriptor(EThread *t);
+
 using NetContHandler = int (NetHandler::*)(int, void *);
 using uint32         = unsigned int;
 
@@ -176,235 +77,6 @@ extern ink_hrtime last_transient_accept_error;
 // function prototype needed for SSLUnixNetVConnection
 unsigned int net_next_connection_number();
 
-struct PollCont : public Continuation {
-  NetHandler *net_handler;
-  PollDescriptor *pollDescriptor;
-  PollDescriptor *nextPollDescriptor;
-  int poll_timeout;
-
-  PollCont(Ptr<ProxyMutex> &m, int pt = net_config_poll_timeout);
-  PollCont(Ptr<ProxyMutex> &m, NetHandler *nh, int pt = net_config_poll_timeout);
-  ~PollCont() override;
-  int pollEvent(int, Event *);
-  void do_poll(ink_hrtime timeout);
-};
-
-/**
-  NetHandler is the processor of NetEvent for the Net sub-system. The NetHandler
-  is the core component of the Net sub-system. Once started, it is responsible
-  for polling socket fds and perform the I/O tasks in NetEvent.
-
-  The NetHandler is executed periodically to perform read/write tasks for
-  NetVConnection. The NetHandler::mainNetEvent() should be viewed as a part of
-  EThread::execute() loop. This is the reason that Net System is a sub-system.
-
-  By get_NetHandler(this_ethread()), you can get the NetHandler object that
-  runs inside the current EThread and then @c startIO / @c stopIO which
-  assign/release a NetEvent to/from NetHandler. Before you call these functions,
-  holding the mutex of this NetHandler is required.
-
-  The NetVConnection provides a set of do_io functions through which you can
-  specify continuations to be called back by its NetHandler. These function
-  calls do not block. Instead they return an VIO object and schedule the
-  callback to the continuation passed in when there are I/O events occurred.
-
-  Multi-thread scheduler:
-
-  The NetHandler should be viewed as multi-threaded schedulers which process
-  NetEvents from their queues. If vc wants to be managed by NetHandler, the vc
-  should be derived from NetEvent. The vc can be made of NetProcessor (allocate_vc)
-  either by directly adding a NetEvent to the queue (NetHandler::startIO), or more
-  conveniently, calling a method service call (NetProcessor::connect_re) which
-  synthesizes the NetEvent and places it in the queue.
-
-  Callback event codes:
-
-  These event codes for do_io_read and reenable(read VIO) task:
-    VC_EVENT_READ_READY, VC_EVENT_READ_COMPLETE,
-    VC_EVENT_EOS, VC_EVENT_ERROR
-
-  These event codes for do_io_write and reenable(write VIO) task:
-    VC_EVENT_WRITE_READY, VC_EVENT_WRITE_COMPLETE
-    VC_EVENT_ERROR
-
-  There is no event and callback for do_io_shutdown / do_io_close task.
-
-  NetVConnection allocation policy:
-
-  VCs are allocated by the NetProcessor and deallocated by NetHandler.
-  A state machine may access the returned, non-recurring NetEvent / VIO until
-  it is closed by do_io_close. For recurring NetEvent, the NetEvent may be
-  accessed until it is closed. Once the NetEvent is closed, it's the
-  NetHandler's responsibility to deallocate it.
-
-  Before assign to NetHandler or after release from NetHandler, it's the
-  NetEvent's responsibility to deallocate itself.
-
- */
-
-//
-// NetHandler
-//
-// A NetHandler handles the Network IO operations.  It maintains
-// lists of operations at multiples of it's periodicity.
-//
-class NetHandler : public Continuation, public EThread::LoopTailHandler
-{
-  using self_type = NetHandler; ///< Self reference type.
-public:
-  // @a thread and @a trigger_event are redundant - you can get the former from the latter.
-  // If we don't get rid of @a trigger_event we should remove @a thread.
-  EThread *thread      = nullptr;
-  Event *trigger_event = nullptr;
-  QueM(NetEvent, NetState, read, ready_link) read_ready_list;
-  QueM(NetEvent, NetState, write, ready_link) write_ready_list;
-  Que(NetEvent, open_link) open_list;
-  DList(NetEvent, cop_link) cop_list;
-  ASLLM(NetEvent, NetState, read, enable_link) read_enable_list;
-  ASLLM(NetEvent, NetState, write, enable_link) write_enable_list;
-  Que(NetEvent, keep_alive_queue_link) keep_alive_queue;
-  uint32_t keep_alive_queue_size = 0;
-  Que(NetEvent, active_queue_link) active_queue;
-  uint32_t active_queue_size = 0;
-
-#ifdef TS_USE_LINUX_IO_URING
-  EventIO uring_evio;
-#endif
-
-  /// configuration settings for managing the active and keep-alive queues
-  struct Config {
-    uint32_t max_connections_in                 = 0;
-    uint32_t max_requests_in                    = 0;
-    uint32_t inactive_threshold_in              = 0;
-    uint32_t transaction_no_activity_timeout_in = 0;
-    uint32_t keep_alive_no_activity_timeout_in  = 0;
-    uint32_t default_inactivity_timeout         = 0;
-
-    /** Return the address of the first value in this struct.
-
-        Doing updates is much easier if we treat this config struct as an array.
-        Making it a method means the knowledge of which member is the first one
-        is localized to this struct, not scattered about.
-     */
-    uint32_t &
-    operator[](int n)
-    {
-      return *(&max_connections_in + n);
-    }
-  };
-  /** Static global config, set and updated per process.
-
-      This is updated asynchronously and then events are sent to the NetHandler instances per thread
-      to copy to the per thread config at a convenient time. Because these are updated independently
-      from the command line, the update events just copy a single value from the global to the
-      local. This mechanism relies on members being identical types.
-  */
-  static Config global_config;
-  Config config; ///< Per thread copy of the @c global_config
-  // Active and keep alive queue values that depend on other configuration values.
-  // These are never updated directly, they are computed from other config values.
-  uint32_t max_connections_per_thread_in = 0;
-  uint32_t max_requests_per_thread_in    = 0;
-  /// Number of configuration items in @c Config.
-  static constexpr int CONFIG_ITEM_COUNT = sizeof(Config) / sizeof(uint32_t);
-  /// Which members of @c Config the per thread values depend on.
-  /// If one of these is updated, the per thread values must also be updated.
-  static const std::bitset<CONFIG_ITEM_COUNT> config_value_affects_per_thread_value;
-  /// Set of thread types in which nethandlers are active.
-  /// This enables signaling the correct instances when the configuration is updated.
-  /// Event type threads that use @c NetHandler must set the corresponding bit.
-  static std::bitset<std::numeric_limits<unsigned int>::digits> active_thread_types;
-
-  int mainNetEvent(int event, Event *data);
-  int waitForActivity(ink_hrtime timeout) override;
-  void process_enabled_list();
-  void process_ready_list();
-  void manage_keep_alive_queue();
-  bool manage_active_queue(NetEvent *ne, bool ignore_queue_size);
-  void add_to_keep_alive_queue(NetEvent *ne);
-  void remove_from_keep_alive_queue(NetEvent *ne);
-  bool add_to_active_queue(NetEvent *ne);
-  void remove_from_active_queue(NetEvent *ne);
-
-  /// Per process initialization logic.
-  static void init_for_process();
-  /// Update configuration values that are per thread and depend on other configuration values.
-  void configure_per_thread_values();
-
-  /**
-    Start to handle read & write event on a NetEvent.
-    Initial the socket fd of ne for polling system.
-    Only be called when holding the mutex of this NetHandler.
-
-    @param ne NetEvent to be managed by this NetHandler.
-    @return 0 on success, ne->nh set to this NetHandler.
-            -ERRNO on failure.
-   */
-  int startIO(NetEvent *ne);
-  /**
-    Stop to handle read & write event on a NetEvent.
-    Remove the socket fd of ne from polling system.
-    Only be called when holding the mutex of this NetHandler and must call stopCop(ne) first.
-
-    @param ne NetEvent to be released.
-    @return ne->nh set to nullptr.
-   */
-  void stopIO(NetEvent *ne);
-
-  /**
-    Start to handle active timeout and inactivity timeout on a NetEvent.
-    Put the ne into open_list. All NetEvents in the open_list is checked for timeout by InactivityCop.
-    Only be called when holding the mutex of this NetHandler and must call startIO(ne) first.
-
-    @param ne NetEvent to be managed by InactivityCop
-   */
-  void startCop(NetEvent *ne);
-  /**
-    Stop to handle active timeout and inactivity on a NetEvent.
-    Remove the ne from open_list and cop_list.
-    Also remove the ne from keep_alive_queue and active_queue if its context is IN.
-    Only be called when holding the mutex of this NetHandler.
-
-    @param ne NetEvent to be released.
-   */
-  void stopCop(NetEvent *ne);
-
-  // Signal the epoll_wait to terminate.
-  void signalActivity() override;
-
-  /**
-    Release a ne and free it.
-
-    @param ne NetEvent to be detached.
-   */
-  void free_netevent(NetEvent *ne);
-
-  NetHandler();
-
-private:
-  void _close_ne(NetEvent *ne, ink_hrtime now, int &handle_event, int &closed, int &total_idle_time, int &total_idle_count);
-
-  /// Static method used as the callback for runtime configuration updates.
-  static int update_nethandler_config(const char *name, RecDataT, RecData data, void *);
-};
-
-static inline NetHandler *
-get_NetHandler(EThread *t)
-{
-  return (NetHandler *)ETHREAD_GET_PTR(t, unix_netProcessor.netHandler_offset);
-}
-static inline PollCont *
-get_PollCont(EThread *t)
-{
-  return (PollCont *)ETHREAD_GET_PTR(t, unix_netProcessor.pollCont_offset);
-}
-static inline PollDescriptor *
-get_PollDescriptor(EThread *t)
-{
-  PollCont *p = get_PollCont(t);
-  return p->pollDescriptor;
-}
-
 enum ThrottleType {
   ACCEPT,
   CONNECT,
@@ -417,11 +89,12 @@ net_connections_to_throttle(ThrottleType t)
   int64_t sval    = 0;
 
   NET_READ_GLOBAL_DYN_SUM(net_connections_currently_open_stat, sval);
-  int currently_open = (int)sval;
+  int currently_open = static_cast<int>(sval);
   // deal with race if we got to multiple net threads
-  if (currently_open < 0)
+  if (currently_open < 0) {
     currently_open = 0;
-  return (int)(currently_open * headroom);
+  }
+  return static_cast<int>(currently_open * headroom);
 }
 
 TS_INLINE void
@@ -439,8 +112,9 @@ check_net_throttle(ThrottleType t)
 {
   int connections = net_connections_to_throttle(t);
 
-  if (net_connections_throttle != 0 && connections >= net_connections_throttle)
+  if (net_connections_throttle != 0 && connections >= net_connections_throttle) {
     return true;
+  }
 
   return false;
 }
@@ -471,8 +145,9 @@ change_net_connections_throttle(const char *token, RecDataT data_type, RecData v
     net_connections_throttle = throttle;
   } else {
     net_connections_throttle = fds_throttle;
-    if (net_connections_throttle > throttle)
+    if (net_connections_throttle > throttle) {
       net_connections_throttle = throttle;
+    }
   }
   return 0;
 }
@@ -579,260 +254,3 @@ write_disable(NetHandler *nh, NetEvent *ne)
   nh->write_ready_list.remove(ne);
   ne->ep.modify(-EVENTIO_WRITE);
 }
-
-TS_INLINE int
-EventIO::start(EventLoop l, DNSConnection *vc, int events)
-{
-  type        = EVENTIO_DNS_CONNECTION;
-  data.dnscon = vc;
-  return start_common(l, vc->fd, events);
-}
-TS_INLINE int
-EventIO::start(EventLoop l, NetAccept *vc, int events)
-{
-  type    = EVENTIO_NETACCEPT;
-  data.na = vc;
-  return start_common(l, vc->server.fd, events);
-}
-TS_INLINE int
-EventIO::start(EventLoop l, NetEvent *ne, int events)
-{
-  type    = EVENTIO_READWRITE_VC;
-  data.ne = ne;
-  return start_common(l, ne->get_fd(), events);
-}
-
-TS_INLINE int
-EventIO::start(EventLoop l, UnixUDPConnection *vc, int events)
-{
-  type    = EVENTIO_UDP_CONNECTION;
-  data.uc = vc;
-  return start_common(l, vc->fd, events);
-}
-
-TS_INLINE int
-EventIO::close()
-{
-  if (!this->syscall) {
-    return 0;
-  }
-
-  stop();
-  switch (type) {
-  default:
-    ink_assert(!"case");
-  // fallthrough
-  case EVENTIO_DNS_CONNECTION:
-    return data.dnscon->close();
-    break;
-  case EVENTIO_NETACCEPT:
-    return data.na->server.close();
-    break;
-  case EVENTIO_READWRITE_VC:
-    return data.ne->close();
-    break;
-  }
-  return -1;
-}
-
-TS_INLINE int
-EventIO::start(EventLoop l, int afd, NetEvent *ne, int e)
-{
-  data.ne = ne;
-  return start_common(l, afd, e);
-}
-
-TS_INLINE int
-EventIO::start_common(EventLoop l, int afd, int e)
-{
-  if (!this->syscall) {
-    return 0;
-  }
-
-  fd         = afd;
-  event_loop = l;
-#if TS_USE_EPOLL
-  struct epoll_event ev;
-  memset(&ev, 0, sizeof(ev));
-  ev.events   = e | EPOLLEXCLUSIVE;
-  ev.data.ptr = this;
-#ifndef USE_EDGE_TRIGGER
-  events = e;
-#endif
-  return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-#endif
-#if TS_USE_KQUEUE
-  events = e;
-  struct kevent ev[2];
-  int n = 0;
-  if (e & EVENTIO_READ)
-    EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-  if (e & EVENTIO_WRITE)
-    EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-  return kevent(l->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
-#endif
-}
-
-TS_INLINE int
-EventIO::modify(int e)
-{
-  if (!this->syscall) {
-    return 0;
-  }
-
-  ink_assert(event_loop);
-#if TS_USE_EPOLL && !defined(USE_EDGE_TRIGGER)
-  struct epoll_event ev;
-  memset(&ev, 0, sizeof(ev));
-  int new_events = events, old_events = events;
-  if (e < 0)
-    new_events &= ~(-e);
-  else
-    new_events |= e;
-  events      = new_events;
-  ev.events   = new_events;
-  ev.data.ptr = this;
-  if (!new_events)
-    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, &ev);
-  else if (!old_events)
-    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_ADD, fd, &ev);
-  else
-    return epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_MOD, fd, &ev);
-#endif
-#if TS_USE_KQUEUE && !defined(USE_EDGE_TRIGGER)
-  int n = 0;
-  struct kevent ev[2];
-  int ee = events;
-  if (e < 0) {
-    ee &= ~(-e);
-    if ((-e) & EVENTIO_READ)
-      EV_SET(&ev[n++], fd, EVFILT_READ, EV_DELETE, 0, 0, this);
-    if ((-e) & EVENTIO_WRITE)
-      EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_DELETE, 0, 0, this);
-  } else {
-    ee |= e;
-    if (e & EVENTIO_READ)
-      EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-    if (e & EVENTIO_WRITE)
-      EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-  }
-  events = ee;
-  if (n)
-    return kevent(event_loop->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
-  else
-    return 0;
-#endif
-  (void)e; // ATS_UNUSED
-  return 0;
-}
-
-TS_INLINE int
-EventIO::refresh(int e)
-{
-  if (!this->syscall) {
-    return 0;
-  }
-
-  ink_assert(event_loop);
-#if TS_USE_KQUEUE && defined(USE_EDGE_TRIGGER)
-  e = e & events;
-  struct kevent ev[2];
-  int n = 0;
-  if (e & EVENTIO_READ)
-    EV_SET(&ev[n++], fd, EVFILT_READ, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-  if (e & EVENTIO_WRITE)
-    EV_SET(&ev[n++], fd, EVFILT_WRITE, EV_ADD | INK_EV_EDGE_TRIGGER, 0, 0, this);
-  if (n)
-    return kevent(event_loop->kqueue_fd, &ev[0], n, nullptr, 0, nullptr);
-  else
-    return 0;
-#endif
-  (void)e; // ATS_UNUSED
-  return 0;
-}
-
-TS_INLINE int
-EventIO::stop()
-{
-  if (!this->syscall) {
-    return 0;
-  }
-  if (event_loop) {
-    int retval = 0;
-#if TS_USE_EPOLL
-    struct epoll_event ev;
-    memset(&ev, 0, sizeof(struct epoll_event));
-    ev.events = EPOLLIN | EPOLLOUT | EPOLLET;
-    retval    = epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, &ev);
-#endif
-    event_loop = nullptr;
-    return retval;
-  }
-  return 0;
-}
-
-TS_INLINE int
-NetHandler::startIO(NetEvent *ne)
-{
-  ink_assert(this->mutex->thread_holding == this_ethread());
-  ink_assert(ne->get_thread() == this_ethread());
-  int res = 0;
-
-  PollDescriptor *pd = get_PollDescriptor(this->thread);
-  if (ne->ep.start(pd, ne, EVENTIO_READ | EVENTIO_WRITE) < 0) {
-    res = errno;
-    // EEXIST should be ok, though it should have been cleared before we got back here
-    if (errno != EEXIST) {
-      Debug("iocore_net", "NetHandler::startIO : failed on EventIO::start, errno = [%d](%s)", errno, strerror(errno));
-      return -res;
-    }
-  }
-
-  if (ne->read.triggered == 1) {
-    read_ready_list.enqueue(ne);
-  }
-  ne->nh = this;
-  return res;
-}
-
-TS_INLINE void
-NetHandler::stopIO(NetEvent *ne)
-{
-  ink_release_assert(ne->nh == this);
-
-  ne->ep.stop();
-
-  read_ready_list.remove(ne);
-  write_ready_list.remove(ne);
-  if (ne->read.in_enabled_list) {
-    read_enable_list.remove(ne);
-    ne->read.in_enabled_list = 0;
-  }
-  if (ne->write.in_enabled_list) {
-    write_enable_list.remove(ne);
-    ne->write.in_enabled_list = 0;
-  }
-
-  ne->nh = nullptr;
-}
-
-TS_INLINE void
-NetHandler::startCop(NetEvent *ne)
-{
-  ink_assert(this->mutex->thread_holding == this_ethread());
-  ink_release_assert(ne->nh == this);
-  ink_assert(!open_list.in(ne));
-
-  open_list.enqueue(ne);
-}
-
-TS_INLINE void
-NetHandler::stopCop(NetEvent *ne)
-{
-  ink_release_assert(ne->nh == this);
-
-  open_list.remove(ne);
-  cop_list.remove(ne);
-  remove_from_keep_alive_queue(ne);
-  remove_from_active_queue(ne);
-}
diff --git a/iocore/net/P_UnixPollDescriptor.h b/iocore/net/P_UnixPollDescriptor.h
index 1ae606644b..d4c981f4b4 100644
--- a/iocore/net/P_UnixPollDescriptor.h
+++ b/iocore/net/P_UnixPollDescriptor.h
@@ -56,7 +56,30 @@ struct PollDescriptor {
   int kqueue_fd;
 #endif
 
-  PollDescriptor() { init(); }
+  PollDescriptor()
+  {
+    result = 0;
+#if TS_USE_EPOLL
+    nfds     = 0;
+    epoll_fd = epoll_create(POLL_DESCRIPTOR_SIZE);
+    memset(ePoll_Triggered_Events, 0, sizeof(ePoll_Triggered_Events));
+    memset(pfd, 0, sizeof(pfd));
+#endif
+#if TS_USE_KQUEUE
+    kqueue_fd = kqueue();
+    memset(kq_Triggered_Events, 0, sizeof(kq_Triggered_Events));
+#endif
+  }
+
+  virtual ~PollDescriptor()
+  {
+#if TS_USE_EPOLL
+    close(epoll_fd);
+#endif
+#if TS_USE_KQUEUE
+    close(kqueue_fd);
+#endif
+  }
 #if TS_USE_EPOLL
 #define get_ev_port(a)      ((a)->epoll_fd)
 #define get_ev_events(a, x) ((a)->ePoll_Triggered_Events[(x)].events)
@@ -103,21 +126,4 @@ struct PollDescriptor {
     return nullptr;
 #endif
   }
-
-private:
-  void
-  init()
-  {
-    result = 0;
-#if TS_USE_EPOLL
-    nfds     = 0;
-    epoll_fd = epoll_create(POLL_DESCRIPTOR_SIZE);
-    memset(ePoll_Triggered_Events, 0, sizeof(ePoll_Triggered_Events));
-    memset(pfd, 0, sizeof(pfd));
-#endif
-#if TS_USE_KQUEUE
-    kqueue_fd = kqueue();
-    memset(kq_Triggered_Events, 0, sizeof(kq_Triggered_Events));
-#endif
-  }
 };
diff --git a/iocore/net/PollCont.cc b/iocore/net/PollCont.cc
new file mode 100644
index 0000000000..3996121c6e
--- /dev/null
+++ b/iocore/net/PollCont.cc
@@ -0,0 +1,95 @@
+/**@file
+
+   A brief file description
+
+ @section license License
+
+   Licensed to the Apache Software
+   Foundation(ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "PollCont.h"
+#include "P_Net.h"
+
+PollCont::PollCont(Ptr<ProxyMutex> &m, int pt)
+  : Continuation(m.get()), net_handler(nullptr), nextPollDescriptor(nullptr), poll_timeout(pt)
+{
+  pollDescriptor = new PollDescriptor();
+  SET_HANDLER(&PollCont::pollEvent);
+}
+
+PollCont::PollCont(Ptr<ProxyMutex> &m, NetHandler *nh, int pt)
+  : Continuation(m.get()), net_handler(nh), nextPollDescriptor(nullptr), poll_timeout(pt)
+{
+  pollDescriptor = new PollDescriptor();
+  SET_HANDLER(&PollCont::pollEvent);
+}
+
+PollCont::~PollCont()
+{
+  delete pollDescriptor;
+  if (nextPollDescriptor != nullptr) {
+    delete nextPollDescriptor;
+  }
+}
+
+//
+// PollCont continuation which does the epoll_wait
+// and stores the resultant events in ePoll_Triggered_Events
+//
+int
+PollCont::pollEvent(int, Event *)
+{
+  this->do_poll(-1);
+  return EVENT_CONT;
+}
+
+void
+PollCont::do_poll(ink_hrtime timeout)
+{
+  if (likely(net_handler)) {
+    /* checking to see whether there are connections on the ready_queue (either
+     * read or write) that need processing [ebalsa] */
+    if (likely(!net_handler->read_ready_list.empty() || !net_handler->write_ready_list.empty() ||
+               !net_handler->read_enable_list.empty() || !net_handler->write_enable_list.empty())) {
+      NetDebug("iocore_net_poll", "rrq: %d, wrq: %d, rel: %d, wel: %d", net_handler->read_ready_list.empty(),
+               net_handler->write_ready_list.empty(), net_handler->read_enable_list.empty(),
+               net_handler->write_enable_list.empty());
+      poll_timeout = 0; // poll immediately returns -- we have triggered stuff
+                        // to process right now
+    } else if (timeout >= 0) {
+      poll_timeout = ink_hrtime_to_msec(timeout);
+    } else {
+      poll_timeout = net_config_poll_timeout;
+    }
+  }
+// wait for fd's to trigger, or don't wait if timeout is 0
+#if TS_USE_EPOLL
+  pollDescriptor->result =
+    epoll_wait(pollDescriptor->epoll_fd, pollDescriptor->ePoll_Triggered_Events, POLL_DESCRIPTOR_SIZE, poll_timeout);
+  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] epoll_fd: %d, timeout: %d, results: %d", pollDescriptor->epoll_fd,
+           poll_timeout, pollDescriptor->result);
+#elif TS_USE_KQUEUE
+  struct timespec tv;
+  tv.tv_sec  = poll_timeout / 1000;
+  tv.tv_nsec = 1000000 * (poll_timeout % 1000);
+  pollDescriptor->result =
+    kevent(pollDescriptor->kqueue_fd, nullptr, 0, pollDescriptor->kq_Triggered_Events, POLL_DESCRIPTOR_SIZE, &tv);
+  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] kqueue_fd: %d, timeout: %d, results: %d", pollDescriptor->kqueue_fd,
+           poll_timeout, pollDescriptor->result);
+#endif
+}
diff --git a/iocore/net/PollCont.h b/iocore/net/PollCont.h
new file mode 100644
index 0000000000..bea7da4921
--- /dev/null
+++ b/iocore/net/PollCont.h
@@ -0,0 +1,43 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#pragma once
+
+#include "I_Continuation.h"
+#include "I_Net.h"
+
+class NetHandler;
+struct PollDescriptor;
+
+struct PollCont : public Continuation {
+  NetHandler *net_handler;
+  PollDescriptor *pollDescriptor;
+  PollDescriptor *nextPollDescriptor;
+  int poll_timeout;
+
+  PollCont(Ptr<ProxyMutex> &m, int pt = net_config_poll_timeout);
+  PollCont(Ptr<ProxyMutex> &m, NetHandler *nh, int pt = net_config_poll_timeout);
+  ~PollCont() override;
+  int pollEvent(int, Event *);
+  void do_poll(ink_hrtime timeout);
+};
diff --git a/iocore/net/UnixNet.cc b/iocore/net/UnixNet.cc
index 8ce096b9c6..0b33378cc6 100644
--- a/iocore/net/UnixNet.cc
+++ b/iocore/net/UnixNet.cc
@@ -1,8 +1,8 @@
 /** @file
 
-  A brief file description
+   A brief file description
 
-  @section license License
+ @section license License
 
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
@@ -22,15 +22,13 @@
  */
 
 #include "P_Net.h"
-#include "I_AIO.h"
+#include "P_UnixNet.h"
 #include "tscore/ink_hrtime.h"
 
 #if TS_USE_LINUX_IO_URING
 #include "I_IO_URING.h"
 #endif
 
-using namespace std::literals;
-
 ink_hrtime last_throttle_warning;
 ink_hrtime last_shedding_warning;
 int net_connections_throttle;
@@ -43,7 +41,24 @@ NetHandler::Config NetHandler::global_config;
 std::bitset<std::numeric_limits<unsigned int>::digits> NetHandler::active_thread_types;
 const std::bitset<NetHandler::CONFIG_ITEM_COUNT> NetHandler::config_value_affects_per_thread_value{0x3};
 
-extern "C" void fd_reify(struct ev_loop *);
+NetHandler *
+get_NetHandler(EThread *t)
+{
+  return (NetHandler *)ETHREAD_GET_PTR(t, unix_netProcessor.netHandler_offset);
+}
+
+PollCont *
+get_PollCont(EThread *t)
+{
+  return (PollCont *)ETHREAD_GET_PTR(t, unix_netProcessor.pollCont_offset);
+}
+
+PollDescriptor *
+get_PollDescriptor(EThread *t)
+{
+  PollCont *p = get_PollCont(t);
+  return p->pollDescriptor;
+}
 
 // INKqa10496
 // One Inactivity cop runs on each thread once every second and
@@ -137,85 +152,6 @@ public:
   }
 };
 
-PollCont::PollCont(Ptr<ProxyMutex> &m, int pt)
-  : Continuation(m.get()), net_handler(nullptr), nextPollDescriptor(nullptr), poll_timeout(pt)
-{
-  pollDescriptor = new PollDescriptor();
-  SET_HANDLER(&PollCont::pollEvent);
-}
-
-PollCont::PollCont(Ptr<ProxyMutex> &m, NetHandler *nh, int pt)
-  : Continuation(m.get()), net_handler(nh), nextPollDescriptor(nullptr), poll_timeout(pt)
-{
-  pollDescriptor = new PollDescriptor();
-  SET_HANDLER(&PollCont::pollEvent);
-}
-
-PollCont::~PollCont()
-{
-  delete pollDescriptor;
-  if (nextPollDescriptor != nullptr) {
-    delete nextPollDescriptor;
-  }
-}
-
-//
-// PollCont continuation which does the epoll_wait
-// and stores the resultant events in ePoll_Triggered_Events
-//
-int
-PollCont::pollEvent(int, Event *)
-{
-  this->do_poll(-1);
-  return EVENT_CONT;
-}
-
-void
-PollCont::do_poll(ink_hrtime timeout)
-{
-  if (likely(net_handler)) {
-    /* checking to see whether there are connections on the ready_queue (either read or write) that need processing [ebalsa] */
-    if (likely(!net_handler->read_ready_list.empty() || !net_handler->write_ready_list.empty() ||
-               !net_handler->read_enable_list.empty() || !net_handler->write_enable_list.empty())) {
-      NetDebug("iocore_net_poll", "rrq: %d, wrq: %d, rel: %d, wel: %d", net_handler->read_ready_list.empty(),
-               net_handler->write_ready_list.empty(), net_handler->read_enable_list.empty(),
-               net_handler->write_enable_list.empty());
-      poll_timeout = 0; // poll immediately returns -- we have triggered stuff to process right now
-    } else if (timeout >= 0) {
-      poll_timeout = ink_hrtime_to_msec(timeout);
-    } else {
-      poll_timeout = net_config_poll_timeout;
-    }
-  }
-// wait for fd's to trigger, or don't wait if timeout is 0
-#if TS_USE_EPOLL
-  pollDescriptor->result =
-    epoll_wait(pollDescriptor->epoll_fd, pollDescriptor->ePoll_Triggered_Events, POLL_DESCRIPTOR_SIZE, poll_timeout);
-  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] epoll_fd: %d, timeout: %d, results: %d", pollDescriptor->epoll_fd,
-           poll_timeout, pollDescriptor->result);
-#elif TS_USE_KQUEUE
-  struct timespec tv;
-  tv.tv_sec  = poll_timeout / 1000;
-  tv.tv_nsec = 1000000 * (poll_timeout % 1000);
-  pollDescriptor->result =
-    kevent(pollDescriptor->kqueue_fd, nullptr, 0, pollDescriptor->kq_Triggered_Events, POLL_DESCRIPTOR_SIZE, &tv);
-  NetDebug("v_iocore_net_poll", "[PollCont::pollEvent] kqueue_fd: %d, timeout: %d, results: %d", pollDescriptor->kqueue_fd,
-           poll_timeout, pollDescriptor->result);
-#endif
-}
-
-static void
-net_signal_hook_callback(EThread *thread)
-{
-#if HAVE_EVENTFD
-  uint64_t counter;
-  ATS_UNUSED_RETURN(read(thread->evfd, &counter, sizeof(uint64_t)));
-#else
-  char dummy[1024];
-  ATS_UNUSED_RETURN(read(thread->evpipe[0], &dummy[0], 1024));
-#endif
-}
-
 void
 initialize_thread_for_net(EThread *thread)
 {
@@ -252,524 +188,3 @@ initialize_thread_for_net(EThread *thread)
   nh->uring_evio.start(pd, IOUringContext::local_context()->register_eventfd(), nullptr, EVENTIO_READ);
 #endif
 }
-
-// NetHandler method definitions
-
-NetHandler::NetHandler() : Continuation(nullptr)
-{
-  SET_HANDLER(&NetHandler::mainNetEvent);
-}
-
-int
-NetHandler::update_nethandler_config(const char *str, RecDataT, RecData data, void *)
-{
-  uint32_t *updated_member = nullptr; // direct pointer to config member for update.
-  std::string_view name{str};
-
-  if (name == "proxy.config.net.max_connections_in"sv) {
-    updated_member = &NetHandler::global_config.max_connections_in;
-    Debug("net_queue", "proxy.config.net.max_connections_in updated to %" PRId64, data.rec_int);
-  } else if (name == "proxy.config.net.max_requests_in"sv) {
-    updated_member = &NetHandler::global_config.max_requests_in;
-    Debug("net_queue", "proxy.config.net.max_requests_in updated to %" PRId64, data.rec_int);
-  } else if (name == "proxy.config.net.inactive_threshold_in"sv) {
-    updated_member = &NetHandler::global_config.inactive_threshold_in;
-    Debug("net_queue", "proxy.config.net.inactive_threshold_in updated to %" PRId64, data.rec_int);
-  } else if (name == "proxy.config.net.transaction_no_activity_timeout_in"sv) {
-    updated_member = &NetHandler::global_config.transaction_no_activity_timeout_in;
-    Debug("net_queue", "proxy.config.net.transaction_no_activity_timeout_in updated to %" PRId64, data.rec_int);
-  } else if (name == "proxy.config.net.keep_alive_no_activity_timeout_in"sv) {
-    updated_member = &NetHandler::global_config.keep_alive_no_activity_timeout_in;
-    Debug("net_queue", "proxy.config.net.keep_alive_no_activity_timeout_in updated to %" PRId64, data.rec_int);
-  } else if (name == "proxy.config.net.default_inactivity_timeout"sv) {
-    updated_member = &NetHandler::global_config.default_inactivity_timeout;
-    Debug("net_queue", "proxy.config.net.default_inactivity_timeout updated to %" PRId64, data.rec_int);
-  }
-
-  if (updated_member) {
-    *updated_member = data.rec_int; // do the actual update.
-    // portable form of the update, an index converted to <void*> so it can be passed as an event cookie.
-    void *idx = reinterpret_cast<void *>(static_cast<intptr_t>(updated_member - &global_config[0]));
-    // Signal the NetHandler instances, passing the index of the updated config value.
-    for (int i = 0; i < eventProcessor.n_thread_groups; ++i) {
-      if (!active_thread_types[i]) {
-        continue;
-      }
-      for (EThread **tp    = eventProcessor.thread_group[i]._thread,
-                   **limit = eventProcessor.thread_group[i]._thread + eventProcessor.thread_group[i]._count;
-           tp < limit; ++tp) {
-        NetHandler *nh = get_NetHandler(*tp);
-        if (nh) {
-          nh->thread->schedule_imm(nh, TS_EVENT_MGMT_UPDATE, idx);
-        }
-      }
-    }
-  }
-
-  return REC_ERR_OKAY;
-}
-
-void
-NetHandler::init_for_process()
-{
-  // read configuration values and setup callbacks for when they change
-  REC_ReadConfigInt32(global_config.max_connections_in, "proxy.config.net.max_connections_in");
-  REC_ReadConfigInt32(global_config.max_requests_in, "proxy.config.net.max_requests_in");
-  REC_ReadConfigInt32(global_config.inactive_threshold_in, "proxy.config.net.inactive_threshold_in");
-  REC_ReadConfigInt32(global_config.transaction_no_activity_timeout_in, "proxy.config.net.transaction_no_activity_timeout_in");
-  REC_ReadConfigInt32(global_config.keep_alive_no_activity_timeout_in, "proxy.config.net.keep_alive_no_activity_timeout_in");
-  REC_ReadConfigInt32(global_config.default_inactivity_timeout, "proxy.config.net.default_inactivity_timeout");
-
-  RecRegisterConfigUpdateCb("proxy.config.net.max_connections_in", update_nethandler_config, nullptr);
-  RecRegisterConfigUpdateCb("proxy.config.net.max_requests_in", update_nethandler_config, nullptr);
-  RecRegisterConfigUpdateCb("proxy.config.net.inactive_threshold_in", update_nethandler_config, nullptr);
-  RecRegisterConfigUpdateCb("proxy.config.net.transaction_no_activity_timeout_in", update_nethandler_config, nullptr);
-  RecRegisterConfigUpdateCb("proxy.config.net.keep_alive_no_activity_timeout_in", update_nethandler_config, nullptr);
-  RecRegisterConfigUpdateCb("proxy.config.net.default_inactivity_timeout", update_nethandler_config, nullptr);
-
-  Debug("net_queue", "proxy.config.net.max_connections_in updated to %d", global_config.max_connections_in);
-  Debug("net_queue", "proxy.config.net.max_requests_in updated to %d", global_config.max_requests_in);
-  Debug("net_queue", "proxy.config.net.inactive_threshold_in updated to %d", global_config.inactive_threshold_in);
-  Debug("net_queue", "proxy.config.net.transaction_no_activity_timeout_in updated to %d",
-        global_config.transaction_no_activity_timeout_in);
-  Debug("net_queue", "proxy.config.net.keep_alive_no_activity_timeout_in updated to %d",
-        global_config.keep_alive_no_activity_timeout_in);
-  Debug("net_queue", "proxy.config.net.default_inactivity_timeout updated to %d", global_config.default_inactivity_timeout);
-}
-
-//
-// Function used to release a NetEvent and free it.
-//
-void
-NetHandler::free_netevent(NetEvent *ne)
-{
-  EThread *t = this->thread;
-
-  ink_assert(t == this_ethread());
-  ink_release_assert(ne->get_thread() == t);
-  ink_release_assert(ne->nh == this);
-
-  // Release ne from InactivityCop
-  stopCop(ne);
-  // Release ne from NetHandler
-  stopIO(ne);
-  // Clear and deallocate ne
-  ne->free(t);
-}
-
-//
-// Move VC's enabled on a different thread to the ready list
-//
-void
-NetHandler::process_enabled_list()
-{
-  NetEvent *ne = nullptr;
-
-  SListM(NetEvent, NetState, read, enable_link) rq(read_enable_list.popall());
-  while ((ne = rq.pop())) {
-    ne->ep.modify(EVENTIO_READ);
-    ne->ep.refresh(EVENTIO_READ);
-    ne->read.in_enabled_list = 0;
-    if ((ne->read.enabled && ne->read.triggered) || ne->closed) {
-      read_ready_list.in_or_enqueue(ne);
-    }
-  }
-
-  SListM(NetEvent, NetState, write, enable_link) wq(write_enable_list.popall());
-  while ((ne = wq.pop())) {
-    ne->ep.modify(EVENTIO_WRITE);
-    ne->ep.refresh(EVENTIO_WRITE);
-    ne->write.in_enabled_list = 0;
-    if ((ne->write.enabled && ne->write.triggered) || ne->closed) {
-      write_ready_list.in_or_enqueue(ne);
-    }
-  }
-}
-
-//
-// Walk through the ready list
-//
-void
-NetHandler::process_ready_list()
-{
-  NetEvent *ne = nullptr;
-
-#if defined(USE_EDGE_TRIGGER)
-  // NetEvent *
-  while ((ne = read_ready_list.dequeue())) {
-    // Initialize the thread-local continuation flags
-    set_cont_flags(ne->get_control_flags());
-    if (ne->closed) {
-      free_netevent(ne);
-    } else if (ne->read.enabled && ne->read.triggered) {
-      ne->net_read_io(this, this->thread);
-    } else if (!ne->read.enabled) {
-      read_ready_list.remove(ne);
-    }
-  }
-  while ((ne = write_ready_list.dequeue())) {
-    set_cont_flags(ne->get_control_flags());
-    if (ne->closed) {
-      free_netevent(ne);
-    } else if (ne->write.enabled && ne->write.triggered) {
-      ne->net_write_io(this, this->thread);
-    } else if (!ne->write.enabled) {
-      write_ready_list.remove(ne);
-    }
-  }
-#else  /* !USE_EDGE_TRIGGER */
-  while ((ne = read_ready_list.dequeue())) {
-    set_cont_flags(ne->get_control_flags());
-    if (ne->closed)
-      free_netevent(ne);
-    else if (ne->read.enabled && ne->read.triggered)
-      ne->net_read_io(this, this->thread);
-    else if (!ne->read.enabled)
-      ne->ep.modify(-EVENTIO_READ);
-  }
-  while ((ne = write_ready_list.dequeue())) {
-    set_cont_flags(ne->get_control_flags());
-    if (ne->closed)
-      free_netevent(ne);
-    else if (ne->write.enabled && ne->write.triggered)
-      write_to_net(this, ne, this->thread);
-    else if (!ne->write.enabled)
-      ne->ep.modify(-EVENTIO_WRITE);
-  }
-#endif /* !USE_EDGE_TRIGGER */
-}
-
-//
-// The main event for NetHandler
-int
-NetHandler::mainNetEvent(int event, Event *e)
-{
-  if (TS_EVENT_MGMT_UPDATE == event) {
-    intptr_t idx = reinterpret_cast<intptr_t>(e->cookie);
-    // Copy to the same offset in the instance struct.
-    config[idx] = global_config[idx];
-    if (config_value_affects_per_thread_value[idx]) {
-      this->configure_per_thread_values();
-    }
-    return EVENT_CONT;
-  } else {
-    ink_assert(trigger_event == e && (event == EVENT_INTERVAL || event == EVENT_POLL));
-    return this->waitForActivity(-1);
-  }
-}
-
-int
-NetHandler::waitForActivity(ink_hrtime timeout)
-{
-  EventIO *epd = nullptr;
-#if TS_USE_LINUX_IO_URING
-  IOUringContext *ur = IOUringContext::local_context();
-  bool servicedh     = false;
-#endif
-
-  NET_INCREMENT_DYN_STAT(net_handler_run_stat);
-  SCOPED_MUTEX_LOCK(lock, mutex, this->thread);
-
-  process_enabled_list();
-
-#if TS_USE_LINUX_IO_URING
-  ur->submit();
-#endif
-
-  // Polling event by PollCont
-  PollCont *p = get_PollCont(this->thread);
-  p->do_poll(timeout);
-
-  // Get & Process polling result
-  PollDescriptor *pd = get_PollDescriptor(this->thread);
-  NetEvent *ne       = nullptr;
-  for (int x = 0; x < pd->result; x++) {
-    epd = static_cast<EventIO *> get_ev_data(pd, x);
-    if (epd->type == EVENTIO_READWRITE_VC) {
-      ne = epd->data.ne;
-      // Remove triggered NetEvent from cop_list because it won't be timeout before next InactivityCop runs.
-      if (cop_list.in(ne)) {
-        cop_list.remove(ne);
-      }
-      int flags = get_ev_events(pd, x);
-      if (flags & (EVENTIO_ERROR)) {
-        ne->set_error_from_socket();
-      }
-      if (flags & (EVENTIO_READ)) {
-        ne->read.triggered = 1;
-        if (!read_ready_list.in(ne)) {
-          read_ready_list.enqueue(ne);
-        }
-      }
-      if (flags & (EVENTIO_WRITE)) {
-        ne->write.triggered = 1;
-        if (!write_ready_list.in(ne)) {
-          write_ready_list.enqueue(ne);
-        }
-      } else if (!(flags & (EVENTIO_READ))) {
-        Debug("iocore_net_main", "Unhandled epoll event: 0x%04x", flags);
-        // In practice we sometimes see EPOLLERR and EPOLLHUP through there
-        // Anything else would be surprising
-        ink_assert((flags & ~(EVENTIO_ERROR)) == 0);
-        ne->write.triggered = 1;
-        if (!write_ready_list.in(ne)) {
-          write_ready_list.enqueue(ne);
-        }
-      }
-    } else if (epd->type == EVENTIO_DNS_CONNECTION) {
-      if (epd->data.dnscon != nullptr) {
-        epd->data.dnscon->trigger(); // Make sure the DNSHandler for this con knows we triggered
-#if defined(USE_EDGE_TRIGGER)
-        epd->refresh(EVENTIO_READ);
-#endif
-      }
-    } else if (epd->type == EVENTIO_ASYNC_SIGNAL) {
-      net_signal_hook_callback(this->thread);
-    } else if (epd->type == EVENTIO_NETACCEPT) {
-      this->thread->schedule_imm(epd->data.na);
-#if TS_USE_LINUX_IO_URING
-    } else if (epd->type == EVENTIO_IO_URING) {
-      servicedh = true;
-#endif
-    }
-    ev_next_event(pd, x);
-  }
-
-  pd->result = 0;
-
-  process_ready_list();
-
-#if TS_USE_LINUX_IO_URING
-  if (servicedh) {
-    ur->service();
-  }
-#endif
-
-  return EVENT_CONT;
-}
-
-void
-NetHandler::signalActivity()
-{
-#if HAVE_EVENTFD
-  uint64_t counter = 1;
-  ATS_UNUSED_RETURN(write(thread->evfd, &counter, sizeof(uint64_t)));
-#else
-  char dummy = 1;
-  ATS_UNUSED_RETURN(write(thread->evpipe[1], &dummy, 1));
-#endif
-}
-
-bool
-NetHandler::manage_active_queue(NetEvent *enabling_ne, bool ignore_queue_size = false)
-{
-  const int total_connections_in = active_queue_size + keep_alive_queue_size;
-  Debug("v_net_queue",
-        "max_connections_per_thread_in: %d max_requests_per_thread_in: %d total_connections_in: %d "
-        "active_queue_size: %d keep_alive_queue_size: %d",
-        max_connections_per_thread_in, max_requests_per_thread_in, total_connections_in, active_queue_size, keep_alive_queue_size);
-
-  if (!max_requests_per_thread_in) {
-    // active queue has no max
-    return true;
-  }
-
-  if (ignore_queue_size == false && max_requests_per_thread_in > active_queue_size) {
-    return true;
-  }
-
-  ink_hrtime now = Thread::get_hrtime();
-
-  // loop over the non-active connections and try to close them
-  NetEvent *ne         = active_queue.head;
-  NetEvent *ne_next    = nullptr;
-  int closed           = 0;
-  int handle_event     = 0;
-  int total_idle_time  = 0;
-  int total_idle_count = 0;
-  for (; ne != nullptr; ne = ne_next) {
-    ne_next = ne->active_queue_link.next;
-    // It seems dangerous closing the current ne at this point
-    // Let the activity_cop deal with it
-    if (ne == enabling_ne) {
-      continue;
-    }
-    if ((ne->next_inactivity_timeout_at && ne->next_inactivity_timeout_at <= now) ||
-        (ne->next_activity_timeout_at && ne->next_activity_timeout_at <= now)) {
-      _close_ne(ne, now, handle_event, closed, total_idle_time, total_idle_count);
-    }
-    if (ignore_queue_size == false && max_requests_per_thread_in > active_queue_size) {
-      return true;
-    }
-  }
-
-  if (max_requests_per_thread_in > active_queue_size) {
-    return true;
-  }
-
-  return false; // failed to make room in the queue, all connections are active
-}
-
-void
-NetHandler::configure_per_thread_values()
-{
-  // figure out the number of threads and calculate the number of connections per thread
-  int threads                   = eventProcessor.thread_group[ET_NET]._count;
-  max_connections_per_thread_in = config.max_connections_in / threads;
-  max_requests_per_thread_in    = config.max_requests_in / threads;
-  Debug("net_queue", "max_connections_per_thread_in updated to %d threads: %d", max_connections_per_thread_in, threads);
-  Debug("net_queue", "max_requests_per_thread_in updated to %d threads: %d", max_requests_per_thread_in, threads);
-}
-
-void
-NetHandler::manage_keep_alive_queue()
-{
-  uint32_t total_connections_in = active_queue_size + keep_alive_queue_size;
-  ink_hrtime now                = Thread::get_hrtime();
-
-  Debug("v_net_queue", "max_connections_per_thread_in: %d total_connections_in: %d active_queue_size: %d keep_alive_queue_size: %d",
-        max_connections_per_thread_in, total_connections_in, active_queue_size, keep_alive_queue_size);
-
-  if (!max_connections_per_thread_in || total_connections_in <= max_connections_per_thread_in) {
-    return;
-  }
-
-  // loop over the non-active connections and try to close them
-  NetEvent *ne_next    = nullptr;
-  int closed           = 0;
-  int handle_event     = 0;
-  int total_idle_time  = 0;
-  int total_idle_count = 0;
-  for (NetEvent *ne = keep_alive_queue.head; ne != nullptr; ne = ne_next) {
-    ne_next = ne->keep_alive_queue_link.next;
-    _close_ne(ne, now, handle_event, closed, total_idle_time, total_idle_count);
-
-    total_connections_in = active_queue_size + keep_alive_queue_size;
-    if (total_connections_in <= max_connections_per_thread_in) {
-      break;
-    }
-  }
-
-  if (total_idle_count > 0) {
-    Debug("net_queue", "max cons: %d active: %d idle: %d already closed: %d, close event: %d mean idle: %d",
-          max_connections_per_thread_in, total_connections_in, keep_alive_queue_size, closed, handle_event,
-          total_idle_time / total_idle_count);
-  }
-}
-
-void
-NetHandler::_close_ne(NetEvent *ne, ink_hrtime now, int &handle_event, int &closed, int &total_idle_time, int &total_idle_count)
-{
-  if (ne->get_thread() != this_ethread()) {
-    return;
-  }
-  MUTEX_TRY_LOCK(lock, ne->get_mutex(), this_ethread());
-  if (!lock.is_locked()) {
-    return;
-  }
-  ink_hrtime diff = (now - (ne->next_inactivity_timeout_at - ne->inactivity_timeout_in)) / HRTIME_SECOND;
-  if (diff > 0) {
-    total_idle_time += diff;
-    ++total_idle_count;
-    NET_SUM_DYN_STAT(keep_alive_queue_timeout_total_stat, diff);
-    NET_INCREMENT_DYN_STAT(keep_alive_queue_timeout_count_stat);
-  }
-  Debug("net_queue", "closing connection NetEvent=%p idle: %u now: %" PRId64 " at: %" PRId64 " in: %" PRId64 " diff: %" PRId64, ne,
-        keep_alive_queue_size, ink_hrtime_to_sec(now), ink_hrtime_to_sec(ne->next_inactivity_timeout_at),
-        ink_hrtime_to_sec(ne->inactivity_timeout_in), diff);
-  if (ne->closed) {
-    free_netevent(ne);
-    ++closed;
-  } else {
-    ne->next_inactivity_timeout_at = now;
-    // create a dummy event
-    Event event;
-    event.ethread = this_ethread();
-    if (ne->inactivity_timeout_in && ne->next_inactivity_timeout_at <= now) {
-      if (ne->callback(VC_EVENT_INACTIVITY_TIMEOUT, &event) == EVENT_DONE) {
-        ++handle_event;
-      }
-    } else if (ne->active_timeout_in && ne->next_activity_timeout_at <= now) {
-      if (ne->callback(VC_EVENT_ACTIVE_TIMEOUT, &event) == EVENT_DONE) {
-        ++handle_event;
-      }
-    }
-  }
-}
-
-void
-NetHandler::add_to_keep_alive_queue(NetEvent *ne)
-{
-  Debug("net_queue", "NetEvent: %p", ne);
-  ink_assert(mutex->thread_holding == this_ethread());
-
-  if (keep_alive_queue.in(ne)) {
-    // already in the keep-alive queue, move the head
-    keep_alive_queue.remove(ne);
-  } else {
-    // in the active queue or no queue, new to this queue
-    remove_from_active_queue(ne);
-    ++keep_alive_queue_size;
-  }
-  keep_alive_queue.enqueue(ne);
-
-  // if keep-alive queue is over size then close connections
-  manage_keep_alive_queue();
-}
-
-void
-NetHandler::remove_from_keep_alive_queue(NetEvent *ne)
-{
-  Debug("net_queue", "NetEvent: %p", ne);
-  ink_assert(mutex->thread_holding == this_ethread());
-
-  if (keep_alive_queue.in(ne)) {
-    keep_alive_queue.remove(ne);
-    --keep_alive_queue_size;
-  }
-}
-
-bool
-NetHandler::add_to_active_queue(NetEvent *ne)
-{
-  Debug("net_queue", "NetEvent: %p", ne);
-  Debug("net_queue", "max_connections_per_thread_in: %d active_queue_size: %d keep_alive_queue_size: %d",
-        max_connections_per_thread_in, active_queue_size, keep_alive_queue_size);
-  ink_assert(mutex->thread_holding == this_ethread());
-
-  bool active_queue_full = false;
-
-  // if active queue is over size then close inactive connections
-  if (manage_active_queue(ne) == false) {
-    active_queue_full = true;
-  }
-
-  if (active_queue.in(ne)) {
-    // already in the active queue, move the head
-    active_queue.remove(ne);
-  } else {
-    if (active_queue_full) {
-      // there is no room left in the queue
-      NET_SUM_DYN_STAT(net_requests_max_throttled_in_stat, 1);
-      return false;
-    }
-    // in the keep-alive queue or no queue, new to this queue
-    remove_from_keep_alive_queue(ne);
-    ++active_queue_size;
-  }
-  active_queue.enqueue(ne);
-
-  return true;
-}
-
-void
-NetHandler::remove_from_active_queue(NetEvent *ne)
-{
-  Debug("net_queue", "NetEvent: %p", ne);
-  ink_assert(mutex->thread_holding == this_ethread());
-
-  if (active_queue.in(ne)) {
-    active_queue.remove(ne);
-    --active_queue_size;
-  }
-}
diff --git a/iocore/net/UnixNetProcessor.cc b/iocore/net/UnixNetProcessor.cc
index 1f96c993ed..511274d8ce 100644
--- a/iocore/net/UnixNetProcessor.cc
+++ b/iocore/net/UnixNetProcessor.cc
@@ -49,29 +49,6 @@ net_next_connection_number()
 
 NetProcessor::AcceptOptions const NetProcessor::DEFAULT_ACCEPT_OPTIONS;
 
-NetProcessor::AcceptOptions &
-NetProcessor::AcceptOptions::reset()
-{
-  local_port = 0;
-  local_ip.invalidate();
-  accept_threads        = -1;
-  ip_family             = AF_INET;
-  etype                 = ET_NET;
-  localhost_only        = false;
-  frequent_accept       = true;
-  recv_bufsize          = 0;
-  send_bufsize          = 0;
-  sockopt_flags         = 0;
-  packet_mark           = 0;
-  packet_tos            = 0;
-  packet_notsent_lowat  = 0;
-  tfo_queue_length      = 0;
-  f_inbound_transparent = false;
-  f_mptcp               = false;
-  f_proxy_protocol      = false;
-  return *this;
-}
-
 Action *
 UnixNetProcessor::accept(Continuation *cont, AcceptOptions const &opt)
 {
diff --git a/iocore/net/UnixUDPNet.cc b/iocore/net/UnixUDPNet.cc
index 9f4e579945..290586d54c 100644
--- a/iocore/net/UnixUDPNet.cc
+++ b/iocore/net/UnixUDPNet.cc
@@ -34,6 +34,7 @@
 #define __APPLE_USE_RFC_3542
 #endif
 
+#include "P_DNSConnection.h"
 #include "P_Net.h"
 #include "P_UDPNet.h"
 
diff --git a/iocore/net/test_I_UDPNet.cc b/iocore/net/test_I_UDPNet.cc
index f752f0e34f..b05eb7b65d 100644
--- a/iocore/net/test_I_UDPNet.cc
+++ b/iocore/net/test_I_UDPNet.cc
@@ -34,6 +34,7 @@
 #include "I_UDPNet.h"
 #include "I_UDPPacket.h"
 #include "I_UDPConnection.h"
+#include "P_UDPConnection.h"
 
 #include "diags.i"
 
diff --git a/proxy/http/HttpDebugNames.cc b/proxy/http/HttpDebugNames.cc
index c1c5db04d7..c8bf7f4596 100644
--- a/proxy/http/HttpDebugNames.cc
+++ b/proxy/http/HttpDebugNames.cc
@@ -21,6 +21,7 @@
   limitations under the License.
  */
 
+#include "I_DNSProcessor.h"
 #include "HttpDebugNames.h"
 #include "P_EventSystem.h"
 #include "StatPages.h"
diff --git a/src/traffic_server/InkIOCoreAPI.cc b/src/traffic_server/InkIOCoreAPI.cc
index 88e0012b39..f73c9749c9 100644
--- a/src/traffic_server/InkIOCoreAPI.cc
+++ b/src/traffic_server/InkIOCoreAPI.cc
@@ -34,6 +34,7 @@
 #include "I_Net.h"
 #include "I_Cache.h"
 #include "I_HostDB.h"
+#include "P_UnixUDPConnection.h"
 
 // This assert is for internal API use only.
 #if TS_USE_FAST_SDK