fadd
This commit is contained in:
@@ -1,18 +1,25 @@
|
|||||||
# 戎行专用:OpenVPN 客户端 + Dante SOCKS5(仅内网代理)
|
# 戎行专用:OpenVPN 客户端 + microsocks SOCKS5(走系统路由/tun0)
|
||||||
|
FROM alpine:3.19 AS socks-builder
|
||||||
|
|
||||||
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||||
|
&& apk add --no-cache build-base
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
COPY third_party/microsocks/ ./
|
||||||
|
RUN make && strip microsocks
|
||||||
|
|
||||||
FROM alpine:3.19
|
FROM alpine:3.19
|
||||||
|
|
||||||
# 与业务镜像一致,使用国内 apk 源
|
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories \
|
||||||
&& apk add --no-cache \
|
&& apk add --no-cache \
|
||||||
openvpn \
|
openvpn \
|
||||||
bash \
|
bash \
|
||||||
iproute2 \
|
iproute2 \
|
||||||
dante-server \
|
|
||||||
curl
|
curl
|
||||||
|
|
||||||
|
COPY --from=socks-builder /src/microsocks /usr/local/bin/microsocks
|
||||||
COPY entrypoint.sh /entrypoint.sh
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
COPY sockd.conf.template /etc/sockd.conf.template
|
RUN chmod +x /entrypoint.sh /usr/local/bin/microsocks
|
||||||
RUN chmod +x /entrypoint.sh
|
|
||||||
|
|
||||||
ENV VPN_DIR=/vpn \
|
ENV VPN_DIR=/vpn \
|
||||||
SOCKS_HOST=0.0.0.0 \
|
SOCKS_HOST=0.0.0.0 \
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
| 文件 | 用途 |
|
| 文件 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `Dockerfile` | OpenVPN + dante-server(sockd)自建镜像 |
|
| `Dockerfile` | OpenVPN + 自编译 microsocks |
|
||||||
| `entrypoint.sh` | 启动 VPN(仅路由戎行 IP)并监听 SOCKS5 `:1080` |
|
| `entrypoint.sh` | 启动 VPN(仅路由戎行 IP)并监听 SOCKS5 `:1080` |
|
||||||
| `sockd.conf.template` | Dante SOCKS 配置模板 |
|
| `third_party/microsocks/` | SOCKS5 源码(避免 Alpine 无包 / Dante 绑错网卡) |
|
||||||
| `tiany0721.ovpn.example` | ovpn 模板 |
|
| `tiany0721.ovpn.example` | ovpn 模板 |
|
||||||
| `docker-compose.rongxing-vpn.snippet.yml` | 独立编排参考(prod 已内置) |
|
| `docker-compose.rongxing-vpn.snippet.yml` | 独立编排参考(prod 已内置) |
|
||||||
| `upload-checklist.txt` | 上传清单 |
|
| `upload-checklist.txt` | 上传清单 |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# OpenVPN 连通后拉起 SOCKS5(dante/sockd);仅服务戎行内网代理。
|
# OpenVPN 连通后拉起 microsocks;出口跟随系统路由(含 tun0),避免 Dante 绑错网卡。
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VPN_DIR="${VPN_DIR:-/vpn}"
|
VPN_DIR="${VPN_DIR:-/vpn}"
|
||||||
@@ -48,15 +48,25 @@ for i in $(seq 1 90); do
|
|||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# 生成 dante 配置并后台启动 sockd
|
echo "[rongxing-vpn] 路由表:"
|
||||||
sed "s/__SOCKS_PORT__/${SOCKS_PORT}/g" /etc/sockd.conf.template > /etc/sockd.conf
|
ip route || true
|
||||||
sockd -f /etc/sockd.conf -D
|
|
||||||
SOCKS_PID="$(pidof sockd | awk '{print $1}')"
|
# 直连自检(不阻断)
|
||||||
if [ -z "${SOCKS_PID}" ]; then
|
if curl -sS -m 5 -o /tmp/rx_probe.out -w "[rongxing-vpn] 直连探测 HTTP %{http_code}\n" "http://${RONGXING_HOST}:7007/" ; then
|
||||||
echo "[rongxing-vpn] sockd 启动失败"
|
head -c 200 /tmp/rx_probe.out 2>/dev/null || true
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
echo "[rongxing-vpn] 直连探测失败: http://${RONGXING_HOST}:7007/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
microsocks -i "$SOCKS_HOST" -p "$SOCKS_PORT" &
|
||||||
|
SOCKS_PID=$!
|
||||||
|
sleep 1
|
||||||
|
if ! kill -0 "$SOCKS_PID" 2>/dev/null; then
|
||||||
|
echo "[rongxing-vpn] microsocks 启动失败"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "[rongxing-vpn] SOCKS5 已监听 :${SOCKS_PORT} (pid=${SOCKS_PID})"
|
echo "[rongxing-vpn] SOCKS5 已监听 :${SOCKS_PORT} (microsocks pid=${SOCKS_PID})"
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
kill "$SOCKS_PID" 2>/dev/null || true
|
kill "$SOCKS_PID" 2>/dev/null || true
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
# Dante SOCKS5 — entrypoint 会替换 __SOCKS_PORT__
|
|
||||||
logoutput: stderr
|
|
||||||
internal: 0.0.0.0 port = __SOCKS_PORT__
|
|
||||||
external: eth0
|
|
||||||
socksmethod: none
|
|
||||||
clientmethod: none
|
|
||||||
|
|
||||||
client pass {
|
|
||||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
|
|
||||||
socks pass {
|
|
||||||
from: 0.0.0.0/0 to: 0.0.0.0/0
|
|
||||||
}
|
|
||||||
3
deployments/openvpn/third_party/microsocks/.gitignore
vendored
Normal file
3
deployments/openvpn/third_party/microsocks/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*.o
|
||||||
|
*.out
|
||||||
|
|
||||||
24
deployments/openvpn/third_party/microsocks/COPYING
vendored
Normal file
24
deployments/openvpn/third_party/microsocks/COPYING
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
microSocks is licensed under the following standard MIT license:
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
Copyright © 2017 rofl0r.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
----------------------------------------------------------------------
|
||||||
35
deployments/openvpn/third_party/microsocks/Makefile
vendored
Normal file
35
deployments/openvpn/third_party/microsocks/Makefile
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# if you want to change/override some variables, do so in a file called
|
||||||
|
# config.mak, which is gets included automatically if it exists.
|
||||||
|
|
||||||
|
prefix = /usr/local
|
||||||
|
bindir = $(prefix)/bin
|
||||||
|
|
||||||
|
PROG = microsocks
|
||||||
|
SRCS = sockssrv.c server.c sblist.c sblist_delete.c
|
||||||
|
OBJS = $(SRCS:.c=.o)
|
||||||
|
|
||||||
|
LIBS = -lpthread
|
||||||
|
|
||||||
|
CFLAGS += -Wall -std=c99
|
||||||
|
|
||||||
|
INSTALL = ./install.sh
|
||||||
|
|
||||||
|
-include config.mak
|
||||||
|
|
||||||
|
all: $(PROG)
|
||||||
|
|
||||||
|
install: $(PROG)
|
||||||
|
$(INSTALL) -D -m 755 $(PROG) $(DESTDIR)$(bindir)/$(PROG)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(PROG)
|
||||||
|
rm -f $(OBJS)
|
||||||
|
|
||||||
|
%.o: %.c
|
||||||
|
$(CC) $(CPPFLAGS) $(CFLAGS) $(INC) $(PIC) -c -o $@ $<
|
||||||
|
|
||||||
|
$(PROG): $(OBJS)
|
||||||
|
$(CC) $(LDFLAGS) $(OBJS) $(LIBS) -o $@
|
||||||
|
|
||||||
|
.PHONY: all clean install
|
||||||
|
|
||||||
65
deployments/openvpn/third_party/microsocks/README.md
vendored
Normal file
65
deployments/openvpn/third_party/microsocks/README.md
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
MicroSocks - multithreaded, small, efficient SOCKS5 server.
|
||||||
|
===========================================================
|
||||||
|
|
||||||
|
a SOCKS5 service that you can run on your remote boxes to tunnel connections
|
||||||
|
through them, if for some reason SSH doesn't cut it for you.
|
||||||
|
|
||||||
|
It's very lightweight, and very light on resources too:
|
||||||
|
|
||||||
|
for every client, a thread with a stack size of 8KB is spawned.
|
||||||
|
the main process basically doesn't consume any resources at all.
|
||||||
|
|
||||||
|
the only limits are the amount of file descriptors and the RAM.
|
||||||
|
|
||||||
|
It's also designed to be robust: it handles resource exhaustion
|
||||||
|
gracefully by simply denying new connections, instead of calling abort()
|
||||||
|
as most other programs do these days.
|
||||||
|
|
||||||
|
another plus is ease-of-use: no config file necessary, everything can be
|
||||||
|
done from the command line and doesn't even need any parameters for quick
|
||||||
|
setup.
|
||||||
|
|
||||||
|
History
|
||||||
|
-------
|
||||||
|
|
||||||
|
This is the successor of "rocksocks5", and it was written with
|
||||||
|
different goals in mind:
|
||||||
|
|
||||||
|
- prefer usage of standard libc functions over homegrown ones
|
||||||
|
- no artificial limits
|
||||||
|
- do not aim for minimal binary size, but for minimal source code size,
|
||||||
|
and maximal readability, reusability, and extensibility.
|
||||||
|
|
||||||
|
as a result of that, ipv4, dns, and ipv6 is supported out of the box
|
||||||
|
and can use the same code, while rocksocks5 has several compile time
|
||||||
|
defines to bring down the size of the resulting binary to extreme values
|
||||||
|
like 10 KB static linked when only ipv4 support is enabled.
|
||||||
|
|
||||||
|
still, if optimized for size, *this* program when static linked against musl
|
||||||
|
libc is not even 50 KB. that's easily usable even on the cheapest routers.
|
||||||
|
|
||||||
|
command line options
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
microsocks -1 -i listenip -p port -u user -P password -b bindaddr
|
||||||
|
|
||||||
|
all arguments are optional.
|
||||||
|
by default listenip is 0.0.0.0 and port 1080.
|
||||||
|
|
||||||
|
option -1 activates auth_once mode: once a specific ip address
|
||||||
|
authed successfully with user/pass, it is added to a whitelist
|
||||||
|
and may use the proxy without auth.
|
||||||
|
this is handy for programs like firefox that don't support
|
||||||
|
user/pass auth. for it to work you'd basically make one connection
|
||||||
|
with another program that supports it, and then you can use firefox too.
|
||||||
|
for example, authenticate once using curl:
|
||||||
|
|
||||||
|
curl --socks5 user:password@listenip:port anyurl
|
||||||
|
|
||||||
|
|
||||||
|
Supported SOCKS5 Features
|
||||||
|
-------------------------
|
||||||
|
- authentication: none, password, one-time
|
||||||
|
- IPv4, IPv6, DNS
|
||||||
|
- TCP (no UDP at this time)
|
||||||
|
|
||||||
28
deployments/openvpn/third_party/microsocks/create-dist.sh
vendored
Normal file
28
deployments/openvpn/third_party/microsocks/create-dist.sh
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
if [ -z "$VER" ] ; then
|
||||||
|
echo set VER!
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
me=`pwd`
|
||||||
|
|
||||||
|
proj=microsocks
|
||||||
|
projver=${proj}-${VER}
|
||||||
|
|
||||||
|
tempdir=/tmp/${proj}-0000
|
||||||
|
rm -rf "$tempdir"
|
||||||
|
mkdir -p "$tempdir"
|
||||||
|
|
||||||
|
cd $tempdir
|
||||||
|
GITDIR=https://github.com/rofl0r/$proj
|
||||||
|
GITDIR=$me
|
||||||
|
git clone "$GITDIR" $projver
|
||||||
|
|
||||||
|
rm -rf $projver/.git
|
||||||
|
rm -rf $projver/docs
|
||||||
|
rm -f $projver/.gitignore
|
||||||
|
rm -f $projver/create-dist.sh
|
||||||
|
|
||||||
|
tar cf $proj.tar $projver/
|
||||||
|
xz -z -9 -e $proj.tar
|
||||||
|
mv $proj.tar.xz $me/$projver.tar.xz
|
||||||
|
rm -rf "$tempdir"
|
||||||
67
deployments/openvpn/third_party/microsocks/install.sh
vendored
Normal file
67
deployments/openvpn/third_party/microsocks/install.sh
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Written by Rich Felker, originally as part of musl libc.
|
||||||
|
# Multi-licensed under MIT, 0BSD, and CC0.
|
||||||
|
#
|
||||||
|
# This is an actually-safe install command which installs the new
|
||||||
|
# file atomically in the new location, rather than overwriting
|
||||||
|
# existing files.
|
||||||
|
#
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
printf "usage: %s [-D] [-l] [-m mode] src dest\n" "$0" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdirp=
|
||||||
|
symlink=
|
||||||
|
mode=755
|
||||||
|
|
||||||
|
while getopts Dlm: name ; do
|
||||||
|
case "$name" in
|
||||||
|
D) mkdirp=yes ;;
|
||||||
|
l) symlink=yes ;;
|
||||||
|
m) mode=$OPTARG ;;
|
||||||
|
?) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
shift $(($OPTIND - 1))
|
||||||
|
|
||||||
|
test "$#" -eq 2 || usage
|
||||||
|
src=$1
|
||||||
|
dst=$2
|
||||||
|
tmp="$dst.tmp.$$"
|
||||||
|
|
||||||
|
case "$dst" in
|
||||||
|
*/) printf "%s: %s ends in /\n", "$0" "$dst" 1>&2 ; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
set -C
|
||||||
|
set -e
|
||||||
|
|
||||||
|
if test "$mkdirp" ; then
|
||||||
|
umask 022
|
||||||
|
case "$2" in
|
||||||
|
*/*) mkdir -p "${dst%/*}" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
trap 'rm -f "$tmp"' EXIT INT QUIT TERM HUP
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
if test "$symlink" ; then
|
||||||
|
ln -s "$1" "$tmp"
|
||||||
|
else
|
||||||
|
cat < "$1" > "$tmp"
|
||||||
|
chmod "$mode" "$tmp"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv -f "$tmp" "$2"
|
||||||
|
test -d "$2" && {
|
||||||
|
rm -f "$2/$tmp"
|
||||||
|
printf "%s: %s is a directory\n" "$0" "$dst" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
exit 0
|
||||||
73
deployments/openvpn/third_party/microsocks/sblist.c
vendored
Normal file
73
deployments/openvpn/third_party/microsocks/sblist.c
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#undef _POSIX_C_SOURCE
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include "sblist.h"
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#define MY_PAGE_SIZE 4096
|
||||||
|
|
||||||
|
sblist* sblist_new(size_t itemsize, size_t blockitems) {
|
||||||
|
sblist* ret = (sblist*) malloc(sizeof(sblist));
|
||||||
|
sblist_init(ret, itemsize, blockitems);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void sblist_clear(sblist* l) {
|
||||||
|
l->items = NULL;
|
||||||
|
l->capa = 0;
|
||||||
|
l->count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sblist_init(sblist* l, size_t itemsize, size_t blockitems) {
|
||||||
|
if(l) {
|
||||||
|
l->blockitems = blockitems ? blockitems : MY_PAGE_SIZE / itemsize;
|
||||||
|
l->itemsize = itemsize;
|
||||||
|
sblist_clear(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sblist_free_items(sblist* l) {
|
||||||
|
if(l) {
|
||||||
|
if(l->items) free(l->items);
|
||||||
|
sblist_clear(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sblist_free(sblist* l) {
|
||||||
|
if(l) {
|
||||||
|
sblist_free_items(l);
|
||||||
|
free(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* sblist_item_from_index(sblist* l, size_t idx) {
|
||||||
|
return l->items + (idx * l->itemsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* sblist_get(sblist* l, size_t item) {
|
||||||
|
if(item < l->count) return (void*) sblist_item_from_index(l, item);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sblist_set(sblist* l, void* item, size_t pos) {
|
||||||
|
if(pos >= l->count) return 0;
|
||||||
|
memcpy(sblist_item_from_index(l, pos), item, l->itemsize);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sblist_grow_if_needed(sblist* l) {
|
||||||
|
char* temp;
|
||||||
|
if(l->count == l->capa) {
|
||||||
|
temp = realloc(l->items, (l->capa + l->blockitems) * l->itemsize);
|
||||||
|
if(!temp) return 0;
|
||||||
|
l->capa += l->blockitems;
|
||||||
|
l->items = temp;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sblist_add(sblist* l, void* item) {
|
||||||
|
if(!sblist_grow_if_needed(l)) return 0;
|
||||||
|
l->count++;
|
||||||
|
return sblist_set(l, item, l->count - 1);
|
||||||
|
}
|
||||||
92
deployments/openvpn/third_party/microsocks/sblist.h
vendored
Normal file
92
deployments/openvpn/third_party/microsocks/sblist.h
vendored
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
#ifndef SBLIST_H
|
||||||
|
#define SBLIST_H
|
||||||
|
|
||||||
|
/* this file is part of libulz, as of commit 8ab361a27743aaf025323ee43b8b8876dc054fdd
|
||||||
|
modified for direct inclusion in microsocks. */
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
/*
|
||||||
|
* simple buffer list.
|
||||||
|
*
|
||||||
|
* this thing here is basically a generic dynamic array
|
||||||
|
* will realloc after every blockitems inserts
|
||||||
|
* can store items of any size.
|
||||||
|
*
|
||||||
|
* so think of it as a by-value list, as opposed to a typical by-ref list.
|
||||||
|
* you typically use it by having some struct on the stack, and pass a pointer
|
||||||
|
* to sblist_add, which will copy the contents into its internal memory.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t itemsize;
|
||||||
|
size_t blockitems;
|
||||||
|
size_t count;
|
||||||
|
size_t capa;
|
||||||
|
char* items;
|
||||||
|
} sblist;
|
||||||
|
|
||||||
|
#define sblist_getsize(X) ((X)->count)
|
||||||
|
#define sblist_get_count(X) ((X)->count)
|
||||||
|
#define sblist_empty(X) ((X)->count == 0)
|
||||||
|
|
||||||
|
// for dynamic style
|
||||||
|
sblist* sblist_new(size_t itemsize, size_t blockitems);
|
||||||
|
void sblist_free(sblist* l);
|
||||||
|
|
||||||
|
//for static style
|
||||||
|
void sblist_init(sblist* l, size_t itemsize, size_t blockitems);
|
||||||
|
void sblist_free_items(sblist* l);
|
||||||
|
|
||||||
|
// accessors
|
||||||
|
void* sblist_get(sblist* l, size_t item);
|
||||||
|
// returns 1 on success, 0 on OOM
|
||||||
|
int sblist_add(sblist* l, void* item);
|
||||||
|
int sblist_set(sblist* l, void* item, size_t pos);
|
||||||
|
void sblist_delete(sblist* l, size_t item);
|
||||||
|
char* sblist_item_from_index(sblist* l, size_t idx);
|
||||||
|
int sblist_grow_if_needed(sblist* l);
|
||||||
|
int sblist_insert(sblist* l, void* item, size_t pos);
|
||||||
|
/* same as sblist_add, but returns list index of new item, or -1 */
|
||||||
|
size_t sblist_addi(sblist* l, void* item);
|
||||||
|
void sblist_sort(sblist *l, int (*compar)(const void *, const void *));
|
||||||
|
/* insert element into presorted list, returns listindex of new entry or -1*/
|
||||||
|
size_t sblist_insert_sorted(sblist* l, void* o, int (*compar)(const void *, const void *));
|
||||||
|
|
||||||
|
#ifndef __COUNTER__
|
||||||
|
#define __COUNTER__ __LINE__
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __sblist_concat_impl( x, y ) x##y
|
||||||
|
#define __sblist_macro_concat( x, y ) __sblist_concat_impl( x, y )
|
||||||
|
#define __sblist_iterator_name __sblist_macro_concat(sblist_iterator, __COUNTER__)
|
||||||
|
|
||||||
|
// use with custom iterator variable
|
||||||
|
#define sblist_iter_counter(LIST, ITER, PTR) \
|
||||||
|
for(size_t ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++)
|
||||||
|
|
||||||
|
// use with custom iterator variable, which is predeclared
|
||||||
|
#define sblist_iter_counter2(LIST, ITER, PTR) \
|
||||||
|
for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < sblist_getsize(LIST); ITER++)
|
||||||
|
|
||||||
|
// use with custom iterator variable, which is predeclared and signed
|
||||||
|
// useful for a loop which can delete items from the list, and then decrease the iterator var.
|
||||||
|
#define sblist_iter_counter2s(LIST, ITER, PTR) \
|
||||||
|
for(ITER = 0; (PTR = sblist_get(LIST, ITER)), ITER < (ssize_t) sblist_getsize(LIST); ITER++)
|
||||||
|
|
||||||
|
|
||||||
|
// uses "magic" iterator variable
|
||||||
|
#define sblist_iter(LIST, PTR) sblist_iter_counter(LIST, __sblist_iterator_name, PTR)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma RcB2 DEP "sblist.c" "sblist_delete.c"
|
||||||
|
|
||||||
|
#endif
|
||||||
9
deployments/openvpn/third_party/microsocks/sblist_delete.c
vendored
Normal file
9
deployments/openvpn/third_party/microsocks/sblist_delete.c
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "sblist.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void sblist_delete(sblist* l, size_t item) {
|
||||||
|
if (l->count && item < l->count) {
|
||||||
|
memmove(sblist_item_from_index(l, item), sblist_item_from_index(l, item + 1), (sblist_getsize(l) - (item + 1)) * l->itemsize);
|
||||||
|
l->count--;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
deployments/openvpn/third_party/microsocks/server.c
vendored
Normal file
64
deployments/openvpn/third_party/microsocks/server.c
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include "server.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
int resolve(const char *host, unsigned short port, struct addrinfo** addr) {
|
||||||
|
struct addrinfo hints = {
|
||||||
|
.ai_family = AF_UNSPEC,
|
||||||
|
.ai_socktype = SOCK_STREAM,
|
||||||
|
.ai_flags = AI_PASSIVE,
|
||||||
|
};
|
||||||
|
char port_buf[8];
|
||||||
|
snprintf(port_buf, sizeof port_buf, "%u", port);
|
||||||
|
return getaddrinfo(host, port_buf, &hints, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int resolve_sa(const char *host, unsigned short port, union sockaddr_union *res) {
|
||||||
|
struct addrinfo *ainfo = 0;
|
||||||
|
int ret;
|
||||||
|
SOCKADDR_UNION_AF(res) = AF_UNSPEC;
|
||||||
|
if((ret = resolve(host, port, &ainfo))) return ret;
|
||||||
|
memcpy(res, ainfo->ai_addr, ainfo->ai_addrlen);
|
||||||
|
freeaddrinfo(ainfo);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bindtoip(int fd, union sockaddr_union *bindaddr) {
|
||||||
|
socklen_t sz = SOCKADDR_UNION_LENGTH(bindaddr);
|
||||||
|
if(sz)
|
||||||
|
return bind(fd, (struct sockaddr*) bindaddr, sz);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_waitclient(struct server *server, struct client* client) {
|
||||||
|
socklen_t clen = sizeof client->addr;
|
||||||
|
return ((client->fd = accept(server->fd, (void*)&client->addr, &clen)) == -1)*-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_setup(struct server *server, const char* listenip, unsigned short port) {
|
||||||
|
struct addrinfo *ainfo = 0;
|
||||||
|
if(resolve(listenip, port, &ainfo)) return -1;
|
||||||
|
struct addrinfo* p;
|
||||||
|
int listenfd = -1;
|
||||||
|
for(p = ainfo; p; p = p->ai_next) {
|
||||||
|
if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
|
||||||
|
continue;
|
||||||
|
int yes = 1;
|
||||||
|
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
||||||
|
if(bind(listenfd, p->ai_addr, p->ai_addrlen) < 0) {
|
||||||
|
close(listenfd);
|
||||||
|
listenfd = -1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
freeaddrinfo(ainfo);
|
||||||
|
if(listenfd < 0) return -2;
|
||||||
|
if(listen(listenfd, SOMAXCONN) < 0) {
|
||||||
|
close(listenfd);
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
server->fd = listenfd;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
49
deployments/openvpn/third_party/microsocks/server.h
vendored
Normal file
49
deployments/openvpn/third_party/microsocks/server.h
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef SERVER_H
|
||||||
|
#define SERVER_H
|
||||||
|
|
||||||
|
#undef _POSIX_C_SOURCE
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
#pragma RcB2 DEP "server.c"
|
||||||
|
|
||||||
|
union sockaddr_union {
|
||||||
|
struct sockaddr_in v4;
|
||||||
|
struct sockaddr_in6 v6;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SOCKADDR_UNION_AF(PTR) (PTR)->v4.sin_family
|
||||||
|
|
||||||
|
#define SOCKADDR_UNION_LENGTH(PTR) ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? sizeof((PTR)->v4) : ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? sizeof((PTR)->v6) : 0 ) )
|
||||||
|
|
||||||
|
#define SOCKADDR_UNION_ADDRESS(PTR) ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (void*) &(PTR)->v4.sin_addr : ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (void*) &(PTR)->v6.sin6_addr : (void*) 0 ) )
|
||||||
|
|
||||||
|
#define SOCKADDR_UNION_PORT(PTR) ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET ) ? (PTR)->v4.sin_port : ( \
|
||||||
|
( SOCKADDR_UNION_AF(PTR) == AF_INET6 ) ? (PTR)->v6.sin6_port : 0 ) )
|
||||||
|
|
||||||
|
struct client {
|
||||||
|
union sockaddr_union addr;
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct server {
|
||||||
|
int fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
int resolve(const char *host, unsigned short port, struct addrinfo** addr);
|
||||||
|
int resolve_sa(const char *host, unsigned short port, union sockaddr_union *res);
|
||||||
|
int bindtoip(int fd, union sockaddr_union *bindaddr);
|
||||||
|
|
||||||
|
int server_waitclient(struct server *server, struct client* client);
|
||||||
|
int server_setup(struct server *server, const char* listenip, unsigned short port);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
482
deployments/openvpn/third_party/microsocks/sockssrv.c
vendored
Normal file
482
deployments/openvpn/third_party/microsocks/sockssrv.c
vendored
Normal file
@@ -0,0 +1,482 @@
|
|||||||
|
/*
|
||||||
|
MicroSocks - multithreaded, small, efficient SOCKS5 server.
|
||||||
|
|
||||||
|
Copyright (C) 2017 rofl0r.
|
||||||
|
|
||||||
|
This is the successor of "rocksocks5", and it was written with
|
||||||
|
different goals in mind:
|
||||||
|
|
||||||
|
- prefer usage of standard libc functions over homegrown ones
|
||||||
|
- no artificial limits
|
||||||
|
- do not aim for minimal binary size, but for minimal source code size,
|
||||||
|
and maximal readability, reusability, and extensibility.
|
||||||
|
|
||||||
|
as a result of that, ipv4, dns, and ipv6 is supported out of the box
|
||||||
|
and can use the same code, while rocksocks5 has several compile time
|
||||||
|
defines to bring down the size of the resulting binary to extreme values
|
||||||
|
like 10 KB static linked when only ipv4 support is enabled.
|
||||||
|
|
||||||
|
still, if optimized for size, *this* program when static linked against musl
|
||||||
|
libc is not even 50 KB. that's easily usable even on the cheapest routers.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <unistd.h>
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include "server.h"
|
||||||
|
#include "sblist.h"
|
||||||
|
|
||||||
|
/* timeout in microseconds on resource exhaustion to prevent excessive
|
||||||
|
cpu usage. */
|
||||||
|
#ifndef FAILURE_TIMEOUT
|
||||||
|
#define FAILURE_TIMEOUT 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MAX
|
||||||
|
#define MAX(x, y) ((x) > (y) ? (x) : (y))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PTHREAD_STACK_MIN
|
||||||
|
#define THREAD_STACK_SIZE MAX(8*1024, PTHREAD_STACK_MIN)
|
||||||
|
#else
|
||||||
|
#define THREAD_STACK_SIZE 64*1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#undef THREAD_STACK_SIZE
|
||||||
|
#define THREAD_STACK_SIZE 64*1024
|
||||||
|
#elif defined(__GLIBC__) || defined(__FreeBSD__)
|
||||||
|
#undef THREAD_STACK_SIZE
|
||||||
|
#define THREAD_STACK_SIZE 32*1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char* auth_user;
|
||||||
|
static const char* auth_pass;
|
||||||
|
static sblist* auth_ips;
|
||||||
|
static pthread_rwlock_t auth_ips_lock = PTHREAD_RWLOCK_INITIALIZER;
|
||||||
|
static const struct server* server;
|
||||||
|
static union sockaddr_union bind_addr = {.v4.sin_family = AF_UNSPEC};
|
||||||
|
|
||||||
|
enum socksstate {
|
||||||
|
SS_1_CONNECTED,
|
||||||
|
SS_2_NEED_AUTH, /* skipped if NO_AUTH method supported */
|
||||||
|
SS_3_AUTHED,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum authmethod {
|
||||||
|
AM_NO_AUTH = 0,
|
||||||
|
AM_GSSAPI = 1,
|
||||||
|
AM_USERNAME = 2,
|
||||||
|
AM_INVALID = 0xFF
|
||||||
|
};
|
||||||
|
|
||||||
|
enum errorcode {
|
||||||
|
EC_SUCCESS = 0,
|
||||||
|
EC_GENERAL_FAILURE = 1,
|
||||||
|
EC_NOT_ALLOWED = 2,
|
||||||
|
EC_NET_UNREACHABLE = 3,
|
||||||
|
EC_HOST_UNREACHABLE = 4,
|
||||||
|
EC_CONN_REFUSED = 5,
|
||||||
|
EC_TTL_EXPIRED = 6,
|
||||||
|
EC_COMMAND_NOT_SUPPORTED = 7,
|
||||||
|
EC_ADDRESSTYPE_NOT_SUPPORTED = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct thread {
|
||||||
|
pthread_t pt;
|
||||||
|
struct client client;
|
||||||
|
enum socksstate state;
|
||||||
|
volatile int done;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifndef CONFIG_LOG
|
||||||
|
#define CONFIG_LOG 1
|
||||||
|
#endif
|
||||||
|
#if CONFIG_LOG
|
||||||
|
/* we log to stderr because it's not using line buffering, i.e. malloc which would need
|
||||||
|
locking when called from different threads. for the same reason we use dprintf,
|
||||||
|
which writes directly to an fd. */
|
||||||
|
#define dolog(...) dprintf(2, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
static void dolog(const char* fmt, ...) { }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct addrinfo* addr_choose(struct addrinfo* list, union sockaddr_union* bind_addr) {
|
||||||
|
int af = SOCKADDR_UNION_AF(bind_addr);
|
||||||
|
if(af == AF_UNSPEC) return list;
|
||||||
|
struct addrinfo* p;
|
||||||
|
for(p=list; p; p=p->ai_next)
|
||||||
|
if(p->ai_family == af) return p;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connect_socks_target(unsigned char *buf, size_t n, struct client *client) {
|
||||||
|
if(n < 5) return -EC_GENERAL_FAILURE;
|
||||||
|
if(buf[0] != 5) return -EC_GENERAL_FAILURE;
|
||||||
|
if(buf[1] != 1) return -EC_COMMAND_NOT_SUPPORTED; /* we support only CONNECT method */
|
||||||
|
if(buf[2] != 0) return -EC_GENERAL_FAILURE; /* malformed packet */
|
||||||
|
|
||||||
|
int af = AF_INET;
|
||||||
|
size_t minlen = 4 + 4 + 2, l;
|
||||||
|
char namebuf[256];
|
||||||
|
struct addrinfo* remote;
|
||||||
|
|
||||||
|
switch(buf[3]) {
|
||||||
|
case 4: /* ipv6 */
|
||||||
|
af = AF_INET6;
|
||||||
|
minlen = 4 + 2 + 16;
|
||||||
|
/* fall through */
|
||||||
|
case 1: /* ipv4 */
|
||||||
|
if(n < minlen) return -EC_GENERAL_FAILURE;
|
||||||
|
if(namebuf != inet_ntop(af, buf+4, namebuf, sizeof namebuf))
|
||||||
|
return -EC_GENERAL_FAILURE; /* malformed or too long addr */
|
||||||
|
break;
|
||||||
|
case 3: /* dns name */
|
||||||
|
l = buf[4];
|
||||||
|
minlen = 4 + 2 + l + 1;
|
||||||
|
if(n < 4 + 2 + l + 1) return -EC_GENERAL_FAILURE;
|
||||||
|
memcpy(namebuf, buf+4+1, l);
|
||||||
|
namebuf[l] = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return -EC_ADDRESSTYPE_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
unsigned short port;
|
||||||
|
port = (buf[minlen-2] << 8) | buf[minlen-1];
|
||||||
|
/* there's no suitable errorcode in rfc1928 for dns lookup failure */
|
||||||
|
if(resolve(namebuf, port, &remote)) return -EC_GENERAL_FAILURE;
|
||||||
|
struct addrinfo* raddr = addr_choose(remote, &bind_addr);
|
||||||
|
int fd = socket(raddr->ai_family, SOCK_STREAM, 0);
|
||||||
|
if(fd == -1) {
|
||||||
|
eval_errno:
|
||||||
|
if(fd != -1) close(fd);
|
||||||
|
freeaddrinfo(remote);
|
||||||
|
switch(errno) {
|
||||||
|
case ETIMEDOUT:
|
||||||
|
return -EC_TTL_EXPIRED;
|
||||||
|
case EPROTOTYPE:
|
||||||
|
case EPROTONOSUPPORT:
|
||||||
|
case EAFNOSUPPORT:
|
||||||
|
return -EC_ADDRESSTYPE_NOT_SUPPORTED;
|
||||||
|
case ECONNREFUSED:
|
||||||
|
return -EC_CONN_REFUSED;
|
||||||
|
case ENETDOWN:
|
||||||
|
case ENETUNREACH:
|
||||||
|
return -EC_NET_UNREACHABLE;
|
||||||
|
case EHOSTUNREACH:
|
||||||
|
return -EC_HOST_UNREACHABLE;
|
||||||
|
case EBADF:
|
||||||
|
default:
|
||||||
|
perror("socket/connect");
|
||||||
|
return -EC_GENERAL_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(SOCKADDR_UNION_AF(&bind_addr) == raddr->ai_family &&
|
||||||
|
bindtoip(fd, &bind_addr) == -1)
|
||||||
|
goto eval_errno;
|
||||||
|
if(connect(fd, raddr->ai_addr, raddr->ai_addrlen) == -1)
|
||||||
|
goto eval_errno;
|
||||||
|
|
||||||
|
freeaddrinfo(remote);
|
||||||
|
if(CONFIG_LOG) {
|
||||||
|
char clientname[256];
|
||||||
|
af = SOCKADDR_UNION_AF(&client->addr);
|
||||||
|
void *ipdata = SOCKADDR_UNION_ADDRESS(&client->addr);
|
||||||
|
inet_ntop(af, ipdata, clientname, sizeof clientname);
|
||||||
|
dolog("client[%d] %s: connected to %s:%d\n", client->fd, clientname, namebuf, port);
|
||||||
|
}
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_authed(union sockaddr_union *client, union sockaddr_union *authedip) {
|
||||||
|
int af = SOCKADDR_UNION_AF(authedip);
|
||||||
|
if(af == SOCKADDR_UNION_AF(client)) {
|
||||||
|
size_t cmpbytes = af == AF_INET ? 4 : 16;
|
||||||
|
void *cmp1 = SOCKADDR_UNION_ADDRESS(client);
|
||||||
|
void *cmp2 = SOCKADDR_UNION_ADDRESS(authedip);
|
||||||
|
if(!memcmp(cmp1, cmp2, cmpbytes)) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_in_authed_list(union sockaddr_union *caddr) {
|
||||||
|
size_t i;
|
||||||
|
for(i=0;i<sblist_getsize(auth_ips);i++)
|
||||||
|
if(is_authed(caddr, sblist_get(auth_ips, i)))
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_auth_ip(union sockaddr_union *caddr) {
|
||||||
|
sblist_add(auth_ips, caddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum authmethod check_auth_method(unsigned char *buf, size_t n, struct client*client) {
|
||||||
|
if(buf[0] != 5) return AM_INVALID;
|
||||||
|
size_t idx = 1;
|
||||||
|
if(idx >= n ) return AM_INVALID;
|
||||||
|
int n_methods = buf[idx];
|
||||||
|
idx++;
|
||||||
|
while(idx < n && n_methods > 0) {
|
||||||
|
if(buf[idx] == AM_NO_AUTH) {
|
||||||
|
if(!auth_user) return AM_NO_AUTH;
|
||||||
|
else if(auth_ips) {
|
||||||
|
int authed = 0;
|
||||||
|
if(pthread_rwlock_rdlock(&auth_ips_lock) == 0) {
|
||||||
|
authed = is_in_authed_list(&client->addr);
|
||||||
|
pthread_rwlock_unlock(&auth_ips_lock);
|
||||||
|
}
|
||||||
|
if(authed) return AM_NO_AUTH;
|
||||||
|
}
|
||||||
|
} else if(buf[idx] == AM_USERNAME) {
|
||||||
|
if(auth_user) return AM_USERNAME;
|
||||||
|
}
|
||||||
|
idx++;
|
||||||
|
n_methods--;
|
||||||
|
}
|
||||||
|
return AM_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_auth_response(int fd, int version, enum authmethod meth) {
|
||||||
|
unsigned char buf[2];
|
||||||
|
buf[0] = version;
|
||||||
|
buf[1] = meth;
|
||||||
|
write(fd, buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_error(int fd, enum errorcode ec) {
|
||||||
|
/* position 4 contains ATYP, the address type, which is the same as used in the connect
|
||||||
|
request. we're lazy and return always IPV4 address type in errors. */
|
||||||
|
char buf[10] = { 5, ec, 0, 1 /*AT_IPV4*/, 0,0,0,0, 0,0 };
|
||||||
|
write(fd, buf, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void copyloop(int fd1, int fd2) {
|
||||||
|
struct pollfd fds[2] = {
|
||||||
|
[0] = {.fd = fd1, .events = POLLIN},
|
||||||
|
[1] = {.fd = fd2, .events = POLLIN},
|
||||||
|
};
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
/* inactive connections are reaped after 15 min to free resources.
|
||||||
|
usually programs send keep-alive packets so this should only happen
|
||||||
|
when a connection is really unused. */
|
||||||
|
switch(poll(fds, 2, 60*15*1000)) {
|
||||||
|
case 0:
|
||||||
|
return;
|
||||||
|
case -1:
|
||||||
|
if(errno == EINTR || errno == EAGAIN) continue;
|
||||||
|
else perror("poll");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int infd = (fds[0].revents & POLLIN) ? fd1 : fd2;
|
||||||
|
int outfd = infd == fd2 ? fd1 : fd2;
|
||||||
|
char buf[1024];
|
||||||
|
ssize_t sent = 0, n = read(infd, buf, sizeof buf);
|
||||||
|
if(n <= 0) return;
|
||||||
|
while(sent < n) {
|
||||||
|
ssize_t m = write(outfd, buf+sent, n-sent);
|
||||||
|
if(m < 0) return;
|
||||||
|
sent += m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum errorcode check_credentials(unsigned char* buf, size_t n) {
|
||||||
|
if(n < 5) return EC_GENERAL_FAILURE;
|
||||||
|
if(buf[0] != 1) return EC_GENERAL_FAILURE;
|
||||||
|
unsigned ulen, plen;
|
||||||
|
ulen=buf[1];
|
||||||
|
if(n < 2 + ulen + 2) return EC_GENERAL_FAILURE;
|
||||||
|
plen=buf[2+ulen];
|
||||||
|
if(n < 2 + ulen + 1 + plen) return EC_GENERAL_FAILURE;
|
||||||
|
char user[256], pass[256];
|
||||||
|
memcpy(user, buf+2, ulen);
|
||||||
|
memcpy(pass, buf+2+ulen+1, plen);
|
||||||
|
user[ulen] = 0;
|
||||||
|
pass[plen] = 0;
|
||||||
|
if(!strcmp(user, auth_user) && !strcmp(pass, auth_pass)) return EC_SUCCESS;
|
||||||
|
return EC_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void* clientthread(void *data) {
|
||||||
|
struct thread *t = data;
|
||||||
|
t->state = SS_1_CONNECTED;
|
||||||
|
unsigned char buf[1024];
|
||||||
|
ssize_t n;
|
||||||
|
int ret;
|
||||||
|
int remotefd = -1;
|
||||||
|
enum authmethod am;
|
||||||
|
while((n = recv(t->client.fd, buf, sizeof buf, 0)) > 0) {
|
||||||
|
switch(t->state) {
|
||||||
|
case SS_1_CONNECTED:
|
||||||
|
am = check_auth_method(buf, n, &t->client);
|
||||||
|
if(am == AM_NO_AUTH) t->state = SS_3_AUTHED;
|
||||||
|
else if (am == AM_USERNAME) t->state = SS_2_NEED_AUTH;
|
||||||
|
send_auth_response(t->client.fd, 5, am);
|
||||||
|
if(am == AM_INVALID) goto breakloop;
|
||||||
|
break;
|
||||||
|
case SS_2_NEED_AUTH:
|
||||||
|
ret = check_credentials(buf, n);
|
||||||
|
send_auth_response(t->client.fd, 1, ret);
|
||||||
|
if(ret != EC_SUCCESS)
|
||||||
|
goto breakloop;
|
||||||
|
t->state = SS_3_AUTHED;
|
||||||
|
if(auth_ips && !pthread_rwlock_wrlock(&auth_ips_lock)) {
|
||||||
|
if(!is_in_authed_list(&t->client.addr))
|
||||||
|
add_auth_ip(&t->client.addr);
|
||||||
|
pthread_rwlock_unlock(&auth_ips_lock);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SS_3_AUTHED:
|
||||||
|
ret = connect_socks_target(buf, n, &t->client);
|
||||||
|
if(ret < 0) {
|
||||||
|
send_error(t->client.fd, ret*-1);
|
||||||
|
goto breakloop;
|
||||||
|
}
|
||||||
|
remotefd = ret;
|
||||||
|
send_error(t->client.fd, EC_SUCCESS);
|
||||||
|
copyloop(t->client.fd, remotefd);
|
||||||
|
goto breakloop;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
breakloop:
|
||||||
|
|
||||||
|
if(remotefd != -1)
|
||||||
|
close(remotefd);
|
||||||
|
|
||||||
|
close(t->client.fd);
|
||||||
|
t->done = 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void collect(sblist *threads) {
|
||||||
|
size_t i;
|
||||||
|
for(i=0;i<sblist_getsize(threads);) {
|
||||||
|
struct thread* thread = *((struct thread**)sblist_get(threads, i));
|
||||||
|
if(thread->done) {
|
||||||
|
pthread_join(thread->pt, 0);
|
||||||
|
sblist_delete(threads, i);
|
||||||
|
free(thread);
|
||||||
|
} else
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int usage(void) {
|
||||||
|
dprintf(2,
|
||||||
|
"MicroSocks SOCKS5 Server\n"
|
||||||
|
"------------------------\n"
|
||||||
|
"usage: microsocks -1 -i listenip -p port -u user -P password -b bindaddr\n"
|
||||||
|
"all arguments are optional.\n"
|
||||||
|
"by default listenip is 0.0.0.0 and port 1080.\n\n"
|
||||||
|
"option -b specifies which ip outgoing connections are bound to\n"
|
||||||
|
"option -1 activates auth_once mode: once a specific ip address\n"
|
||||||
|
"authed successfully with user/pass, it is added to a whitelist\n"
|
||||||
|
"and may use the proxy without auth.\n"
|
||||||
|
"this is handy for programs like firefox that don't support\n"
|
||||||
|
"user/pass auth. for it to work you'd basically make one connection\n"
|
||||||
|
"with another program that supports it, and then you can use firefox too.\n"
|
||||||
|
);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* prevent username and password from showing up in top. */
|
||||||
|
static void zero_arg(char *s) {
|
||||||
|
size_t i, l = strlen(s);
|
||||||
|
for(i=0;i<l;i++) s[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
int ch;
|
||||||
|
const char *listenip = "0.0.0.0";
|
||||||
|
unsigned port = 1080;
|
||||||
|
while((ch = getopt(argc, argv, ":1b:i:p:u:P:")) != -1) {
|
||||||
|
switch(ch) {
|
||||||
|
case '1':
|
||||||
|
auth_ips = sblist_new(sizeof(union sockaddr_union), 8);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
resolve_sa(optarg, 0, &bind_addr);
|
||||||
|
break;
|
||||||
|
case 'u':
|
||||||
|
auth_user = strdup(optarg);
|
||||||
|
zero_arg(optarg);
|
||||||
|
break;
|
||||||
|
case 'P':
|
||||||
|
auth_pass = strdup(optarg);
|
||||||
|
zero_arg(optarg);
|
||||||
|
break;
|
||||||
|
case 'i':
|
||||||
|
listenip = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
port = atoi(optarg);
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
dprintf(2, "error: option -%c requires an operand\n", optopt);
|
||||||
|
/* fall through */
|
||||||
|
case '?':
|
||||||
|
return usage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if((auth_user && !auth_pass) || (!auth_user && auth_pass)) {
|
||||||
|
dprintf(2, "error: user and pass must be used together\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if(auth_ips && !auth_pass) {
|
||||||
|
dprintf(2, "error: auth-once option must be used together with user/pass\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
struct server s;
|
||||||
|
sblist *threads = sblist_new(sizeof (struct thread*), 8);
|
||||||
|
if(server_setup(&s, listenip, port)) {
|
||||||
|
perror("server_setup");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
server = &s;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
collect(threads);
|
||||||
|
struct client c;
|
||||||
|
struct thread *curr = malloc(sizeof (struct thread));
|
||||||
|
if(!curr) goto oom;
|
||||||
|
curr->done = 0;
|
||||||
|
if(server_waitclient(&s, &c)) {
|
||||||
|
dolog("failed to accept connection\n");
|
||||||
|
free(curr);
|
||||||
|
usleep(FAILURE_TIMEOUT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
curr->client = c;
|
||||||
|
if(!sblist_add(threads, &curr)) {
|
||||||
|
close(curr->client.fd);
|
||||||
|
free(curr);
|
||||||
|
oom:
|
||||||
|
dolog("rejecting connection due to OOM\n");
|
||||||
|
usleep(FAILURE_TIMEOUT); /* prevent 100% CPU usage in OOM situation */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pthread_attr_t *a = 0, attr;
|
||||||
|
if(pthread_attr_init(&attr) == 0) {
|
||||||
|
a = &attr;
|
||||||
|
pthread_attr_setstacksize(a, THREAD_STACK_SIZE);
|
||||||
|
}
|
||||||
|
if(pthread_create(&curr->pt, a, clientthread, curr) != 0)
|
||||||
|
dolog("pthread_create failed. OOM?\n");
|
||||||
|
if(a) pthread_attr_destroy(&attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// generateCurlCommand 生成可直接复现的 curl 命令,便于联调排查。
|
// generateCurlCommand 生成可直接复现的 curl 命令,便于联调排查。
|
||||||
func generateCurlCommand(method, url string, headers map[string]string, body string) string {
|
// proxyURL 非空时附加 -x,便于确认线上是否应走 SOCKS。
|
||||||
|
func generateCurlCommand(method, requestURL string, headers map[string]string, body, proxyURL string) string {
|
||||||
var cmd strings.Builder
|
var cmd strings.Builder
|
||||||
cmd.WriteString("curl -X ")
|
cmd.WriteString("curl -X ")
|
||||||
cmd.WriteString(method)
|
cmd.WriteString(method)
|
||||||
cmd.WriteString(" '")
|
cmd.WriteString(" '")
|
||||||
cmd.WriteString(url)
|
cmd.WriteString(requestURL)
|
||||||
cmd.WriteString("'")
|
cmd.WriteString("'")
|
||||||
|
|
||||||
|
if p := strings.TrimSpace(proxyURL); p != "" {
|
||||||
|
cmd.WriteString(" \\\n -x '")
|
||||||
|
cmd.WriteString(escapeSingleQuotes(p))
|
||||||
|
cmd.WriteString("'")
|
||||||
|
}
|
||||||
|
|
||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
cmd.WriteString(" \\\n -H '")
|
cmd.WriteString(" \\\n -H '")
|
||||||
cmd.WriteString(key)
|
cmd.WriteString(key)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -163,7 +164,7 @@ func (s *RongxingService) CallAPI(ctx context.Context, apiPath string, reqData m
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
headerDmsToken: token,
|
headerDmsToken: token,
|
||||||
}
|
}
|
||||||
curlCmd := generateCurlCommand(http.MethodPost, requestURL, headers, bodyStr)
|
curlCmd := generateCurlCommand(http.MethodPost, requestURL, headers, bodyStr, s.config.Proxy)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewBuffer(bodyBytes))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewBuffer(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -298,7 +299,7 @@ func (s *RongxingService) login(ctx context.Context, transactionID string) (stri
|
|||||||
bodyStr := string(bodyBytes)
|
bodyStr := string(bodyBytes)
|
||||||
|
|
||||||
headers := map[string]string{"Content-Type": "application/json"}
|
headers := map[string]string{"Content-Type": "application/json"}
|
||||||
curlCmd := generateCurlCommand(http.MethodPost, requestURL, headers, bodyStr)
|
curlCmd := generateCurlCommand(http.MethodPost, requestURL, headers, bodyStr, s.config.Proxy)
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewBuffer(bodyBytes))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, requestURL, bytes.NewBuffer(bodyBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -356,7 +357,7 @@ func (s *RongxingService) login(ctx context.Context, transactionID string) (stri
|
|||||||
func (s *RongxingService) doHTTP(req *http.Request) ([]byte, int, error) {
|
func (s *RongxingService) doHTTP(req *http.Request) ([]byte, int, error) {
|
||||||
resp, err := s.client.Do(req)
|
resp, err := s.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, s.wrapTransportError(req.URL.String(), err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
@@ -367,6 +368,48 @@ func (s *RongxingService) doHTTP(req *http.Request) ([]byte, int, error) {
|
|||||||
return body, resp.StatusCode, nil
|
return body, resp.StatusCode, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wrapTransportError 把超时/拒连/代理失败等包装成可读诊断,便于区分「联不通」原因。
|
||||||
|
func (s *RongxingService) wrapTransportError(targetURL string, err error) error {
|
||||||
|
diag := classifyTransportError(err)
|
||||||
|
proxyMode := strings.TrimSpace(s.config.Proxy)
|
||||||
|
if proxyMode == "" {
|
||||||
|
proxyMode = "(直连,未配置 proxy)"
|
||||||
|
}
|
||||||
|
return fmt.Errorf(
|
||||||
|
"戎行 HTTP 失败 target=%s proxy=%s diagnosis=%s cause=%w",
|
||||||
|
targetURL, proxyMode, diag, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func classifyTransportError(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
msg := err.Error()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case os.IsTimeout(err) || errors.Is(err, context.DeadlineExceeded) ||
|
||||||
|
strings.Contains(msg, "Client.Timeout") || strings.Contains(msg, "deadline exceeded"):
|
||||||
|
return "请求超时(未在 timeout 内收到响应头;可能:目标 192.168.3.43:7007 不可达、VPN/SOCKS 未转发、或服务无响应)"
|
||||||
|
case strings.Contains(msg, "connection refused"):
|
||||||
|
return "连接被拒绝(端口未监听或代理/目标拒绝)"
|
||||||
|
case strings.Contains(msg, "no such host") || strings.Contains(msg, "lookup"):
|
||||||
|
return "DNS/主机名解析失败(检查 rongxing-vpn 服务名或目标域名)"
|
||||||
|
case strings.Contains(msg, "network is unreachable") || strings.Contains(msg, "no route to host"):
|
||||||
|
return "网络不可达(无路由;直连内网 IP 时常见于未走 VPN/代理)"
|
||||||
|
case strings.Contains(msg, "i/o timeout") || strings.Contains(msg, "TLS handshake timeout"):
|
||||||
|
return "传输层超时(链路通但握手/读写超时)"
|
||||||
|
case strings.Contains(msg, "proxy") || strings.Contains(msg, "socks"):
|
||||||
|
return "代理链路异常(检查 socks5://rongxing-vpn:1080 与 VPN 容器)"
|
||||||
|
}
|
||||||
|
|
||||||
|
var netErr net.Error
|
||||||
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||||
|
return "网络超时"
|
||||||
|
}
|
||||||
|
return "其他网络错误(详见 cause)"
|
||||||
|
}
|
||||||
|
|
||||||
func (s *RongxingService) validateConfig() error {
|
func (s *RongxingService) validateConfig() error {
|
||||||
if s.config.BaseURL == "" {
|
if s.config.BaseURL == "" {
|
||||||
return errors.New("戎行 url 未配置")
|
return errors.New("戎行 url 未配置")
|
||||||
@@ -429,12 +472,35 @@ func (s *RongxingService) logErrorWithCurl(transactionID, apiKey string, err err
|
|||||||
if s.logger == nil {
|
if s.logger == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
proxyMode := strings.TrimSpace(s.config.Proxy)
|
||||||
|
if proxyMode == "" {
|
||||||
|
proxyMode = "(直连,未配置 proxy)"
|
||||||
|
}
|
||||||
s.logger.LogErrorWithFields("rongxing API错误",
|
s.logger.LogErrorWithFields("rongxing API错误",
|
||||||
zap.String("transaction_id", transactionID),
|
zap.String("transaction_id", transactionID),
|
||||||
zap.String("api_code", apiKey),
|
zap.String("api_code", apiKey),
|
||||||
|
zap.String("base_url", s.config.BaseURL),
|
||||||
|
zap.String("proxy", proxyMode),
|
||||||
|
zap.String("diagnosis", extractDiagnosis(err)),
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
zap.Any("params", payload),
|
zap.Any("params", payload),
|
||||||
zap.String("curl", curlCmd),
|
zap.String("curl", curlCmd),
|
||||||
zap.String("response_body", respBody),
|
zap.String("response_body", respBody),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractDiagnosis(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const marker = "diagnosis="
|
||||||
|
msg := err.Error()
|
||||||
|
if i := strings.Index(msg, marker); i >= 0 {
|
||||||
|
rest := msg[i+len(marker):]
|
||||||
|
if j := strings.Index(rest, " cause="); j >= 0 {
|
||||||
|
return rest[:j]
|
||||||
|
}
|
||||||
|
return rest
|
||||||
|
}
|
||||||
|
return classifyTransportError(err)
|
||||||
|
}
|
||||||
|
|||||||
43
internal/infrastructure/external/rongxing/transport_error_test.go
vendored
Normal file
43
internal/infrastructure/external/rongxing/transport_error_test.go
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package rongxing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClassifyTransportError_Timeout(t *testing.T) {
|
||||||
|
err := errors.New(`Post "http://192.168.3.43:7007/auth/login": context deadline exceeded (Client.Timeout exceeded while awaiting headers)`)
|
||||||
|
got := classifyTransportError(err)
|
||||||
|
if !strings.Contains(got, "请求超时") {
|
||||||
|
t.Fatalf("got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClassifyTransportError_Deadline(t *testing.T) {
|
||||||
|
got := classifyTransportError(context.DeadlineExceeded)
|
||||||
|
if !strings.Contains(got, "请求超时") {
|
||||||
|
t.Fatalf("got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapTransportError_IncludesProxy(t *testing.T) {
|
||||||
|
svc, err := NewRongxingService(serviceConfig{
|
||||||
|
BaseURL: "http://192.168.3.43:7007",
|
||||||
|
Timeout: time.Second,
|
||||||
|
Proxy: "socks5://rongxing-vpn:1080",
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
wrapped := svc.wrapTransportError("http://192.168.3.43:7007/auth/login", context.DeadlineExceeded)
|
||||||
|
msg := wrapped.Error()
|
||||||
|
if !strings.Contains(msg, "proxy=socks5://rongxing-vpn:1080") {
|
||||||
|
t.Fatalf("missing proxy in error: %s", msg)
|
||||||
|
}
|
||||||
|
if !strings.Contains(msg, "diagnosis=") {
|
||||||
|
t.Fatalf("missing diagnosis in error: %s", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user