From bca5a001363ad9149d63f3975ca12a82adbe3b74 Mon Sep 17 00:00:00 2001
From: klzgrad <kizdiv@gmail.com>
Date: Sat, 20 Jan 2018 12:15:45 -0500
Subject: [PATCH] Add Naive

---
 src/net/BUILD.gn                              |  40 ++
 src/net/log/net_log_event_type_list.h         |   5 +
 .../tools/naive/http_proxy_server_socket.cc   | 481 +++++++++++++
 .../tools/naive/http_proxy_server_socket.h    | 135 ++++
 src/net/tools/naive/naive_command_line.cc     |  74 ++
 src/net/tools/naive/naive_command_line.h      |  36 +
 src/net/tools/naive/naive_config.cc           | 272 +++++++
 src/net/tools/naive/naive_config.h            |  72 ++
 src/net/tools/naive/naive_connection.cc       | 478 +++++++++++++
 src/net/tools/naive/naive_connection.h        | 134 ++++
 src/net/tools/naive/naive_padding_framer.cc   | 123 ++++
 src/net/tools/naive/naive_padding_framer.h    |  76 ++
 src/net/tools/naive/naive_padding_socket.cc   | 271 +++++++
 src/net/tools/naive/naive_padding_socket.h    | 105 +++
 src/net/tools/naive/naive_protocol.cc         |  56 ++
 src/net/tools/naive/naive_protocol.h          |  64 ++
 src/net/tools/naive/naive_proxy.cc            | 204 ++++++
 src/net/tools/naive/naive_proxy.h             |  90 +++
 src/net/tools/naive/naive_proxy_bin.cc        | 506 +++++++++++++
 src/net/tools/naive/naive_proxy_delegate.cc   | 207 ++++++
 src/net/tools/naive/naive_proxy_delegate.h    | 101 +++
 src/net/tools/naive/redirect_resolver.cc      | 243 +++++++
 src/net/tools/naive/redirect_resolver.h       |  69 ++
 src/net/tools/naive/socks5_server_socket.cc   | 664 ++++++++++++++++++
 src/net/tools/naive/socks5_server_socket.h    | 165 +++++
 25 files changed, 4671 insertions(+)
 create mode 100644 src/net/tools/naive/http_proxy_server_socket.cc
 create mode 100644 src/net/tools/naive/http_proxy_server_socket.h
 create mode 100644 src/net/tools/naive/naive_command_line.cc
 create mode 100644 src/net/tools/naive/naive_command_line.h
 create mode 100644 src/net/tools/naive/naive_config.cc
 create mode 100644 src/net/tools/naive/naive_config.h
 create mode 100644 src/net/tools/naive/naive_connection.cc
 create mode 100644 src/net/tools/naive/naive_connection.h
 create mode 100644 src/net/tools/naive/naive_padding_framer.cc
 create mode 100644 src/net/tools/naive/naive_padding_framer.h
 create mode 100644 src/net/tools/naive/naive_padding_socket.cc
 create mode 100644 src/net/tools/naive/naive_padding_socket.h
 create mode 100644 src/net/tools/naive/naive_protocol.cc
 create mode 100644 src/net/tools/naive/naive_protocol.h
 create mode 100644 src/net/tools/naive/naive_proxy.cc
 create mode 100644 src/net/tools/naive/naive_proxy.h
 create mode 100644 src/net/tools/naive/naive_proxy_bin.cc
 create mode 100644 src/net/tools/naive/naive_proxy_delegate.cc
 create mode 100644 src/net/tools/naive/naive_proxy_delegate.h
 create mode 100644 src/net/tools/naive/redirect_resolver.cc
 create mode 100644 src/net/tools/naive/redirect_resolver.h
 create mode 100644 src/net/tools/naive/socks5_server_socket.cc
 create mode 100644 src/net/tools/naive/socks5_server_socket.h

diff --git a/src/net/BUILD.gn b/src/net/BUILD.gn
index 5b6aa04698..7db51a6172 100644
--- a/src/net/BUILD.gn
+++ b/src/net/BUILD.gn
@@ -1837,3 +1837,43 @@ static_library("preload_decoder") {
   ]
   deps = [ "//base" ]
 }
+
+executable("naive") {
+  sources = [
+    "tools/naive/http_proxy_server_socket.cc",
+    "tools/naive/http_proxy_server_socket.h",
+    "tools/naive/naive_command_line.cc",
+    "tools/naive/naive_command_line.h",
+    "tools/naive/naive_config.cc",
+    "tools/naive/naive_config.h",
+    "tools/naive/naive_connection.cc",
+    "tools/naive/naive_connection.h",
+    "tools/naive/naive_padding_framer.cc",
+    "tools/naive/naive_padding_framer.h",
+    "tools/naive/naive_padding_socket.cc",
+    "tools/naive/naive_padding_socket.h",
+    "tools/naive/naive_protocol.cc",
+    "tools/naive/naive_protocol.h",
+    "tools/naive/naive_proxy_bin.cc",
+    "tools/naive/naive_proxy_delegate.cc",
+    "tools/naive/naive_proxy_delegate.h",
+    "tools/naive/naive_proxy.cc",
+    "tools/naive/naive_proxy.h",
+    "tools/naive/redirect_resolver.cc",
+    "tools/naive/redirect_resolver.h",
+    "tools/naive/socks5_server_socket.cc",
+    "tools/naive/socks5_server_socket.h",
+  ]
+
+  deps = [
+    ":net",
+    "//base",
+    "//build/win:default_exe_manifest",
+    "//components/version_info:version_info",
+    "//url",
+  ]
+
+  if (is_apple) {
+    deps += [ "//base/allocator:early_zone_registration_apple" ]
+  }
+}
diff --git a/src/net/log/net_log_event_type_list.h b/src/net/log/net_log_event_type_list.h
index c96b36a502..e19b5d134e 100644
--- a/src/net/log/net_log_event_type_list.h
+++ b/src/net/log/net_log_event_type_list.h
@@ -558,6 +558,11 @@ EVENT_TYPE(SOCKS_HOSTNAME_TOO_BIG)
 EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING)
 EVENT_TYPE(SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE)
 
+EVENT_TYPE(SOCKS_NO_REQUESTED_AUTH)
+EVENT_TYPE(SOCKS_NO_ACCEPTABLE_AUTH)
+EVENT_TYPE(SOCKS_ZERO_LENGTH_DOMAIN)
+EVENT_TYPE(SOCKS_UNEXPECTED_COMMAND)
+
 // This event indicates that a bad version number was received in the
 // proxy server's response. The extra parameters show its value:
 //   {
