mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-30 14:45:49 +08:00
Compare commits
No commits in common. "94feaf27287b070e09137242d5f0f5adabd1b222" and "98e8dfb004ca7ee129e2270bb811a61d35174b0c" have entirely different histories.
94feaf2728
...
98e8dfb004
@ -63,7 +63,7 @@ option(3PROXY_USE_OPENSSL "Enable TLS/SSL support via OpenSSL (fallback when wol
|
|||||||
option(3PROXY_USE_PCRE2 "Enable PCRE2 regex filtering" ON)
|
option(3PROXY_USE_PCRE2 "Enable PCRE2 regex filtering" ON)
|
||||||
option(3PROXY_USE_PAM "Enable PAM/PamAuth" ON)
|
option(3PROXY_USE_PAM "Enable PAM/PamAuth" ON)
|
||||||
option(3PROXY_USE_ODBC "Enable ODBC support (Unix only, always ON on Windows)" OFF)
|
option(3PROXY_USE_ODBC "Enable ODBC support (Unix only, always ON on Windows)" OFF)
|
||||||
option(3PROXY_USE_SPLICE "Build Linux splice() support, used by services given -s (Linux only)" ON)
|
option(3PROXY_USE_SPLICE "Build Linux splice() support, slower than read/write for most traffic (Linux only)" OFF)
|
||||||
option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON)
|
option(3PROXY_USE_POLL "Use poll() instead of select() (Unix only)" ON)
|
||||||
option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON)
|
option(3PROXY_USE_WSAPOLL "Use WSAPoll instead of select() (Windows only)" ON)
|
||||||
option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON)
|
option(3PROXY_USE_NETFILTER "Enable Linux netfilter support (Linux only)" ON)
|
||||||
|
|||||||
@ -4,9 +4,9 @@
|
|||||||
# add -DWITH_ODBC to CFLAGS and -lodbc to LIBS to compile with ODBC
|
# add -DWITH_ODBC to CFLAGS and -lodbc to LIBS to compile with ODBC
|
||||||
# library support. Add -DSAFESQL for poorely written ODBC library / drivers.
|
# library support. Add -DSAFESQL for poorely written ODBC library / drivers.
|
||||||
#
|
#
|
||||||
# Linux splice() support is built, but used only by services given the -s
|
# Linux splice() support is not built by default, it is slower than the
|
||||||
# option: it is faster for large transfers and slower for small requests.
|
# read/write path for most traffic. Add -DWITHSPLICE to CFLAGS to build it,
|
||||||
# Remove -DWITHSPLICE from CFLAGS to build without it.
|
# it must additionally be enabled per service with the -s option.
|
||||||
|
|
||||||
BUILDDIR = ../bin/
|
BUILDDIR = ../bin/
|
||||||
PREFIX ?= 3proxy_
|
PREFIX ?= 3proxy_
|
||||||
@ -14,7 +14,7 @@ CRYPT_PREFIX ?= $(PREFIX)
|
|||||||
CC ?= gcc
|
CC ?= gcc
|
||||||
|
|
||||||
CFLAGS ?= -O3 -flto
|
CFLAGS ?= -O3 -flto
|
||||||
CFLAGS += -fno-strict-aliasing -c -pthread -DWITHSPLICE -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER -D WITH_UN
|
CFLAGS += -fno-strict-aliasing -c -pthread -D_GNU_SOURCE -DGETHOSTBYNAME_R -D_THREAD_SAFE -D_REENTRANT -DFD_SETSIZE=4096 -DWITH_POLL -DWITH_NETFILTER -D WITH_UN
|
||||||
COUT = -o
|
COUT = -o
|
||||||
LN ?= ${CC}
|
LN ?= ${CC}
|
||||||
DCFLAGS ?= -fPIC
|
DCFLAGS ?= -fPIC
|
||||||
|
|||||||
2
bench/.gitignore
vendored
2
bench/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
bin/
|
|
||||||
results/
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
# Benchmarks
|
|
||||||
|
|
||||||
Compares the speed and the cost of 3proxy versions, and of other proxies doing
|
|
||||||
the same job, on Linux.
|
|
||||||
|
|
||||||
## How the measurement is isolated
|
|
||||||
|
|
||||||
The client, the proxy and the origin each run in a network namespace of
|
|
||||||
their own, linked by veth pairs:
|
|
||||||
|
|
||||||
```
|
|
||||||
[bcli] 10.0.1.2 --veth-- 10.0.1.1 [bprx] 10.0.2.1 --veth-- 10.0.2.2 [bsrv]
|
|
||||||
wrk, iperf3 the proxy nginx (files), iperf3 -s
|
|
||||||
```
|
|
||||||
|
|
||||||
so they share no loopback, and a packet crosses the same path whichever proxy
|
|
||||||
is measured. Each role also runs in a cgroup of its own pinned to separate
|
|
||||||
CPUs (`--cpus`, default `cli=0-3,prx=4-5,srv=6-7`), so one does not take CPU
|
|
||||||
from another, and RPS steers what each veth receives to the receiver's CPUs.
|
|
||||||
The proxy's cgroup is created anew for every run, which is what makes its
|
|
||||||
CPU time and peak memory readable on their own.
|
|
||||||
|
|
||||||
Every proxy is given the same CPUs: nginx gets as many workers and haproxy as
|
|
||||||
many threads as the proxy cgroup has CPUs; 3proxy runs a thread per
|
|
||||||
connection inside the same cpuset. Logging is off everywhere.
|
|
||||||
|
|
||||||
`none` is no proxy at all: the client reaches the origin through the proxy
|
|
||||||
namespace with the kernel forwarding, the upper bound for every scenario.
|
|
||||||
|
|
||||||
## What is reported
|
|
||||||
|
|
||||||
For every run, in a CSV under `bench/results/`:
|
|
||||||
|
|
||||||
| column | meaning |
|
|
||||||
|---|---|
|
|
||||||
| `gbit`, `rate` | throughput, requests per second for HTTP |
|
|
||||||
| `p50_ms`, `p99_ms` | latency from wrk (closed loop: compare at equal load only) |
|
|
||||||
| `prx_cpu_s` | CPU seconds the proxy used, all its threads and processes |
|
|
||||||
| `cpu_s_per_gb`, `cpu_us_per_req` | the proxy's cost per unit of work: the fairest figure, least affected by which side was the limit |
|
|
||||||
| `peak_mb` | the proxy's peak memory |
|
|
||||||
| `cli_util`, `prx_util`, `srv_util` | how busy each role's CPUs were: a client or origin near 100% means the result says nothing about the proxy |
|
|
||||||
| `errors`, `retrans` | socket errors and non-2xx answers; TCP retransmissions in the stream scenarios |
|
|
||||||
|
|
||||||
Repetitions rotate the order of the proxies, so drift over time (heat, the
|
|
||||||
host) falls on all of them alike; the summary gives medians.
|
|
||||||
|
|
||||||
## Scenarios
|
|
||||||
|
|
||||||
| name | load |
|
|
||||||
|---|---|
|
|
||||||
| `stream1`, `stream1-down` | one iperf3 stream through a TCP relay (3proxy `tcppm`, nginx `stream`, haproxy `mode tcp`), upload and download |
|
|
||||||
| `stream16` | sixteen streams |
|
|
||||||
| `http-ka-1k`, `http-ka-1m` | HTTP over kept-alive connections through an HTTP proxy (absolute URIs), 1 KB and 1 MB answers |
|
|
||||||
| `http-close-1k` | a new connection for every request |
|
|
||||||
|
|
||||||
nginx is not a forward proxy: it answers absolute URIs by passing them to its
|
|
||||||
upstream, which is the origin here, and it has no CONNECT or SOCKS, so only
|
|
||||||
these scenarios compare like with like.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
Root, `iproute2`, `iperf3`, `wrk`, `nginx` (with the stream module:
|
|
||||||
`libnginx-mod-stream` on Debian and Ubuntu) and, to compare with it,
|
|
||||||
`haproxy`.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bench/build.sh 0.9.9.1 1.0.1 # tags into bench/bin/
|
|
||||||
NAME=devel bench/build.sh . # the working tree
|
|
||||||
CC="gcc -fcommon" bench/build.sh 0.8.13 # before 0.9, newer gcc
|
|
||||||
|
|
||||||
sudo bench/bench.py \
|
|
||||||
--proxy none \
|
|
||||||
--proxy 3proxy:bench/bin/3proxy-1.0.1 \
|
|
||||||
--proxy 3proxy:bench/bin/3proxy-devel \
|
|
||||||
--proxy "3proxy:bench/bin/3proxy-0.9.4:-s0" \
|
|
||||||
--proxy nginx --proxy haproxy
|
|
||||||
```
|
|
||||||
|
|
||||||
After the binary, a 3proxy entry can carry options for both its services:
|
|
||||||
`-s0` keeps 0.9.0 to 0.9.8, which use splice() by default, from using it, and
|
|
||||||
`-ocTCP_NODELAY -osTCP_NODELAY` turns Nagle's algorithm off on the client and
|
|
||||||
server sockets.
|
|
||||||
|
|
||||||
`--scenario` picks scenarios, `--reps` and `--duration` set the repetitions
|
|
||||||
and their length, `--netem "delay 1ms rate 10gbit"` puts a slower, longer link
|
|
||||||
between the client and the proxy, `--mtu` changes the links' MTU.
|
|
||||||
|
|
||||||
## Caveats
|
|
||||||
|
|
||||||
* A virtual machine cannot pin its virtual CPUs to physical ones, so its
|
|
||||||
absolute numbers move between sessions: compare within one session, and
|
|
||||||
keep absolute figures for real hardware.
|
|
||||||
* wrk measures latency in a closed loop, so a slower proxy is also offered
|
|
||||||
less load; compare latencies only between runs of similar throughput.
|
|
||||||
* Versions are built with the Makefile they shipped with, so compiler flags
|
|
||||||
differ between them (0.8 builds with `-O2`, 0.9.9 and later with
|
|
||||||
`-O3 -flto`) as they did for their users.
|
|
||||||
575
bench/bench.py
575
bench/bench.py
@ -1,575 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Compares the speed and the cost of proxies on Linux: 3proxy versions, and
|
|
||||||
other servers doing the same job.
|
|
||||||
|
|
||||||
The client, the proxy and the origin each run in a network namespace of their
|
|
||||||
own, linked by veth pairs, and in a cgroup of their own pinned to separate
|
|
||||||
CPUs, so they neither share a loopback nor take CPU from each other, and the
|
|
||||||
proxy's CPU time and memory can be read on their own. See README.md.
|
|
||||||
|
|
||||||
Needs root, iproute2, iperf3, wrk and nginx (the origin); haproxy only when
|
|
||||||
it is one of the proxies compared.
|
|
||||||
|
|
||||||
sudo bench/bench.py --proxy 3proxy:bench/bin/3proxy-1.0.1 \\
|
|
||||||
--proxy 3proxy:bench/bin/3proxy-devel --proxy nginx --proxy none
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import csv
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import resource
|
|
||||||
import shutil
|
|
||||||
import signal
|
|
||||||
import statistics
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
|
|
||||||
CG = "/sys/fs/cgroup/3proxy-bench"
|
|
||||||
|
|
||||||
# client 10.0.1.2 -- 10.0.1.1 proxy 10.0.2.1 -- 10.0.2.2 origin
|
|
||||||
CLI_ADDR, PRX_IN, PRX_OUT, SRV_ADDR = "10.0.1.2", "10.0.1.1", "10.0.2.1", "10.0.2.2"
|
|
||||||
HTTP_PORT, TCP_PORT, ORIGIN_HTTP, ORIGIN_TCP = 3128, 5201, 8080, 5201
|
|
||||||
|
|
||||||
FILES = {"1k": 1024, "100k": 100 * 1024, "1m": 1024 * 1024}
|
|
||||||
|
|
||||||
# name: (kind, parameters)
|
|
||||||
SCENARIOS = {
|
|
||||||
"stream1": ("stream", {"streams": 1}),
|
|
||||||
"stream16": ("stream", {"streams": 16}),
|
|
||||||
"stream1-down": ("stream", {"streams": 1, "reverse": True}),
|
|
||||||
"http-ka-1k": ("http", {"file": "1k", "conns": 64}),
|
|
||||||
"http-ka-1m": ("http", {"file": "1m", "conns": 16}),
|
|
||||||
"http-close-1k": ("http", {"file": "1k", "conns": 64, "close": True}),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def sh(cmd, check=True, **kw):
|
|
||||||
return subprocess.run(cmd, shell=True, check=check, text=True,
|
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kw).stdout
|
|
||||||
|
|
||||||
|
|
||||||
def cpulist(spec):
|
|
||||||
cpus = []
|
|
||||||
for part in spec.split(","):
|
|
||||||
a, _, b = part.partition("-")
|
|
||||||
cpus.extend(range(int(a), int(b or a) + 1))
|
|
||||||
return cpus
|
|
||||||
|
|
||||||
|
|
||||||
def cpumask(spec):
|
|
||||||
return "%x" % sum(1 << c for c in cpulist(spec))
|
|
||||||
|
|
||||||
|
|
||||||
# --- namespaces --------------------------------------------------------------
|
|
||||||
|
|
||||||
def net_up(args):
|
|
||||||
net_down()
|
|
||||||
for ns in ("bcli", "bprx", "bsrv"):
|
|
||||||
sh(f"ip netns add {ns}")
|
|
||||||
sh(f"ip -n {ns} link set lo up")
|
|
||||||
sh("ip link add bc0 netns bcli type veth peer name bp0 netns bprx")
|
|
||||||
sh("ip link add bp1 netns bprx type veth peer name bs0 netns bsrv")
|
|
||||||
for ns, dev, addr in (("bcli", "bc0", CLI_ADDR), ("bprx", "bp0", PRX_IN),
|
|
||||||
("bprx", "bp1", PRX_OUT), ("bsrv", "bs0", SRV_ADDR)):
|
|
||||||
sh(f"ip -n {ns} addr add {addr}/24 dev {dev}")
|
|
||||||
sh(f"ip -n {ns} link set {dev} mtu {args.mtu} up")
|
|
||||||
# the direct baseline goes through the proxy namespace, routed by the
|
|
||||||
# kernel
|
|
||||||
sh(f"ip -n bcli route add 10.0.2.0/24 via {PRX_IN}")
|
|
||||||
sh(f"ip -n bsrv route add 10.0.1.0/24 via {PRX_OUT}")
|
|
||||||
sh("ip netns exec bprx sysctl -qw net.ipv4.ip_forward=1")
|
|
||||||
for ns in ("bcli", "bprx", "bsrv"):
|
|
||||||
sh(f"ip netns exec {ns} sysctl -qw net.core.somaxconn=65535 "
|
|
||||||
"net.ipv4.tcp_max_syn_backlog=65535 net.ipv4.tcp_tw_reuse=1 "
|
|
||||||
"net.ipv4.ip_local_port_range='1024 65535'")
|
|
||||||
# A veth hands what it receives to the CPU that sent it; steer it to the
|
|
||||||
# receiver's own CPUs instead.
|
|
||||||
for ns, dev, role in (("bcli", "bc0", "cli"), ("bprx", "bp0", "prx"),
|
|
||||||
("bprx", "bp1", "prx"), ("bsrv", "bs0", "srv")):
|
|
||||||
sh(f"ip netns exec {ns} sh -c 'echo {cpumask(args.cpus[role])} "
|
|
||||||
f"> /sys/class/net/{dev}/queues/rx-0/rps_cpus'", check=False)
|
|
||||||
if args.netem:
|
|
||||||
for ns, dev in (("bcli", "bc0"), ("bprx", "bp0")):
|
|
||||||
sh(f"ip netns exec {ns} tc qdisc add dev {dev} root netem {args.netem}")
|
|
||||||
|
|
||||||
|
|
||||||
def net_down():
|
|
||||||
for ns in ("bcli", "bprx", "bsrv"):
|
|
||||||
sh(f"ip netns del {ns}", check=False)
|
|
||||||
|
|
||||||
|
|
||||||
# --- cgroups -----------------------------------------------------------------
|
|
||||||
|
|
||||||
def cg_write(path, value):
|
|
||||||
with open(path, "w") as f:
|
|
||||||
f.write(value)
|
|
||||||
|
|
||||||
|
|
||||||
def cg_read(path):
|
|
||||||
with open(path) as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def cg_up(args):
|
|
||||||
os.makedirs(CG, exist_ok=True)
|
|
||||||
cg_write("/sys/fs/cgroup/cgroup.subtree_control", "+cpuset +cpu +memory")
|
|
||||||
cg_write(f"{CG}/cgroup.subtree_control", "+cpuset +cpu +memory")
|
|
||||||
for role in ("cli", "srv"):
|
|
||||||
cg_make(role, args.cpus[role])
|
|
||||||
|
|
||||||
|
|
||||||
def cg_make(role, cpus):
|
|
||||||
path = f"{CG}/{role}"
|
|
||||||
os.makedirs(path, exist_ok=True)
|
|
||||||
cg_write(f"{path}/cpuset.cpus", cpus)
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def cg_remove(role):
|
|
||||||
path = f"{CG}/{role}"
|
|
||||||
if not os.path.isdir(path):
|
|
||||||
return
|
|
||||||
for _ in range(50):
|
|
||||||
try:
|
|
||||||
os.rmdir(path)
|
|
||||||
return
|
|
||||||
except OSError:
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
|
|
||||||
def cg_down():
|
|
||||||
for role in ("cli", "prx", "srv"):
|
|
||||||
cg_remove(role)
|
|
||||||
try:
|
|
||||||
os.rmdir(CG)
|
|
||||||
except OSError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def cg_usage(role):
|
|
||||||
"""CPU seconds used so far by everything in the cgroup."""
|
|
||||||
for line in cg_read(f"{CG}/{role}/cpu.stat").splitlines():
|
|
||||||
name, value = line.split()
|
|
||||||
if name == "usage_usec":
|
|
||||||
return int(value) / 1e6
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def cg_peak(role):
|
|
||||||
try:
|
|
||||||
return int(cg_read(f"{CG}/{role}/memory.peak")) / 2**20
|
|
||||||
except OSError:
|
|
||||||
return float("nan")
|
|
||||||
|
|
||||||
|
|
||||||
# --- processes ---------------------------------------------------------------
|
|
||||||
|
|
||||||
def spawn(ns, role, argv, log):
|
|
||||||
"""Start a process in a namespace and a cgroup; threads it starts stay
|
|
||||||
there with it."""
|
|
||||||
procs = f"{CG}/{role}/cgroup.procs"
|
|
||||||
|
|
||||||
def enter():
|
|
||||||
cg_write(procs, str(os.getpid()))
|
|
||||||
resource.setrlimit(resource.RLIMIT_NOFILE, (1048576, 1048576))
|
|
||||||
os.setsid()
|
|
||||||
|
|
||||||
return subprocess.Popen(["ip", "netns", "exec", ns] + argv, stdout=log,
|
|
||||||
stderr=subprocess.STDOUT, preexec_fn=enter)
|
|
||||||
|
|
||||||
|
|
||||||
def run_in(ns, role, argv, timeout):
|
|
||||||
p = spawn(ns, role, argv, subprocess.PIPE)
|
|
||||||
out, _ = p.communicate(timeout=timeout)
|
|
||||||
return out.decode("utf-8", "replace")
|
|
||||||
|
|
||||||
|
|
||||||
def stop(p):
|
|
||||||
if p and p.poll() is None:
|
|
||||||
try:
|
|
||||||
os.killpg(p.pid, signal.SIGTERM)
|
|
||||||
p.wait(timeout=5)
|
|
||||||
except (ProcessLookupError, subprocess.TimeoutExpired):
|
|
||||||
try:
|
|
||||||
os.killpg(p.pid, signal.SIGKILL)
|
|
||||||
except ProcessLookupError:
|
|
||||||
pass
|
|
||||||
p.wait()
|
|
||||||
|
|
||||||
|
|
||||||
def wait_port(ns, addr, port, timeout=10):
|
|
||||||
deadline = time.time() + timeout
|
|
||||||
while time.time() < deadline:
|
|
||||||
# /dev/tcp is bash's
|
|
||||||
if subprocess.run(["ip", "netns", "exec", ns, "bash", "-c",
|
|
||||||
f"exec 3<>/dev/tcp/{addr}/{port}"],
|
|
||||||
stderr=subprocess.DEVNULL).returncode == 0:
|
|
||||||
return True
|
|
||||||
time.sleep(0.1)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# --- the origin --------------------------------------------------------------
|
|
||||||
|
|
||||||
def origin_up(args, work):
|
|
||||||
www = os.path.join(work, "www")
|
|
||||||
os.makedirs(www, exist_ok=True)
|
|
||||||
for name, size in FILES.items():
|
|
||||||
with open(os.path.join(www, name), "wb") as f:
|
|
||||||
f.write(b"x" * size)
|
|
||||||
n = len(cpulist(args.cpus["srv"]))
|
|
||||||
conf = os.path.join(work, "origin.conf")
|
|
||||||
with open(conf, "w") as f:
|
|
||||||
f.write(f"""daemon off;
|
|
||||||
worker_processes {n};
|
|
||||||
pid {work}/origin.pid;
|
|
||||||
error_log {work}/origin.err;
|
|
||||||
worker_rlimit_nofile 1048576;
|
|
||||||
events {{ worker_connections 65536; }}
|
|
||||||
http {{
|
|
||||||
access_log off;
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
keepalive_requests 1000000;
|
|
||||||
keepalive_timeout 120s;
|
|
||||||
server {{
|
|
||||||
listen {SRV_ADDR}:{ORIGIN_HTTP} backlog=65535 reuseport;
|
|
||||||
root {www};
|
|
||||||
default_type application/octet-stream;
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
log = open(os.path.join(work, "origin.out"), "w")
|
|
||||||
procs = [spawn("bsrv", "srv", ["nginx", "-c", conf, "-p", work], log),
|
|
||||||
spawn("bsrv", "srv", ["iperf3", "-s", "-B", SRV_ADDR, "-p", str(ORIGIN_TCP)], log)]
|
|
||||||
if not (wait_port("bsrv", SRV_ADDR, ORIGIN_HTTP) and wait_port("bsrv", SRV_ADDR, ORIGIN_TCP)):
|
|
||||||
sys.exit("the origin did not start, see " + work)
|
|
||||||
return procs
|
|
||||||
|
|
||||||
|
|
||||||
# --- proxies -----------------------------------------------------------------
|
|
||||||
|
|
||||||
def proxy_conf(kind, work, ncpu, label, opts=""):
|
|
||||||
"""Configuration for one proxy: an HTTP proxy on HTTP_PORT and a TCP relay
|
|
||||||
on TCP_PORT, both towards the origin. Returns the argv. opts are added to
|
|
||||||
both 3proxy services, e.g. -s0 to keep a version from using splice()."""
|
|
||||||
if kind == "3proxy":
|
|
||||||
conf = os.path.join(work, f"{label}.cfg")
|
|
||||||
with open(conf, "w") as f:
|
|
||||||
f.write(f"""maxconn 100000
|
|
||||||
auth none
|
|
||||||
proxy {opts} -i{PRX_IN} -e{PRX_OUT} -p{HTTP_PORT}
|
|
||||||
tcppm {opts} -i{PRX_IN} -e{PRX_OUT} {TCP_PORT} {SRV_ADDR} {ORIGIN_TCP}
|
|
||||||
""")
|
|
||||||
return [conf]
|
|
||||||
if kind == "nginx":
|
|
||||||
conf = os.path.join(work, f"{label}.conf")
|
|
||||||
with open(conf, "w") as f:
|
|
||||||
# distributions build the stream module as a dynamic one
|
|
||||||
stream = "/usr/lib/nginx/modules/ngx_stream_module.so"
|
|
||||||
load = f"load_module {stream};\n" if os.path.exists(stream) else ""
|
|
||||||
f.write(f"""{load}daemon off;
|
|
||||||
worker_processes {ncpu};
|
|
||||||
pid {work}/{label}.pid;
|
|
||||||
error_log {work}/{label}.err;
|
|
||||||
worker_rlimit_nofile 1048576;
|
|
||||||
events {{ worker_connections 65536; }}
|
|
||||||
http {{
|
|
||||||
access_log off;
|
|
||||||
upstream origin {{ server {SRV_ADDR}:{ORIGIN_HTTP}; keepalive 1024; }}
|
|
||||||
server {{
|
|
||||||
listen {PRX_IN}:{HTTP_PORT} backlog=65535 reuseport;
|
|
||||||
keepalive_requests 1000000;
|
|
||||||
location / {{
|
|
||||||
proxy_pass http://origin;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Connection "";
|
|
||||||
proxy_bind {PRX_OUT};
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
stream {{
|
|
||||||
server {{
|
|
||||||
listen {PRX_IN}:{TCP_PORT} backlog=65535 reuseport;
|
|
||||||
proxy_pass {SRV_ADDR}:{ORIGIN_TCP};
|
|
||||||
proxy_bind {PRX_OUT};
|
|
||||||
}}
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
return ["-c", conf, "-p", work]
|
|
||||||
if kind == "haproxy":
|
|
||||||
conf = os.path.join(work, f"{label}.cfg")
|
|
||||||
with open(conf, "w") as f:
|
|
||||||
f.write(f"""global
|
|
||||||
nbthread {ncpu}
|
|
||||||
maxconn 200000
|
|
||||||
defaults
|
|
||||||
timeout connect 5s
|
|
||||||
timeout client 60s
|
|
||||||
timeout server 60s
|
|
||||||
frontend http
|
|
||||||
mode http
|
|
||||||
bind {PRX_IN}:{HTTP_PORT}
|
|
||||||
option http-keep-alive
|
|
||||||
default_backend origin
|
|
||||||
backend origin
|
|
||||||
mode http
|
|
||||||
http-reuse always
|
|
||||||
source {PRX_OUT}
|
|
||||||
server origin {SRV_ADDR}:{ORIGIN_HTTP}
|
|
||||||
listen tcp
|
|
||||||
mode tcp
|
|
||||||
bind {PRX_IN}:{TCP_PORT}
|
|
||||||
source {PRX_OUT}
|
|
||||||
server origin {SRV_ADDR}:{ORIGIN_TCP}
|
|
||||||
""")
|
|
||||||
return ["-f", conf]
|
|
||||||
raise ValueError(kind)
|
|
||||||
|
|
||||||
|
|
||||||
class Proxy:
|
|
||||||
def __init__(self, spec):
|
|
||||||
kind, _, path = spec.partition(":")
|
|
||||||
self.kind = kind
|
|
||||||
self.opts = ""
|
|
||||||
if kind == "3proxy":
|
|
||||||
path, _, self.opts = path.partition(":")
|
|
||||||
if not path:
|
|
||||||
sys.exit("--proxy 3proxy:<binary>[:<service options>]")
|
|
||||||
self.binary = os.path.abspath(path)
|
|
||||||
self.label = os.path.basename(path) + (self.opts.replace(" ", "") if self.opts else "")
|
|
||||||
elif kind in ("nginx", "haproxy"):
|
|
||||||
self.binary = path or shutil.which(kind)
|
|
||||||
self.label = kind
|
|
||||||
elif kind == "none":
|
|
||||||
self.binary = None
|
|
||||||
self.label = "direct"
|
|
||||||
else:
|
|
||||||
sys.exit("unknown proxy " + spec)
|
|
||||||
|
|
||||||
def start(self, args, work):
|
|
||||||
cg_remove("prx")
|
|
||||||
cg_make("prx", args.cpus["prx"])
|
|
||||||
if self.kind == "none":
|
|
||||||
return None
|
|
||||||
argv = [self.binary] + proxy_conf(self.kind, work, len(cpulist(args.cpus["prx"])),
|
|
||||||
self.label, self.opts)
|
|
||||||
log = open(os.path.join(work, self.label + ".out"), "a")
|
|
||||||
p = spawn("bprx", "prx", argv, log)
|
|
||||||
if not (wait_port("bcli", PRX_IN, HTTP_PORT) and wait_port("bcli", PRX_IN, TCP_PORT)):
|
|
||||||
stop(p)
|
|
||||||
sys.exit(f"{self.label} did not start, see {work}")
|
|
||||||
return p
|
|
||||||
|
|
||||||
|
|
||||||
# --- load --------------------------------------------------------------------
|
|
||||||
|
|
||||||
WRK_SCRIPT = """
|
|
||||||
local path, close = "%s", %s
|
|
||||||
local headers = {["Host"] = "%s:%d"}
|
|
||||||
if close then headers["Connection"] = "close" end
|
|
||||||
local req = wrk.format("GET", path, headers)
|
|
||||||
request = function() return req end
|
|
||||||
"""
|
|
||||||
|
|
||||||
UNITS = {"": 1, "k": 1e3, "K": 1e3, "M": 1e6, "G": 1e9, "KB": 2**10, "MB": 2**20,
|
|
||||||
"GB": 2**30, "B": 1}
|
|
||||||
|
|
||||||
|
|
||||||
def to_ms(text):
|
|
||||||
m = re.match(r"([\d.]+)(us|ms|s|m)", text)
|
|
||||||
v, u = float(m.group(1)), m.group(2)
|
|
||||||
return v * {"us": 1e-3, "ms": 1, "s": 1e3, "m": 6e4}[u]
|
|
||||||
|
|
||||||
|
|
||||||
def run_http(proxy, params, args, work):
|
|
||||||
direct = proxy.kind == "none"
|
|
||||||
host = SRV_ADDR if direct else PRX_IN
|
|
||||||
port = ORIGIN_HTTP if direct else HTTP_PORT
|
|
||||||
path = "/" + params["file"]
|
|
||||||
if not direct:
|
|
||||||
path = f"http://{SRV_ADDR}:{ORIGIN_HTTP}{path}"
|
|
||||||
script = os.path.join(work, "wrk.lua")
|
|
||||||
with open(script, "w") as f:
|
|
||||||
f.write(WRK_SCRIPT % (path, "true" if params.get("close") else "false",
|
|
||||||
SRV_ADDR, ORIGIN_HTTP))
|
|
||||||
threads = len(cpulist(args.cpus["cli"]))
|
|
||||||
conns = max(params["conns"], threads)
|
|
||||||
out = run_in("bcli", "cli", ["wrk", "-t", str(threads), "-c", str(conns),
|
|
||||||
"-d", f"{args.duration}s", "--latency", "-s", script,
|
|
||||||
f"http://{host}:{port}/"], args.duration + 30)
|
|
||||||
res = {"raw": out}
|
|
||||||
m = re.search(r"(\d+) requests in", out)
|
|
||||||
res["requests"] = int(m.group(1)) if m else 0
|
|
||||||
m = re.search(r"Requests/sec:\s+([\d.]+)", out)
|
|
||||||
res["rate"] = float(m.group(1)) if m else 0.0
|
|
||||||
m = re.search(r"Transfer/sec:\s+([\d.]+)(\w+)", out)
|
|
||||||
res["gbit"] = float(m.group(1)) * UNITS[m.group(2)] * 8 / 1e9 if m else 0.0
|
|
||||||
for pct in ("50", "99"):
|
|
||||||
m = re.search(rf"^\s+{pct}%\s+(\S+)", out, re.M)
|
|
||||||
res[f"p{pct}_ms"] = to_ms(m.group(1)) if m else float("nan")
|
|
||||||
errors = 0
|
|
||||||
m = re.search(r"Socket errors: connect (\d+), read (\d+), write (\d+), timeout (\d+)", out)
|
|
||||||
if m:
|
|
||||||
errors += sum(int(x) for x in m.groups())
|
|
||||||
m = re.search(r"Non-2xx or 3xx responses: (\d+)", out)
|
|
||||||
if m:
|
|
||||||
errors += int(m.group(1))
|
|
||||||
res["errors"] = errors
|
|
||||||
res["retrans"] = 0
|
|
||||||
res["bytes"] = res["gbit"] * 1e9 / 8 * args.duration
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def run_stream(proxy, params, args, work):
|
|
||||||
direct = proxy.kind == "none"
|
|
||||||
host = SRV_ADDR if direct else PRX_IN
|
|
||||||
port = ORIGIN_TCP if direct else TCP_PORT
|
|
||||||
argv = ["iperf3", "-c", host, "-p", str(port), "-t", str(args.duration),
|
|
||||||
"-P", str(params["streams"]), "-J"]
|
|
||||||
if params.get("reverse"):
|
|
||||||
argv.append("-R")
|
|
||||||
out = run_in("bcli", "cli", argv, args.duration + 30)
|
|
||||||
res = {"raw": out, "rate": float("nan"), "p50_ms": float("nan"),
|
|
||||||
"p99_ms": float("nan"), "requests": 0, "errors": 0, "retrans": 0}
|
|
||||||
try:
|
|
||||||
j = json.loads(out)
|
|
||||||
s = j["end"]["sum_received"]
|
|
||||||
res["gbit"] = s["bits_per_second"] / 1e9
|
|
||||||
res["bytes"] = s["bytes"]
|
|
||||||
res["retrans"] = j["end"].get("sum_sent", {}).get("retransmits", 0)
|
|
||||||
if "error" in j:
|
|
||||||
res["errors"] = 1
|
|
||||||
except (ValueError, KeyError):
|
|
||||||
res["gbit"], res["bytes"], res["errors"] = 0.0, 0, 1
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def measure(proxy, scenario, args, work):
|
|
||||||
kind, params = SCENARIOS[scenario]
|
|
||||||
p = proxy.start(args, work)
|
|
||||||
try:
|
|
||||||
time.sleep(0.5)
|
|
||||||
before = {r: cg_usage(r) for r in ("cli", "prx", "srv")}
|
|
||||||
t0 = time.time()
|
|
||||||
res = (run_http if kind == "http" else run_stream)(proxy, params, args, work)
|
|
||||||
wall = time.time() - t0
|
|
||||||
after = {r: cg_usage(r) for r in ("cli", "prx", "srv")}
|
|
||||||
res["peak_mb"] = cg_peak("prx")
|
|
||||||
finally:
|
|
||||||
stop(p)
|
|
||||||
for r in ("cli", "prx", "srv"):
|
|
||||||
used = after[r] - before[r]
|
|
||||||
res[f"{r}_cpu_s"] = used
|
|
||||||
# share of the CPUs given to that role, to see which side was the limit
|
|
||||||
res[f"{r}_util"] = used / (wall * len(cpulist(args.cpus[r])))
|
|
||||||
gb = res["bytes"] / 1e9
|
|
||||||
res["cpu_s_per_gb"] = res["prx_cpu_s"] / gb if gb else float("nan")
|
|
||||||
res["cpu_us_per_req"] = (res["prx_cpu_s"] * 1e6 / res["requests"]
|
|
||||||
if res["requests"] else float("nan"))
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
# --- main --------------------------------------------------------------------
|
|
||||||
|
|
||||||
FIELDS = ["proxy", "scenario", "rep", "gbit", "rate", "p50_ms", "p99_ms", "errors", "retrans",
|
|
||||||
"prx_cpu_s", "cpu_s_per_gb", "cpu_us_per_req", "peak_mb",
|
|
||||||
"cli_util", "prx_util", "srv_util"]
|
|
||||||
|
|
||||||
|
|
||||||
def summary(rows):
|
|
||||||
keys = []
|
|
||||||
for r in rows:
|
|
||||||
if (r["proxy"], r["scenario"]) not in keys:
|
|
||||||
keys.append((r["proxy"], r["scenario"]))
|
|
||||||
cols = ["gbit", "rate", "p99_ms", "cpu_s_per_gb", "cpu_us_per_req", "peak_mb",
|
|
||||||
"prx_util", "cli_util", "srv_util", "errors"]
|
|
||||||
print("\nmedians (" + ", ".join(cols) + ")")
|
|
||||||
print("%-24s %-14s " % ("proxy", "scenario") + " ".join("%10s" % c[:10] for c in cols))
|
|
||||||
for proxy, scenario in sorted(keys, key=lambda k: (k[1], k[0])):
|
|
||||||
sel = [r for r in rows if r["proxy"] == proxy and r["scenario"] == scenario]
|
|
||||||
vals = []
|
|
||||||
for c in cols:
|
|
||||||
xs = [r[c] for r in sel if r[c] == r[c]]
|
|
||||||
vals.append("%10.3g" % statistics.median(xs) if xs else "%10s" % "-")
|
|
||||||
print("%-24s %-14s " % (proxy, scenario) + " ".join(vals))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
||||||
ap.add_argument("--proxy", action="append", required=True,
|
|
||||||
help="3proxy:<binary>[:<service options>], nginx[:<binary>], haproxy[:<binary>] "
|
|
||||||
"or none (the kernel routing, an upper bound); repeat")
|
|
||||||
ap.add_argument("--scenario", action="append", choices=sorted(SCENARIOS),
|
|
||||||
help="default: all")
|
|
||||||
ap.add_argument("--reps", type=int, default=3)
|
|
||||||
ap.add_argument("--duration", type=int, default=10, help="seconds per run")
|
|
||||||
ap.add_argument("--cpus", default="cli=0-3,prx=4-5,srv=6-7",
|
|
||||||
help="CPUs per role (default %(default)s)")
|
|
||||||
ap.add_argument("--mtu", type=int, default=1500)
|
|
||||||
ap.add_argument("--netem", help='netem on the client link, e.g. "delay 1ms rate 10gbit"')
|
|
||||||
ap.add_argument("--out", default="bench/results", help="directory for the CSV and logs")
|
|
||||||
args = ap.parse_args()
|
|
||||||
if os.geteuid():
|
|
||||||
sys.exit("needs root")
|
|
||||||
args.cpus = dict(x.split("=") for x in args.cpus.split(","))
|
|
||||||
proxies = [Proxy(s) for s in args.proxy]
|
|
||||||
scenarios = args.scenario or list(SCENARIOS)
|
|
||||||
|
|
||||||
stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
||||||
os.makedirs(args.out, exist_ok=True)
|
|
||||||
work = tempfile.mkdtemp(prefix="3proxy-bench-")
|
|
||||||
os.chmod(work, 0o755)
|
|
||||||
csvpath = os.path.join(args.out, f"{stamp}.csv")
|
|
||||||
rawpath = os.path.join(args.out, f"{stamp}.raw.txt")
|
|
||||||
with open(os.path.join(args.out, f"{stamp}.env.txt"), "w") as f:
|
|
||||||
f.write(" ".join(sys.argv) + "\n")
|
|
||||||
f.write(sh("uname -a; lscpu | head -20; nproc; "
|
|
||||||
"cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null; "
|
|
||||||
"iperf3 --version | head -1; wrk -v 2>&1 | head -1; nginx -v 2>&1; "
|
|
||||||
"haproxy -v 2>/dev/null | head -1", check=False))
|
|
||||||
|
|
||||||
rows = []
|
|
||||||
origin = []
|
|
||||||
try:
|
|
||||||
net_up(args)
|
|
||||||
cg_up(args)
|
|
||||||
origin = origin_up(args, work)
|
|
||||||
with open(csvpath, "w", newline="") as f, open(rawpath, "w") as raw:
|
|
||||||
w = csv.DictWriter(f, FIELDS, extrasaction="ignore")
|
|
||||||
w.writeheader()
|
|
||||||
# Every repetition runs each proxy once, rotating the order, so a
|
|
||||||
# drift over time is shared by all of them.
|
|
||||||
for rep in range(args.reps):
|
|
||||||
for scenario in scenarios:
|
|
||||||
order = proxies[rep % len(proxies):] + proxies[:rep % len(proxies)]
|
|
||||||
for proxy in order:
|
|
||||||
res = measure(proxy, scenario, args, work)
|
|
||||||
res.update(proxy=proxy.label, scenario=scenario, rep=rep)
|
|
||||||
rows.append(res)
|
|
||||||
w.writerow(res)
|
|
||||||
f.flush()
|
|
||||||
raw.write(f"=== {proxy.label} {scenario} {rep}\n{res['raw']}\n")
|
|
||||||
print("%-24s %-14s rep %d: %7.2f Gbit/s %10.0f req/s proxy cpu %5.2fs "
|
|
||||||
"util cli %3.0f%% prx %3.0f%% srv %3.0f%% errors %d" % (
|
|
||||||
proxy.label, scenario, rep, res["gbit"],
|
|
||||||
res["rate"] if res["rate"] == res["rate"] else 0,
|
|
||||||
res["prx_cpu_s"], res["cli_util"] * 100,
|
|
||||||
res["prx_util"] * 100, res["srv_util"] * 100,
|
|
||||||
res["errors"]), flush=True)
|
|
||||||
finally:
|
|
||||||
for p in origin:
|
|
||||||
stop(p)
|
|
||||||
cg_down()
|
|
||||||
net_down()
|
|
||||||
summary(rows)
|
|
||||||
print(f"\n{csvpath}\nlogs and configurations: {work}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
# Builds 3proxy versions for comparison, each from a clean tree and with the
|
|
||||||
# Makefile it shipped with, into bench/bin/3proxy-<name>.
|
|
||||||
#
|
|
||||||
# bench/build.sh 0.9.9.1 1.0.1 devel git refs (tags, branches, commits)
|
|
||||||
# bench/build.sh . the working tree, uncommitted
|
|
||||||
# changes included, as "worktree"
|
|
||||||
# NAME=devel-splice EXTRA_CFLAGS=-DWITHSPLICE bench/build.sh devel
|
|
||||||
# CC="gcc -fcommon" bench/build.sh 0.8.13 old Makefiles set CFLAGS
|
|
||||||
# outright, so a flag a newer
|
|
||||||
# compiler needs goes with CC
|
|
||||||
#
|
|
||||||
# Run from anywhere inside the repository.
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
top=$(git rev-parse --show-toplevel)
|
|
||||||
out="$top/bench/bin"
|
|
||||||
work="${TMPDIR:-/tmp}/3proxy-bench-src"
|
|
||||||
mkdir -p "$out" "$work"
|
|
||||||
|
|
||||||
for ref in "$@"; do
|
|
||||||
if [ "$ref" = "." ]; then
|
|
||||||
name=${NAME:-worktree}
|
|
||||||
src="$work/$name"
|
|
||||||
rm -rf "$src"
|
|
||||||
mkdir -p "$src"
|
|
||||||
# tracked and untracked files as they are now, nothing ignored
|
|
||||||
(cd "$top" && git ls-files -co --exclude-standard -z |
|
|
||||||
xargs -0 sh -c 'for f; do [ -f "$f" ] && printf "%s\0" "$f"; done' sh |
|
|
||||||
tar cf - --null -T - ) | (cd "$src" && tar xf -)
|
|
||||||
else
|
|
||||||
name=${NAME:-$ref}
|
|
||||||
src="$work/$name"
|
|
||||||
rm -rf "$src"
|
|
||||||
mkdir -p "$src"
|
|
||||||
(cd "$top" && git archive "$ref") | (cd "$src" && tar xf -)
|
|
||||||
fi
|
|
||||||
echo "== $name"
|
|
||||||
(
|
|
||||||
cd "$src"
|
|
||||||
mkdir -p bin
|
|
||||||
make -f Makefile.Linux CC="${CC:-gcc}" EXTRA_CFLAGS="$EXTRA_CFLAGS" >build.log 2>&1 ||
|
|
||||||
{ tail -20 build.log; exit 1; }
|
|
||||||
)
|
|
||||||
# versions before 0.9 leave the binary in src/
|
|
||||||
for b in "$src/bin/3proxy" "$src/src/3proxy"; do
|
|
||||||
[ -f "$b" ] && break
|
|
||||||
done
|
|
||||||
cp "$b" "$out/3proxy-$name"
|
|
||||||
echo " $out/3proxy-$name"
|
|
||||||
done
|
|
||||||
@ -34,8 +34,7 @@ Each connected client requires:
|
|||||||
<ul>
|
<ul>
|
||||||
<li>1 thread (process)
|
<li>1 thread (process)
|
||||||
<li>2 sockets (file descriptors). For FTP, 4 sockets are required.
|
<li>2 sockets (file descriptors). For FTP, 4 sockets are required.
|
||||||
<br>With splice() (Linux, -s), 2 more pipes (file descriptors) = 4 file descriptors, 6 for FTP.
|
<br>Up to 128K of kernel buffer memory. This is the theoretical maximum; actual numbers depend on connection quality and traffic amount.
|
||||||
<br>Up to 128K (up to 256K with splice()) of kernel buffer memory. This is the theoretical maximum; actual numbers depend on connection quality and traffic amount.
|
|
||||||
<br>1 additional socket (file descriptor) during name resolution for non-cached names
|
<br>1 additional socket (file descriptor) during name resolution for non-cached names
|
||||||
<br>1 additional socket during authentication or logging for RADIUS authentication or logging.
|
<br>1 additional socket during authentication or logging for RADIUS authentication or logging.
|
||||||
<li>1 ephemeral port (3 ephemeral ports for FTP connections).
|
<li>1 ephemeral port (3 ephemeral ports for FTP connections).
|
||||||
@ -280,8 +279,6 @@ tuning anything else.
|
|||||||
|
|
||||||
If 3proxy is used in a VPS environment, there can be additional limitations.
|
If 3proxy is used in a VPS environment, there can be additional limitations.
|
||||||
For example, kernel resources, system CPU usage, and IOCTLs can be limited differently, and this can become a bottleneck.
|
For example, kernel resources, system CPU usage, and IOCTLs can be limited differently, and this can become a bottleneck.
|
||||||
splice() (see below) moves work into the kernel, so where the hosting limits kernel resources
|
|
||||||
(seen as nearly 100% CPU usage without any real activity of the application) do not enable it.
|
|
||||||
|
|
||||||
<h4>Extending the Ephemeral Port Range</h4>
|
<h4>Extending the Ephemeral Port Range</h4>
|
||||||
|
|
||||||
@ -644,37 +641,17 @@ monitor "/some/path/3proxy/3proxy.ver"
|
|||||||
e.g., with "touch /some/path/3proxy/3proxy.ver".
|
e.g., with "touch /some/path/3proxy/3proxy.ver".
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
<h4>TCP_NODELAY for Connections with Small Amounts of Data</h4>
|
<h4>Use TCP_NODELAY to Speed Up Connections with Small Amounts of Data</h4>
|
||||||
|
|
||||||
TCP_NODELAY is set by default on client (oc) and server (os)
|
If most requests require an exchange with a small amount of data in both directions
|
||||||
connections: without it, Nagle's algorithm holds back a short write until the
|
without the need for bandwidth, e.g., messengers or small web requests,
|
||||||
previous one is acknowledged, and a peer delays its acknowledgement, so a reply
|
you can eliminate Nagle's algorithm delay with the TCP_NODELAY flag. Usage example:
|
||||||
written in more than one part (e.g. HTTP headers, then a short body) waits tens
|
|
||||||
of milliseconds. On slow connections with high delays, where the bandwidth is the
|
|
||||||
bottleneck and fewer packets matter more than latency, it can be turned off:
|
|
||||||
<pre>
|
<pre>
|
||||||
proxy -osTCP_NODELAY=0 -ocTCP_NODELAY=0
|
proxy -osTCP_NODELAY -ocTCP_NODELAY
|
||||||
</pre>
|
</pre>
|
||||||
|
sets TCP_NODELAY for client (oc) and server (os) connections.
|
||||||
<h4>Use Splice to Speed Up Large Data Transfers</h4>
|
<p>Do not use TCP_NODELAY on slow connections with high delays when
|
||||||
|
connection bandwidth is a bottleneck.
|
||||||
On Linux, splice() passes data between the client and the server connections
|
|
||||||
through a kernel pipe, without copying it into the 3proxy process. Linux builds
|
|
||||||
include it, but a service uses it only when given the -s option:
|
|
||||||
<pre>
|
|
||||||
proxy -s
|
|
||||||
socks -s
|
|
||||||
tcppm -s 8080 192.168.1.1 80
|
|
||||||
</pre>
|
|
||||||
-s0 turns it off explicitly. In builds without it (CMake option 3PROXY_USE_SPLICE=OFF,
|
|
||||||
or -DWITHSPLICE removed from the Makefile) -s is accepted and ignored.
|
|
||||||
<p>splice() pays off when most of the traffic is large transfers: in the
|
|
||||||
<a href="https://github.com/3proxy/3proxy/tree/master/bench">benchmarks</a> it moved
|
|
||||||
1.4 to 1.7 times more data than the read/write path at about half the CPU per gigabyte.
|
|
||||||
It does not help small requests and short connections, which go slower with it, and
|
|
||||||
every spliced connection needs two more pipes (file descriptors) and more kernel
|
|
||||||
buffer memory. Data filters are not applied to spliced data.
|
|
||||||
<p>TCP_NODELAY and splice() do not conflict and can be combined on high-speed connections.
|
|
||||||
|
|
||||||
<h4>Add Grace Delay to Reduce System Calls</h4>
|
<h4>Add Grace Delay to Reduce System Calls</h4>
|
||||||
|
|
||||||
@ -684,7 +661,7 @@ the minimal number of packets in the same direction to apply the algorithm,
|
|||||||
and the last value is the delay added after polling and prior to reading data.
|
and the last value is the delay added after polling and prior to reading data.
|
||||||
The example above adds a 10-millisecond delay before reading data if the average
|
The example above adds a 10-millisecond delay before reading data if the average
|
||||||
polling size is below 8000 bytes and 3 read operations have been made in the same
|
polling size is below 8000 bytes and 3 read operations have been made in the same
|
||||||
direction. It's especially useful with splice. <pre>logdump 1 1</pre> is useful
|
direction. <pre>logdump 1 1</pre> is useful
|
||||||
to see how grace delays work; choose a delay value to avoid filling the read
|
to see how grace delays work; choose a delay value to avoid filling the read
|
||||||
buffer (typically 64K) but keep the request sizes close to the chosen average
|
buffer (typically 64K) but keep the request sizes close to the chosen average
|
||||||
on large file uploads/downloads.
|
on large file uploads/downloads.
|
||||||
|
|||||||
@ -184,10 +184,8 @@ connect to given remote HOST:port instead of listening local connection on -p or
|
|||||||
.br
|
.br
|
||||||
.B -oc\fIOPTIONS\fB, -os\fIOPTIONS\fB, -ol\fIOPTIONS\fB, -or\fIOPTIONS\fB, -oR\fIOPTIONS\fR
|
.B -oc\fIOPTIONS\fB, -os\fIOPTIONS\fB, -ol\fIOPTIONS\fB, -or\fIOPTIONS\fB, -oR\fIOPTIONS\fR
|
||||||
options for proxy-to-client (\fB-oc\fR), proxy-to-server (\fB-os\fR), proxy listening (\fB-ol\fR), connect back client (\fB-or\fR), connect back listening (\fB-oR\fR) sockets.
|
options for proxy-to-client (\fB-oc\fR), proxy-to-server (\fB-os\fR), proxy listening (\fB-ol\fR), connect back client (\fB-or\fR), connect back listening (\fB-oR\fR) sockets.
|
||||||
\fIOPTIONS\fR is a comma separated list of \fINAME\fR or \fINAME\fR=\fIVALUE\fR; a name alone sets the option to 1 (TCP_MAXSEG to the value of \fBmaxseg\fR), and \fINAME\fR=0 turns it off. Options add to the defaults and to the options given earlier for the same socket. An unknown name or a value which is not a number is an error.
|
|
||||||
TCP_NODELAY is set by default on proxy-to-client and proxy-to-server sockets, so a reply written in several parts is not delayed by Nagle's algorithm; use \fB-ocTCP_NODELAY=0\fR or \fB-osTCP_NODELAY=0\fR to turn it off, e.g. to send fewer packets on slow links.
|
|
||||||
Options like TCP_CORK, TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS, TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT, SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY, SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be supported depending on OS.
|
Options like TCP_CORK, TCP_NODELAY, TCP_DEFER_ACCEPT, TCP_QUICKACK, TCP_TIMESTAMPS, TCP_FASTOPEN, SO_REUSEADDR, SO_REUSEPORT, SO_EXCLUSIVEADDRUSE, SO_PORT_SCALABILITY, SO_REUSE_UNICASTPORT, SO_KEEPALIVE, SO_DONTROUTE may be supported depending on OS.
|
||||||
SO_REUSEADDR and SO_REUSEPORT are set by default on the listening sockets (\fB-ol\fR, \fB-oR\fR) on Unix; \fB-olSO_REUSEPORT=0\fR turns one off. On Windows SO_REUSEADDR is not set: it is not required to rebind a listening port and it only lets another local process bind the same address and port. Use SO_EXCLUSIVEADDRUSE (Windows) on the listening socket (\fB-ol\fR) to prevent that.
|
SO_REUSEADDR and SO_REUSEPORT are set on the listening socket by default on Unix. On Windows SO_REUSEADDR is not set: it is not required to rebind a listening port and it only lets another local process bind the same address and port. Use SO_EXCLUSIVEADDRUSE (Windows) on the listening socket (\fB-ol\fR) to prevent that.
|
||||||
.br
|
.br
|
||||||
.B -H
|
.B -H
|
||||||
(for all services) Expect HAProxy PROXY protocol v1 header on incoming connection.
|
(for all services) Expect HAProxy PROXY protocol v1 header on incoming connection.
|
||||||
@ -202,13 +200,11 @@ delay GRACE_DELAY milliseconds before polling if average polling size is below G
|
|||||||
(for admin) secure, allow only secure operations, currently only traffic counters
|
(for admin) secure, allow only secure operations, currently only traffic counters
|
||||||
view without ability to reset.
|
view without ability to reset.
|
||||||
.br
|
.br
|
||||||
(for TCP services, Linux) use splice() to pass data between the client and the
|
(for TCP services, Linux) enable splice(). splice() is not built by default and
|
||||||
server without copying it through 3proxy. It is off unless -s is given (-s0 turns
|
is disabled even when built, because current Linux does not implement
|
||||||
it off explicitly). splice() suits large transfers: they go faster and use less
|
SPLICE_F_MOVE, so no real zero-copy takes place and the read/write path is faster
|
||||||
CPU. Small requests and short connections go slower with it, every connection
|
for most traffic. Rebuild with -DWITHSPLICE to make -s available, -s0 disables it
|
||||||
needs two more pipes (file descriptors) and more kernel memory, and data filters
|
explicitly.
|
||||||
are not applied to spliced data. Available in builds with WITHSPLICE, the default
|
|
||||||
on Linux.
|
|
||||||
.br
|
.br
|
||||||
.B -C
|
.B -C
|
||||||
(for TCP services) a connection closed by any of the sides terminates the
|
(for TCP services) a connection closed by any of the sides terminates the
|
||||||
|
|||||||
56
src/3proxy.c
56
src/3proxy.c
@ -74,8 +74,13 @@ void __stdcall CommandHandler( DWORD dwCommand )
|
|||||||
SetStatus( SERVICE_STOP_PENDING, 0, 1 );
|
SetStatus( SERVICE_STOP_PENDING, 0, 1 );
|
||||||
conf.timetoexit = 1;
|
conf.timetoexit = 1;
|
||||||
conf.paused++;
|
conf.paused++;
|
||||||
/* ServiceMain() reports the service stopped once cyclestep() has
|
Sleep(2000);
|
||||||
seen this and the services are done */
|
SetStatus( SERVICE_STOPPED, 0, 0 );
|
||||||
|
#ifdef WITH_ODBC
|
||||||
|
_3proxy_mutex_lock(&log_mutex);
|
||||||
|
close_sql();
|
||||||
|
_3proxy_mutex_unlock(&log_mutex);
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case SERVICE_CONTROL_PAUSE:
|
case SERVICE_CONTROL_PAUSE:
|
||||||
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
|
SetStatus( SERVICE_PAUSE_PENDING, 0, 1 );
|
||||||
@ -101,13 +106,6 @@ void __stdcall ServiceMain(int argc, unsigned char* argv[] )
|
|||||||
SetStatus( SERVICE_START_PENDING, 0, 1 );
|
SetStatus( SERVICE_START_PENDING, 0, 1 );
|
||||||
SetStatus( SERVICE_RUNNING, 0, 0 );
|
SetStatus( SERVICE_RUNNING, 0, 0 );
|
||||||
cyclestep();
|
cyclestep();
|
||||||
#ifdef WITH_ODBC
|
|
||||||
_3proxy_mutex_lock(&log_mutex);
|
|
||||||
close_sql();
|
|
||||||
_3proxy_mutex_unlock(&log_mutex);
|
|
||||||
#endif
|
|
||||||
/* the last thing: the process may be ended once this is reported */
|
|
||||||
SetStatus( SERVICE_STOPPED, 0, 0 );
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -242,39 +240,6 @@ void dumpcounters(struct trafcount *tlin, int counterd){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lets the services finish before the process exits, for EXITWAIT ms at
|
|
||||||
most: each one waits for its connections, and a connection may hang on
|
|
||||||
its peer. Posts left over from reloads are consumed on the way, the
|
|
||||||
count decides. */
|
|
||||||
#define EXITWAIT 3000
|
|
||||||
static unsigned long nowms(void){
|
|
||||||
#ifdef _WIN32
|
|
||||||
return (unsigned long)GetTickCount();
|
|
||||||
#else
|
|
||||||
struct timeval tv;
|
|
||||||
|
|
||||||
gettimeofday(&tv, NULL);
|
|
||||||
return (unsigned long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void waitservices(void){
|
|
||||||
unsigned long start = nowms(), passed;
|
|
||||||
|
|
||||||
for(;;){
|
|
||||||
int n;
|
|
||||||
|
|
||||||
_3proxy_mutex_lock(&runningservices_mutex);
|
|
||||||
n = runningservices;
|
|
||||||
_3proxy_mutex_unlock(&runningservices_mutex);
|
|
||||||
if(!n) return;
|
|
||||||
/* a clock set back makes this huge, which ends the wait as well */
|
|
||||||
passed = nowms() - start;
|
|
||||||
if(passed >= EXITWAIT) return;
|
|
||||||
if(_3proxy_sem_timedlock(serviceexited, (unsigned)(EXITWAIT - passed))) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cyclestep(void){
|
void cyclestep(void){
|
||||||
struct tm *tm;
|
struct tm *tm;
|
||||||
time_t minutecounter;
|
time_t minutecounter;
|
||||||
@ -371,7 +336,9 @@ void cyclestep(void){
|
|||||||
if(conf.timetoexit){
|
if(conf.timetoexit){
|
||||||
conf.paused++;
|
conf.paused++;
|
||||||
doschedule();
|
doschedule();
|
||||||
waitservices();
|
usleep(SLEEPTIME*999);
|
||||||
|
usleep(SLEEPTIME*999);
|
||||||
|
usleep(SLEEPTIME*999);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,7 +517,6 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
}
|
}
|
||||||
|
|
||||||
_3proxy_mutex_init(&config_mutex);
|
_3proxy_mutex_init(&config_mutex);
|
||||||
_3proxy_mutex_init(&runningservices_mutex);
|
|
||||||
_3proxy_mutex_init(&bandlim_mutex);
|
_3proxy_mutex_init(&bandlim_mutex);
|
||||||
_3proxy_mutex_init(&connlim_mutex);
|
_3proxy_mutex_init(&connlim_mutex);
|
||||||
_3proxy_mutex_init(&tc_mutex);
|
_3proxy_mutex_init(&tc_mutex);
|
||||||
@ -558,7 +524,7 @@ int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
|
|||||||
#ifndef NORADIUS
|
#ifndef NORADIUS
|
||||||
_3proxy_mutex_init(&rad_mutex);
|
_3proxy_mutex_init(&rad_mutex);
|
||||||
#endif
|
#endif
|
||||||
if(_3proxy_sem_init(conf.threadinit, 1, 1) || _3proxy_sem_init_timed(serviceexited, 0, 0x7fffffff)){
|
if(_3proxy_sem_init(conf.threadinit, 1, 1)){
|
||||||
fprintf(stderr, "semaphore init failed\n");
|
fprintf(stderr, "semaphore init failed\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -387,6 +387,7 @@ int
|
|||||||
return shutdown(s, how);
|
return shutdown(s, how);
|
||||||
}
|
}
|
||||||
int WINAPI def_closesocket(void* state, SOCKET s){
|
int WINAPI def_closesocket(void* state, SOCKET s){
|
||||||
|
usleep(SLEEPTIME);
|
||||||
return closesocket(s);
|
return closesocket(s);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
@ -714,9 +715,7 @@ int doconnect(struct clientparam * param){
|
|||||||
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);}
|
if(param->srv->so._getpeername(param->sostate, param->remsock, (struct sockaddr *)¶m->sinsr, &size)==-1) {return (14);}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
#ifndef _WIN32
|
|
||||||
struct linger lg = {1,conf.timeouts[LINGER_TO]};
|
struct linger lg = {1,conf.timeouts[LINGER_TO]};
|
||||||
#endif
|
|
||||||
int tries = RANGETRIES;
|
int tries = RANGETRIES;
|
||||||
|
|
||||||
if(SAISNULL(¶m->sinsr)){
|
if(SAISNULL(¶m->sinsr)){
|
||||||
@ -746,12 +745,9 @@ int doconnect(struct clientparam * param){
|
|||||||
param->sinsl = param->srv->extsa;
|
param->sinsl = param->srv->extsa;
|
||||||
}
|
}
|
||||||
*SAPORT(¶m->sinsl) = 0;
|
*SAPORT(¶m->sinsl) = 0;
|
||||||
setopts(param->remsock, ¶m->srv->srvsockopts);
|
setopts(param->remsock, param->srv->srvsockopts);
|
||||||
|
|
||||||
#ifndef _WIN32
|
|
||||||
/* not on Windows, see the accepting side in proxymain.c */
|
|
||||||
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
#endif
|
|
||||||
if(extbindtodevice(param, param->remsock)) return 12;
|
if(extbindtodevice(param, param->remsock)) return 12;
|
||||||
#ifdef WITH_UN
|
#ifdef WITH_UN
|
||||||
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
if(*SAFAMILY(¶m->sinsl) != AF_UNIX)
|
||||||
|
|||||||
14
src/conf.c
14
src/conf.c
@ -38,9 +38,6 @@ _3proxy_mutex_t bandlim_mutex;
|
|||||||
_3proxy_mutex_t connlim_mutex;
|
_3proxy_mutex_t connlim_mutex;
|
||||||
_3proxy_mutex_t tc_mutex;
|
_3proxy_mutex_t tc_mutex;
|
||||||
_3proxy_mutex_t config_mutex;
|
_3proxy_mutex_t config_mutex;
|
||||||
int runningservices = 0;
|
|
||||||
_3proxy_mutex_t runningservices_mutex;
|
|
||||||
_3proxy_sem_t serviceexited;
|
|
||||||
|
|
||||||
int haveerror = 0;
|
int haveerror = 0;
|
||||||
int linenum = 0;
|
int linenum = 0;
|
||||||
@ -79,18 +76,7 @@ DWORD WINAPI startsrv(LPVOID data) {
|
|||||||
void * startsrv(void * data) {
|
void * startsrv(void * data) {
|
||||||
#endif
|
#endif
|
||||||
struct child *d = (struct child *)data;
|
struct child *d = (struct child *)data;
|
||||||
|
|
||||||
/* counted here rather than where the service is listed, so every way
|
|
||||||
out of it is counted too; start_proxy_thread() waits for the service
|
|
||||||
to start, so it is counted by the time that returns */
|
|
||||||
_3proxy_mutex_lock(&runningservices_mutex);
|
|
||||||
runningservices++;
|
|
||||||
_3proxy_mutex_unlock(&runningservices_mutex);
|
|
||||||
mainfunc(d->argc, (char **)d->argv);
|
mainfunc(d->argc, (char **)d->argv);
|
||||||
_3proxy_mutex_lock(&runningservices_mutex);
|
|
||||||
runningservices--;
|
|
||||||
_3proxy_mutex_unlock(&runningservices_mutex);
|
|
||||||
_3proxy_sem_unlock(serviceexited);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,18 +23,14 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
|
SOCKET sc = INVALID_SOCKET, ss = INVALID_SOCKET, clidatasock = INVALID_SOCKET;
|
||||||
SASIZETYPE sasize;
|
SASIZETYPE sasize;
|
||||||
char * req = NULL;
|
char * req = NULL;
|
||||||
#ifndef _WIN32
|
|
||||||
struct linger lg;
|
struct linger lg;
|
||||||
#endif
|
|
||||||
struct pollfd fds;
|
struct pollfd fds;
|
||||||
|
|
||||||
if(!(buf = malloc(BUFSIZE))) RETURN(876);
|
if(!(buf = malloc(BUFSIZE))) RETURN(876);
|
||||||
param->ctrlsock = param->clisock;
|
param->ctrlsock = param->clisock;
|
||||||
param->operation = CONNECT;
|
param->operation = CONNECT;
|
||||||
#ifndef _WIN32
|
|
||||||
lg.l_onoff = 1;
|
lg.l_onoff = 1;
|
||||||
lg.l_linger = conf.timeouts[STRING_L];
|
lg.l_linger = conf.timeouts[STRING_L];;
|
||||||
#endif
|
|
||||||
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
if(socksend(param, param->ctrlsock, (unsigned char *)"220 Ready\r\n", 11, conf.timeouts[STRING_S])!=11) {RETURN (801);}
|
||||||
for(;;){
|
for(;;){
|
||||||
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
|
i = sockgetlinebuf(param, CLIENT, buf, BUFSIZE - 10, '\n', conf.timeouts[CONNECTION_S]);
|
||||||
@ -266,11 +262,8 @@ void * ftpprchild(struct clientparam* param) {
|
|||||||
}
|
}
|
||||||
sc = param->remsock;
|
sc = param->remsock;
|
||||||
param->remsock = ss;
|
param->remsock = ss;
|
||||||
#ifndef _WIN32
|
|
||||||
/* not on Windows, see the accepting side in proxymain.c */
|
|
||||||
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, param->remsock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
param->srv->so._setsockopt(param->sostate, clidatasock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
#endif
|
|
||||||
param->clisock = clidatasock;
|
param->clisock = clidatasock;
|
||||||
res = mapsocket(param, conf.timeouts[CONNECTION_S]);
|
res = mapsocket(param, conf.timeouts[CONNECTION_S]);
|
||||||
if(param->remsock != INVALID_SOCKET) {
|
if(param->remsock != INVALID_SOCKET) {
|
||||||
|
|||||||
@ -1154,37 +1154,6 @@ static void httpsrv_drain(struct clientparam *param, uint64_t len)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ends a connection this server is closing while the client may still be
|
|
||||||
sending: a body which was not read, or a request sent behind one answered
|
|
||||||
with a close. Closing with that unread resets the connection, and the
|
|
||||||
client loses the answer. The sending side is shut down instead and what
|
|
||||||
arrives is thrown away until the client closes too, for a bounded time
|
|
||||||
and amount, which is what lets the answer be read. */
|
|
||||||
static void httpsrv_linger(struct clientparam *param)
|
|
||||||
{
|
|
||||||
char buf[HTTPSRV_BLOCK];
|
|
||||||
struct pollfd fd;
|
|
||||||
uint64_t left = HTTPSRV_MAXBODY;
|
|
||||||
time_t deadline = time(NULL) + conf.timeouts[LINGER_TO];
|
|
||||||
|
|
||||||
if(param->clisock == INVALID_SOCKET) return;
|
|
||||||
if(param->srv->so._shutdown(param->sostate, param->clisock, SHUT_WR)) return;
|
|
||||||
while(left){
|
|
||||||
int timeo = (int)(deadline - time(NULL));
|
|
||||||
int got;
|
|
||||||
|
|
||||||
if(timeo <= 0) break;
|
|
||||||
memset(&fd, 0, sizeof(fd));
|
|
||||||
fd.fd = param->clisock;
|
|
||||||
fd.events = POLLIN;
|
|
||||||
if(param->srv->so._poll(param->sostate, &fd, 1, timeo * 1000) <= 0) break;
|
|
||||||
got = (int)param->srv->so._recv(param->sostate, param->clisock, buf,
|
|
||||||
(left > sizeof(buf))? (int)sizeof(buf) : (int)left, 0);
|
|
||||||
if(got <= 0) break;
|
|
||||||
left -= (uint64_t)got;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keeps the request as the client wrote it. Only what a handoff needs: the
|
/* Keeps the request as the client wrote it. Only what a handoff needs: the
|
||||||
request line and the headers, exactly as they came, since the proxy code
|
request line and the headers, exactly as they came, since the proxy code
|
||||||
reads them again from the beginning. */
|
reads them again from the beginning. */
|
||||||
@ -1482,12 +1451,6 @@ static int httpsrv_request(struct clientparam *param, struct httpreq *r)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(i && i != 10){
|
if(i && i != 10){
|
||||||
/* The body goes before the answer, as for any answer this server
|
|
||||||
gives: left in the stream it would be read as the next request. */
|
|
||||||
if(!r->drained){
|
|
||||||
if(r->contentlen) httpsrv_drain(param, r->contentlen);
|
|
||||||
r->drained = 1;
|
|
||||||
}
|
|
||||||
/* 4 no credentials, 5 unknown user, 6 wrong password: all of them
|
/* 4 no credentials, 5 unknown user, 6 wrong password: all of them
|
||||||
should let the client offer credentials again. */
|
should let the client offer credentials again. */
|
||||||
if(i >= 4 && i <= 6) op_authrequired(r);
|
if(i >= 4 && i <= 6) op_authrequired(r);
|
||||||
@ -1610,12 +1573,7 @@ void * httpsrvchild(struct clientparam *param)
|
|||||||
dolog(param, (unsigned char *)logbuf);
|
dolog(param, (unsigned char *)logbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(handoff) break;
|
if(handoff || !r.keepalive) break;
|
||||||
if(!r.keepalive){
|
|
||||||
/* the client may still be sending; let it read the answer */
|
|
||||||
httpsrv_linger(param);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
first = 0;
|
first = 0;
|
||||||
}
|
}
|
||||||
/* A child named by an access rule takes the connection over, which the
|
/* A child named by an access rule takes the connection over, which the
|
||||||
|
|||||||
@ -467,10 +467,6 @@ struct dictionary;
|
|||||||
struct node;
|
struct node;
|
||||||
struct property;
|
struct property;
|
||||||
extern _3proxy_mutex_t config_mutex;
|
extern _3proxy_mutex_t config_mutex;
|
||||||
/* services running, and a post each time one of them has finished */
|
|
||||||
extern int runningservices;
|
|
||||||
extern _3proxy_mutex_t runningservices_mutex;
|
|
||||||
extern _3proxy_sem_t serviceexited;
|
|
||||||
extern _3proxy_mutex_t bandlim_mutex;
|
extern _3proxy_mutex_t bandlim_mutex;
|
||||||
extern _3proxy_mutex_t connlim_mutex;
|
extern _3proxy_mutex_t connlim_mutex;
|
||||||
extern _3proxy_mutex_t tc_mutex;
|
extern _3proxy_mutex_t tc_mutex;
|
||||||
@ -501,8 +497,7 @@ extern struct socketoptions {
|
|||||||
int opt;
|
int opt;
|
||||||
char * optname;
|
char * optname;
|
||||||
} sockopts[];
|
} sockopts[];
|
||||||
int getopts(const char *s, struct sockoptset *opts);
|
void setopts(SOCKET s, int opts);
|
||||||
void setopts(SOCKET s, const struct sockoptset *opts);
|
|
||||||
char * printopts(char *sep);
|
char * printopts(char *sep);
|
||||||
|
|
||||||
#ifdef _WINCE
|
#ifdef _WINCE
|
||||||
|
|||||||
175
src/proxymain.c
175
src/proxymain.c
@ -224,62 +224,27 @@ char * printopts(char *sep){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* A comma separated list of options, each NAME or NAME=VALUE; a name alone
|
int getopts(const char *s){
|
||||||
stands for 1, or for TCP_MAXSEG for the value of maxseg. Options are added
|
int i=0, ret=0;
|
||||||
to what is already set, so a default is changed only by naming it, and
|
for(; sockopts[i].optname; i++)if(strstr(s,sockopts[i].optname)) ret |= (1<<i);
|
||||||
turned off with NAME=0. An unknown name or a value which is not a number
|
return ret;
|
||||||
makes the whole list fail. */
|
|
||||||
int getopts(const char *s, struct sockoptset *opts){
|
|
||||||
while(*s){
|
|
||||||
const char *end = strchr(s, ',');
|
|
||||||
const char *eq;
|
|
||||||
size_t len;
|
|
||||||
int i, value = 1;
|
|
||||||
|
|
||||||
if(!end) end = s + strlen(s);
|
|
||||||
eq = memchr(s, '=', (size_t)(end - s));
|
|
||||||
len = (size_t)((eq? eq : end) - s);
|
|
||||||
if(eq){
|
|
||||||
char *num;
|
|
||||||
|
|
||||||
if(!len) return -1;
|
|
||||||
value = (int)strtol(eq + 1, &num, 0);
|
|
||||||
if(num == eq + 1 || num != end) return -1;
|
|
||||||
}
|
}
|
||||||
else if(len) {
|
|
||||||
|
void setopts(SOCKET s, int opts){
|
||||||
|
int i, opt, set;
|
||||||
|
for(i = 0; opts >= (opt = (1<<i)); i++){
|
||||||
|
set = 1;
|
||||||
#ifdef TCP_MAXSEG
|
#ifdef TCP_MAXSEG
|
||||||
if(len == 10 && !strncmp(s, "TCP_MAXSEG", 10)) value = 0;
|
if(sockopts[i].opt == TCP_MAXSEG){
|
||||||
#endif
|
if(!conf.maxseg) continue;
|
||||||
}
|
set = conf.maxseg;
|
||||||
if(len){
|
|
||||||
for(i = 0; sockopts[i].optname; i++)
|
|
||||||
if(strlen(sockopts[i].optname) == len && !strncmp(s, sockopts[i].optname, len)) break;
|
|
||||||
if(!sockopts[i].optname || i >= MAXSOCKOPTS) return -1;
|
|
||||||
opts->set |= (1u << i);
|
|
||||||
opts->value[i] = value;
|
|
||||||
}
|
|
||||||
s = *end? end + 1 : end;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setopts(SOCKET s, const struct sockoptset *opts){
|
|
||||||
int i, value;
|
|
||||||
|
|
||||||
for(i = 0; sockopts[i].optname && i < MAXSOCKOPTS; i++){
|
|
||||||
if(!(opts->set & (1u << i))) continue;
|
|
||||||
value = opts->value[i];
|
|
||||||
#ifdef TCP_MAXSEG
|
|
||||||
if(sockopts[i].opt == TCP_MAXSEG && *sockopts[i].optname == 'T'){
|
|
||||||
if(!value) value = conf.maxseg;
|
|
||||||
if(!value) continue;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
setsockopt(s, *sockopts[i].optname == 'T'? IPPROTO_TCP:
|
if(opts & opt) setsockopt(s, *sockopts[i].optname == 'T'? IPPROTO_TCP:
|
||||||
#ifdef SOL_IP
|
#ifdef SOL_IP
|
||||||
*sockopts[i].optname == 'I'? SOL_IP:
|
*sockopts[i].optname == 'I'? SOL_IP:
|
||||||
#endif
|
#endif
|
||||||
SOL_SOCKET, sockopts[i].opt, (char *)&value, sizeof(value));
|
SOL_SOCKET, sockopts[i].opt, (char *)&set, sizeof(set));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,9 +306,7 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
unsigned char *cbc_string = NULL, *cbl_string = NULL;
|
unsigned char *cbc_string = NULL, *cbl_string = NULL;
|
||||||
PROXYSOCKADDRTYPE cbsa;
|
PROXYSOCKADDRTYPE cbsa;
|
||||||
FILE *fp = NULL;
|
FILE *fp = NULL;
|
||||||
#ifndef _WIN32
|
|
||||||
struct linger lg;
|
struct linger lg;
|
||||||
#endif
|
|
||||||
int nlog = 5000;
|
int nlog = 5000;
|
||||||
int haveslot = 0, nfull = 0;
|
int haveslot = 0, nfull = 0;
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
@ -391,9 +354,7 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
" -64 Prefer IPv6 for outgoing connections, use both IPv4 and IPv6\n"
|
" -64 Prefer IPv6 for outgoing connections, use both IPv4 and IPv6\n"
|
||||||
" -ocOPTIONS, -osOPTIONS, -olOPTIONS, -orOPTIONS -oROPTIONS - options for\n"
|
" -ocOPTIONS, -osOPTIONS, -olOPTIONS, -orOPTIONS -oROPTIONS - options for\n"
|
||||||
" to-client (oc), to-server (os), listening (ol) socket, connect back client\n"
|
" to-client (oc), to-server (os), listening (ol) socket, connect back client\n"
|
||||||
" (or) socket, connect back server (oR) listening socket, a comma separated\n"
|
" (or) socket, connect back server (oR) listening socket\n"
|
||||||
" list of NAME or NAME=VALUE (NAME=0 turns an option off); TCP_NODELAY is\n"
|
|
||||||
" set on to-client and to-server sockets by default\n"
|
|
||||||
" where possible options are: ";
|
" where possible options are: ";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -438,34 +399,6 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
srvinit(&srv, &defparam);
|
srvinit(&srv, &defparam);
|
||||||
srv.pf = childdef.pf;
|
srv.pf = childdef.pf;
|
||||||
isudp = childdef.isudp;
|
isudp = childdef.isudp;
|
||||||
|
|
||||||
/* Default socket options, which -oc, -os, -ol and -oR change by naming
|
|
||||||
them, NAME=0 to turn one off.
|
|
||||||
|
|
||||||
Nagle's algorithm holds back a short write until the previous one is
|
|
||||||
acknowledged, and a peer delays its acknowledgement, which stalls every
|
|
||||||
reply written in more than one piece.
|
|
||||||
|
|
||||||
SO_REUSEADDR is not set on Windows: it is not needed to rebind a
|
|
||||||
listening port there, and it only allows another local process to bind
|
|
||||||
the same address and port, with undefined behaviour as to which socket
|
|
||||||
receives the connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that
|
|
||||||
instead. A Windows build which does not share the listening socket is the
|
|
||||||
exception: its UDP services bind a second socket to the same address to
|
|
||||||
answer from, and Windows only allows that when both sockets ask for it.
|
|
||||||
*/
|
|
||||||
(void)getopts("TCP_NODELAY", &srv.clisockopts);
|
|
||||||
(void)getopts("TCP_NODELAY", &srv.srvsockopts);
|
|
||||||
#ifndef _WIN32
|
|
||||||
(void)getopts("SO_REUSEADDR", &srv.lissockopts);
|
|
||||||
(void)getopts("SO_REUSEADDR", &srv.cbssockopts);
|
|
||||||
#elif !defined SHARE_UDP_SOCKET
|
|
||||||
if(isudp) (void)getopts("SO_REUSEADDR", &srv.lissockopts);
|
|
||||||
#endif
|
|
||||||
#ifdef SO_REUSEPORT
|
|
||||||
(void)getopts("SO_REUSEPORT", &srv.lissockopts);
|
|
||||||
(void)getopts("SO_REUSEPORT", &srv.cbssockopts);
|
|
||||||
#endif
|
|
||||||
#ifndef NOUDPMAIN
|
#ifndef NOUDPMAIN
|
||||||
if(isudp) {
|
if(isudp) {
|
||||||
if(!udp_table.ihashtable)inithashtable(&udp_table, 64, 256, 65536);
|
if(!udp_table.ihashtable)inithashtable(&udp_table, 64, 256, 65536);
|
||||||
@ -692,8 +625,7 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
/* admin is an httpsrv, where -s is the limited mode */
|
if(isudp || srv.service == S_ADMIN)
|
||||||
if(isudp || srv.service == S_ADMIN || srv.service == S_HTTPSRV)
|
|
||||||
#endif
|
#endif
|
||||||
srv.s_option = 1 + atoi(argv[i]+2);
|
srv.s_option = 1 + atoi(argv[i]+2);
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
@ -704,19 +636,19 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
case 'o':
|
case 'o':
|
||||||
switch(argv[i][2]){
|
switch(argv[i][2]){
|
||||||
case 's':
|
case 's':
|
||||||
error = getopts(argv[i]+3, &srv.srvsockopts);
|
srv.srvsockopts = getopts(argv[i]+3);
|
||||||
break;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
error = getopts(argv[i]+3, &srv.clisockopts);
|
srv.clisockopts = getopts(argv[i]+3);
|
||||||
break;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
error = getopts(argv[i]+3, &srv.lissockopts);
|
srv.lissockopts = getopts(argv[i]+3);
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
error = getopts(argv[i]+3, &srv.cbcsockopts);
|
srv.cbcsockopts = getopts(argv[i]+3);
|
||||||
break;
|
break;
|
||||||
case 'R':
|
case 'R':
|
||||||
error = getopts(argv[i]+3, &srv.cbssockopts);
|
srv.cbcsockopts = getopts(argv[i]+3);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
error = 1;
|
error = 1;
|
||||||
@ -895,13 +827,42 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
perror("socket()");
|
perror("socket()");
|
||||||
return -2;
|
return -2;
|
||||||
}
|
}
|
||||||
setopts(sock, &srv.lissockopts);
|
setopts(sock, srv.lissockopts);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
ioctlsocket(sock, FIONBIO, &ul);
|
ioctlsocket(sock, FIONBIO, &ul);
|
||||||
#else
|
#else
|
||||||
fcntl(sock,F_SETFL,O_NONBLOCK | fcntl(sock,F_GETFL));
|
fcntl(sock,F_SETFL,O_NONBLOCK | fcntl(sock,F_GETFL));
|
||||||
#endif
|
#endif
|
||||||
srv.srvsock = sock;
|
srv.srvsock = sock;
|
||||||
|
#ifdef WITH_UN
|
||||||
|
if(*SAFAMILY(&srv.intsa) != AF_UNIX)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
/* SO_REUSEADDR is not set on Windows: it is not needed to rebind a listening
|
||||||
|
port there, and it only allows another local process to bind the same
|
||||||
|
address and port, with undefined behaviour as to which socket receives the
|
||||||
|
connections. Use -olSO_EXCLUSIVEADDRUSE to prevent that instead.
|
||||||
|
|
||||||
|
A Windows build which does not share the listening socket is the exception:
|
||||||
|
its UDP services bind a second socket to the same address to answer from,
|
||||||
|
and Windows only allows that when both sockets ask for it.
|
||||||
|
*/
|
||||||
|
#ifndef _WIN32
|
||||||
|
opt = 1;
|
||||||
|
if(srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int)))perror("setsockopt()");
|
||||||
|
#else
|
||||||
|
#ifndef SHARE_UDP_SOCKET
|
||||||
|
if(isudp){
|
||||||
|
opt = 1;
|
||||||
|
if(srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int)))perror("setsockopt()");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef SO_REUSEPORT
|
||||||
|
opt = 1;
|
||||||
|
srv.so._setsockopt(srv.so.state, sock, SOL_SOCKET, SO_REUSEPORT, (char *)&opt, sizeof(int));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
||||||
if(bindtodevice(&srv.so, srv.so.state, sock, srv.ibindtodevice, *SAFAMILY(&srv.intsa))) {
|
if(bindtodevice(&srv.so, srv.so.state, sock, srv.ibindtodevice, *SAFAMILY(&srv.intsa))) {
|
||||||
dolog(&defparam, (unsigned char *)"failed to bind device");
|
dolog(&defparam, (unsigned char *)"failed to bind device");
|
||||||
@ -994,7 +955,16 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
freesrvstrings(&srv, cbc_string, cbl_string);
|
freesrvstrings(&srv, cbc_string, cbl_string);
|
||||||
return -6;
|
return -6;
|
||||||
}
|
}
|
||||||
setopts(srv.cbsock, &srv.cbssockopts);
|
#ifndef _WIN32
|
||||||
|
opt = 1;
|
||||||
|
srv.so._setsockopt(srv.so.state, srv.cbsock, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(int));
|
||||||
|
#endif
|
||||||
|
#ifdef SO_REUSEPORT
|
||||||
|
opt = 1;
|
||||||
|
srv.so._setsockopt(srv.so.state, srv.cbsock, SOL_SOCKET, SO_REUSEPORT, (char *)&opt, sizeof(int));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
setopts(srv.cbsock, srv.cbssockopts);
|
||||||
|
|
||||||
if(srv.so._bind(srv.so.state, srv.cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) {
|
if(srv.so._bind(srv.so.state, srv.cbsock, (struct sockaddr*)&cbsa, SASIZE(&cbsa))==-1) {
|
||||||
dolog(&defparam, (unsigned char *)"Failed to bind connect back socket");
|
dolog(&defparam, (unsigned char *)"Failed to bind connect back socket");
|
||||||
@ -1053,7 +1023,7 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
if(iscbc){
|
if(iscbc){
|
||||||
new_sock=so._socket(so.state, SASOCK(&defparam.sincr), SOCK_STREAM, IPPROTO_TCP);
|
new_sock=so._socket(so.state, SASOCK(&defparam.sincr), SOCK_STREAM, IPPROTO_TCP);
|
||||||
if(new_sock != INVALID_SOCKET){
|
if(new_sock != INVALID_SOCKET){
|
||||||
setopts(new_sock, &srv.cbcsockopts);
|
setopts(new_sock, srv.cbcsockopts);
|
||||||
|
|
||||||
parsehost(srv.family, cbc_string, (struct sockaddr *)&defparam.sincr);
|
parsehost(srv.family, cbc_string, (struct sockaddr *)&defparam.sincr);
|
||||||
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
#if defined SO_BINDTODEVICE || defined IP_BOUND_IF
|
||||||
@ -1130,7 +1100,7 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
setopts(new_sock, &srv.clisockopts);
|
setopts(new_sock, srv.clisockopts);
|
||||||
}
|
}
|
||||||
size = sizeof(defparam.sincl);
|
size = sizeof(defparam.sincl);
|
||||||
if(srv.so._getsockname(srv.so.state, new_sock, (struct sockaddr *)&defparam.sincl, &size)){
|
if(srv.so._getsockname(srv.so.state, new_sock, (struct sockaddr *)&defparam.sincl, &size)){
|
||||||
@ -1147,14 +1117,10 @@ int MODULEMAINFUNC (int argc, char** argv){
|
|||||||
#else
|
#else
|
||||||
fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL));
|
fcntl(new_sock,F_SETFL,O_NONBLOCK | fcntl(new_sock,F_GETFL));
|
||||||
#endif
|
#endif
|
||||||
#ifndef _WIN32
|
|
||||||
/* Not on Windows: there closesocket() on a non-blocking socket with a linger
|
|
||||||
timeout fails with WSAEWOULDBLOCK while data is queued, leaving the socket
|
|
||||||
open. Without it the close completes in the background, data first. */
|
|
||||||
lg.l_onoff = 1;
|
lg.l_onoff = 1;
|
||||||
lg.l_linger = conf.timeouts[STRING_L];
|
lg.l_linger = conf.timeouts[STRING_L];
|
||||||
|
|
||||||
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_LINGER, (char *)&lg, sizeof(lg));
|
||||||
#endif
|
|
||||||
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int));
|
srv.so._setsockopt(srv.so.state, new_sock, SOL_SOCKET, SO_OOBINLINE, (char *)&opt, sizeof(int));
|
||||||
}
|
}
|
||||||
#ifndef NOUDPMAIN
|
#ifndef NOUDPMAIN
|
||||||
@ -1447,34 +1413,21 @@ void freeparam(struct clientparam * param) {
|
|||||||
}
|
}
|
||||||
if(param->clibuf) free(param->clibuf);
|
if(param->clibuf) free(param->clibuf);
|
||||||
if(param->srvbuf) free(param->srvbuf);
|
if(param->srvbuf) free(param->srvbuf);
|
||||||
/* Not shut down on Windows: there shutting down receiving resets the
|
|
||||||
connection as soon as the peer sends anything more, and close()
|
|
||||||
alone sends whatever is queued before its FIN. Elsewhere it stays:
|
|
||||||
on the BSDs it discards what is left unread, so the close that
|
|
||||||
follows is not turned into a reset. */
|
|
||||||
if(param->srv) {
|
if(param->srv) {
|
||||||
if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) {
|
if(param->ctrlsocksrv != INVALID_SOCKET && param->ctrlsocksrv != param->remsock) {
|
||||||
#ifndef _WIN32
|
|
||||||
param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR);
|
param->srv->so._shutdown(param->sostate, param->ctrlsocksrv, SHUT_RDWR);
|
||||||
#endif
|
|
||||||
param->srv->so._closesocket(param->sostate, param->ctrlsocksrv);
|
param->srv->so._closesocket(param->sostate, param->ctrlsocksrv);
|
||||||
}
|
}
|
||||||
if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) {
|
if(param->ctrlsock != INVALID_SOCKET && param->ctrlsock != param->clisock) {
|
||||||
#ifndef _WIN32
|
|
||||||
param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR);
|
param->srv->so._shutdown(param->sostate, param->ctrlsock, SHUT_RDWR);
|
||||||
#endif
|
|
||||||
param->srv->so._closesocket(param->sostate, param->ctrlsock);
|
param->srv->so._closesocket(param->sostate, param->ctrlsock);
|
||||||
}
|
}
|
||||||
if(param->remsock != INVALID_SOCKET) {
|
if(param->remsock != INVALID_SOCKET) {
|
||||||
#ifndef _WIN32
|
|
||||||
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
|
param->srv->so._shutdown(param->sostate, param->remsock, SHUT_RDWR);
|
||||||
#endif
|
|
||||||
param->srv->so._closesocket(param->sostate, param->remsock);
|
param->srv->so._closesocket(param->sostate, param->remsock);
|
||||||
}
|
}
|
||||||
if(param->clisock != INVALID_SOCKET) {
|
if(param->clisock != INVALID_SOCKET) {
|
||||||
#ifndef _WIN32
|
|
||||||
param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR);
|
param->srv->so._shutdown(param->sostate, param->clisock, SHUT_RDWR);
|
||||||
#endif
|
|
||||||
param->srv->so._closesocket(param->sostate, param->clisock);
|
param->srv->so._closesocket(param->sostate, param->clisock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -819,6 +819,9 @@ log("timeout");
|
|||||||
else if(inclientpipe || inserverpipe) res = 94;
|
else if(inclientpipe || inserverpipe) res = 94;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if((param->nwrites > 0 && !SERVERTERMWRITE) || (param->nreads > 0 && !CLIENTTERMWRITE))
|
||||||
|
usleep(SLEEPTIME * 10);
|
||||||
|
|
||||||
CLEANRET:
|
CLEANRET:
|
||||||
|
|
||||||
#ifdef WITHSPLICE
|
#ifdef WITHSPLICE
|
||||||
|
|||||||
@ -184,7 +184,7 @@ void * sockschild(struct clientparam* param) {
|
|||||||
param->operation = command == 2?BIND:UDPASSOC;
|
param->operation = command == 2?BIND:UDPASSOC;
|
||||||
if(command == 2){
|
if(command == 2){
|
||||||
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
|
if ((param->remsock=param->srv->so._socket(param->sostate, SASOCK(¶m->req), SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) {RETURN (11);}
|
||||||
setopts(param->remsock, ¶m->srv->lissockopts);
|
setopts(param->remsock, param->srv->lissockopts);
|
||||||
if(extbindtodevice(param, param->remsock)) {RETURN (12);}
|
if(extbindtodevice(param, param->remsock)) {RETURN (12);}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -529,14 +529,6 @@ struct filterp {
|
|||||||
|
|
||||||
#define MAX_FILTERS 16
|
#define MAX_FILTERS 16
|
||||||
|
|
||||||
/* Socket options for one kind of socket, -oc and the like: which of
|
|
||||||
sockopts[] are given, by their index there, and the value of each. */
|
|
||||||
#define MAXSOCKOPTS 32
|
|
||||||
struct sockoptset {
|
|
||||||
unsigned set;
|
|
||||||
int value[MAXSOCKOPTS];
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct sockfuncs {
|
struct sockfuncs {
|
||||||
void * state;
|
void * state;
|
||||||
@ -621,7 +613,7 @@ struct srvparam {
|
|||||||
int stacksize;
|
int stacksize;
|
||||||
int noforce;
|
int noforce;
|
||||||
int anonymous;
|
int anonymous;
|
||||||
struct sockoptset clisockopts, srvsockopts, lissockopts, cbcsockopts, cbssockopts;
|
int clisockopts, srvsockopts, lissockopts, cbcsockopts, cbssockopts;
|
||||||
int gracetraf, gracenum, gracedelay;
|
int gracetraf, gracenum, gracedelay;
|
||||||
int requirecert;
|
int requirecert;
|
||||||
int haproxy;
|
int haproxy;
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
"""The admin interface, now a set of handlers on the HTTP server."""
|
"""The admin interface, now a set of handlers on the HTTP server."""
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
def run(t):
|
||||||
adm = t.free_port()
|
adm = t.free_port()
|
||||||
lim = t.free_port()
|
lim = t.free_port()
|
||||||
server = t.start("admin", f"""
|
t.start("admin", f"""
|
||||||
log
|
log
|
||||||
auth iponly
|
auth iponly
|
||||||
allow *
|
allow *
|
||||||
@ -22,34 +20,16 @@ def run(t):
|
|||||||
|
|
||||||
url = f"http://127.0.0.1:{adm}"
|
url = f"http://127.0.0.1:{adm}"
|
||||||
|
|
||||||
def reload(path):
|
|
||||||
"""Ask for a reload and wait for it to be done. It happens on the next
|
|
||||||
tick of the main loop, at most a second later; both old services stop
|
|
||||||
and the new ones start, and a request in between is refused (Windows
|
|
||||||
cannot bind a port still listening) or reset."""
|
|
||||||
before = server.output()
|
|
||||||
accepting = before.count("Accepting connections")
|
|
||||||
exiting = before.count("Exiting thread")
|
|
||||||
reply = t.http(url + path)
|
|
||||||
deadline = time.time() + 10
|
|
||||||
while time.time() < deadline:
|
|
||||||
now = server.output()
|
|
||||||
if (now.count("Accepting connections") >= accepting + 2
|
|
||||||
and now.count("Exiting thread") >= exiting + 2):
|
|
||||||
break
|
|
||||||
time.sleep(0.05)
|
|
||||||
return reply
|
|
||||||
|
|
||||||
# --- the predefined pages -----------------------------------------
|
# --- the predefined pages -----------------------------------------
|
||||||
t.eq(200, t.http(url + "/").status, "the main page")
|
t.eq(200, t.http(url + "/").status, "the main page")
|
||||||
t.eq(200, t.http(url + "/C").status, "the counters page")
|
t.eq(200, t.http(url + "/C").status, "the counters page")
|
||||||
t.eq(200, reload("/R").status, "the reload page")
|
t.eq(200, t.http(url + "/R").status, "the reload page")
|
||||||
t.eq(200, t.http(url + "/S").status, "the services page")
|
t.eq(200, t.http(url + "/S").status, "the services page")
|
||||||
|
|
||||||
counters = t.http(url + "/C")
|
counters = t.http(url + "/C")
|
||||||
t.contains(counters, "countin", "the counters page names the counter type")
|
t.contains(counters, "countin", "the counters page names the counter type")
|
||||||
t.contains(counters, "<tr>", "the counters page renders a table")
|
t.contains(counters, "<tr>", "the counters page renders a table")
|
||||||
t.contains(reload("/R"), "Reload", "the reload page confirms the request")
|
t.contains(t.http(url + "/R"), "Reload", "the reload page confirms the request")
|
||||||
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
t.contains(t.http(url + "/S"), "<", "the services page returns markup")
|
||||||
|
|
||||||
# --- the menu no longer offers the removed config editor -----------
|
# --- the menu no longer offers the removed config editor -----------
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user