diff --git a/src/net/tools/naive/http_proxy_server_socket.cc b/src/net/tools/naive/http_proxy_server_socket.cc
new file mode 100644
index 0000000000..f3a8a1a732
--- /dev/null
+++ b/src/net/tools/naive/http_proxy_server_socket.cc
@@ -0,0 +1,481 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "net/tools/naive/http_proxy_server_socket.h"
+
+#include <algorithm>
+#include <cstring>
+#include <utility>
+#include <vector>
+
+#include "base/base64.h"
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_split.h"
+#include "base/sys_byteorder.h"
+#include "net/base/ip_address.h"
+#include "net/base/net_errors.h"
+#include "net/base/url_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/log/net_log.h"
+#include "net/third_party/quiche/src/quiche/http2/hpack/hpack_constants.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "net/tools/naive/naive_proxy_delegate.h"
+#include "url/gurl.h"
+
+namespace net {
+
+namespace {
+constexpr int kBufferSize = 64 * 1024;
+constexpr size_t kMaxHeaderSize = 64 * 1024;
+constexpr char kResponseHeader[] = "HTTP/1.1 200 OK\r\nPadding: ";
+constexpr int kResponseHeaderSize = sizeof(kResponseHeader) - 1;
+// A plain 200 is 10 bytes. Expected 48 bytes. "Padding" uses up 7 bytes.
+constexpr int kMinPaddingSize = 30;
+constexpr int kMaxPaddingSize = kMinPaddingSize + 32;
+}  // namespace
+
+HttpProxyServerSocket::HttpProxyServerSocket(
+    std::unique_ptr<StreamSocket> transport_socket,
+    const std::string& user,
+    const std::string& pass,
+    ClientPaddingDetectorDelegate* padding_detector_delegate,
+    const NetworkTrafficAnnotationTag& traffic_annotation,
+    const std::vector<PaddingType>& supported_padding_types)
+    : io_callback_(base::BindRepeating(&HttpProxyServerSocket::OnIOComplete,
+                                       base::Unretained(this))),
+      transport_(std::move(transport_socket)),
+      padding_detector_delegate_(padding_detector_delegate),
+      next_state_(STATE_NONE),
+      completed_handshake_(false),
+      was_ever_used_(false),
+      header_write_size_(-1),
+      net_log_(transport_->NetLog()),
+      traffic_annotation_(traffic_annotation),
+      supported_padding_types_(supported_padding_types) {
+  if (!user.empty() || !pass.empty()) {
+    basic_auth_ =
+        std::string("Basic ").append(base::Base64Encode(user + ":" + pass));
+  }
+}
+
+HttpProxyServerSocket::~HttpProxyServerSocket() {
+  Disconnect();
+}
+
+const HostPortPair& HttpProxyServerSocket::request_endpoint() const {
+  return request_endpoint_;
+}
+
+int HttpProxyServerSocket::Connect(CompletionOnceCallback callback) {
+  DCHECK(transport_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+
+  // If already connected, then just return OK.
+  if (completed_handshake_)
+    return OK;
+
+  next_state_ = STATE_HEADER_READ;
+  buffer_.clear();
+
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING) {
+    user_callback_ = std::move(callback);
+  }
+  return rv;
+}
+
+void HttpProxyServerSocket::Disconnect() {
+  completed_handshake_ = false;
+  transport_->Disconnect();
+
+  // Reset other states to make sure they aren't mistakenly used later.
+  // These are the states initialized by Connect().
+  next_state_ = STATE_NONE;
+  user_callback_.Reset();
+}
+
+bool HttpProxyServerSocket::IsConnected() const {
+  return completed_handshake_ && transport_->IsConnected();
+}
+
+bool HttpProxyServerSocket::IsConnectedAndIdle() const {
+  return completed_handshake_ && transport_->IsConnectedAndIdle();
+}
+
+const NetLogWithSource& HttpProxyServerSocket::NetLog() const {
+  return net_log_;
+}
+
+bool HttpProxyServerSocket::WasEverUsed() const {
+  return was_ever_used_;
+}
+
+NextProto HttpProxyServerSocket::GetNegotiatedProtocol() const {
+  if (transport_) {
+    return transport_->GetNegotiatedProtocol();
+  }
+  NOTREACHED();
+  return kProtoUnknown;
+}
+
+bool HttpProxyServerSocket::GetSSLInfo(SSLInfo* ssl_info) {
+  if (transport_) {
+    return transport_->GetSSLInfo(ssl_info);
+  }
+  NOTREACHED();
+  return false;
+}
+
+int64_t HttpProxyServerSocket::GetTotalReceivedBytes() const {
+  return transport_->GetTotalReceivedBytes();
+}
+
+void HttpProxyServerSocket::ApplySocketTag(const SocketTag& tag) {
+  return transport_->ApplySocketTag(tag);
+}
+
+// Read is called by the transport layer above to read. This can only be done
+// if the HTTP header is complete.
+int HttpProxyServerSocket::Read(IOBuffer* buf,
+                                int buf_len,
+                                CompletionOnceCallback callback) {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+  DCHECK(callback);
+
+  if (!buffer_.empty()) {
+    was_ever_used_ = true;
+    int data_len = buffer_.size();
+    if (data_len <= buf_len) {
+      std::memcpy(buf->data(), buffer_.data(), data_len);
+      buffer_.clear();
+      return data_len;
+    } else {
+      std::memcpy(buf->data(), buffer_.data(), buf_len);
+      buffer_ = buffer_.substr(buf_len);
+      return buf_len;
+    }
+  }
+
+  int rv = transport_->Read(
+      buf, buf_len,
+      base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete,
+                     base::Unretained(this), std::move(callback)));
+  if (rv > 0)
+    was_ever_used_ = true;
+  return rv;
+}
+
+// Write is called by the transport layer. This can only be done if the
+// HTTP CONNECT request is complete.
+int HttpProxyServerSocket::Write(
+    IOBuffer* buf,
+    int buf_len,
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+  DCHECK(callback);
+
+  int rv = transport_->Write(
+      buf, buf_len,
+      base::BindOnce(&HttpProxyServerSocket::OnReadWriteComplete,
+                     base::Unretained(this), std::move(callback)),
+      traffic_annotation);
+  if (rv > 0)
+    was_ever_used_ = true;
+  return rv;
+}
+
+int HttpProxyServerSocket::SetReceiveBufferSize(int32_t size) {
+  return transport_->SetReceiveBufferSize(size);
+}
+
+int HttpProxyServerSocket::SetSendBufferSize(int32_t size) {
+  return transport_->SetSendBufferSize(size);
+}
+
+void HttpProxyServerSocket::DoCallback(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(user_callback_);
+
+  // Since Run() may result in Read being called,
+  // clear user_callback_ up front.
+  std::move(user_callback_).Run(result);
+}
+
+void HttpProxyServerSocket::OnIOComplete(int result) {
+  DCHECK_NE(STATE_NONE, next_state_);
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    DoCallback(rv);
+  }
+}
+
+void HttpProxyServerSocket::OnReadWriteComplete(CompletionOnceCallback callback,
+                                                int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(callback);
+
+  if (result > 0)
+    was_ever_used_ = true;
+  std::move(callback).Run(result);
+}
+
+int HttpProxyServerSocket::DoLoop(int last_io_result) {
+  DCHECK_NE(next_state_, STATE_NONE);
+  int rv = last_io_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_HEADER_READ:
+        DCHECK_EQ(OK, rv);
+        rv = DoHeaderRead();
+        break;
+      case STATE_HEADER_READ_COMPLETE:
+        rv = DoHeaderReadComplete(rv);
+        break;
+      case STATE_HEADER_WRITE:
+        DCHECK_EQ(OK, rv);
+        rv = DoHeaderWrite();
+        break;
+      case STATE_HEADER_WRITE_COMPLETE:
+        rv = DoHeaderWriteComplete(rv);
+        break;
+      default:
+        NOTREACHED() << "bad state";
+        rv = ERR_UNEXPECTED;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+  return rv;
+}
+
+int HttpProxyServerSocket::DoHeaderRead() {
+  next_state_ = STATE_HEADER_READ_COMPLETE;
+
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
+  return transport_->Read(handshake_buf_.get(), kBufferSize, io_callback_);
+}
+
+std::optional<PaddingType> HttpProxyServerSocket::ParsePaddingHeaders(
+    const HttpRequestHeaders& headers) {
+  bool has_padding = headers.HasHeader(kPaddingHeader);
+  std::optional<std::string> padding_type_request =
+      headers.GetHeader(kPaddingTypeRequestHeader);
+
+  if (!padding_type_request.has_value()) {
+    // Backward compatibility with before kVariant1 when the padding-version
+    // header does not exist.
+    if (has_padding) {
+      return PaddingType::kVariant1;
+    } else {
+      return PaddingType::kNone;
+    }
+  }
+
+  std::vector<std::string_view> padding_type_strs = base::SplitStringPiece(
+      *padding_type_request, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  for (std::string_view padding_type_str : padding_type_strs) {
+    std::optional<PaddingType> padding_type =
+        ParsePaddingType(padding_type_str);
+    if (!padding_type.has_value()) {
+      LOG(ERROR) << "Invalid padding type: " << padding_type_str;
+      return std::nullopt;
+    }
+    if (std::find(supported_padding_types_.begin(),
+                  supported_padding_types_.end(),
+                  *padding_type) != supported_padding_types_.end()) {
+      return padding_type;
+    }
+  }
+  LOG(ERROR) << "No padding type is supported: " << *padding_type_request;
+  return std::nullopt;
+}
+
+int HttpProxyServerSocket::DoHeaderReadComplete(int result) {
+  if (result < 0)
+    return result;
+
+  if (result == 0) {
+    return ERR_CONNECTION_CLOSED;
+  }
+
+  buffer_.append(handshake_buf_->data(), result);
+  if (buffer_.size() > kMaxHeaderSize) {
+    return ERR_MSG_TOO_BIG;
+  }
+
+  size_t header_end = buffer_.find("\r\n\r\n");
+  if (header_end == std::string::npos) {
+    next_state_ = STATE_HEADER_READ;
+    return OK;
+  }
+
+  size_t first_line_end = buffer_.find("\r\n");
+  size_t first_space = buffer_.find(' ');
+  bool is_http_1_0 = false;
+  if (first_space == std::string::npos || first_space + 1 >= first_line_end) {
+    LOG(WARNING) << "Invalid request: " << buffer_.substr(0, first_line_end);
+    return ERR_INVALID_ARGUMENT;
+  }
+  size_t second_space = buffer_.find(' ', first_space + 1);
+  if (second_space == std::string::npos || second_space >= first_line_end) {
+    LOG(WARNING) << "Invalid request: " << buffer_.substr(0, first_line_end);
+    return ERR_INVALID_ARGUMENT;
+  }
+
+  std::string method = buffer_.substr(0, first_space);
+  std::string uri =
+      buffer_.substr(first_space + 1, second_space - (first_space + 1));
+  std::string version =
+      buffer_.substr(second_space + 1, first_line_end - (second_space + 1));
+  if (method == HttpRequestHeaders::kConnectMethod) {
+    request_endpoint_ = HostPortPair::FromString(uri);
+  } else {
+    // postprobe endpoint handling
+    is_http_1_0 = true;
+  }
+
+  size_t second_line = first_line_end + 2;
+  HttpRequestHeaders headers;
+  std::string headers_str;
+  if (second_line < header_end) {
+    headers_str = buffer_.substr(second_line, header_end - second_line);
+    headers.AddHeadersFromString(headers_str);
+  }
+
+  if (!basic_auth_.empty()) {
+    std::optional<std::string> proxy_auth;
+    proxy_auth = headers.GetHeader(HttpRequestHeaders::kProxyAuthorization);
+    if (proxy_auth != basic_auth_) {
+      LOG(WARNING) << "Invalid Proxy-Authorization: " << proxy_auth.value_or("");
+      return ERR_INVALID_ARGUMENT;
+    }
+  }
+
+  if (is_http_1_0) {
+    GURL url(uri);
+    if (!url.is_valid()) {
+      LOG(WARNING) << "Invalid URI: " << uri;
+      return ERR_INVALID_ARGUMENT;
+    }
+
+    std::string host;
+    int port;
+
+    std::optional<std::string> host_str = headers.GetHeader(HttpRequestHeaders::kHost);
+    if (host_str.has_value()) {
+      if (!ParseHostAndPort(*host_str, &host, &port)) {
+        LOG(WARNING) << "Invalid Host: " << *host_str;
+        return ERR_INVALID_ARGUMENT;
+      }
+      if (port == -1) {
+        port = 80;
+      }
+    } else {
+      if (!url.has_host()) {
+        LOG(WARNING) << "Missing host: " << uri;
+        return ERR_INVALID_ARGUMENT;
+      }
+      host = url.host();
+      port = url.EffectiveIntPort();
+
+      *host_str = url.host();
+      if (url.has_port()) {
+        host_str->append(":").append(url.port());
+      }
+      headers.SetHeader(HttpRequestHeaders::kHost, *host_str);
+    }
+    // Host is already known. Converts any absolute URI to relative.
+    uri = url.path();
+    if (url.has_query()) {
+      uri.append("?").append(url.query());
+    }
+
+    request_endpoint_.set_host(host);
+    request_endpoint_.set_port(port);
+  }
+
+  std::optional<PaddingType> padding_type = ParsePaddingHeaders(headers);
+  if (!padding_type.has_value()) {
+    return ERR_INVALID_ARGUMENT;
+  }
+  padding_detector_delegate_->SetClientPaddingType(*padding_type);
+
+  if (is_http_1_0) {
+    // Regenerates http header to make sure don't leak them to end servers
+    HttpRequestHeaders sanitized_headers = headers;
+    sanitized_headers.RemoveHeader(HttpRequestHeaders::kProxyConnection);
+    sanitized_headers.RemoveHeader(HttpRequestHeaders::kProxyAuthorization);
+    std::ostringstream ss;
+    ss << method << " " << uri << " " << version << "\r\n"
+       << sanitized_headers.ToString();
+    if (buffer_.size() > header_end + 4) {
+      ss << buffer_.substr(header_end + 4);
+    }
+    buffer_ = ss.str();
+    // Skips padding write for raw http proxy
+    completed_handshake_ = true;
+    next_state_ = STATE_NONE;
+    return OK;
+  }
+
+  buffer_ = buffer_.substr(header_end + 4);
+
+  next_state_ = STATE_HEADER_WRITE;
+  return OK;
+}
+
+int HttpProxyServerSocket::DoHeaderWrite() {
+  next_state_ = STATE_HEADER_WRITE_COMPLETE;
+
+  // Adds padding.
+  int padding_size = base::RandInt(kMinPaddingSize, kMaxPaddingSize);
+  header_write_size_ = kResponseHeaderSize + padding_size + 4;
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(header_write_size_);
+  char* p = handshake_buf_->data();
+  std::memcpy(p, kResponseHeader, kResponseHeaderSize);
+  FillNonindexHeaderValue(base::RandUint64(), p + kResponseHeaderSize,
+                          padding_size);
+  std::memcpy(p + kResponseHeaderSize + padding_size, "\r\n\r\n", 4);
+
+  return transport_->Write(handshake_buf_.get(), header_write_size_,
+                           io_callback_, traffic_annotation_);
+}
+
+int HttpProxyServerSocket::DoHeaderWriteComplete(int result) {
+  if (result < 0)
+    return result;
+
+  if (result != header_write_size_) {
+    return ERR_FAILED;
+  }
+
+  completed_handshake_ = true;
+  next_state_ = STATE_NONE;
+  return OK;
+}
+
+int HttpProxyServerSocket::GetPeerAddress(IPEndPoint* address) const {
+  return transport_->GetPeerAddress(address);
+}
+
+int HttpProxyServerSocket::GetLocalAddress(IPEndPoint* address) const {
+  return transport_->GetLocalAddress(address);
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/http_proxy_server_socket.h b/src/net/tools/naive/http_proxy_server_socket.h
new file mode 100644
index 0000000000..81a735abc8
--- /dev/null
+++ b/src/net/tools/naive/http_proxy_server_socket.h
@@ -0,0 +1,135 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_
+#define NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/scoped_refptr.h"
+#include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/http/http_request_headers.h"
+#include "net/log/net_log_with_source.h"
+#include "net/socket/connection_attempts.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/ssl_info.h"
+#include "net/tools/naive/naive_protocol.h"
+
+namespace net {
+struct NetworkTrafficAnnotationTag;
+class ClientPaddingDetectorDelegate;
+
+// This StreamSocket is used to setup a HTTP CONNECT tunnel.
+class HttpProxyServerSocket : public StreamSocket {
+ public:
+  HttpProxyServerSocket(
+      std::unique_ptr<StreamSocket> transport_socket,
+      const std::string& user,
+      const std::string& pass,
+      ClientPaddingDetectorDelegate* padding_detector_delegate,
+      const NetworkTrafficAnnotationTag& traffic_annotation,
+      const std::vector<PaddingType>& supported_padding_types);
+  HttpProxyServerSocket(const HttpProxyServerSocket&) = delete;
+  HttpProxyServerSocket& operator=(const HttpProxyServerSocket&) = delete;
+
+  // On destruction Disconnect() is called.
+  ~HttpProxyServerSocket() override;
+
+  const HostPortPair& request_endpoint() const;
+
+  // StreamSocket implementation.
+
+  int Connect(CompletionOnceCallback callback) override;
+  void Disconnect() override;
+  bool IsConnected() const override;
+  bool IsConnectedAndIdle() const override;
+  const NetLogWithSource& NetLog() const override;
+  bool WasEverUsed() const override;
+  NextProto GetNegotiatedProtocol() const override;
+  bool GetSSLInfo(SSLInfo* ssl_info) override;
+  int64_t GetTotalReceivedBytes() const override;
+  void ApplySocketTag(const SocketTag& tag) override;
+
+  // Socket implementation.
+  int Read(IOBuffer* buf,
+           int buf_len,
+           CompletionOnceCallback callback) override;
+  int Write(IOBuffer* buf,
+            int buf_len,
+            CompletionOnceCallback callback,
+            const NetworkTrafficAnnotationTag& traffic_annotation) override;
+
+  int SetReceiveBufferSize(int32_t size) override;
+  int SetSendBufferSize(int32_t size) override;
+
+  int GetPeerAddress(IPEndPoint* address) const override;
+  int GetLocalAddress(IPEndPoint* address) const override;
+
+ private:
+  enum State {
+    STATE_HEADER_READ,
+    STATE_HEADER_READ_COMPLETE,
+    STATE_HEADER_WRITE,
+    STATE_HEADER_WRITE_COMPLETE,
+    STATE_NONE,
+  };
+
+  void DoCallback(int result);
+  void OnIOComplete(int result);
+  void OnReadWriteComplete(CompletionOnceCallback callback, int result);
+
+  int DoLoop(int last_io_result);
+  int DoHeaderWrite();
+  int DoHeaderWriteComplete(int result);
+  int DoHeaderRead();
+  int DoHeaderReadComplete(int result);
+
+  std::optional<PaddingType> ParsePaddingHeaders(
+      const HttpRequestHeaders& headers);
+
+  CompletionRepeatingCallback io_callback_;
+
+  // Stores the underlying socket.
+  std::unique_ptr<StreamSocket> transport_;
+  ClientPaddingDetectorDelegate* padding_detector_delegate_;
+
+  State next_state_;
+
+  // Stores the callback to the layer above, called on completing Connect().
+  CompletionOnceCallback user_callback_;
+
+  // This IOBuffer is used by the class to read and write
+  // SOCKS handshake data. The length contains the expected size to
+  // read or write.
+  scoped_refptr<IOBuffer> handshake_buf_;
+
+  std::string buffer_;
+  bool completed_handshake_;
+  bool was_ever_used_;
+  int header_write_size_;
+
+  std::string basic_auth_;
+
+  HostPortPair request_endpoint_;
+
+  NetLogWithSource net_log_;
+
+  // Traffic annotation for socket control.
+  const NetworkTrafficAnnotationTag& traffic_annotation_;
+
+  std::vector<PaddingType> supported_padding_types_;
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_HTTP_PROXY_SERVER_SOCKET_H_
diff --git a/src/net/tools/naive/naive_command_line.cc b/src/net/tools/naive/naive_command_line.cc
new file mode 100644
index 0000000000..28c3c9c0a1
--- /dev/null
+++ b/src/net/tools/naive/naive_command_line.cc
@@ -0,0 +1,74 @@
+// Copyright 2024 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "net/tools/naive/naive_command_line.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+
+#if BUILDFLAG(IS_WIN)
+#include "base/strings/string_util_win.h"
+#endif
+
+DuplicateSwitchCollector::DuplicateSwitchCollector() = default;
+DuplicateSwitchCollector::~DuplicateSwitchCollector() = default;
+
+void DuplicateSwitchCollector::ResolveDuplicate(
+    std::string_view key,
+    base::CommandLine::StringViewType new_value,
+    base::CommandLine::StringType& out_value) {
+  out_value = new_value;
+  values_by_key_[std::string(key)].push_back(
+      base::CommandLine::StringType(new_value));
+}
+
+const std::vector<base::CommandLine::StringType>&
+DuplicateSwitchCollector::GetValuesByKey(std::string_view key) {
+  return values_by_key_[std::string(key)];
+}
+
+namespace {
+DuplicateSwitchCollector* g_duplicate_switch_collector;
+}
+
+void DuplicateSwitchCollector::InitInstance() {
+  auto new_duplicate_switch_collector =
+      std::make_unique<DuplicateSwitchCollector>();
+  g_duplicate_switch_collector = new_duplicate_switch_collector.get();
+  base::CommandLine::SetDuplicateSwitchHandler(
+      std::move(new_duplicate_switch_collector));
+}
+
+DuplicateSwitchCollector& DuplicateSwitchCollector::GetInstance() {
+  CHECK(g_duplicate_switch_collector != nullptr);
+  return *g_duplicate_switch_collector;
+}
+
+base::Value::Dict GetSwitchesAsValue(const base::CommandLine& cmdline) {
+  base::Value::Dict dict;
+  for (const auto& [key, value] : cmdline.GetSwitches()) {
+    const std::vector<base::CommandLine::StringType>& values =
+        DuplicateSwitchCollector::GetInstance().GetValuesByKey(key);
+    if (values.size() > 1) {
+      base::Value::List list;
+      for (const base::CommandLine::StringType& v : values) {
+#if BUILDFLAG(IS_WIN)
+        list.Append(base::AsStringPiece16(v));
+#else
+        list.Append(v);
+#endif
+      }
+      dict.Set(key, std::move(list));
+    } else {
+#if BUILDFLAG(IS_WIN)
+      dict.Set(key, base::AsStringPiece16(value));
+#else
+      dict.Set(key, value);
+#endif
+    }
+  }
+  return dict;
+}
diff --git a/src/net/tools/naive/naive_command_line.h b/src/net/tools/naive/naive_command_line.h
new file mode 100644
index 0000000000..4f749882db
--- /dev/null
+++ b/src/net/tools/naive/naive_command_line.h
@@ -0,0 +1,36 @@
+// Copyright 2024 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_COMMAND_LINE_H_
+#define NET_TOOLS_NAIVE_NAIVE_COMMAND_LINE_H_
+
+#include <map>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "base/command_line.h"
+#include "base/values.h"
+
+class DuplicateSwitchCollector : public base::DuplicateSwitchHandler {
+ public:
+  DuplicateSwitchCollector();
+  ~DuplicateSwitchCollector() override;
+
+  void ResolveDuplicate(std::string_view key,
+                        base::CommandLine::StringViewType new_value,
+                        base::CommandLine::StringType& out_value) override;
+
+  const std::vector<base::CommandLine::StringType>& GetValuesByKey(
+      std::string_view key);
+
+  static void InitInstance();
+  static DuplicateSwitchCollector& GetInstance();
+
+ private:
+  std::map<std::string, std::vector<base::CommandLine::StringType>>
+      values_by_key_;
+};
+
+base::Value::Dict GetSwitchesAsValue(const base::CommandLine& cmdline);
+#endif  // NET_TOOLS_NAIVE_NAIVE_COMMAND_LINE_H_
diff --git a/src/net/tools/naive/naive_config.cc b/src/net/tools/naive/naive_config.cc
new file mode 100644
index 0000000000..8d5b36aba4
--- /dev/null
+++ b/src/net/tools/naive/naive_config.cc
@@ -0,0 +1,272 @@
+// Copyright 2024 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "net/tools/naive/naive_config.h"
+
+#include <algorithm>
+#include <iostream>
+
+#include "base/strings/escape.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_tokenizer.h"
+#include "net/base/proxy_server.h"
+#include "net/base/proxy_string_util.h"
+#include "net/base/url_util.h"
+#include "url/gurl.h"
+
+namespace net {
+namespace {
+ProxyServer MyProxyUriToProxyServer(std::string_view uri) {
+  if (uri.compare(0, 7, "quic://") == 0) {
+    return ProxySchemeHostAndPortToProxyServer(ProxyServer::SCHEME_QUIC,
+                                               uri.substr(7));
+  }
+  return ProxyUriToProxyServer(uri, ProxyServer::SCHEME_INVALID);
+}
+}  // namespace
+
+NaiveListenConfig::NaiveListenConfig() = default;
+NaiveListenConfig::NaiveListenConfig(const NaiveListenConfig&) = default;
+NaiveListenConfig::~NaiveListenConfig() = default;
+
+bool NaiveListenConfig::Parse(const std::string& str) {
+  GURL url(str);
+  if (url.scheme() == "socks") {
+    protocol = ClientProtocol::kSocks5;
+  } else if (url.scheme() == "http") {
+    protocol = ClientProtocol::kHttp;
+  } else if (url.scheme() == "redir") {
+#if BUILDFLAG(IS_LINUX)
+    protocol = ClientProtocol::kRedir;
+#else
+    std::cerr << "Redir protocol only supports Linux." << std::endl;
+    return false;
+#endif
+  } else {
+    std::cerr << "Invalid scheme in " << str << std::endl;
+    return false;
+  }
+
+  if (!url.username().empty()) {
+    user = base::UnescapeBinaryURLComponent(url.username());
+  }
+  if (!url.password().empty()) {
+    pass = base::UnescapeBinaryURLComponent(url.password());
+  }
+
+  if (!url.host().empty()) {
+    addr = url.HostNoBrackets();
+  }
+
+  int effective_port = url.EffectiveIntPort();
+  if (effective_port == url::PORT_INVALID) {
+    std::cerr << "Invalid port in " << str << std::endl;
+    return false;
+  }
+  if (effective_port != url::PORT_UNSPECIFIED) {
+    port = effective_port;
+  }
+
+  return true;
+}
+
+NaiveConfig::NaiveConfig() = default;
+NaiveConfig::NaiveConfig(const NaiveConfig&) = default;
+NaiveConfig::~NaiveConfig() = default;
+
+bool NaiveConfig::Parse(const base::Value::Dict& value) {
+  if (const base::Value* v = value.Find("listen")) {
+    listen.clear();
+    if (const std::string* str = v->GetIfString()) {
+      if (!listen.emplace_back().Parse(*str)) {
+        return false;
+      }
+    } else if (const base::Value::List* strs = v->GetIfList()) {
+      for (const auto& str_e : *strs) {
+        if (const std::string* s = str_e.GetIfString()) {
+          if (!listen.emplace_back().Parse(*s)) {
+            return false;
+          }
+        } else {
+          std::cerr << "Invalid listen element" << std::endl;
+          return false;
+        }
+      }
+    } else {
+      std::cerr << "Invalid listen" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("insecure-concurrency")) {
+    if (std::optional<int> i = v->GetIfInt()) {
+      insecure_concurrency = *i;
+    } else if (const std::string* str = v->GetIfString()) {
+      if (!base::StringToInt(*str, &insecure_concurrency)) {
+        std::cerr << "Invalid concurrency" << std::endl;
+        return false;
+      }
+    } else {
+      std::cerr << "Invalid concurrency" << std::endl;
+      return false;
+    }
+    if (insecure_concurrency < 1) {
+      std::cerr << "Invalid concurrency" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("extra-headers")) {
+    if (const std::string* str = v->GetIfString()) {
+      extra_headers.AddHeadersFromString(*str);
+    } else {
+      std::cerr << "Invalid extra-headers" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("proxy")) {
+    if (const std::string* str = v->GetIfString(); str && !str->empty()) {
+      base::StringTokenizer proxy_uri_list(*str, ",");
+      std::vector<ProxyServer> proxy_servers;
+      bool seen_tcp = false;
+      while (proxy_uri_list.GetNext()) {
+        std::string token(proxy_uri_list.token());
+        GURL url(token);
+
+        std::u16string proxy_user;
+        std::u16string proxy_pass;
+        net::GetIdentityFromURL(url, &proxy_user, &proxy_pass);
+        GURL::Replacements remove_auth;
+        remove_auth.ClearUsername();
+        remove_auth.ClearPassword();
+        GURL url_no_auth = url.ReplaceComponents(remove_auth);
+        std::string proxy_uri = url_no_auth.GetWithEmptyPath().spec();
+        if (proxy_uri.back() == '/') {
+          proxy_uri.pop_back();
+        }
+
+        proxy_servers.emplace_back(MyProxyUriToProxyServer(proxy_uri));
+        const ProxyServer& last = proxy_servers.back();
+        if (last.is_quic()) {
+          if (seen_tcp) {
+            std::cerr << "QUIC proxy cannot follow TCP-based proxies"
+                      << std::endl;
+            return false;
+          }
+          origins_to_force_quic_on.insert(HostPortPair::FromURL(url));
+        } else if (last.is_https() || last.is_http() || last.is_socks()) {
+          seen_tcp = true;
+        } else {
+          std::cerr << "Invalid proxy scheme" << std::endl;
+          return false;
+        }
+
+        AuthCredentials auth(proxy_user, proxy_pass);
+        if (!auth.Empty()) {
+          if (last.is_socks()) {
+            std::cerr << "SOCKS proxy with auth is not supported" << std::endl;
+          } else {
+            std::string proxy_url(token);
+            if (proxy_url.compare(0, 7, "quic://") == 0) {
+              proxy_url.replace(0, 4, "https");
+            }
+            auth_store[url::SchemeHostPort{GURL{proxy_url}}] = auth;
+          }
+        }
+      }
+
+      if (proxy_servers.size() > 1 &&
+          std::any_of(proxy_servers.begin(), proxy_servers.end(),
+                      [](const ProxyServer& s) { return s.is_socks(); })) {
+        // See net/socket/connect_job_params_factory.cc
+        // DCHECK(proxy_server.is_socks());
+        // DCHECK_EQ(1u, proxy_chain.length());
+        std::cerr
+            << "Multi-proxy chain containing SOCKS proxies is not supported."
+            << std::endl;
+        return false;
+      }
+      if (std::any_of(proxy_servers.begin(), proxy_servers.end(),
+                      [](const ProxyServer& s) { return s.is_quic(); })) {
+        proxy_chain = ProxyChain::ForIpProtection(proxy_servers);
+      } else {
+        proxy_chain = ProxyChain(proxy_servers);
+      }
+
+      if (!proxy_chain.IsValid()) {
+        std::cerr << "Invalid proxy chain" << std::endl;
+        return false;
+      }
+    } else {
+      std::cerr << "Invalid proxy argument" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("host-resolver-rules")) {
+    if (const std::string* str = v->GetIfString()) {
+      host_resolver_rules = *str;
+    } else {
+      std::cerr << "Invalid host-resolver-rules" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("resolver-range")) {
+    if (const std::string* str = v->GetIfString(); str && !str->empty()) {
+      if (!net::ParseCIDRBlock(*str, &resolver_range, &resolver_prefix)) {
+        std::cerr << "Invalid resolver-range" << std::endl;
+        return false;
+      }
+      if (resolver_range.IsIPv6()) {
+        std::cerr << "IPv6 resolver range not supported" << std::endl;
+        return false;
+      }
+    } else {
+      std::cerr << "Invalid resolver-range" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("log")) {
+    if (const std::string* str = v->GetIfString()) {
+      if (!str->empty()) {
+        log.logging_dest = logging::LOG_TO_FILE;
+        log_file = base::FilePath::FromUTF8Unsafe(*str);
+        log.log_file_path = log_file.value().c_str();
+      } else {
+        log.logging_dest = logging::LOG_TO_STDERR;
+      }
+    } else {
+      std::cerr << "Invalid log" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("log-net-log")) {
+    if (const std::string* str = v->GetIfString(); str && !str->empty()) {
+      log_net_log = base::FilePath::FromUTF8Unsafe(*str);
+    } else {
+      std::cerr << "Invalid log-net-log" << std::endl;
+      return false;
+    }
+  }
+
+  if (const base::Value* v = value.Find("ssl-key-log-file")) {
+    if (const std::string* str = v->GetIfString(); str && !str->empty()) {
+      ssl_key_log_file = base::FilePath::FromUTF8Unsafe(*str);
+    } else {
+      std::cerr << "Invalid ssl-key-log-file" << std::endl;
+      return false;
+    }
+  }
+
+  if (value.contains("no-post-quantum")) {
+    no_post_quantum = true;
+  }
+
+  return true;
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_config.h b/src/net/tools/naive/naive_config.h
new file mode 100644
index 0000000000..186fca1b2f
--- /dev/null
+++ b/src/net/tools/naive/naive_config.h
@@ -0,0 +1,72 @@
+// Copyright 2024 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
+#define NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
+
+#include <map>
+#include <optional>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/values.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/ip_address.h"
+#include "net/base/proxy_chain.h"
+#include "net/http/http_request_headers.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "url/scheme_host_port.h"
+
+namespace net {
+
+struct NaiveListenConfig {
+  ClientProtocol protocol = ClientProtocol::kSocks5;
+  std::string user;
+  std::string pass;
+  std::string addr = "0.0.0.0";
+  int port = 1080;
+
+  NaiveListenConfig();
+  NaiveListenConfig(const NaiveListenConfig&);
+  ~NaiveListenConfig();
+  bool Parse(const std::string& str);
+};
+
+struct NaiveConfig {
+  std::vector<NaiveListenConfig> listen = {NaiveListenConfig()};
+
+  int insecure_concurrency = 1;
+
+  HttpRequestHeaders extra_headers;
+
+  // The last server is assumed to be Naive.
+  ProxyChain proxy_chain = ProxyChain::Direct();
+  std::set<HostPortPair> origins_to_force_quic_on;
+  std::map<url::SchemeHostPort, AuthCredentials> auth_store;
+
+  std::string host_resolver_rules;
+
+  IPAddress resolver_range = {100, 64, 0, 0};
+  size_t resolver_prefix = 10;
+
+  logging::LoggingSettings log = {.logging_dest = logging::LOG_NONE};
+  base::FilePath log_file;
+
+  base::FilePath log_net_log;
+
+  base::FilePath ssl_key_log_file;
+
+  std::optional<bool> no_post_quantum;
+
+  NaiveConfig();
+  NaiveConfig(const NaiveConfig&);
+  ~NaiveConfig();
+  bool Parse(const base::Value::Dict& value);
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_CONFIG_H_
diff --git a/src/net/tools/naive/naive_connection.cc b/src/net/tools/naive/naive_connection.cc
new file mode 100644
index 0000000000..1ab1a3941d
--- /dev/null
+++ b/src/net/tools/naive/naive_connection.cc
@@ -0,0 +1,478 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "net/tools/naive/naive_connection.h"
+
+#include <cstring>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/strcat.h"
+#include "base/task/single_thread_task_runner.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/privacy_mode.h"
+#include "net/base/url_util.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "net/socket/client_socket_handle.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/stream_socket.h"
+#include "net/spdy/spdy_session.h"
+#include "net/tools/naive/http_proxy_server_socket.h"
+#include "net/tools/naive/naive_padding_socket.h"
+#include "net/tools/naive/redirect_resolver.h"
+#include "net/tools/naive/socks5_server_socket.h"
+#include "url/scheme_host_port.h"
+
+#if BUILDFLAG(IS_LINUX)
+#include <linux/netfilter_ipv4.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include "net/base/ip_endpoint.h"
+#include "net/base/sockaddr_storage.h"
+#include "net/socket/tcp_client_socket.h"
+#endif
+
+namespace net {
+
+namespace {
+constexpr int kBufferSize = 64 * 1024;
+}  // namespace
+
+NaiveConnection::NaiveConnection(
+    unsigned int id,
+    ClientProtocol protocol,
+    std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
+    const ProxyInfo& proxy_info,
+    RedirectResolver* resolver,
+    HttpNetworkSession* session,
+    const NetworkAnonymizationKey& network_anonymization_key,
+    const NetLogWithSource& net_log,
+    std::unique_ptr<StreamSocket> accepted_socket,
+    const NetworkTrafficAnnotationTag& traffic_annotation)
+    : id_(id),
+      protocol_(protocol),
+      padding_detector_delegate_(std::move(padding_detector_delegate)),
+      proxy_info_(proxy_info),
+      resolver_(resolver),
+      session_(session),
+      network_anonymization_key_(network_anonymization_key),
+      net_log_(net_log),
+      next_state_(STATE_NONE),
+      client_socket_(std::move(accepted_socket)),
+      server_socket_handle_(std::make_unique<ClientSocketHandle>()),
+      sockets_{nullptr, nullptr},
+      errors_{OK, OK},
+      write_pending_{false, false},
+      early_pull_pending_(false),
+      can_push_to_server_(false),
+      early_pull_result_(ERR_IO_PENDING),
+      full_duplex_(false),
+      time_func_(&base::TimeTicks::Now),
+      traffic_annotation_(traffic_annotation) {
+  io_callback_ = base::BindRepeating(&NaiveConnection::OnIOComplete,
+                                     weak_ptr_factory_.GetWeakPtr());
+}
+
+NaiveConnection::~NaiveConnection() {
+  Disconnect();
+}
+
+int NaiveConnection::Connect(CompletionOnceCallback callback) {
+  DCHECK(client_socket_);
+  DCHECK_EQ(next_state_, STATE_NONE);
+  DCHECK(!connect_callback_);
+
+  if (full_duplex_)
+    return OK;
+
+  next_state_ = STATE_CONNECT_CLIENT;
+
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING) {
+    connect_callback_ = std::move(callback);
+  }
+  return rv;
+}
+
+void NaiveConnection::Disconnect() {
+  full_duplex_ = false;
+  // Closes server side first because latency is higher.
+  if (server_socket_handle_->socket())
+    server_socket_handle_->socket()->Disconnect();
+  client_socket_->Disconnect();
+
+  next_state_ = STATE_NONE;
+  connect_callback_.Reset();
+  run_callback_.Reset();
+}
+
+void NaiveConnection::DoCallback(int result) {
+  DCHECK_NE(result, ERR_IO_PENDING);
+  DCHECK(connect_callback_);
+
+  // Since Run() may result in Read being called,
+  // clear connect_callback_ up front.
+  std::move(connect_callback_).Run(result);
+}
+
+void NaiveConnection::OnIOComplete(int result) {
+  DCHECK_NE(next_state_, STATE_NONE);
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    DoCallback(rv);
+  }
+}
+
+int NaiveConnection::DoLoop(int last_io_result) {
+  DCHECK_NE(next_state_, STATE_NONE);
+  int rv = last_io_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_CONNECT_CLIENT:
+        DCHECK_EQ(rv, OK);
+        rv = DoConnectClient();
+        break;
+      case STATE_CONNECT_CLIENT_COMPLETE:
+        rv = DoConnectClientComplete(rv);
+        break;
+      case STATE_CONNECT_SERVER:
+        DCHECK_EQ(rv, OK);
+        rv = DoConnectServer();
+        break;
+      case STATE_CONNECT_SERVER_COMPLETE:
+        rv = DoConnectServerComplete(rv);
+        break;
+      default:
+        NOTREACHED() << "bad state";
+        rv = ERR_UNEXPECTED;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+  return rv;
+}
+
+int NaiveConnection::DoConnectClient() {
+  next_state_ = STATE_CONNECT_CLIENT_COMPLETE;
+
+  return client_socket_->Connect(io_callback_);
+}
+
+int NaiveConnection::DoConnectClientComplete(int result) {
+  if (result < 0)
+    return result;
+
+  std::optional<PaddingType> client_padding_type =
+      padding_detector_delegate_->GetClientPaddingType();
+  CHECK(client_padding_type.has_value());
+
+  sockets_[kClient] = std::make_unique<NaivePaddingSocket>(
+      client_socket_.get(), *client_padding_type, kClient);
+
+  // For proxy client sockets, padding support detection is finished after the
+  // first server response which means there will be one missed early pull. For
+  // proxy server sockets (HttpProxyServerSocket), padding support detection is
+  // done during client connect, so there shouldn't be any missed early pull.
+  if (!padding_detector_delegate_->GetServerPaddingType().has_value()) {
+    early_pull_pending_ = false;
+    early_pull_result_ = 0;
+    next_state_ = STATE_CONNECT_SERVER;
+    return OK;
+  }
+
+  early_pull_pending_ = true;
+  Pull(kClient, kServer);
+  if (early_pull_result_ != ERR_IO_PENDING) {
+    // Pull has completed synchronously.
+    if (early_pull_result_ <= 0) {
+      return early_pull_result_ ? early_pull_result_ : ERR_CONNECTION_CLOSED;
+    }
+  }
+
+  next_state_ = STATE_CONNECT_SERVER;
+  return OK;
+}
+
+int NaiveConnection::DoConnectServer() {
+  next_state_ = STATE_CONNECT_SERVER_COMPLETE;
+
+  HostPortPair origin;
+  if (protocol_ == ClientProtocol::kSocks5) {
+    const auto* socket =
+        static_cast<const Socks5ServerSocket*>(client_socket_.get());
+    origin = socket->request_endpoint();
+  } else if (protocol_ == ClientProtocol::kHttp) {
+    const auto* socket =
+        static_cast<const HttpProxyServerSocket*>(client_socket_.get());
+    origin = socket->request_endpoint();
+  } else if (protocol_ == ClientProtocol::kRedir) {
+#if BUILDFLAG(IS_LINUX)
+    const auto* socket =
+        static_cast<const TCPClientSocket*>(client_socket_.get());
+    IPEndPoint peer_endpoint;
+    int rv;
+    rv = socket->GetPeerAddress(&peer_endpoint);
+    if (rv != OK) {
+      LOG(ERROR) << "Connection " << id_
+                 << " cannot get peer address: " << ErrorToShortString(rv);
+      return rv;
+    }
+    int sd = socket->SocketDescriptorForTesting();
+    SockaddrStorage dst;
+    if (peer_endpoint.GetFamily() == ADDRESS_FAMILY_IPV4 ||
+        peer_endpoint.address().IsIPv4MappedIPv6()) {
+      rv = getsockopt(sd, SOL_IP, SO_ORIGINAL_DST, dst.addr, &dst.addr_len);
+    } else {
+      rv = getsockopt(sd, SOL_IPV6, SO_ORIGINAL_DST, dst.addr, &dst.addr_len);
+    }
+    if (rv == 0) {
+      IPEndPoint ipe;
+      if (ipe.FromSockAddr(dst.addr, dst.addr_len)) {
+        const auto& addr = ipe.address();
+        auto name = resolver_->FindNameByAddress(addr);
+        if (!name.empty()) {
+          origin = HostPortPair(name, ipe.port());
+        } else if (!resolver_->IsInResolvedRange(addr)) {
+          origin = HostPortPair::FromIPEndPoint(ipe);
+        } else {
+          LOG(ERROR) << "Connection " << id_ << " to unresolved name for "
+                     << addr.ToString();
+          return ERR_ADDRESS_INVALID;
+        }
+      }
+    } else {
+      LOG(ERROR) << "Failed to get original destination address";
+      return ERR_ADDRESS_INVALID;
+    }
+#else
+    static_cast<void>(resolver_);
+#endif
+  }
+
+  url::CanonHostInfo host_info;
+  url::SchemeHostPort endpoint(
+      "http", CanonicalizeHost(origin.HostForURL(), &host_info), origin.port(),
+      url::SchemeHostPort::ALREADY_CANONICALIZED);
+  if (!endpoint.IsValid()) {
+    LOG(ERROR) << "Connection " << id_ << " to invalid origin "
+               << origin.ToString();
+    return ERR_ADDRESS_INVALID;
+  }
+
+  LOG(INFO) << "Connection " << id_ << " to " << origin.ToString();
+
+  // Ignores socket limit set by socket pool for this type of socket.
+  return InitSocketHandleForHttpRequest(
+      std::move(endpoint), LOAD_IGNORE_LIMITS, MAXIMUM_PRIORITY, session_,
+      proxy_info_, {}, PRIVACY_MODE_DISABLED,
+      network_anonymization_key_, SecureDnsPolicy::kDisable, SocketTag(),
+      net_log_, server_socket_handle_.get(), io_callback_,
+      ClientSocketPool::ProxyAuthCallback());
+}
+
+int NaiveConnection::DoConnectServerComplete(int result) {
+  if (result < 0)
+    return result;
+
+  std::optional<PaddingType> server_padding_type =
+      padding_detector_delegate_->GetServerPaddingType();
+  CHECK(server_padding_type.has_value());
+
+  sockets_[kServer] = std::make_unique<NaivePaddingSocket>(
+      server_socket_handle_->socket(), *server_padding_type, kServer);
+
+  full_duplex_ = true;
+  next_state_ = STATE_NONE;
+  return OK;
+}
+
+int NaiveConnection::Run(CompletionOnceCallback callback) {
+  DCHECK(sockets_[kServer]);
+  DCHECK_EQ(next_state_, STATE_NONE);
+  DCHECK(!connect_callback_);
+
+  // The client-side socket may be closed before the server-side
+  // socket is connected.
+  if (errors_[kClient] != OK || sockets_[kClient] == nullptr)
+    return errors_[kClient];
+  if (errors_[kServer] != OK)
+    return errors_[kServer];
+
+  run_callback_ = std::move(callback);
+
+  bytes_passed_without_yielding_[kClient] = 0;
+  bytes_passed_without_yielding_[kServer] = 0;
+
+  yield_after_time_[kClient] =
+      time_func_() + base::Milliseconds(kYieldAfterDurationMilliseconds);
+  yield_after_time_[kServer] = yield_after_time_[kClient];
+
+  can_push_to_server_ = true;
+  // early_pull_result_ == 0 means the early pull was not started because
+  // padding support was not yet known.
+  if (!early_pull_pending_ && early_pull_result_ == 0) {
+    Pull(kClient, kServer);
+  } else if (!early_pull_pending_) {
+    DCHECK_GT(early_pull_result_, 0);
+    Push(kClient, kServer, early_pull_result_);
+  }
+  Pull(kServer, kClient);
+
+  return ERR_IO_PENDING;
+}
+
+void NaiveConnection::Pull(Direction from, Direction to) {
+  if (errors_[kClient] < 0 || errors_[kServer] < 0)
+    return;
+
+  int read_size = kBufferSize;
+  read_buffers_[from] = base::MakeRefCounted<IOBufferWithSize>(kBufferSize);
+
+  DCHECK(sockets_[from]);
+  int rv = sockets_[from]->Read(
+      read_buffers_[from].get(), read_size,
+      base::BindRepeating(&NaiveConnection::OnPullComplete,
+                          weak_ptr_factory_.GetWeakPtr(), from, to));
+
+  if (from == kClient && early_pull_pending_)
+    early_pull_result_ = rv;
+
+  if (rv != ERR_IO_PENDING)
+    OnPullComplete(from, to, rv);
+}
+
+void NaiveConnection::Push(Direction from, Direction to, int size) {
+  write_buffers_[to] = base::MakeRefCounted<DrainableIOBuffer>(
+      std::move(read_buffers_[from]), size);
+  write_pending_[to] = true;
+  DCHECK(sockets_[to]);
+  int rv = sockets_[to]->Write(
+      write_buffers_[to].get(), write_buffers_[to]->BytesRemaining(),
+      base::BindRepeating(&NaiveConnection::OnPushComplete,
+                          weak_ptr_factory_.GetWeakPtr(), from, to),
+      traffic_annotation_);
+
+  if (rv != ERR_IO_PENDING)
+    OnPushComplete(from, to, rv);
+}
+
+void NaiveConnection::Disconnect(Direction side) {
+  if (sockets_[side]) {
+    sockets_[side]->Disconnect();
+    sockets_[side] = nullptr;
+    write_pending_[side] = false;
+  }
+}
+
+bool NaiveConnection::IsConnected(Direction side) {
+  return sockets_[side] != nullptr;
+}
+
+void NaiveConnection::OnBothDisconnected() {
+  if (run_callback_) {
+    int error = OK;
+    if (errors_[kClient] != ERR_CONNECTION_CLOSED && errors_[kClient] < 0)
+      error = errors_[kClient];
+    if (errors_[kServer] != ERR_CONNECTION_CLOSED && errors_[kClient] < 0)
+      error = errors_[kServer];
+    std::move(run_callback_).Run(error);
+  }
+}
+
+void NaiveConnection::OnPullError(Direction from, Direction to, int error) {
+  DCHECK_LT(error, 0);
+
+  errors_[from] = error;
+  Disconnect(from);
+
+  if (!write_pending_[to])
+    Disconnect(to);
+
+  if (!IsConnected(from) && !IsConnected(to))
+    OnBothDisconnected();
+}
+
+void NaiveConnection::OnPushError(Direction from, Direction to, int error) {
+  DCHECK_LE(error, 0);
+  DCHECK(!write_pending_[to]);
+
+  if (error < 0) {
+    errors_[to] = error;
+    Disconnect(kServer);
+    Disconnect(kClient);
+  } else if (!IsConnected(from)) {
+    Disconnect(to);
+  }
+
+  if (!IsConnected(from) && !IsConnected(to))
+    OnBothDisconnected();
+}
+
+void NaiveConnection::OnPullComplete(Direction from, Direction to, int result) {
+  if (from == kClient && early_pull_pending_) {
+    early_pull_pending_ = false;
+    early_pull_result_ = result ? result : ERR_CONNECTION_CLOSED;
+  }
+
+  if (result <= 0) {
+    OnPullError(from, to, result ? result : ERR_CONNECTION_CLOSED);
+    return;
+  }
+
+  if (from == kClient && !can_push_to_server_)
+    return;
+
+  Push(from, to, result);
+}
+
+void NaiveConnection::OnPushComplete(Direction from, Direction to, int result) {
+  if (result >= 0 && write_buffers_[to] != nullptr) {
+    bytes_passed_without_yielding_[from] += result;
+    write_buffers_[to]->DidConsume(result);
+    int size = write_buffers_[to]->BytesRemaining();
+    if (size > 0) {
+      int rv = sockets_[to]->Write(
+          write_buffers_[to].get(), size,
+          base::BindRepeating(&NaiveConnection::OnPushComplete,
+                              weak_ptr_factory_.GetWeakPtr(), from, to),
+          traffic_annotation_);
+      if (rv != ERR_IO_PENDING)
+        OnPushComplete(from, to, rv);
+      return;
+    }
+  }
+
+  write_pending_[to] = false;
+  // Checks for termination even if result is OK.
+  OnPushError(from, to, result >= 0 ? OK : result);
+
+  if (bytes_passed_without_yielding_[from] > kYieldAfterBytesRead ||
+      time_func_() > yield_after_time_[from]) {
+    bytes_passed_without_yielding_[from] = 0;
+    yield_after_time_[from] =
+        time_func_() + base::Milliseconds(kYieldAfterDurationMilliseconds);
+    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+        FROM_HERE,
+        base::BindRepeating(&NaiveConnection::Pull,
+                            weak_ptr_factory_.GetWeakPtr(), from, to));
+  } else {
+    Pull(from, to);
+  }
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_connection.h b/src/net/tools/naive/naive_connection.h
new file mode 100644
index 0000000000..83b14fff28
--- /dev/null
+++ b/src/net/tools/naive/naive_connection.h
@@ -0,0 +1,134 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_
+#define NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_
+
+#include <memory>
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
+#include "net/tools/naive/naive_padding_socket.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "net/tools/naive/naive_proxy_delegate.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class DrainableIOBuffer;
+class HttpNetworkSession;
+class IOBuffer;
+class NetLogWithSource;
+class ProxyInfo;
+class StreamSocket;
+struct NetworkTrafficAnnotationTag;
+struct SSLConfig;
+class RedirectResolver;
+class NetworkAnonymizationKey;
+
+class NaiveConnection {
+ public:
+  using TimeFunc = base::TimeTicks (*)();
+
+  NaiveConnection(
+      unsigned int id,
+      ClientProtocol protocol,
+      std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate,
+      const ProxyInfo& proxy_info,
+      RedirectResolver* resolver,
+      HttpNetworkSession* session,
+      const NetworkAnonymizationKey& network_anonymization_key,
+      const NetLogWithSource& net_log,
+      std::unique_ptr<StreamSocket> accepted_socket,
+      const NetworkTrafficAnnotationTag& traffic_annotation);
+  ~NaiveConnection();
+  NaiveConnection(const NaiveConnection&) = delete;
+  NaiveConnection& operator=(const NaiveConnection&) = delete;
+
+  unsigned int id() const { return id_; }
+  int Connect(CompletionOnceCallback callback);
+  void Disconnect();
+  int Run(CompletionOnceCallback callback);
+
+ private:
+  enum State {
+    STATE_CONNECT_CLIENT,
+    STATE_CONNECT_CLIENT_COMPLETE,
+    STATE_CONNECT_SERVER,
+    STATE_CONNECT_SERVER_COMPLETE,
+    STATE_NONE,
+  };
+
+  enum PaddingState {
+    STATE_READ_PAYLOAD_LENGTH_1,
+    STATE_READ_PAYLOAD_LENGTH_2,
+    STATE_READ_PADDING_LENGTH,
+    STATE_READ_PAYLOAD,
+    STATE_READ_PADDING,
+  };
+
+  void DoCallback(int result);
+  void OnIOComplete(int result);
+  int DoLoop(int last_io_result);
+  int DoConnectClient();
+  int DoConnectClientComplete(int result);
+  int DoConnectServer();
+  int DoConnectServerComplete(int result);
+  void Pull(Direction from, Direction to);
+  void Push(Direction from, Direction to, int size);
+  void Disconnect(Direction side);
+  bool IsConnected(Direction side);
+  void OnBothDisconnected();
+  void OnPullError(Direction from, Direction to, int error);
+  void OnPushError(Direction from, Direction to, int error);
+  void OnPullComplete(Direction from, Direction to, int result);
+  void OnPushComplete(Direction from, Direction to, int result);
+
+  unsigned int id_;
+  ClientProtocol protocol_;
+  std::unique_ptr<PaddingDetectorDelegate> padding_detector_delegate_;
+  const ProxyInfo& proxy_info_;
+  RedirectResolver* resolver_;
+  HttpNetworkSession* session_;
+  const NetworkAnonymizationKey& network_anonymization_key_;
+  const NetLogWithSource& net_log_;
+
+  CompletionRepeatingCallback io_callback_;
+  CompletionOnceCallback connect_callback_;
+  CompletionOnceCallback run_callback_;
+
+  State next_state_;
+
+  std::unique_ptr<StreamSocket> client_socket_;
+  std::unique_ptr<ClientSocketHandle> server_socket_handle_;
+
+  std::unique_ptr<NaivePaddingSocket> sockets_[kNumDirections];
+  scoped_refptr<IOBuffer> read_buffers_[kNumDirections];
+  scoped_refptr<DrainableIOBuffer> write_buffers_[kNumDirections];
+  int errors_[kNumDirections];
+  bool write_pending_[kNumDirections];
+  int bytes_passed_without_yielding_[kNumDirections];
+  base::TimeTicks yield_after_time_[kNumDirections];
+
+  bool early_pull_pending_;
+  bool can_push_to_server_;
+  int early_pull_result_;
+
+  bool full_duplex_;
+
+  TimeFunc time_func_;
+
+  // Traffic annotation for socket control.
+  const NetworkTrafficAnnotationTag& traffic_annotation_;
+
+  base::WeakPtrFactory<NaiveConnection> weak_ptr_factory_{this};
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_CONNECTION_H_
diff --git a/src/net/tools/naive/naive_padding_framer.cc b/src/net/tools/naive/naive_padding_framer.cc
new file mode 100644
index 0000000000..064bc93a54
--- /dev/null
+++ b/src/net/tools/naive/naive_padding_framer.cc
@@ -0,0 +1,123 @@
+// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "net/tools/naive/naive_padding_framer.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+#include <optional>
+
+#include "base/check.h"
+#include "base/check_op.h"
+
+namespace net {
+NaivePaddingFramer::NaivePaddingFramer(std::optional<int> max_read_frames)
+    : max_read_frames_(max_read_frames) {
+  if (max_read_frames.has_value()) {
+    CHECK_GE(*max_read_frames, 0);
+  }
+}
+
+int NaivePaddingFramer::Read(const char* padded,
+                             int padded_len,
+                             char* payload_buf,
+                             int payload_buf_capacity) {
+  // This check guarantees write_ptr does not overflow.
+  CHECK_GE(payload_buf_capacity, padded_len);
+
+  char* write_ptr = payload_buf;
+  while (padded_len > 0) {
+    int copy_size;
+    switch (state_) {
+      case ReadState::kPayloadLength1:
+        if (max_read_frames_.has_value() &&
+            num_read_frames_ >= *max_read_frames_) {
+          std::memcpy(write_ptr, padded, padded_len);
+          padded += padded_len;
+          write_ptr += padded_len;
+          padded_len = 0;
+          break;
+        }
+        read_payload_length_ = static_cast<uint8_t>(padded[0]);
+        ++padded;
+        --padded_len;
+        state_ = ReadState::kPayloadLength2;
+        break;
+      case ReadState::kPayloadLength2:
+        read_payload_length_ =
+            read_payload_length_ * 256 + static_cast<uint8_t>(padded[0]);
+        ++padded;
+        --padded_len;
+        state_ = ReadState::kPaddingLength1;
+        break;
+      case ReadState::kPaddingLength1:
+        read_padding_length_ = static_cast<uint8_t>(padded[0]);
+        ++padded;
+        --padded_len;
+        state_ = ReadState::kPayload;
+        break;
+      case ReadState::kPayload:
+        copy_size = std::min(read_payload_length_, padded_len);
+        read_payload_length_ -= copy_size;
+        if (read_payload_length_ == 0) {
+          state_ = ReadState::kPadding;
+        }
+
+        std::memcpy(write_ptr, padded, copy_size);
+        padded += copy_size;
+        write_ptr += copy_size;
+        padded_len -= copy_size;
+        break;
+      case ReadState::kPadding:
+        copy_size = std::min(read_padding_length_, padded_len);
+        read_padding_length_ -= copy_size;
+        if (read_padding_length_ == 0) {
+          if (num_read_frames_ < std::numeric_limits<int>::max() - 1) {
+            ++num_read_frames_;
+          }
+          state_ = ReadState::kPayloadLength1;
+        }
+
+        padded += copy_size;
+        padded_len -= copy_size;
+        break;
+    }
+  }
+  return write_ptr - payload_buf;
+}
+
+int NaivePaddingFramer::Write(const char* payload_buf,
+                              int payload_buf_len,
+                              int padding_size,
+                              char* padded,
+                              int padded_capacity,
+                              int& payload_consumed_len) {
+  CHECK_GE(payload_buf_len, 0);
+  CHECK_LE(padding_size, max_padding_size());
+  CHECK_GE(padding_size, 0);
+
+  payload_consumed_len = std::min(
+      payload_buf_len, padded_capacity - frame_header_size() - padding_size);
+  int padded_buf_len =
+      frame_header_size() + payload_consumed_len + padding_size;
+
+  padded[0] = payload_consumed_len / 256;
+  padded[1] = payload_consumed_len % 256;
+  padded[2] = padding_size;
+  std::memcpy(padded + frame_header_size(), payload_buf, payload_consumed_len);
+  std::memset(padded + frame_header_size() + payload_consumed_len, '\0',
+              padding_size);
+
+  if (num_written_frames_ < std::numeric_limits<int>::max() - 1) {
+    ++num_written_frames_;
+  }
+  return padded_buf_len;
+}
+}  // namespace net
diff --git a/src/net/tools/naive/naive_padding_framer.h b/src/net/tools/naive/naive_padding_framer.h
new file mode 100644
index 0000000000..bdb5865276
--- /dev/null
+++ b/src/net/tools/naive/naive_padding_framer.h
@@ -0,0 +1,76 @@
+// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_NAIVE_NAIVE_PADDING_FRAMER_H_
+#define NET_TOOLS_NAIVE_NAIVE_PADDING_FRAMER_H_
+
+#include <cstdint>
+#include <limits>
+#include <optional>
+
+namespace net {
+
+// struct PaddedFrame {
+//   uint16_t payload_size;  // big-endian
+//   uint8_t padding_size;  // big-endian
+//   uint8_t payload[payload_size];
+//   uint8_t zeros[padding_size];
+// };
+class NaivePaddingFramer {
+ public:
+  // `max_read_frames`: Assumes the byte stream stops using the padding
+  //   framing after `max_read_frames` frames. If -1, it means
+  //   the byte stream always uses the padding framing.
+  explicit NaivePaddingFramer(std::optional<int> max_read_frames);
+
+  int max_payload_size() const { return std::numeric_limits<uint16_t>::max(); }
+
+  int max_padding_size() const { return std::numeric_limits<uint8_t>::max(); }
+
+  int frame_header_size() const { return 3; }
+
+  int num_read_frames() const { return num_read_frames_; }
+
+  int num_written_frames() const { return num_written_frames_; }
+
+  // Reads `padded` for `padded_len` bytes and extracts unpadded payload to
+  // `payload_buf`.
+  // Returns the number of payload bytes extracted.
+  // Returning zero indicates a pure padding instead of EOF.
+  int Read(const char* padded,
+           int padded_len,
+           char* payload_buf,
+           int payload_buf_capacity);
+
+  // Writes `payload_buf` for up to `payload_buf_len` bytes into `padded`.
+  // Returns the number of padded bytes written.
+  // If the padded bytes would exceed `padded_capacity`, the payload is
+  // truncated to `payload_consumed_len`.
+  int Write(const char* payload_buf,
+            int payload_buf_len,
+            int padding_size,
+            char* padded,
+            int padded_capacity,
+            int& payload_consumed_len);
+
+ private:
+  enum class ReadState {
+    kPayloadLength1,
+    kPayloadLength2,
+    kPaddingLength1,
+    kPayload,
+    kPadding,
+  };
+
+  std::optional<int> max_read_frames_;
+
+  ReadState state_ = ReadState::kPayloadLength1;
+  int read_payload_length_ = 0;
+  int read_padding_length_ = 0;
+  int num_read_frames_ = 0;
+
+  int num_written_frames_ = 0;
+};
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_PADDING_FRAMER_H_
diff --git a/src/net/tools/naive/naive_padding_socket.cc b/src/net/tools/naive/naive_padding_socket.cc
new file mode 100644
index 0000000000..3dfd2ec14a
--- /dev/null
+++ b/src/net/tools/naive/naive_padding_socket.cc
@@ -0,0 +1,271 @@
+// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "net/tools/naive/naive_padding_socket.h"
+
+#include <algorithm>
+#include <cstring>
+#include <optional>
+#include <tuple>
+#include <utility>
+
+#include "base/rand_util.h"
+#include "base/task/single_thread_task_runner.h"
+#include "net/base/io_buffer.h"
+
+namespace net {
+
+namespace {
+constexpr int kMaxBufferSize = 64 * 1024;
+constexpr int kFirstPaddings = 8;
+}  // namespace
+
+NaivePaddingSocket::NaivePaddingSocket(StreamSocket* transport_socket,
+                                       PaddingType padding_type,
+                                       Direction direction)
+    : transport_socket_(transport_socket),
+      padding_type_(padding_type),
+      direction_(direction),
+      read_buf_(base::MakeRefCounted<IOBufferWithSize>(kMaxBufferSize)),
+      framer_(kFirstPaddings) {}
+
+NaivePaddingSocket::~NaivePaddingSocket() {
+  Disconnect();
+}
+
+void NaivePaddingSocket::Disconnect() {
+  transport_socket_->Disconnect();
+}
+
+int NaivePaddingSocket::Read(IOBuffer* buf,
+                             int buf_len,
+                             CompletionOnceCallback callback) {
+  DCHECK(!callback.is_null());
+
+  switch (padding_type_) {
+    case PaddingType::kNone:
+      return ReadNoPadding(buf, buf_len, std::move(callback));
+    case PaddingType::kVariant1:
+      if (framer_.num_read_frames() < kFirstPaddings) {
+        return ReadPaddingV1(buf, buf_len, std::move(callback));
+      } else {
+        return ReadNoPadding(buf, buf_len, std::move(callback));
+      }
+    default:
+      NOTREACHED();
+  }
+}
+
+int NaivePaddingSocket::ReadNoPadding(IOBuffer* buf,
+                                      int buf_len,
+                                      CompletionOnceCallback callback) {
+  int rv = transport_socket_->Read(
+      buf, buf_len,
+      base::BindOnce(&NaivePaddingSocket::OnReadNoPaddingComplete,
+                     base::Unretained(this), std::move(callback)));
+  return rv;
+}
+
+void NaivePaddingSocket::OnReadNoPaddingComplete(
+    CompletionOnceCallback callback,
+    int rv) {
+  DCHECK_NE(ERR_IO_PENDING, rv);
+  DCHECK(callback);
+
+  std::move(callback).Run(rv);
+}
+
+int NaivePaddingSocket::ReadPaddingV1(IOBuffer* buf,
+                                      int buf_len,
+                                      CompletionOnceCallback callback) {
+  DCHECK(!callback.is_null());
+  DCHECK(read_user_buf_ == nullptr);
+
+  // Truncates user requested buf len if it is too large for the padding
+  // buffer.
+  buf_len = std::min(buf_len, kMaxBufferSize);
+  read_user_buf_ = buf;
+  read_user_buf_len_ = buf_len;
+
+  int rv = ReadPaddingV1Payload();
+
+  if (rv == ERR_IO_PENDING) {
+    read_callback_ = std::move(callback);
+    return rv;
+  }
+
+  read_user_buf_ = nullptr;
+
+  return rv;
+}
+
+void NaivePaddingSocket::OnReadPaddingV1Complete(int rv) {
+  DCHECK_NE(ERR_IO_PENDING, rv);
+  DCHECK(read_callback_);
+  DCHECK(read_user_buf_ != nullptr);
+
+  if (rv > 0) {
+    rv = framer_.Read(read_buf_->data(), rv, read_user_buf_->data(),
+                      read_user_buf_len_);
+    if (rv == 0) {
+      rv = ReadPaddingV1Payload();
+      if (rv == ERR_IO_PENDING)
+        return;
+    }
+  }
+
+  // Must reset read_user_buf_ before invoking read_callback_, which may reenter
+  // Read().
+  read_user_buf_ = nullptr;
+
+  std::move(read_callback_).Run(rv);
+}
+
+int NaivePaddingSocket::ReadPaddingV1Payload() {
+  for (;;) {
+    int rv = transport_socket_->Read(
+        read_buf_.get(), read_user_buf_len_,
+        base::BindOnce(&NaivePaddingSocket::OnReadPaddingV1Complete,
+                       base::Unretained(this)));
+    if (rv <= 0) {
+      return rv;
+    }
+    rv = framer_.Read(read_buf_->data(), rv, read_user_buf_->data(),
+                      read_user_buf_len_);
+    if (rv > 0) {
+      return rv;
+    }
+  }
+}
+
+int NaivePaddingSocket::Write(
+    IOBuffer* buf,
+    int buf_len,
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  switch (padding_type_) {
+    case PaddingType::kNone:
+      return WriteNoPadding(buf, buf_len, std::move(callback),
+                            traffic_annotation);
+    case PaddingType::kVariant1:
+      if (framer_.num_written_frames() < kFirstPaddings) {
+        return WritePaddingV1(buf, buf_len, std::move(callback),
+                              traffic_annotation);
+      } else {
+        return WriteNoPadding(buf, buf_len, std::move(callback),
+                              traffic_annotation);
+      }
+    default:
+      NOTREACHED();
+  }
+}
+
+int NaivePaddingSocket::WriteNoPadding(
+    IOBuffer* buf,
+    int buf_len,
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  return transport_socket_->Write(
+      buf, buf_len,
+      base::BindOnce(&NaivePaddingSocket::OnWriteNoPaddingComplete,
+                     base::Unretained(this), std::move(callback),
+                     traffic_annotation),
+      traffic_annotation);
+}
+
+void NaivePaddingSocket::OnWriteNoPaddingComplete(
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation,
+    int rv) {
+  DCHECK_NE(ERR_IO_PENDING, rv);
+  DCHECK(callback);
+
+  std::move(callback).Run(rv);
+}
+
+int NaivePaddingSocket::WritePaddingV1(
+    IOBuffer* buf,
+    int buf_len,
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(write_buf_ == nullptr);
+
+  auto padded = base::MakeRefCounted<IOBufferWithSize>(kMaxBufferSize);
+  int padding_size;
+  if (direction_ == kServer) {
+    if (buf_len < 100) {
+      padding_size = base::RandInt(framer_.max_padding_size() - buf_len,
+                                   framer_.max_padding_size());
+    } else {
+      padding_size = base::RandInt(0, framer_.max_padding_size());
+    }
+  } else {
+    padding_size = base::RandInt(0, framer_.max_padding_size());
+  }
+  int write_buf_len =
+      framer_.Write(buf->data(), buf_len, padding_size, padded->data(),
+                    kMaxBufferSize, write_user_payload_len_);
+  // Using DrainableIOBuffer here because we do not want to
+  // repeatedly encode the padding frames when short writes happen.
+  write_buf_ =
+      base::MakeRefCounted<DrainableIOBuffer>(std::move(padded), write_buf_len);
+
+  int rv = WritePaddingV1Drain(traffic_annotation);
+  if (rv == ERR_IO_PENDING) {
+    write_callback_ = std::move(callback);
+    return rv;
+  }
+
+  write_buf_ = nullptr;
+  write_user_payload_len_ = 0;
+
+  return rv;
+}
+
+void NaivePaddingSocket::OnWritePaddingV1Complete(
+    const NetworkTrafficAnnotationTag& traffic_annotation,
+    int rv) {
+  DCHECK_NE(ERR_IO_PENDING, rv);
+  DCHECK(write_callback_);
+  DCHECK(write_buf_ != nullptr);
+
+  if (rv > 0) {
+    write_buf_->DidConsume(rv);
+    rv = WritePaddingV1Drain(traffic_annotation);
+    if (rv == ERR_IO_PENDING)
+      return;
+  }
+
+  // Must reset these before invoking write_callback_, which may reenter
+  // Write().
+  write_buf_ = nullptr;
+  write_user_payload_len_ = 0;
+
+  std::move(write_callback_).Run(rv);
+}
+
+int NaivePaddingSocket::WritePaddingV1Drain(
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(write_buf_ != nullptr);
+
+  while (write_buf_->BytesRemaining() > 0) {
+    int remaining = write_buf_->BytesRemaining();
+    if (direction_ == kServer && write_user_payload_len_ > 400 &&
+        write_user_payload_len_ < 1024) {
+      remaining = std::min(remaining, base::RandInt(200, 300));
+    }
+    int rv = transport_socket_->Write(
+        write_buf_.get(), remaining,
+        base::BindOnce(&NaivePaddingSocket::OnWritePaddingV1Complete,
+                       base::Unretained(this), traffic_annotation),
+        traffic_annotation);
+    if (rv <= 0) {
+      return rv;
+    }
+    write_buf_->DidConsume(rv);
+  }
+  // Synchronously drained the buffer.
+  return write_user_payload_len_;
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_padding_socket.h b/src/net/tools/naive/naive_padding_socket.h
new file mode 100644
index 0000000000..01a5720e32
--- /dev/null
+++ b/src/net/tools/naive/naive_padding_socket.h
@@ -0,0 +1,105 @@
+// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_
+#define NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_
+
+#include <cstddef>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
+#include "net/base/address_list.h"
+#include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_export.h"
+#include "net/socket/stream_socket.h"
+#include "net/tools/naive/naive_padding_framer.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "url/gurl.h"
+
+namespace net {
+
+class NaivePaddingSocket {
+ public:
+  NaivePaddingSocket(StreamSocket* transport_socket,
+                     PaddingType padding_type,
+                     Direction direction);
+
+  NaivePaddingSocket(const NaivePaddingSocket&) = delete;
+  NaivePaddingSocket& operator=(const NaivePaddingSocket&) = delete;
+
+  // On destruction Disconnect() is called.
+  ~NaivePaddingSocket();
+
+  void Disconnect();
+
+  int Read(IOBuffer* buf, int buf_len, CompletionOnceCallback callback);
+
+  int Write(IOBuffer* buf,
+            int buf_len,
+            CompletionOnceCallback callback,
+            const NetworkTrafficAnnotationTag& traffic_annotation);
+
+ private:
+  int ReadNoPadding(IOBuffer* buf,
+                    int buf_len,
+                    CompletionOnceCallback callback);
+  int WriteNoPadding(IOBuffer* buf,
+                     int buf_len,
+                     CompletionOnceCallback callback,
+                     const NetworkTrafficAnnotationTag& traffic_annotation);
+  void OnReadNoPaddingComplete(CompletionOnceCallback callback, int rv);
+  void OnWriteNoPaddingComplete(
+      CompletionOnceCallback callback,
+      const NetworkTrafficAnnotationTag& traffic_annotation,
+      int rv);
+
+  int ReadPaddingV1(IOBuffer* buf,
+                    int buf_len,
+                    CompletionOnceCallback callback);
+  int WritePaddingV1(IOBuffer* buf,
+                     int buf_len,
+                     CompletionOnceCallback callback,
+                     const NetworkTrafficAnnotationTag& traffic_annotation);
+  void OnReadPaddingV1Complete(int rv);
+  void OnWritePaddingV1Complete(
+      const NetworkTrafficAnnotationTag& traffic_annotation,
+      int rv);
+
+  // Exhausts synchronous reads if it is a pure padding
+  // so this does not return zero for non-EOF condition.
+  int ReadPaddingV1Payload();
+
+  int WritePaddingV1Drain(
+      const NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // Stores the underlying socket.
+  // Non-owning because this socket does not take part in the client socket pool
+  // handling and making it owning the transport socket may interfere badly
+  // with the client socket pool.
+  StreamSocket* transport_socket_;
+
+  PaddingType padding_type_;
+  Direction direction_;
+
+  IOBuffer* read_user_buf_ = nullptr;
+  int read_user_buf_len_ = 0;
+  CompletionOnceCallback read_callback_;
+  scoped_refptr<IOBuffer> read_buf_;
+
+  int write_user_payload_len_ = 0;
+  CompletionOnceCallback write_callback_;
+  scoped_refptr<DrainableIOBuffer> write_buf_;
+
+  NaivePaddingFramer framer_;
+};
+
+}  // namespace net
+
+#endif  // NET_TOOLS_NAIVE_NAIVE_PADDING_SOCKET_H_
diff --git a/src/net/tools/naive/naive_protocol.cc b/src/net/tools/naive/naive_protocol.cc
new file mode 100644
index 0000000000..589de10989
--- /dev/null
+++ b/src/net/tools/naive/naive_protocol.cc
@@ -0,0 +1,56 @@
+// Copyright 2023 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#include "net/tools/naive/naive_protocol.h"
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace net {
+const char* ToString(ClientProtocol value) {
+  switch (value) {
+    case ClientProtocol::kSocks5:
+      return "socks";
+    case ClientProtocol::kHttp:
+      return "http";
+    case ClientProtocol::kRedir:
+      return "redir";
+    default:
+      return "";
+  }
+}
+
+std::optional<PaddingType> ParsePaddingType(std::string_view str) {
+  if (str == "0") {
+    return PaddingType::kNone;
+  } else if (str == "1") {
+    return PaddingType::kVariant1;
+  } else {
+    return std::nullopt;
+  }
+}
+
+const char* ToString(PaddingType value) {
+  switch (value) {
+    case PaddingType::kNone:
+      return "0";
+    case PaddingType::kVariant1:
+      return "1";
+    default:
+      return "";
+  }
+}
+
+const char* ToReadableString(PaddingType value) {
+  switch (value) {
+    case PaddingType::kNone:
+      return "None";
+    case PaddingType::kVariant1:
+      return "Variant1";
+    default:
+      return "";
+  }
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_protocol.h b/src/net/tools/naive/naive_protocol.h
new file mode 100644
index 0000000000..1935269e20
--- /dev/null
+++ b/src/net/tools/naive/naive_protocol.h
@@ -0,0 +1,64 @@
+// Copyright 2020 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
+#define NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+namespace net {
+enum class ClientProtocol {
+  kSocks5,
+  kHttp,
+  kRedir,
+};
+
+const char* ToString(ClientProtocol value);
+
+// Adds padding for traffic from this direction.
+// Removes padding for traffic from the opposite direction.
+enum Direction {
+  kClient = 0,
+  kServer = 1,
+  kNumDirections = 2,
+  kNone = 2,
+};
+
+enum class PaddingType {
+  // Wire format: "0".
+  kNone = 0,
+
+  // Pads the first 8 reads and writes with padding bytes of random size
+  // uniformly distributed in [0, 255].
+  // struct PaddedFrame {
+  //   uint8_t original_data_size_high;  // original_data_size / 256
+  //   uint8_t original_data_size_low;  // original_data_size % 256
+  //   uint8_t padding_size;
+  //   uint8_t original_data[original_data_size];
+  //   uint8_t zeros[padding_size];
+  // };
+  // Wire format: "1".
+  kVariant1 = 1,
+};
+
+// Returns empty if `str` is invalid.
+std::optional<PaddingType> ParsePaddingType(std::string_view str);
+
+const char* ToString(PaddingType value);
+
+const char* ToReadableString(PaddingType value);
+
+constexpr const char* kPaddingHeader = "padding";
+
+// Contains a comma separated list of requested padding types.
+// Preferred types come first.
+constexpr const char* kPaddingTypeRequestHeader = "padding-type-request";
+
+// Contains a single number representing the negotiated padding type.
+// Must be one of PaddingType.
+constexpr const char* kPaddingTypeReplyHeader = "padding-type-reply";
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_PROTOCOL_H_
diff --git a/src/net/tools/naive/naive_proxy.cc b/src/net/tools/naive/naive_proxy.cc
new file mode 100644
index 0000000000..7e07b9f4ac
--- /dev/null
+++ b/src/net/tools/naive/naive_proxy.cc
@@ -0,0 +1,204 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/tools/naive/naive_proxy.h"
+
+#include <string>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/single_thread_task_runner.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_network_session.h"
+#include "net/proxy_resolution/configured_proxy_resolution_service.h"
+#include "net/proxy_resolution/proxy_config.h"
+#include "net/proxy_resolution/proxy_list.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/server_socket.h"
+#include "net/socket/stream_socket.h"
+#include "net/tools/naive/http_proxy_server_socket.h"
+#include "net/tools/naive/naive_proxy_delegate.h"
+#include "net/tools/naive/socks5_server_socket.h"
+
+namespace net {
+
+NaiveProxy::NaiveProxy(std::unique_ptr<ServerSocket> listen_socket,
+                       ClientProtocol protocol,
+                       const std::string& listen_user,
+                       const std::string& listen_pass,
+                       int concurrency,
+                       RedirectResolver* resolver,
+                       HttpNetworkSession* session,
+                       const NetworkTrafficAnnotationTag& traffic_annotation,
+                       const std::vector<PaddingType>& supported_padding_types)
+    : listen_socket_(std::move(listen_socket)),
+      protocol_(protocol),
+      listen_user_(listen_user),
+      listen_pass_(listen_pass),
+      concurrency_(concurrency),
+      resolver_(resolver),
+      session_(session),
+      net_log_(
+          NetLogWithSource::Make(session->net_log(), NetLogSourceType::NONE)),
+      last_id_(0),
+      traffic_annotation_(traffic_annotation),
+      supported_padding_types_(supported_padding_types) {
+  const auto& proxy_config = static_cast<ConfiguredProxyResolutionService*>(
+                                 session_->proxy_resolution_service())
+                                 ->config();
+  DCHECK(proxy_config);
+  const ProxyList& proxy_list =
+      proxy_config.value().value().proxy_rules().single_proxies;
+  DCHECK(!proxy_list.IsEmpty());
+  proxy_info_.UseProxyList(proxy_list);
+  proxy_info_.set_traffic_annotation(
+      net::MutableNetworkTrafficAnnotationTag(traffic_annotation_));
+
+  for (int i = 0; i < concurrency_; i++) {
+    network_anonymization_keys_.push_back(
+        NetworkAnonymizationKey::CreateTransient());
+  }
+
+  DCHECK(listen_socket_);
+  // Start accepting connections in next run loop in case when delegate is not
+  // ready to get callbacks.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(&NaiveProxy::DoAcceptLoop,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+NaiveProxy::~NaiveProxy() = default;
+
+void NaiveProxy::DoAcceptLoop() {
+  int result;
+  do {
+    result = listen_socket_->Accept(
+        &accepted_socket_, base::BindRepeating(&NaiveProxy::OnAcceptComplete,
+                                               weak_ptr_factory_.GetWeakPtr()));
+    if (result == ERR_IO_PENDING)
+      return;
+    HandleAcceptResult(result);
+  } while (result == OK);
+}
+
+void NaiveProxy::OnAcceptComplete(int result) {
+  HandleAcceptResult(result);
+  if (result == OK)
+    DoAcceptLoop();
+}
+
+void NaiveProxy::HandleAcceptResult(int result) {
+  if (result != OK) {
+    LOG(ERROR) << "Accept error: " << ErrorToShortString(result);
+    return;
+  }
+  DoConnect();
+}
+
+void NaiveProxy::DoConnect() {
+  std::unique_ptr<StreamSocket> socket;
+  auto* proxy_delegate =
+      static_cast<NaiveProxyDelegate*>(session_->context().proxy_delegate);
+  DCHECK(proxy_delegate);
+  DCHECK(!proxy_info_.is_empty());
+  const ProxyChain& proxy_server = proxy_info_.proxy_chain();
+  auto padding_detector_delegate = std::make_unique<PaddingDetectorDelegate>(
+      proxy_delegate, proxy_server, protocol_);
+
+  if (protocol_ == ClientProtocol::kSocks5) {
+    socket = std::make_unique<Socks5ServerSocket>(std::move(accepted_socket_),
+                                                  listen_user_, listen_pass_,
+                                                  traffic_annotation_);
+  } else if (protocol_ == ClientProtocol::kHttp) {
+    socket = std::make_unique<HttpProxyServerSocket>(
+        std::move(accepted_socket_), listen_user_, listen_pass_,
+        padding_detector_delegate.get(), traffic_annotation_,
+        supported_padding_types_);
+  } else if (protocol_ == ClientProtocol::kRedir) {
+    socket = std::move(accepted_socket_);
+  } else {
+    return;
+  }
+
+  last_id_++;
+  int tunnel_session_id = last_id_ % concurrency_;
+  const auto& nak = network_anonymization_keys_[tunnel_session_id];
+  auto connection_ptr = std::make_unique<NaiveConnection>(
+      last_id_, protocol_, std::move(padding_detector_delegate), proxy_info_,
+      resolver_, session_, nak, net_log_, std::move(socket),
+      traffic_annotation_);
+  auto* connection = connection_ptr.get();
+  connection_by_id_[connection->id()] = std::move(connection_ptr);
+  int result = connection->Connect(
+      base::BindRepeating(&NaiveProxy::OnConnectComplete,
+                          weak_ptr_factory_.GetWeakPtr(), connection->id()));
+  if (result == ERR_IO_PENDING)
+    return;
+  HandleConnectResult(connection, result);
+}
+
+void NaiveProxy::OnConnectComplete(unsigned int connection_id, int result) {
+  auto* connection = FindConnection(connection_id);
+  if (!connection)
+    return;
+  HandleConnectResult(connection, result);
+}
+
+void NaiveProxy::HandleConnectResult(NaiveConnection* connection, int result) {
+  if (result != OK) {
+    Close(connection->id(), result);
+    return;
+  }
+  DoRun(connection);
+}
+
+void NaiveProxy::DoRun(NaiveConnection* connection) {
+  int result = connection->Run(
+      base::BindRepeating(&NaiveProxy::OnRunComplete,
+                          weak_ptr_factory_.GetWeakPtr(), connection->id()));
+  if (result == ERR_IO_PENDING)
+    return;
+  HandleRunResult(connection, result);
+}
+
+void NaiveProxy::OnRunComplete(unsigned int connection_id, int result) {
+  auto* connection = FindConnection(connection_id);
+  if (!connection)
+    return;
+  HandleRunResult(connection, result);
+}
+
+void NaiveProxy::HandleRunResult(NaiveConnection* connection, int result) {
+  Close(connection->id(), result);
+}
+
+void NaiveProxy::Close(unsigned int connection_id, int reason) {
+  auto it = connection_by_id_.find(connection_id);
+  if (it == connection_by_id_.end())
+    return;
+
+  LOG(INFO) << "Connection " << connection_id
+            << " closed: " << ErrorToShortString(reason);
+
+  // The call stack might have callbacks which still have the pointer of
+  // connection. Instead of referencing connection with ID all the time,
+  // destroys the connection in next run loop to make sure any pending
+  // callbacks in the call stack return.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->DeleteSoon(
+      FROM_HERE, std::move(it->second));
+  connection_by_id_.erase(it);
+}
+
+NaiveConnection* NaiveProxy::FindConnection(unsigned int connection_id) {
+  auto it = connection_by_id_.find(connection_id);
+  if (it == connection_by_id_.end())
+    return nullptr;
+  return it->second.get();
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_proxy.h b/src/net/tools/naive/naive_proxy.h
new file mode 100644
index 0000000000..626ac7e950
--- /dev/null
+++ b/src/net/tools/naive/naive_proxy.h
@@ -0,0 +1,90 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_PROXY_H_
+#define NET_TOOLS_NAIVE_NAIVE_PROXY_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_repeating_callback.h"
+#include "net/base/network_isolation_key.h"
+#include "net/log/net_log_with_source.h"
+#include "net/proxy_resolution/proxy_info.h"
+#include "net/ssl/ssl_config.h"
+#include "net/tools/naive/naive_connection.h"
+#include "net/tools/naive/naive_protocol.h"
+
+namespace net {
+
+class ClientSocketHandle;
+class HttpNetworkSession;
+class NaiveConnection;
+class ServerSocket;
+class StreamSocket;
+struct NetworkTrafficAnnotationTag;
+class RedirectResolver;
+
+class NaiveProxy {
+ public:
+  NaiveProxy(std::unique_ptr<ServerSocket> server_socket,
+             ClientProtocol protocol,
+             const std::string& listen_user,
+             const std::string& listen_pass,
+             int concurrency,
+             RedirectResolver* resolver,
+             HttpNetworkSession* session,
+             const NetworkTrafficAnnotationTag& traffic_annotation,
+             const std::vector<PaddingType>& supported_padding_types);
+  ~NaiveProxy();
+  NaiveProxy(const NaiveProxy&) = delete;
+  NaiveProxy& operator=(const NaiveProxy&) = delete;
+
+ private:
+  void DoAcceptLoop();
+  void OnAcceptComplete(int result);
+  void HandleAcceptResult(int result);
+
+  void DoConnect();
+  void OnConnectComplete(unsigned int connection_id, int result);
+  void HandleConnectResult(NaiveConnection* connection, int result);
+
+  void DoRun(NaiveConnection* connection);
+  void OnRunComplete(unsigned int connection_id, int result);
+  void HandleRunResult(NaiveConnection* connection, int result);
+
+  void Close(unsigned int connection_id, int reason);
+
+  NaiveConnection* FindConnection(unsigned int connection_id);
+
+  std::unique_ptr<ServerSocket> listen_socket_;
+  ClientProtocol protocol_;
+  std::string listen_user_;
+  std::string listen_pass_;
+  int concurrency_;
+  ProxyInfo proxy_info_;
+  RedirectResolver* resolver_;
+  HttpNetworkSession* session_;
+  NetLogWithSource net_log_;
+
+  unsigned int last_id_;
+
+  std::unique_ptr<StreamSocket> accepted_socket_;
+
+  std::vector<NetworkAnonymizationKey> network_anonymization_keys_;
+
+  std::map<unsigned int, std::unique_ptr<NaiveConnection>> connection_by_id_;
+
+  const NetworkTrafficAnnotationTag& traffic_annotation_;
+
+  std::vector<PaddingType> supported_padding_types_;
+
+  base::WeakPtrFactory<NaiveProxy> weak_ptr_factory_{this};
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_PROXY_H_
diff --git a/src/net/tools/naive/naive_proxy_bin.cc b/src/net/tools/naive/naive_proxy_bin.cc
new file mode 100644
index 0000000000..080be85215
--- /dev/null
+++ b/src/net/tools/naive/naive_proxy_bin.cc
@@ -0,0 +1,506 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <cstdlib>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "base/allocator/allocator_check.h"
+#include "base/allocator/partition_alloc_support.h"
+#include "base/allocator/partition_allocator/src/partition_alloc/shim/allocator_shim.h"
+#include "base/at_exit.h"
+#include "base/check.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/files/file_path.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/json/json_writer.h"
+#include "base/logging.h"
+#include "base/process/memory.h"
+#include "base/rand_util.h"
+#include "base/run_loop.h"
+#include "base/strings/escape.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/system/sys_info.h"
+#include "base/task/single_thread_task_executor.h"
+#include "base/task/thread_pool/thread_pool_instance.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "components/version_info/version_info.h"
+#include "net/base/auth.h"
+#include "net/base/network_isolation_key.h"
+#include "net/base/url_util.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cert_net/cert_net_fetcher_url_request.h"
+#include "net/dns/host_resolver.h"
+#include "net/dns/mapped_host_resolver.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/log/file_net_log_observer.h"
+#include "net/log/net_log.h"
+#include "net/log/net_log_capture_mode.h"
+#include "net/log/net_log_entry.h"
+#include "net/log/net_log_event_type.h"
+#include "net/log/net_log_source.h"
+#include "net/log/net_log_util.h"
+#include "net/proxy_resolution/configured_proxy_resolution_service.h"
+#include "net/proxy_resolution/proxy_config.h"
+#include "net/proxy_resolution/proxy_config_service_fixed.h"
+#include "net/proxy_resolution/proxy_config_with_annotation.h"
+#include "net/socket/client_socket_pool_manager.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/socket/tcp_server_socket.h"
+#include "net/socket/udp_server_socket.h"
+#include "net/ssl/ssl_config_service.h"
+#include "net/ssl/ssl_key_logger_impl.h"
+#include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h"
+#include "net/tools/naive/naive_command_line.h"
+#include "net/tools/naive/naive_config.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "net/tools/naive/naive_proxy.h"
+#include "net/tools/naive/naive_proxy_delegate.h"
+#include "net/tools/naive/redirect_resolver.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "url/gurl.h"
+#include "url/scheme_host_port.h"
+#include "url/url_util.h"
+
+#if BUILDFLAG(IS_APPLE)
+#include "base/allocator/early_zone_registration_apple.h"
+#include "base/apple/scoped_nsautorelease_pool.h"
+#endif
+
+namespace {
+
+constexpr int kListenBackLog = 512;
+constexpr int kDefaultMaxSocketsPerPool = 256;
+constexpr int kDefaultMaxSocketsPerGroup = 255;
+constexpr int kExpectedMaxUsers = 8;
+constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("naive", "");
+
+std::unique_ptr<base::Value::Dict> GetConstants() {
+  base::Value::Dict constants_dict = net::GetNetConstants();
+  base::Value::Dict dict;
+  std::string os_type = base::StringPrintf(
+      "%s: %s (%s)", base::SysInfo::OperatingSystemName().c_str(),
+      base::SysInfo::OperatingSystemVersion().c_str(),
+      base::SysInfo::OperatingSystemArchitecture().c_str());
+  dict.Set("os_type", os_type);
+  constants_dict.Set("clientInfo", std::move(dict));
+  return std::make_unique<base::Value::Dict>(std::move(constants_dict));
+}
+}  // namespace
+
+namespace net {
+namespace {
+// NetLog::ThreadSafeObserver implementation that simply prints events
+// to the logs.
+class PrintingLogObserver : public NetLog::ThreadSafeObserver {
+ public:
+  PrintingLogObserver() = default;
+  PrintingLogObserver(const PrintingLogObserver&) = delete;
+  PrintingLogObserver& operator=(const PrintingLogObserver&) = delete;
+
+  ~PrintingLogObserver() override {
+    // This is guaranteed to be safe as this program is single threaded.
+    net_log()->RemoveObserver(this);
+  }
+
+  // NetLog::ThreadSafeObserver implementation:
+  void OnAddEntry(const NetLogEntry& entry) override {
+    switch (entry.type) {
+      case NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS:
+      case NetLogEventType::SOCKET_POOL_STALLED_MAX_SOCKETS_PER_GROUP:
+      case NetLogEventType::HTTP2_SESSION_STREAM_STALLED_BY_SESSION_SEND_WINDOW:
+      case NetLogEventType::HTTP2_SESSION_STREAM_STALLED_BY_STREAM_SEND_WINDOW:
+      case NetLogEventType::HTTP2_SESSION_STALLED_MAX_STREAMS:
+      case NetLogEventType::HTTP2_STREAM_FLOW_CONTROL_UNSTALLED:
+        break;
+      default:
+        return;
+    }
+    const char* source_type = NetLog::SourceTypeToString(entry.source.type);
+    const char* event_type = NetLogEventTypeToString(entry.type);
+    const char* event_phase = NetLog::EventPhaseToString(entry.phase);
+    base::Value params(entry.ToDict());
+    std::string params_str;
+    base::JSONWriter::Write(params, &params_str);
+    params_str.insert(0, ": ");
+
+    VLOG(1) << source_type << "(" << entry.source.id << "): " << event_type
+            << ": " << event_phase << params_str;
+  }
+};
+}  // namespace
+
+namespace {
+std::unique_ptr<URLRequestContext> BuildCertURLRequestContext(NetLog* net_log) {
+  URLRequestContextBuilder builder;
+
+  builder.DisableHttpCache();
+  builder.set_net_log(net_log);
+
+  ProxyConfig proxy_config;
+  auto proxy_service =
+      ConfiguredProxyResolutionService::CreateWithoutProxyResolver(
+          std::make_unique<ProxyConfigServiceFixed>(
+              ProxyConfigWithAnnotation(proxy_config, kTrafficAnnotation)),
+          net_log);
+  proxy_service->ForceReloadProxyConfig();
+  builder.set_proxy_resolution_service(std::move(proxy_service));
+
+  return builder.Build();
+}
+
+// Builds a URLRequestContext assuming there's only a single loop.
+std::unique_ptr<URLRequestContext> BuildURLRequestContext(
+    const NaiveConfig& config,
+    scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher,
+    NetLog* net_log) {
+  URLRequestContextBuilder builder;
+
+  builder.DisableHttpCache();
+
+  // Overrides HTTP/2 initial window size default values to accommodate
+  // high BDP links.
+  // See net/http/http_network_session.cc for the default values.
+  // Alternative implementations than fixed large windows:
+  // (1) Dynamic window scaling, see
+  //     https://github.com/dotnet/runtime/pull/54755
+  //     and https://grpc.io/blog/grpc-go-perf-improvements/
+  //     This approach estimates throughput and RTT in userspace
+  //     and incurs big architectural complexity.
+  // (2) Obtains TCP receive windows from Linux-specific TCP_INFO.
+  //     This approach is not portable.
+  // Security impact:
+  // This use of non-default settings creates a fingerprinting feature
+  // that is visible to proxy servers, though this is only exploitable
+  // if the proxy servers can be MITM'd.
+
+  constexpr int kMaxBandwidthMBps = 125;
+  constexpr double kTypicalRttSecond = 0.256;
+  constexpr int kMaxBdpMB = kMaxBandwidthMBps * kTypicalRttSecond;
+
+  // The windows size should be twice the BDP because WINDOW_UPDATEs
+  // are sent after half the window is unacknowledged.
+  constexpr int kTypicalWindow = kMaxBdpMB * 2 * 1024 * 1024;
+  HttpNetworkSessionParams http_network_session_params;
+  http_network_session_params.spdy_session_max_recv_window_size =
+      kTypicalWindow * 2;
+  http_network_session_params
+      .http2_settings[spdy::SETTINGS_INITIAL_WINDOW_SIZE] = kTypicalWindow;
+  builder.set_http_network_session_params(http_network_session_params);
+
+  builder.set_net_log(net_log);
+
+  ProxyConfig proxy_config;
+  proxy_config.proxy_rules().type =
+      net::ProxyConfig::ProxyRules::Type::PROXY_LIST;
+  proxy_config.proxy_rules().single_proxies.SetSingleProxyChain(
+      config.proxy_chain);
+  LOG(INFO) << "Proxying via "
+            << proxy_config.proxy_rules().single_proxies.ToDebugString();
+  auto proxy_service =
+      ConfiguredProxyResolutionService::CreateWithoutProxyResolver(
+          std::make_unique<ProxyConfigServiceFixed>(
+              ProxyConfigWithAnnotation(proxy_config, kTrafficAnnotation)),
+          net_log);
+  proxy_service->ForceReloadProxyConfig();
+  builder.set_proxy_resolution_service(std::move(proxy_service));
+
+  if (!config.host_resolver_rules.empty()) {
+    builder.set_host_mapping_rules(config.host_resolver_rules);
+  }
+
+  builder.SetCertVerifier(
+      CertVerifier::CreateDefault(std::move(cert_net_fetcher)));
+
+  builder.set_proxy_delegate(std::make_unique<NaiveProxyDelegate>(
+      config.extra_headers,
+      std::vector<PaddingType>{PaddingType::kVariant1, PaddingType::kNone}));
+
+  if (config.no_post_quantum == true) {
+    struct NoPostQuantum : public SSLConfigService {
+      SSLContextConfig GetSSLContextConfig() override {
+        SSLContextConfig config;
+        config.post_quantum_override = false;
+        return config;
+      }
+
+      bool CanShareConnectionWithClientCerts(std::string_view) const override {
+        return false;
+      }
+    };
+    builder.set_ssl_config_service(std::make_unique<NoPostQuantum>());
+  }
+
+  auto context = builder.Build();
+
+  if (!config.origins_to_force_quic_on.empty()) {
+    auto* quic = context->quic_context()->params();
+    quic->supported_versions = {quic::ParsedQuicVersion::RFCv1()};
+    quic->origins_to_force_quic_on.insert(
+        config.origins_to_force_quic_on.begin(),
+        config.origins_to_force_quic_on.end());
+  }
+
+  for (const auto& [k, v] : config.auth_store) {
+    auto* session = context->http_transaction_factory()->GetSession();
+    auto* auth_cache = session->http_auth_cache();
+    auth_cache->Add(k, HttpAuth::AUTH_PROXY,
+                    /*realm=*/{}, HttpAuth::AUTH_SCHEME_BASIC, {},
+                    /*challenge=*/"Basic", v, /*path=*/"/");
+  }
+
+  return context;
+}
+}  // namespace
+}  // namespace net
+
+int main(int argc, char* argv[]) {
+  // chrome/app/chrome_exe_main_mac.cc: main()
+#if BUILDFLAG(IS_APPLE)
+  partition_alloc::EarlyMallocZoneRegistration();
+#endif
+
+  // content/app/content_main.cc: RunContentProcess()
+#if BUILDFLAG(IS_APPLE)
+  base::apple::ScopedNSAutoreleasePool pool;
+#endif
+
+  // content/app/content_main.cc: RunContentProcess()
+#if BUILDFLAG(IS_APPLE) && PA_BUILDFLAG(USE_ALLOCATOR_SHIM)
+  // The static initializer function for initializing PartitionAlloc
+  // InitializeDefaultMallocZoneWithPartitionAlloc() would be removed by the
+  // linker if allocator_shim.o is not referenced by the following call,
+  // resulting in undefined behavior of accessing uninitialized TLS
+  // data in PurgeCurrentThread() when PA is enabled.
+  allocator_shim::InitializeAllocatorShim();
+#endif
+
+  // content/app/content_main.cc: RunContentProcess()
+  base::EnableTerminationOnOutOfMemory();
+
+  DuplicateSwitchCollector::InitInstance();
+
+  // content/app/content_main.cc: RunContentProcess()
+  base::CommandLine::Init(argc, argv);
+
+  // content/app/content_main.cc: RunContentProcess()
+  base::EnableTerminationOnHeapCorruption();
+
+  // content/app/content_main.cc: RunContentProcess()
+  //   content/app/content_main_runner_impl.cc: Initialize()
+  base::AtExitManager exit_manager;
+  std::string process_type = "";
+  base::allocator::PartitionAllocSupport::Get()->ReconfigureEarlyish(
+      process_type);
+
+  // content/app/content_main.cc: RunContentProcess()
+  //   content/app/content_main_runner_impl.cc: Initialize()
+  // If we are on a platform where the default allocator is overridden (e.g.
+  // with PartitionAlloc on most platforms) smoke-tests that the overriding
+  // logic is working correctly. If not causes a hard crash, as its unexpected
+  // absence has security implications.
+  CHECK(base::allocator::IsAllocatorInitialized());
+
+  // content/app/content_main.cc: RunContentProcess()
+  //   content/app/content_main_runner_impl.cc: Run()
+  base::FeatureList::InitInstance("PartitionConnectionsByNetworkIsolationKey",
+                                  std::string());
+
+  base::allocator::PartitionAllocSupport::Get()
+      ->ReconfigureAfterFeatureListInit(/*process_type=*/"");
+
+  base::SingleThreadTaskExecutor io_task_executor(base::MessagePumpType::IO);
+  base::ThreadPoolInstance::CreateAndStartWithDefaultParams("naive");
+
+  base::allocator::PartitionAllocSupport::Get()->ReconfigureAfterTaskRunnerInit(
+      process_type);
+
+  url::AddStandardScheme("quic",
+                         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
+  url::AddStandardScheme("socks",
+                         url::SCHEME_WITH_HOST_PORT_AND_USER_INFORMATION);
+  url::AddStandardScheme("redir", url::SCHEME_WITH_HOST_AND_PORT);
+  net::ClientSocketPoolManager::set_max_sockets_per_pool(
+      net::HttpNetworkSession::NORMAL_SOCKET_POOL,
+      kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
+  net::ClientSocketPoolManager::set_max_sockets_per_proxy_chain(
+      net::HttpNetworkSession::NORMAL_SOCKET_POOL,
+      kDefaultMaxSocketsPerPool * kExpectedMaxUsers);
+  net::ClientSocketPoolManager::set_max_sockets_per_group(
+      net::HttpNetworkSession::NORMAL_SOCKET_POOL,
+      kDefaultMaxSocketsPerGroup * kExpectedMaxUsers);
+
+  const auto& proc = *base::CommandLine::ForCurrentProcess();
+  const auto& args = proc.GetArgs();
+  base::Value::Dict config_dict;
+  if (args.empty() && proc.argv().size() >= 2) {
+    config_dict = GetSwitchesAsValue(proc);
+  } else {
+    base::FilePath config_file;
+    if (!args.empty()) {
+      config_file = base::FilePath(args[0]);
+    } else {
+      config_file = base::FilePath::FromUTF8Unsafe("config.json");
+    }
+    JSONFileValueDeserializer reader(config_file);
+    int error_code;
+    std::string error_message;
+    std::unique_ptr<base::Value> value =
+        reader.Deserialize(&error_code, &error_message);
+    if (value == nullptr) {
+      std::cerr << "Error reading " << config_file << ": (" << error_code
+                << ") " << error_message << std::endl;
+      return EXIT_FAILURE;
+    }
+    if (const base::Value::Dict* dict = value->GetIfDict()) {
+      config_dict = dict->Clone();
+    }
+  }
+
+  if (config_dict.contains("h") || config_dict.contains("help")) {
+    std::cout << "Usage: naive { OPTIONS | config.json }\n"
+                 "\n"
+                 "Options:\n"
+                 "-h, --help                 Show this message\n"
+                 "--version                  Print version\n"
+                 "--listen=<proto>://[addr][:port] [--listen=...]\n"
+                 "                           proto: socks, http\n"
+                 "                                  redir (Linux only)\n"
+                 "--proxy=<proto>://[<user>:<pass>@]<hostname>[:<port>]\n"
+                 "                           proto: https, quic\n"
+                 "--insecure-concurrency=<N> Use N connections, insecure\n"
+                 "--extra-headers=...        Extra headers split by CRLF\n"
+                 "--host-resolver-rules=...  Resolver rules\n"
+                 "--resolver-range=...       Redirect resolver range\n"
+                 "--log[=<path>]             Log to stderr, or file\n"
+                 "--log-net-log=<path>       Save NetLog\n"
+                 "--ssl-key-log-file=<path>  Save SSL keys for Wireshark\n"
+                 "--no-post-quantum          No post-quantum key agreement\n"
+              << std::endl;
+    exit(EXIT_SUCCESS);
+  }
+
+  if (config_dict.contains("version")) {
+    std::cout << "naive " << version_info::GetVersionNumber() << std::endl;
+    exit(EXIT_SUCCESS);
+  }
+
+  net::NaiveConfig config;
+  if (!config.Parse(config_dict)) {
+    return EXIT_FAILURE;
+  }
+  CHECK(logging::InitLogging(config.log));
+
+  if (!config.ssl_key_log_file.empty()) {
+    net::SSLClientSocket::SetSSLKeyLogger(
+        std::make_unique<net::SSLKeyLoggerImpl>(config.ssl_key_log_file));
+  }
+
+  // The declaration order for net_log and printing_log_observer is
+  // important. The destructor of PrintingLogObserver removes itself
+  // from net_log, so net_log must be available for entire lifetime of
+  // printing_log_observer.
+  net::NetLog* net_log = net::NetLog::Get();
+  std::unique_ptr<net::FileNetLogObserver> observer;
+  if (!config.log_net_log.empty()) {
+    observer = net::FileNetLogObserver::CreateUnbounded(
+        config.log_net_log, net::NetLogCaptureMode::kDefault, GetConstants());
+    observer->StartObserving(net_log);
+  }
+
+  // Avoids net log overhead if verbose logging is disabled.
+  std::unique_ptr<net::PrintingLogObserver> printing_log_observer;
+  if (config.log.logging_dest != logging::LOG_NONE && VLOG_IS_ON(1)) {
+    printing_log_observer = std::make_unique<net::PrintingLogObserver>();
+    net_log->AddObserver(printing_log_observer.get(),
+                         net::NetLogCaptureMode::kDefault);
+  }
+
+  auto cert_context = net::BuildCertURLRequestContext(net_log);
+  scoped_refptr<net::CertNetFetcherURLRequest> cert_net_fetcher;
+  // The builtin verifier is supported but not enabled by default on Mac,
+  // falling back to CreateSystemVerifyProc() which drops the net fetcher,
+  // causing a DCHECK in ~CertNetFetcherURLRequest().
+  // See CertVerifier::CreateDefaultWithoutCaching() and
+  // CertVerifyProc::CreateSystemVerifyProc() for the build flags.
+#if BUILDFLAG(CHROME_ROOT_STORE_SUPPORTED) || BUILDFLAG(IS_FUCHSIA) || \
+    BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
+  cert_net_fetcher = base::MakeRefCounted<net::CertNetFetcherURLRequest>();
+  cert_net_fetcher->SetURLRequestContext(cert_context.get());
+#endif
+  auto context =
+      net::BuildURLRequestContext(config, std::move(cert_net_fetcher), net_log);
+  auto* session = context->http_transaction_factory()->GetSession();
+
+  std::vector<std::unique_ptr<net::NaiveProxy>> naive_proxies;
+  std::unique_ptr<net::RedirectResolver> resolver;
+
+  for (const net::NaiveListenConfig& listen_config : config.listen) {
+    auto listen_socket =
+        std::make_unique<net::TCPServerSocket>(net_log, net::NetLogSource());
+
+    int result = listen_socket->ListenWithAddressAndPort(
+        listen_config.addr, listen_config.port, kListenBackLog);
+    if (result != net::OK) {
+      LOG(ERROR) << "Failed to listen on "
+                 << net::ToString(listen_config.protocol) << "://"
+                 << listen_config.addr << " " << listen_config.port << ": "
+                 << net::ErrorToShortString(result);
+      return EXIT_FAILURE;
+    }
+    LOG(INFO) << "Listening on " << net::ToString(listen_config.protocol)
+              << "://" << listen_config.addr << ":" << listen_config.port;
+
+    if (resolver == nullptr &&
+        listen_config.protocol == net::ClientProtocol::kRedir) {
+      auto resolver_socket =
+          std::make_unique<net::UDPServerSocket>(net_log, net::NetLogSource());
+      resolver_socket->AllowAddressReuse();
+      net::IPAddress listen_addr;
+      if (!listen_addr.AssignFromIPLiteral(listen_config.addr)) {
+        LOG(ERROR) << "Failed to open resolver: " << listen_config.addr;
+        return EXIT_FAILURE;
+      }
+
+      result = resolver_socket->Listen(
+          net::IPEndPoint(listen_addr, listen_config.port));
+      if (result != net::OK) {
+        LOG(ERROR) << "Failed to open resolver: "
+                   << net::ErrorToShortString(result);
+        return EXIT_FAILURE;
+      }
+
+      resolver = std::make_unique<net::RedirectResolver>(
+          std::move(resolver_socket), config.resolver_range,
+          config.resolver_prefix);
+    }
+
+    auto naive_proxy = std::make_unique<net::NaiveProxy>(
+        std::move(listen_socket), listen_config.protocol, listen_config.user,
+        listen_config.pass, config.insecure_concurrency, resolver.get(),
+        session, kTrafficAnnotation,
+        std::vector<net::PaddingType>{net::PaddingType::kVariant1,
+                                      net::PaddingType::kNone});
+    naive_proxies.push_back(std::move(naive_proxy));
+  }
+
+  base::RunLoop().Run();
+
+  return EXIT_SUCCESS;
+}
diff --git a/src/net/tools/naive/naive_proxy_delegate.cc b/src/net/tools/naive/naive_proxy_delegate.cc
new file mode 100644
index 0000000000..6a199fb932
--- /dev/null
+++ b/src/net/tools/naive/naive_proxy_delegate.cc
@@ -0,0 +1,207 @@
+// Copyright 2020 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "net/tools/naive/naive_proxy_delegate.h"
+
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include "base/logging.h"
+#include "base/rand_util.h"
+#include "base/strings/string_util.h"
+#include "net/base/proxy_string_util.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/third_party/quiche/src/quiche/http2/hpack/hpack_constants.h"
+
+namespace net {
+namespace {
+bool g_nonindex_codes_initialized;
+uint8_t g_nonindex_codes[17];
+}  // namespace
+
+void InitializeNonindexCodes() {
+  if (g_nonindex_codes_initialized)
+    return;
+  g_nonindex_codes_initialized = true;
+  unsigned i = 0;
+  for (const auto& symbol : spdy::HpackHuffmanCodeVector()) {
+    if (symbol.id >= 0x20 && symbol.id <= 0x7f && symbol.length >= 8) {
+      g_nonindex_codes[i++] = symbol.id;
+      if (i >= sizeof(g_nonindex_codes))
+        break;
+    }
+  }
+  CHECK(i == sizeof(g_nonindex_codes));
+}
+
+void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len) {
+  DCHECK(g_nonindex_codes_initialized);
+  int first = len < 16 ? len : 16;
+  for (int i = 0; i < first; i++) {
+    buf[i] = g_nonindex_codes[unique_bits & 0b1111];
+    unique_bits >>= 4;
+  }
+  for (int i = first; i < len; i++) {
+    buf[i] = g_nonindex_codes[16];
+  }
+}
+
+NaiveProxyDelegate::NaiveProxyDelegate(
+    const HttpRequestHeaders& extra_headers,
+    const std::vector<PaddingType>& supported_padding_types)
+    : extra_headers_(extra_headers) {
+  InitializeNonindexCodes();
+
+  std::vector<std::string_view> padding_type_strs;
+  for (PaddingType padding_type : supported_padding_types) {
+    padding_type_strs.push_back(ToString(padding_type));
+  }
+  extra_headers_.SetHeader(kPaddingTypeRequestHeader,
+                           base::JoinString(padding_type_strs, ", "));
+}
+
+NaiveProxyDelegate::~NaiveProxyDelegate() = default;
+
+Error NaiveProxyDelegate::OnBeforeTunnelRequest(
+    const ProxyChain& proxy_chain,
+    size_t chain_index,
+    HttpRequestHeaders* extra_headers) {
+  // Not possible to negotiate padding capability given the underlying
+  // protocols.
+  if (proxy_chain.is_direct())
+    return OK;
+  const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
+  if (proxy_server.is_socks())
+    return OK;
+
+  // Only the last server is attempted for padding
+  // because proxy chaining will corrupt the padding.
+  if (chain_index != proxy_chain.length() - 1)
+    return OK;
+
+  // Sends client-side padding header regardless of server support
+  std::string padding(base::RandInt(16, 32), '~');
+  FillNonindexHeaderValue(base::RandUint64(), &padding[0], padding.size());
+  extra_headers->SetHeader(kPaddingHeader, padding);
+
+  // Enables Fast Open in H2/H3 proxy client socket once the state of server
+  // padding support is known.
+  if (padding_type_by_server_[proxy_server].has_value()) {
+    extra_headers->SetHeader("fastopen", "1");
+  }
+  extra_headers->MergeFrom(extra_headers_);
+
+  return OK;
+}
+
+std::optional<PaddingType> NaiveProxyDelegate::ParsePaddingHeaders(
+    const HttpResponseHeaders& headers) {
+  bool has_padding = headers.HasHeader(kPaddingHeader);
+  std::string padding_type_reply;
+  bool has_padding_type_reply =
+      headers.GetNormalizedHeader(kPaddingTypeReplyHeader, &padding_type_reply);
+
+  if (!has_padding_type_reply) {
+    // Backward compatibility with before kVariant1 when the padding-version
+    // header does not exist.
+    if (has_padding) {
+      return PaddingType::kVariant1;
+    } else {
+      return PaddingType::kNone;
+    }
+  }
+  std::optional<PaddingType> padding_type =
+      ParsePaddingType(padding_type_reply);
+  if (!padding_type.has_value()) {
+    LOG(ERROR) << "Received invalid padding type: " << padding_type_reply;
+  }
+  return padding_type;
+}
+
+Error NaiveProxyDelegate::OnTunnelHeadersReceived(
+    const ProxyChain& proxy_chain,
+    size_t chain_index,
+    const HttpResponseHeaders& response_headers) {
+  // Not possible to negotiate padding capability given the underlying
+  // protocols.
+  if (proxy_chain.is_direct())
+    return OK;
+  const ProxyServer& proxy_server = proxy_chain.GetProxyServer(chain_index);
+  if (proxy_server.is_socks())
+    return OK;
+
+  // Only the last server is attempted for padding
+  // because proxy chaining will corrupt the padding.
+  if (chain_index != proxy_chain.length() - 1)
+    return OK;
+
+  // Detects server padding support, even if it changes dynamically.
+  std::optional<PaddingType> new_padding_type =
+      ParsePaddingHeaders(response_headers);
+  if (!new_padding_type.has_value()) {
+    return ERR_INVALID_RESPONSE;
+  }
+  std::optional<PaddingType>& padding_type =
+      padding_type_by_server_[proxy_server];
+  if (!padding_type.has_value() || padding_type != new_padding_type) {
+    LOG(INFO) << ProxyServerToProxyUri(proxy_server)
+              << " negotiated padding type: "
+              << ToReadableString(*new_padding_type);
+    padding_type = new_padding_type;
+  }
+  return OK;
+}
+
+std::optional<PaddingType> NaiveProxyDelegate::GetProxyChainPaddingType(
+    const ProxyChain& proxy_chain) {
+  // Not possible to negotiate padding capability given the underlying
+  // protocols.
+  if (proxy_chain.is_direct())
+    return PaddingType::kNone;
+  if (proxy_chain.Last().is_socks())
+    return PaddingType::kNone;
+  return padding_type_by_server_[proxy_chain.Last()];
+}
+
+PaddingDetectorDelegate::PaddingDetectorDelegate(
+    NaiveProxyDelegate* naive_proxy_delegate,
+    const ProxyChain& proxy_chain,
+    ClientProtocol client_protocol)
+    : naive_proxy_delegate_(naive_proxy_delegate),
+      proxy_chain_(proxy_chain),
+      client_protocol_(client_protocol) {}
+
+PaddingDetectorDelegate::~PaddingDetectorDelegate() = default;
+
+void PaddingDetectorDelegate::SetClientPaddingType(PaddingType padding_type) {
+  detected_client_padding_type_ = padding_type;
+}
+
+std::optional<PaddingType> PaddingDetectorDelegate::GetClientPaddingType() {
+  // Not possible to negotiate padding capability given the underlying
+  // protocols.
+  if (client_protocol_ == ClientProtocol::kSocks5) {
+    return PaddingType::kNone;
+  } else if (client_protocol_ == ClientProtocol::kRedir) {
+    return PaddingType::kNone;
+  }
+
+  return detected_client_padding_type_;
+}
+
+std::optional<PaddingType> PaddingDetectorDelegate::GetServerPaddingType() {
+  if (cached_server_padding_type_.has_value())
+    return cached_server_padding_type_;
+  cached_server_padding_type_ =
+      naive_proxy_delegate_->GetProxyChainPaddingType(proxy_chain_);
+  return cached_server_padding_type_;
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/naive_proxy_delegate.h b/src/net/tools/naive/naive_proxy_delegate.h
new file mode 100644
index 0000000000..1ccd4ec76d
--- /dev/null
+++ b/src/net/tools/naive/naive_proxy_delegate.h
@@ -0,0 +1,101 @@
+// Copyright 2020 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifndef NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_
+#define NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_
+
+#include <cstdint>
+#include <map>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include "net/base/net_errors.h"
+#include "net/base/proxy_chain.h"
+#include "net/base/proxy_delegate.h"
+#include "net/http/http_request_headers.h"
+#include "net/proxy_resolution/proxy_retry_info.h"
+#include "net/tools/naive/naive_protocol.h"
+#include "url/gurl.h"
+
+namespace net {
+
+void InitializeNonindexCodes();
+// |unique_bits| SHOULD have relatively unique values.
+void FillNonindexHeaderValue(uint64_t unique_bits, char* buf, int len);
+
+class ProxyInfo;
+
+class NaiveProxyDelegate : public ProxyDelegate {
+ public:
+  NaiveProxyDelegate(const HttpRequestHeaders& extra_headers,
+                     const std::vector<PaddingType>& supported_padding_types);
+  ~NaiveProxyDelegate() override;
+
+  void OnResolveProxy(const GURL& url,
+                      const NetworkAnonymizationKey& network_anonymization_key,
+                      const std::string& method,
+                      const ProxyRetryInfoMap& proxy_retry_info,
+                      ProxyInfo* result) override {}
+  void OnFallback(const ProxyChain& bad_proxy, int net_error) override {}
+  void OnSuccessfulRequestAfterFailures(
+      const ProxyRetryInfoMap& proxy_retry_info) override {}
+
+  // This only affects h2 proxy client socket.
+  Error OnBeforeTunnelRequest(const ProxyChain& proxy_chain,
+                              size_t chain_index,
+                              HttpRequestHeaders* extra_headers) override;
+
+  Error OnTunnelHeadersReceived(
+      const ProxyChain& proxy_chain,
+      size_t chain_index,
+      const HttpResponseHeaders& response_headers) override;
+
+  void SetProxyResolutionService(
+      ProxyResolutionService* proxy_resolution_service) override {}
+
+  // Returns empty if the padding type has not been negotiated.
+  std::optional<PaddingType> GetProxyChainPaddingType(
+      const ProxyChain& proxy_chain);
+
+ private:
+  std::optional<PaddingType> ParsePaddingHeaders(
+      const HttpResponseHeaders& headers);
+
+  HttpRequestHeaders extra_headers_;
+
+  // Empty value means padding type has not been negotiated.
+  std::map<ProxyServer, std::optional<PaddingType>> padding_type_by_server_;
+};
+
+class ClientPaddingDetectorDelegate {
+ public:
+  virtual ~ClientPaddingDetectorDelegate() = default;
+
+  virtual void SetClientPaddingType(PaddingType padding_type) = 0;
+};
+
+class PaddingDetectorDelegate : public ClientPaddingDetectorDelegate {
+ public:
+  PaddingDetectorDelegate(NaiveProxyDelegate* naive_proxy_delegate,
+                          const ProxyChain& proxy_chain,
+                          ClientProtocol client_protocol);
+  ~PaddingDetectorDelegate() override;
+
+  std::optional<PaddingType> GetClientPaddingType();
+  std::optional<PaddingType> GetServerPaddingType();
+  void SetClientPaddingType(PaddingType padding_type) override;
+
+ private:
+  NaiveProxyDelegate* naive_proxy_delegate_;
+  const ProxyChain& proxy_chain_;
+  ClientProtocol client_protocol_;
+
+  std::optional<PaddingType> detected_client_padding_type_;
+  // The result is only cached during one connection, so it's still dynamically
+  // updated in the following connections after server changes support.
+  std::optional<PaddingType> cached_server_padding_type_;
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_NAIVE_PROXY_DELEGATE_H_
diff --git a/src/net/tools/naive/redirect_resolver.cc b/src/net/tools/naive/redirect_resolver.cc
new file mode 100644
index 0000000000..6a24deda61
--- /dev/null
+++ b/src/net/tools/naive/redirect_resolver.cc
@@ -0,0 +1,243 @@
+// Copyright 2019 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/tools/naive/redirect_resolver.h"
+
+#include <cstring>
+#include <iterator>
+#include <utility>
+
+#include "base/logging.h"
+#include "base/task/single_thread_task_runner.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/url_util.h"
+#include "net/dns/dns_names_util.h"
+#include "net/dns/dns_query.h"
+#include "net/dns/dns_response.h"
+#include "net/dns/dns_util.h"
+#include "net/socket/datagram_server_socket.h"
+#include "third_party/abseil-cpp/absl/types/optional.h"
+
+namespace {
+constexpr int kUdpReadBufferSize = 1024;
+constexpr int kResolutionTtl = 60;
+constexpr int kResolutionRecycleTime = 60 * 5;
+
+std::string PackedIPv4ToString(uint32_t addr) {
+  return net::IPAddress(addr >> 24, addr >> 16, addr >> 8, addr).ToString();
+}
+}  // namespace
+
+namespace net {
+
+Resolution::Resolution() = default;
+
+Resolution::~Resolution() = default;
+
+RedirectResolver::RedirectResolver(std::unique_ptr<DatagramServerSocket> socket,
+                                   const IPAddress& range,
+                                   size_t prefix)
+    : socket_(std::move(socket)),
+      range_(range),
+      prefix_(prefix),
+      offset_(0),
+      buffer_(base::MakeRefCounted<IOBufferWithSize>(kUdpReadBufferSize)) {
+  DCHECK(socket_);
+  // Start accepting connections in next run loop in case when delegate is not
+  // ready to get callbacks.
+  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
+      FROM_HERE, base::BindOnce(&RedirectResolver::DoRead,
+                                weak_ptr_factory_.GetWeakPtr()));
+}
+
+RedirectResolver::~RedirectResolver() = default;
+
+void RedirectResolver::DoRead() {
+  for (;;) {
+    int rv = socket_->RecvFrom(
+        buffer_.get(), kUdpReadBufferSize, &recv_address_,
+        base::BindOnce(&RedirectResolver::OnRecv, base::Unretained(this)));
+    if (rv == ERR_IO_PENDING)
+      return;
+    rv = HandleReadResult(rv);
+    if (rv == ERR_IO_PENDING)
+      return;
+    if (rv < 0) {
+      LOG(INFO) << "DoRead: ignoring error " << ErrorToShortString(rv);
+    }
+  }
+}
+
+void RedirectResolver::OnRecv(int result) {
+  int rv;
+  rv = HandleReadResult(result);
+  if (rv == ERR_IO_PENDING)
+    return;
+  if (rv < 0) {
+    LOG(INFO) << "OnRecv: ignoring error " << ErrorToShortString(rv);
+  }
+
+  DoRead();
+}
+
+void RedirectResolver::OnSend(int result) {
+  if (result < 0) {
+    LOG(INFO) << "OnSend: ignoring error " << ErrorToShortString(result);
+  }
+
+  DoRead();
+}
+
+int RedirectResolver::HandleReadResult(int result) {
+  if (result < 0)
+    return result;
+
+  DnsQuery query(buffer_.get());
+  if (!query.Parse(result)) {
+    LOG(INFO) << "Malformed DNS query from " << recv_address_.ToString();
+    return ERR_INVALID_ARGUMENT;
+  }
+
+  auto name_or = dns_names_util::NetworkToDottedName(query.qname());
+  DnsResponse response;
+  absl::optional<DnsQuery> query_opt;
+  query_opt.emplace(query.id(), query.qname(), query.qtype());
+  if (!name_or || !IsCanonicalizedHostCompliant(name_or.value())) {
+    response =
+        DnsResponse(query.id(), /*is_authoritative=*/false, /*answers=*/{},
+                    /*authority_records=*/{}, /*additional_records=*/{},
+                    query_opt, dns_protocol::kRcodeFORMERR);
+  } else if (query.qtype() != dns_protocol::kTypeA) {
+    response =
+        DnsResponse(query.id(), /*is_authoritative=*/false, /*answers=*/{},
+                    /*authority_records=*/{}, /*additional_records=*/{},
+                    query_opt, dns_protocol::kRcodeNOTIMP);
+  } else {
+    Resolution res;
+
+    const auto& name = name_or.value();
+
+    auto by_name_lookup = resolution_by_name_.emplace(name, resolutions_.end());
+    auto by_name = by_name_lookup.first;
+    bool has_name = !by_name_lookup.second;
+    if (has_name) {
+      auto res_it = by_name->second;
+      auto by_addr = res_it->by_addr;
+      uint32_t addr = res_it->addr;
+
+      resolutions_.erase(res_it);
+      resolutions_.emplace_back();
+      res_it = std::prev(resolutions_.end());
+
+      by_name->second = res_it;
+      by_addr->second = res_it;
+      res_it->addr = addr;
+      res_it->name = name;
+      res_it->time = base::TimeTicks::Now();
+      res_it->by_name = by_name;
+      res_it->by_addr = by_addr;
+    } else {
+      uint32_t addr = (range_.bytes()[0] << 24) | (range_.bytes()[1] << 16) |
+                      (range_.bytes()[2] << 8) | range_.bytes()[3];
+      uint32_t subnet = ~0U >> prefix_;
+      addr &= ~subnet;
+      addr += offset_;
+      offset_ = (offset_ + 1) & subnet;
+
+      auto by_addr_lookup =
+          resolution_by_addr_.emplace(addr, resolutions_.end());
+      auto by_addr = by_addr_lookup.first;
+      bool has_addr = !by_addr_lookup.second;
+      if (has_addr) {
+        // Too few available addresses. Overwrites old one.
+        auto res_it = by_addr->second;
+
+        LOG(INFO) << "Overwrite " << res_it->name << " "
+                  << PackedIPv4ToString(res_it->addr) << " with " << name << " "
+                  << PackedIPv4ToString(addr);
+        resolution_by_name_.erase(res_it->by_name);
+        resolutions_.erase(res_it);
+        resolutions_.emplace_back();
+        res_it = std::prev(resolutions_.end());
+
+        by_name->second = res_it;
+        by_addr->second = res_it;
+        res_it->addr = addr;
+        res_it->name = name;
+        res_it->time = base::TimeTicks::Now();
+        res_it->by_name = by_name;
+        res_it->by_addr = by_addr;
+      } else {
+        LOG(INFO) << "Add " << name << " " << PackedIPv4ToString(addr);
+        resolutions_.emplace_back();
+        auto res_it = std::prev(resolutions_.end());
+
+        by_name->second = res_it;
+        by_addr->second = res_it;
+        res_it->addr = addr;
+        res_it->name = name;
+        res_it->time = base::TimeTicks::Now();
+        res_it->by_name = by_name;
+        res_it->by_addr = by_addr;
+
+        // Collects garbage.
+        auto now = base::TimeTicks::Now();
+        for (auto it = resolutions_.begin();
+             it != resolutions_.end() &&
+             (now - it->time).InSeconds() > kResolutionRecycleTime;) {
+          auto next = std::next(it);
+          LOG(INFO) << "Drop " << it->name << " "
+                    << PackedIPv4ToString(it->addr);
+          resolution_by_name_.erase(it->by_name);
+          resolution_by_addr_.erase(it->by_addr);
+          resolutions_.erase(it);
+          it = next;
+        }
+      }
+    }
+
+    DnsResourceRecord record;
+    record.name = name;
+    record.type = dns_protocol::kTypeA;
+    record.klass = dns_protocol::kClassIN;
+    record.ttl = kResolutionTtl;
+    uint32_t addr = by_name->second->addr;
+    record.SetOwnedRdata(IPAddressToPackedString(
+        IPAddress(addr >> 24, addr >> 16, addr >> 8, addr)));
+    response = DnsResponse(query.id(), /*is_authoritative=*/false,
+                           /*answers=*/{std::move(record)},
+                           /*authority_records=*/{}, /*additional_records=*/{},
+                           query_opt);
+  }
+  int size = response.io_buffer_size();
+  if (size > buffer_->size() || !response.io_buffer()) {
+    return ERR_NO_BUFFER_SPACE;
+  }
+  std::memcpy(buffer_->data(), response.io_buffer()->data(), size);
+
+  return socket_->SendTo(
+      buffer_.get(), size, recv_address_,
+      base::BindOnce(&RedirectResolver::OnSend, base::Unretained(this)));
+}
+
+bool RedirectResolver::IsInResolvedRange(const IPAddress& address) const {
+  if (!address.IsIPv4())
+    return false;
+  return IPAddressMatchesPrefix(address, range_, prefix_);
+}
+
+std::string RedirectResolver::FindNameByAddress(
+    const IPAddress& address) const {
+  if (!address.IsIPv4())
+    return {};
+  uint32_t addr = (address.bytes()[0] << 24) | (address.bytes()[1] << 16) |
+                  (address.bytes()[2] << 8) | address.bytes()[3];
+  auto by_addr = resolution_by_addr_.find(addr);
+  if (by_addr == resolution_by_addr_.end())
+    return {};
+  return by_addr->second->name;
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/redirect_resolver.h b/src/net/tools/naive/redirect_resolver.h
new file mode 100644
index 0000000000..3b78c4111e
--- /dev/null
+++ b/src/net/tools/naive/redirect_resolver.h
@@ -0,0 +1,69 @@
+// Copyright 2019 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_NAIVE_REDIRECT_RESOLVER_H_
+#define NET_TOOLS_NAIVE_REDIRECT_RESOLVER_H_
+
+#include <cstdint>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time/time.h"
+#include "net/base/ip_address.h"
+#include "net/base/ip_endpoint.h"
+
+namespace net {
+
+class DatagramServerSocket;
+class IOBufferWithSize;
+
+struct Resolution {
+  Resolution();
+  ~Resolution();
+
+  uint32_t addr;
+  std::string name;
+  base::TimeTicks time;
+  std::map<std::string, std::list<Resolution>::iterator>::iterator by_name;
+  std::map<uint32_t, std::list<Resolution>::iterator>::iterator by_addr;
+};
+
+class RedirectResolver {
+ public:
+  RedirectResolver(std::unique_ptr<DatagramServerSocket> socket,
+                   const IPAddress& range,
+                   size_t prefix);
+  ~RedirectResolver();
+  RedirectResolver(const RedirectResolver&) = delete;
+  RedirectResolver& operator=(const RedirectResolver&) = delete;
+
+  bool IsInResolvedRange(const IPAddress& address) const;
+  std::string FindNameByAddress(const IPAddress& address) const;
+
+ private:
+  void DoRead();
+  void OnRecv(int result);
+  void OnSend(int result);
+  int HandleReadResult(int result);
+
+  std::unique_ptr<DatagramServerSocket> socket_;
+  IPAddress range_;
+  size_t prefix_;
+  uint32_t offset_;
+  scoped_refptr<IOBufferWithSize> buffer_;
+  IPEndPoint recv_address_;
+
+  std::map<std::string, std::list<Resolution>::iterator> resolution_by_name_;
+  std::map<uint32_t, std::list<Resolution>::iterator> resolution_by_addr_;
+  std::list<Resolution> resolutions_;
+
+  base::WeakPtrFactory<RedirectResolver> weak_ptr_factory_{this};
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_REDIRECT_RESOLVER_H_
diff --git a/src/net/tools/naive/socks5_server_socket.cc b/src/net/tools/naive/socks5_server_socket.cc
new file mode 100644
index 0000000000..139b497475
--- /dev/null
+++ b/src/net/tools/naive/socks5_server_socket.cc
@@ -0,0 +1,664 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+#ifdef UNSAFE_BUFFERS_BUILD
+// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
+#pragma allow_unsafe_buffers
+#endif
+
+#include "net/tools/naive/socks5_server_socket.h"
+
+#include <cstring>
+#include <utility>
+
+#include "base/functional/bind.h"
+#include "base/functional/callback_helpers.h"
+#include "base/logging.h"
+#include "base/sys_byteorder.h"
+#include "net/base/ip_address.h"
+#include "net/base/net_errors.h"
+#include "net/base/sys_addrinfo.h"
+#include "net/log/net_log.h"
+#include "net/log/net_log_event_type.h"
+
+namespace net {
+
+enum SocksCommandType {
+  kCommandConnect = 0x01,
+  kCommandBind = 0x02,
+  kCommandUDPAssociate = 0x03,
+};
+
+static constexpr unsigned int kGreetReadHeaderSize = 2;
+static constexpr unsigned int kAuthReadHeaderSize = 2;
+static constexpr unsigned int kReadHeaderSize = 5;
+static constexpr char kSOCKS5Version = '\x05';
+static constexpr char kSOCKS5Reserved = '\x00';
+static constexpr char kAuthMethodNone = '\x00';
+static constexpr char kAuthMethodUserPass = '\x02';
+static constexpr char kAuthMethodNoAcceptable = '\xff';
+static constexpr char kSubnegotiationVersion = '\x01';
+static constexpr char kAuthStatusSuccess = '\x00';
+static constexpr char kAuthStatusFailure = '\xff';
+static constexpr char kReplySuccess = '\x00';
+static constexpr char kReplyCommandNotSupported = '\x07';
+
+static_assert(sizeof(struct in_addr) == 4, "incorrect system size of IPv4");
+static_assert(sizeof(struct in6_addr) == 16, "incorrect system size of IPv6");
+
+Socks5ServerSocket::Socks5ServerSocket(
+    std::unique_ptr<StreamSocket> transport_socket,
+    const std::string& user,
+    const std::string& pass,
+    const NetworkTrafficAnnotationTag& traffic_annotation)
+    : io_callback_(base::BindRepeating(&Socks5ServerSocket::OnIOComplete,
+                                       base::Unretained(this))),
+      transport_(std::move(transport_socket)),
+      next_state_(STATE_NONE),
+      completed_handshake_(false),
+      bytes_sent_(0),
+      was_ever_used_(false),
+      user_(user),
+      pass_(pass),
+      net_log_(transport_->NetLog()),
+      traffic_annotation_(traffic_annotation) {}
+
+Socks5ServerSocket::~Socks5ServerSocket() {
+  Disconnect();
+}
+
+const HostPortPair& Socks5ServerSocket::request_endpoint() const {
+  return request_endpoint_;
+}
+
+int Socks5ServerSocket::Connect(CompletionOnceCallback callback) {
+  DCHECK(transport_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+
+  // If already connected, then just return OK.
+  if (completed_handshake_)
+    return OK;
+
+  net_log_.BeginEvent(NetLogEventType::SOCKS5_CONNECT);
+
+  next_state_ = STATE_GREET_READ;
+  buffer_.clear();
+
+  int rv = DoLoop(OK);
+  if (rv == ERR_IO_PENDING) {
+    user_callback_ = std::move(callback);
+  } else {
+    net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_CONNECT, rv);
+  }
+  return rv;
+}
+
+void Socks5ServerSocket::Disconnect() {
+  completed_handshake_ = false;
+  transport_->Disconnect();
+
+  // Reset other states to make sure they aren't mistakenly used later.
+  // These are the states initialized by Connect().
+  next_state_ = STATE_NONE;
+  user_callback_.Reset();
+}
+
+bool Socks5ServerSocket::IsConnected() const {
+  return completed_handshake_ && transport_->IsConnected();
+}
+
+bool Socks5ServerSocket::IsConnectedAndIdle() const {
+  return completed_handshake_ && transport_->IsConnectedAndIdle();
+}
+
+const NetLogWithSource& Socks5ServerSocket::NetLog() const {
+  return net_log_;
+}
+
+bool Socks5ServerSocket::WasEverUsed() const {
+  return was_ever_used_;
+}
+
+NextProto Socks5ServerSocket::GetNegotiatedProtocol() const {
+  if (transport_) {
+    return transport_->GetNegotiatedProtocol();
+  }
+  NOTREACHED();
+  return kProtoUnknown;
+}
+
+bool Socks5ServerSocket::GetSSLInfo(SSLInfo* ssl_info) {
+  if (transport_) {
+    return transport_->GetSSLInfo(ssl_info);
+  }
+  NOTREACHED();
+  return false;
+}
+
+int64_t Socks5ServerSocket::GetTotalReceivedBytes() const {
+  return transport_->GetTotalReceivedBytes();
+}
+
+void Socks5ServerSocket::ApplySocketTag(const SocketTag& tag) {
+  return transport_->ApplySocketTag(tag);
+}
+
+// Read is called by the transport layer above to read. This can only be done
+// if the SOCKS handshake is complete.
+int Socks5ServerSocket::Read(IOBuffer* buf,
+                             int buf_len,
+                             CompletionOnceCallback callback) {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+  DCHECK(callback);
+
+  int rv = transport_->Read(
+      buf, buf_len,
+      base::BindOnce(&Socks5ServerSocket::OnReadWriteComplete,
+                     base::Unretained(this), std::move(callback)));
+  if (rv > 0)
+    was_ever_used_ = true;
+  return rv;
+}
+
+// Write is called by the transport layer. This can only be done if the
+// SOCKS handshake is complete.
+int Socks5ServerSocket::Write(
+    IOBuffer* buf,
+    int buf_len,
+    CompletionOnceCallback callback,
+    const NetworkTrafficAnnotationTag& traffic_annotation) {
+  DCHECK(completed_handshake_);
+  DCHECK_EQ(STATE_NONE, next_state_);
+  DCHECK(!user_callback_);
+  DCHECK(callback);
+
+  int rv = transport_->Write(
+      buf, buf_len,
+      base::BindOnce(&Socks5ServerSocket::OnReadWriteComplete,
+                     base::Unretained(this), std::move(callback)),
+      traffic_annotation);
+  if (rv > 0)
+    was_ever_used_ = true;
+  return rv;
+}
+
+int Socks5ServerSocket::SetReceiveBufferSize(int32_t size) {
+  return transport_->SetReceiveBufferSize(size);
+}
+
+int Socks5ServerSocket::SetSendBufferSize(int32_t size) {
+  return transport_->SetSendBufferSize(size);
+}
+
+void Socks5ServerSocket::DoCallback(int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(user_callback_);
+
+  // Since Run() may result in Read being called,
+  // clear user_callback_ up front.
+  std::move(user_callback_).Run(result);
+}
+
+void Socks5ServerSocket::OnIOComplete(int result) {
+  DCHECK_NE(STATE_NONE, next_state_);
+  int rv = DoLoop(result);
+  if (rv != ERR_IO_PENDING) {
+    net_log_.EndEvent(NetLogEventType::SOCKS5_CONNECT);
+    DoCallback(rv);
+  }
+}
+
+void Socks5ServerSocket::OnReadWriteComplete(CompletionOnceCallback callback,
+                                             int result) {
+  DCHECK_NE(ERR_IO_PENDING, result);
+  DCHECK(callback);
+
+  if (result > 0)
+    was_ever_used_ = true;
+  std::move(callback).Run(result);
+}
+
+int Socks5ServerSocket::DoLoop(int last_io_result) {
+  DCHECK_NE(next_state_, STATE_NONE);
+  int rv = last_io_result;
+  do {
+    State state = next_state_;
+    next_state_ = STATE_NONE;
+    switch (state) {
+      case STATE_GREET_READ:
+        DCHECK_EQ(OK, rv);
+        net_log_.BeginEvent(NetLogEventType::SOCKS5_GREET_READ);
+        rv = DoGreetRead();
+        break;
+      case STATE_GREET_READ_COMPLETE:
+        rv = DoGreetReadComplete(rv);
+        net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_READ,
+                                          rv);
+        break;
+      case STATE_GREET_WRITE:
+        DCHECK_EQ(OK, rv);
+        net_log_.BeginEvent(NetLogEventType::SOCKS5_GREET_WRITE);
+        rv = DoGreetWrite();
+        break;
+      case STATE_GREET_WRITE_COMPLETE:
+        rv = DoGreetWriteComplete(rv);
+        net_log_.EndEventWithNetErrorCode(NetLogEventType::SOCKS5_GREET_WRITE,
+                                          rv);
+        break;
+      case STATE_AUTH_READ:
+        DCHECK_EQ(OK, rv);
+        rv = DoAuthRead();
+        break;
+      case STATE_AUTH_READ_COMPLETE:
+        rv = DoAuthReadComplete(rv);
+        break;
+      case STATE_AUTH_WRITE:
+        DCHECK_EQ(OK, rv);
+        rv = DoAuthWrite();
+        break;
+      case STATE_AUTH_WRITE_COMPLETE:
+        rv = DoAuthWriteComplete(rv);
+        break;
+      case STATE_HANDSHAKE_READ:
+        DCHECK_EQ(OK, rv);
+        net_log_.BeginEvent(NetLogEventType::SOCKS5_HANDSHAKE_READ);
+        rv = DoHandshakeRead();
+        break;
+      case STATE_HANDSHAKE_READ_COMPLETE:
+        rv = DoHandshakeReadComplete(rv);
+        net_log_.EndEventWithNetErrorCode(
+            NetLogEventType::SOCKS5_HANDSHAKE_READ, rv);
+        break;
+      case STATE_HANDSHAKE_WRITE:
+        DCHECK_EQ(OK, rv);
+        net_log_.BeginEvent(NetLogEventType::SOCKS5_HANDSHAKE_WRITE);
+        rv = DoHandshakeWrite();
+        break;
+      case STATE_HANDSHAKE_WRITE_COMPLETE:
+        rv = DoHandshakeWriteComplete(rv);
+        net_log_.EndEventWithNetErrorCode(
+            NetLogEventType::SOCKS5_HANDSHAKE_WRITE, rv);
+        break;
+      default:
+        NOTREACHED() << "bad state";
+        rv = ERR_UNEXPECTED;
+        break;
+    }
+  } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+  return rv;
+}
+
+int Socks5ServerSocket::DoGreetRead() {
+  next_state_ = STATE_GREET_READ_COMPLETE;
+
+  if (buffer_.empty()) {
+    read_header_size_ = kGreetReadHeaderSize;
+  }
+
+  int handshake_buf_len = read_header_size_ - buffer_.size();
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  return transport_->Read(handshake_buf_.get(), handshake_buf_len,
+                          io_callback_);
+}
+
+int Socks5ServerSocket::DoGreetReadComplete(int result) {
+  if (result < 0)
+    return result;
+
+  if (result == 0) {
+    net_log_.AddEvent(
+        NetLogEventType::SOCKS_UNEXPECTEDLY_CLOSED_DURING_GREETING);
+    return ERR_SOCKS_CONNECTION_FAILED;
+  }
+
+  buffer_.append(handshake_buf_->data(), result);
+
+  // When the first few bytes are read, check how many more are required
+  // and accordingly increase them
+  if (buffer_.size() == kGreetReadHeaderSize) {
+    if (buffer_[0] != kSOCKS5Version) {
+      net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
+                                     "version", buffer_[0]);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+    int nmethods = buffer_[1];
+    if (nmethods == 0) {
+      net_log_.AddEvent(NetLogEventType::SOCKS_NO_REQUESTED_AUTH);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+
+    read_header_size_ += nmethods;
+    next_state_ = STATE_GREET_READ;
+    return OK;
+  }
+
+  if (buffer_.size() == read_header_size_) {
+    int nmethods = buffer_[1];
+    char expected_method = kAuthMethodNone;
+    if (!user_.empty() || !pass_.empty()) {
+      expected_method = kAuthMethodUserPass;
+    }
+    void* match =
+        std::memchr(&buffer_[kGreetReadHeaderSize], expected_method, nmethods);
+    if (match) {
+      auth_method_ = expected_method;
+    } else {
+      auth_method_ = kAuthMethodNoAcceptable;
+    }
+    buffer_.clear();
+    next_state_ = STATE_GREET_WRITE;
+    return OK;
+  }
+
+  next_state_ = STATE_GREET_READ;
+  return OK;
+}
+
+int Socks5ServerSocket::DoGreetWrite() {
+  if (buffer_.empty()) {
+    const char write_data[] = {kSOCKS5Version, auth_method_};
+    buffer_ = std::string(write_data, std::size(write_data));
+    bytes_sent_ = 0;
+  }
+
+  next_state_ = STATE_GREET_WRITE_COMPLETE;
+  int handshake_buf_len = buffer_.size() - bytes_sent_;
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  std::memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
+              handshake_buf_len);
+  return transport_->Write(handshake_buf_.get(), handshake_buf_len,
+                           io_callback_, traffic_annotation_);
+}
+
+int Socks5ServerSocket::DoGreetWriteComplete(int result) {
+  if (result < 0)
+    return result;
+
+  bytes_sent_ += result;
+  if (bytes_sent_ == buffer_.size()) {
+    buffer_.clear();
+    if (auth_method_ == kAuthMethodNone) {
+      next_state_ = STATE_HANDSHAKE_READ;
+    } else if (auth_method_ == kAuthMethodUserPass) {
+      next_state_ = STATE_AUTH_READ;
+    } else {
+      net_log_.AddEvent(NetLogEventType::SOCKS_NO_ACCEPTABLE_AUTH);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+  } else {
+    next_state_ = STATE_GREET_WRITE;
+  }
+  return OK;
+}
+
+int Socks5ServerSocket::DoAuthRead() {
+  next_state_ = STATE_AUTH_READ_COMPLETE;
+
+  if (buffer_.empty()) {
+    read_header_size_ = kAuthReadHeaderSize;
+  }
+
+  int handshake_buf_len = read_header_size_ - buffer_.size();
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  return transport_->Read(handshake_buf_.get(), handshake_buf_len,
+                          io_callback_);
+}
+
+int Socks5ServerSocket::DoAuthReadComplete(int result) {
+  if (result < 0)
+    return result;
+
+  if (result == 0) {
+    return ERR_SOCKS_CONNECTION_FAILED;
+  }
+
+  buffer_.append(handshake_buf_->data(), result);
+
+  // When the first few bytes are read, check how many more are required
+  // and accordingly increase them
+  if (buffer_.size() == kAuthReadHeaderSize) {
+    if (buffer_[0] != kSubnegotiationVersion) {
+      net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
+                                     "version", buffer_[0]);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+    int username_len = buffer_[1];
+    read_header_size_ += username_len + 1;
+    next_state_ = STATE_AUTH_READ;
+    return OK;
+  }
+
+  if (buffer_.size() == read_header_size_) {
+    int username_len = buffer_[1];
+    int password_len = buffer_[kAuthReadHeaderSize + username_len];
+    size_t password_offset = kAuthReadHeaderSize + username_len + 1;
+    if (buffer_.size() == password_offset && password_len != 0) {
+      read_header_size_ += password_len;
+      next_state_ = STATE_AUTH_READ;
+      return OK;
+    }
+
+    if (buffer_.compare(kAuthReadHeaderSize, username_len, user_) == 0 &&
+        buffer_.compare(password_offset, password_len, pass_) == 0) {
+      auth_status_ = kAuthStatusSuccess;
+    } else {
+      auth_status_ = kAuthStatusFailure;
+    }
+    buffer_.clear();
+    next_state_ = STATE_AUTH_WRITE;
+    return OK;
+  }
+
+  next_state_ = STATE_AUTH_READ;
+  return OK;
+}
+
+int Socks5ServerSocket::DoAuthWrite() {
+  if (buffer_.empty()) {
+    const char write_data[] = {kSubnegotiationVersion, auth_status_};
+    buffer_ = std::string(write_data, std::size(write_data));
+    bytes_sent_ = 0;
+  }
+
+  next_state_ = STATE_AUTH_WRITE_COMPLETE;
+  int handshake_buf_len = buffer_.size() - bytes_sent_;
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  std::memcpy(handshake_buf_->data(), &buffer_.data()[bytes_sent_],
+              handshake_buf_len);
+  return transport_->Write(handshake_buf_.get(), handshake_buf_len,
+                           io_callback_, traffic_annotation_);
+}
+
+int Socks5ServerSocket::DoAuthWriteComplete(int result) {
+  if (result < 0)
+    return result;
+
+  bytes_sent_ += result;
+  if (bytes_sent_ == buffer_.size()) {
+    buffer_.clear();
+    if (auth_status_ == kAuthStatusSuccess) {
+      next_state_ = STATE_HANDSHAKE_READ;
+    } else {
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+  } else {
+    next_state_ = STATE_AUTH_WRITE;
+  }
+  return OK;
+}
+
+int Socks5ServerSocket::DoHandshakeRead() {
+  next_state_ = STATE_HANDSHAKE_READ_COMPLETE;
+
+  if (buffer_.empty()) {
+    read_header_size_ = kReadHeaderSize;
+  }
+
+  int handshake_buf_len = read_header_size_ - buffer_.size();
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  return transport_->Read(handshake_buf_.get(), handshake_buf_len,
+                          io_callback_);
+}
+
+int Socks5ServerSocket::DoHandshakeReadComplete(int result) {
+  if (result < 0)
+    return result;
+
+  // The underlying socket closed unexpectedly.
+  if (result == 0) {
+    net_log_.AddEvent(
+        NetLogEventType::SOCKS_UNEXPECTEDLY_CLOSED_DURING_HANDSHAKE);
+    return ERR_SOCKS_CONNECTION_FAILED;
+  }
+
+  buffer_.append(handshake_buf_->data(), result);
+
+  // When the first few bytes are read, check how many more are required
+  // and accordingly increase them
+  if (buffer_.size() == kReadHeaderSize) {
+    if (buffer_[0] != kSOCKS5Version || buffer_[2] != kSOCKS5Reserved) {
+      net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_VERSION,
+                                     "version", buffer_[0]);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+    SocksCommandType command = static_cast<SocksCommandType>(buffer_[1]);
+    if (command == kCommandConnect) {
+      // The proxy replies with success immediately without first connecting
+      // to the requested endpoint.
+      reply_ = kReplySuccess;
+    } else if (command == kCommandBind || command == kCommandUDPAssociate) {
+      reply_ = kReplyCommandNotSupported;
+    } else {
+      net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_UNEXPECTED_COMMAND,
+                                     "commmand", buffer_[1]);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+
+    // We check the type of IP/Domain the server returns and accordingly
+    // increase the size of the request. For domains, we need to read the
+    // size of the domain, so the initial request size is upto the domain
+    // size. Since for IPv4/IPv6 the size is fixed and hence no 'size' is
+    // read, we substract 1 byte from the additional request size.
+    address_type_ = static_cast<SocksEndPointAddressType>(buffer_[3]);
+    if (address_type_ == kEndPointDomain) {
+      address_size_ = static_cast<uint8_t>(buffer_[4]);
+      if (address_size_ == 0) {
+        net_log_.AddEvent(NetLogEventType::SOCKS_ZERO_LENGTH_DOMAIN);
+        return ERR_SOCKS_CONNECTION_FAILED;
+      }
+    } else if (address_type_ == kEndPointResolvedIPv4) {
+      address_size_ = sizeof(struct in_addr);
+      --read_header_size_;
+    } else if (address_type_ == kEndPointResolvedIPv6) {
+      address_size_ = sizeof(struct in6_addr);
+      --read_header_size_;
+    } else {
+      // Aborts connection on unspecified address type.
+      net_log_.AddEventWithIntParams(
+          NetLogEventType::SOCKS_UNKNOWN_ADDRESS_TYPE, "address_type",
+          buffer_[3]);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+
+    read_header_size_ += address_size_ + sizeof(uint16_t);
+    next_state_ = STATE_HANDSHAKE_READ;
+    return OK;
+  }
+
+  // When the final bytes are read, setup handshake.
+  if (buffer_.size() == read_header_size_) {
+    size_t port_start = read_header_size_ - sizeof(uint16_t);
+    uint16_t port_net;
+    std::memcpy(&port_net, &buffer_[port_start], sizeof(uint16_t));
+    uint16_t port_host = base::NetToHost16(port_net);
+
+    size_t address_start = port_start - address_size_;
+    if (address_type_ == kEndPointDomain) {
+      std::string domain(&buffer_[address_start], address_size_);
+      request_endpoint_ = HostPortPair(domain, port_host);
+    } else {
+      IPAddress ip_addr(base::span<const uint8_t>{
+          reinterpret_cast<const uint8_t*>(&buffer_[address_start]),
+          static_cast<size_t>(address_size_)});
+      IPEndPoint endpoint(ip_addr, port_host);
+      request_endpoint_ = HostPortPair::FromIPEndPoint(endpoint);
+    }
+    buffer_.clear();
+    next_state_ = STATE_HANDSHAKE_WRITE;
+    return OK;
+  }
+
+  next_state_ = STATE_HANDSHAKE_READ;
+  return OK;
+}
+
+// Writes the SOCKS handshake data to the underlying socket connection.
+int Socks5ServerSocket::DoHandshakeWrite() {
+  next_state_ = STATE_HANDSHAKE_WRITE_COMPLETE;
+
+  if (buffer_.empty()) {
+    const char write_data[] = {
+        // clang-format off
+        kSOCKS5Version,
+        reply_,
+        kSOCKS5Reserved,
+        kEndPointResolvedIPv4,
+        0x00, 0x00, 0x00, 0x00,  // BND.ADDR
+        0x00, 0x00,  // BND.PORT
+        // clang-format on
+    };
+    buffer_ = std::string(write_data, std::size(write_data));
+    bytes_sent_ = 0;
+  }
+
+  int handshake_buf_len = buffer_.size() - bytes_sent_;
+  DCHECK_LT(0, handshake_buf_len);
+  handshake_buf_ = base::MakeRefCounted<IOBufferWithSize>(handshake_buf_len);
+  std::memcpy(handshake_buf_->data(), &buffer_[bytes_sent_], handshake_buf_len);
+  return transport_->Write(handshake_buf_.get(), handshake_buf_len,
+                           io_callback_, traffic_annotation_);
+}
+
+int Socks5ServerSocket::DoHandshakeWriteComplete(int result) {
+  if (result < 0)
+    return result;
+
+  // We ignore the case when result is 0, since the underlying Write
+  // may return spurious writes while waiting on the socket.
+
+  bytes_sent_ += result;
+  if (bytes_sent_ == buffer_.size()) {
+    buffer_.clear();
+    if (reply_ == kReplySuccess) {
+      completed_handshake_ = true;
+      next_state_ = STATE_NONE;
+    } else {
+      net_log_.AddEventWithIntParams(NetLogEventType::SOCKS_SERVER_ERROR,
+                                     "error_code", reply_);
+      return ERR_SOCKS_CONNECTION_FAILED;
+    }
+  } else {
+    next_state_ = STATE_HANDSHAKE_WRITE;
+  }
+
+  return OK;
+}
+
+int Socks5ServerSocket::GetPeerAddress(IPEndPoint* address) const {
+  return transport_->GetPeerAddress(address);
+}
+
+int Socks5ServerSocket::GetLocalAddress(IPEndPoint* address) const {
+  return transport_->GetLocalAddress(address);
+}
+
+}  // namespace net
diff --git a/src/net/tools/naive/socks5_server_socket.h b/src/net/tools/naive/socks5_server_socket.h
new file mode 100644
index 0000000000..c17e74da19
--- /dev/null
+++ b/src/net/tools/naive/socks5_server_socket.h
@@ -0,0 +1,165 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Copyright 2018 klzgrad <kizdiv@gmail.com>. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_
+#define NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "base/memory/scoped_refptr.h"
+#include "net/base/completion_once_callback.h"
+#include "net/base/completion_repeating_callback.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/ip_endpoint.h"
+#include "net/log/net_log_with_source.h"
+#include "net/socket/connection_attempts.h"
+#include "net/socket/next_proto.h"
+#include "net/socket/stream_socket.h"
+#include "net/ssl/ssl_info.h"
+
+namespace net {
+struct NetworkTrafficAnnotationTag;
+
+// This StreamSocket is used to setup a SOCKSv5 handshake with a socks client.
+// Currently no SOCKSv5 authentication is supported.
+class Socks5ServerSocket : public StreamSocket {
+ public:
+  Socks5ServerSocket(std::unique_ptr<StreamSocket> transport_socket,
+                     const std::string& user,
+                     const std::string& pass,
+                     const NetworkTrafficAnnotationTag& traffic_annotation);
+
+  // On destruction Disconnect() is called.
+  ~Socks5ServerSocket() override;
+
+  Socks5ServerSocket(const Socks5ServerSocket&) = delete;
+  Socks5ServerSocket& operator=(const Socks5ServerSocket&) = delete;
+
+  const HostPortPair& request_endpoint() const;
+
+  // StreamSocket implementation.
+
+  // Does the SOCKS handshake and completes the protocol.
+  int Connect(CompletionOnceCallback callback) override;
+  void Disconnect() override;
+  bool IsConnected() const override;
+  bool IsConnectedAndIdle() const override;
+  const NetLogWithSource& NetLog() const override;
+  bool WasEverUsed() const override;
+  NextProto GetNegotiatedProtocol() const override;
+  bool GetSSLInfo(SSLInfo* ssl_info) override;
+  int64_t GetTotalReceivedBytes() const override;
+  void ApplySocketTag(const SocketTag& tag) override;
+
+  // Socket implementation.
+  int Read(IOBuffer* buf,
+           int buf_len,
+           CompletionOnceCallback callback) override;
+  int Write(IOBuffer* buf,
+            int buf_len,
+            CompletionOnceCallback callback,
+            const NetworkTrafficAnnotationTag& traffic_annotation) override;
+
+  int SetReceiveBufferSize(int32_t size) override;
+  int SetSendBufferSize(int32_t size) override;
+
+  int GetPeerAddress(IPEndPoint* address) const override;
+  int GetLocalAddress(IPEndPoint* address) const override;
+
+ private:
+  enum State {
+    STATE_GREET_READ,
+    STATE_GREET_READ_COMPLETE,
+    STATE_GREET_WRITE,
+    STATE_GREET_WRITE_COMPLETE,
+    STATE_AUTH_READ,
+    STATE_AUTH_READ_COMPLETE,
+    STATE_AUTH_WRITE,
+    STATE_AUTH_WRITE_COMPLETE,
+    STATE_HANDSHAKE_WRITE,
+    STATE_HANDSHAKE_WRITE_COMPLETE,
+    STATE_HANDSHAKE_READ,
+    STATE_HANDSHAKE_READ_COMPLETE,
+    STATE_NONE,
+  };
+
+  // Addressing type that can be specified in requests or responses.
+  enum SocksEndPointAddressType {
+    kEndPointDomain = 0x03,
+    kEndPointResolvedIPv4 = 0x01,
+    kEndPointResolvedIPv6 = 0x04,
+  };
+
+  void DoCallback(int result);
+  void OnIOComplete(int result);
+  void OnReadWriteComplete(CompletionOnceCallback callback, int result);
+
+  int DoLoop(int last_io_result);
+  int DoGreetRead();
+  int DoGreetReadComplete(int result);
+  int DoGreetWrite();
+  int DoGreetWriteComplete(int result);
+  int DoAuthRead();
+  int DoAuthReadComplete(int result);
+  int DoAuthWrite();
+  int DoAuthWriteComplete(int result);
+  int DoHandshakeRead();
+  int DoHandshakeReadComplete(int result);
+  int DoHandshakeWrite();
+  int DoHandshakeWriteComplete(int result);
+
+  CompletionRepeatingCallback io_callback_;
+
+  // Stores the underlying socket.
+  std::unique_ptr<StreamSocket> transport_;
+
+  State next_state_;
+
+  // Stores the callback to the layer above, called on completing Connect().
+  CompletionOnceCallback user_callback_;
+
+  // This IOBuffer is used by the class to read and write
+  // SOCKS handshake data. The length contains the expected size to
+  // read or write.
+  scoped_refptr<IOBuffer> handshake_buf_;
+
+  // While writing, this buffer stores the complete write handshake data.
+  // While reading, it stores the handshake information received so far.
+  std::string buffer_;
+
+  // This becomes true when the SOCKS handshake has completed and the
+  // overlying connection is free to communicate.
+  bool completed_handshake_;
+
+  // Contains the bytes sent by the SOCKS handshake.
+  size_t bytes_sent_;
+
+  size_t read_header_size_;
+
+  bool was_ever_used_;
+
+  SocksEndPointAddressType address_type_;
+  int address_size_;
+
+  std::string user_;
+  std::string pass_;
+  char auth_method_;
+  char auth_status_;
+  char reply_;
+
+  HostPortPair request_endpoint_;
+
+  NetLogWithSource net_log_;
+
+  // Traffic annotation for socket control.
+  const NetworkTrafficAnnotationTag& traffic_annotation_;
+};
+
+}  // namespace net
+#endif  // NET_TOOLS_NAIVE_SOCKS5_SERVER_SOCKET_H_