Initial Nginx SNI stream map support

This commit is contained in:
Samuel Huang
2024-09-12 15:21:19 +10:00
parent 289ec93315
commit 0f83efa5b5
4 changed files with 182 additions and 80 deletions

View File

@@ -2,15 +2,15 @@ FROM golang:1.23-alpine3.20 AS builder
ARG XRAYVER='v1.8.24' ARG XRAYVER='v1.8.24'
RUN apk add --no-cache bash git build-base RUN apk add --no-cache bash git build-base curl
WORKDIR /go/src/XTLS/Xray-core WORKDIR /go/src/XTLS/Xray-core
RUN git clone https://github.com/XTLS/Xray-core.git . && \ RUN git clone https://github.com/XTLS/Xray-core.git . && \
git checkout ${XRAYVER} && \ git checkout ${XRAYVER} && \
go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
RUN cd /tmp; wget -c -t3 -T30 https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat RUN cd /tmp; curl -O https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat
RUN cd /tmp; wget -c -t3 -T30 https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat RUN cd /tmp; curl -O https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat
FROM nginx:stable-alpine3.20 FROM nginx:stable-alpine3.20
@@ -26,10 +26,11 @@ RUN cd /root; curl -sSL "https://github.com/acmesh-official/acme.sh/archive/refs
RUN cd /root; ln -s acme.sh-${ACMEVER} acme.sh; mkdir .acme.sh RUN cd /root; ln -s acme.sh-${ACMEVER} acme.sh; mkdir .acme.sh
RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/sbin/nginx RUN setcap CAP_NET_BIND_SERVICE=+eip /usr/sbin/nginx
COPY site-ssl.conf.tpl /etc/nginx/conf.d/ COPY site-ssl.conf.tpl /etc/nginx/conf.d/
COPY nginx-proxy.tpl /etc/nginx/conf.d/ COPY nginx-stream.tpl /etc/nginx/conf.d/
COPY nginx-grpc.tpl /etc/nginx/conf.d/ COPY nginx-proxy.tpl /etc/nginx/conf.d/
COPY nginx-ws.tpl /etc/nginx/conf.d/ COPY nginx-grpc.tpl /etc/nginx/conf.d/
COPY nginx-ws.tpl /etc/nginx/conf.d/
ADD server-lgp.sh /server-lgp.sh ADD server-lgp.sh /server-lgp.sh
ADD server-lgr.sh /server-lgr.sh ADD server-lgr.sh /server-lgr.sh

15
nginx-stream.tpl Normal file
View File

@@ -0,0 +1,15 @@
#STSTUB
stream {
map $ssl_preread_server_name $stream_map {
#MAPSTUB
}
#UPSSTUB
server {
listen STPORT reuseport;
proxy_pass $stream_map;
ssl_preread on;
}
}

161
run.sh
View File

@@ -8,12 +8,14 @@ XCONF=/tmp/server-xray.json
usage() { usage() {
echo "server-xray <server-options>" echo "server-xray <server-options>"
echo " --lgp <VLESS-GRPC-PLN option> p=11443,u=id1,u=id2...,s=svcname" echo " --lgp <VLESS-GRPC-PLN option> p=11443,u=id1,u=id2...,s=svcname"
echo " --lgr <VLESS-GRPC-RLTY option> p=12443,u=id1,u=id2...,s=svcname,d=dest.com,pub=xx,prv=yy[,shortId=zz]" echo " --lgr <VLESS-GRPC-RLTY option> p=12443,u=id1,u=id2...,s=svcname,d=dest.com,pub=xx,prv=yy[,shortId=ab]"
echo " --lgt <VLESS-GRPC-TLS option> p=13443,u=id1,u=id2...,s=svcname,d=domain.com" echo " --lgt <VLESS-GRPC-TLS option> p=13443,u=id1,u=id2...,s=svcname,d=domain.com"
echo " --lsp <VLESS-SPLT-PLN option> p=14443,u=id1,u=id2...,w=/webpath" echo " --lsp <VLESS-SPLT-PLN option> p=14443,u=id1,u=id2...,w=/webpath"
echo " --lst <VLESS-SPLT-TLS option> p=16443,u=id1,u=id2...,w=/webpath,d=domain.com" echo " --lst <VLESS-SPLT-TLS option> p=16443,u=id1,u=id2...,w=/webpath,d=domain.com"
echo " --ltr <VLESS-TCP-RLTY option> p=17443,u=id1,u=id2...,[xtls],d=dest.com,pub=xx,prv=yy[,shortId=zz]" echo " --ltr <VLESS-TCP-RLTY option> p=17443,u=id1,u=id2...,d=dest.com,pub=xx,prv=yy[,shortId=ab],[xtls]"
echo " --ltt <VLESS-TCP-TLS option> p=18443,u=id1,u=id2...,[xtls],d=domain.com" echo " --ltrx <VLESS-TCP-RLTY option> p=17443,u=id1,u=id2...,d=dest.com,pub=xx,prv=yy[,shortId=ab]"
echo " --ltt <VLESS-TCP-TLS option> p=18443,u=id1,u=id2...,d=domain.com,[xtls]"
echo " --lttx <VLESS-TCP-TLS option> p=18443,u=id1,u=id2...,d=domain.com"
echo " --lwp <VLESS-WS-PLN option> p=19443,u=id1,u=id2...,w=/wskpath" echo " --lwp <VLESS-WS-PLN option> p=19443,u=id1,u=id2...,w=/wskpath"
echo " --lwt <VLESS-WS-TLS option> p=22443,u=id1,u=id2...,w=/wskpath,d=domain.com" echo " --lwt <VLESS-WS-TLS option> p=22443,u=id1,u=id2...,w=/wskpath,d=domain.com"
echo " --mtt <VMESS-TCP-TLS option> p=23443,u=id1,u=id2...,d=domain.com" echo " --mtt <VMESS-TCP-TLS option> p=23443,u=id1,u=id2...,d=domain.com"
@@ -22,13 +24,15 @@ usage() {
echo " --ttt <TROJAN-TCP-TLS option> p=26443,u=pw1,u=pw2...,d=domain.com" echo " --ttt <TROJAN-TCP-TLS option> p=26443,u=pw1,u=pw2...,d=domain.com"
echo " --twp <TROJAN-WS-PLN option> p=27443,u=pw1,u=pw2...,w=/wskpath" echo " --twp <TROJAN-WS-PLN option> p=27443,u=pw1,u=pw2...,w=/wskpath"
echo " --twt <TROJAN-WS-TLS option> p=28443,u=pw1,u=pw2...,w=/wskpath,d=domain.com" echo " --twt <TROJAN-WS-TLS option> p=28443,u=pw1,u=pw2...,w=/wskpath,d=domain.com"
echo " --ng-opt <nginx-options> p=443,d=domain0.com,d=domain1.com..." echo " --ng-server <nginx-server-options> p=8443,d=domain0.com,d=domain1.com..."
echo " --ng-proxy <nginx-proxy-options> d=domain0.com,d=domain1.com,p=port-backend,l=location,n=ws|grpc|splt" echo " --ng-proxy <nginx-proxy-options> d=domain0.com,d=domain1.com,p=port-backend,l=location,n=ws|grpc|splt"
echo " --domain-block <domain-rule> Add a domain rule for routing block, like geosite:category-ads-all" echo " --st-port <stream-port-number> 443"
echo " --ip-block <ip-rule> Add a ip-addr rule for routing block, like geoip:private" echo " --st-map <stream-map-options> sni=domain.com,ups=127.0.0.1:8443"
echo " --domain-block <domain-rule> Add a domain rule for routing-server block, like geosite:category-ads-all"
echo " --ip-block <ip-rule> Add a ip-addr rule for routing block, like geoip:private"
echo " --cn-block Add routing rules to avoid domains and IPs located in China being proxied" echo " --cn-block Add routing rules to avoid domains and IPs located in China being proxied"
echo " -u|--user <global-user-options> u=id0,u=id1..." echo " -u|--user <global-user-options> u=id0,u=id1..."
echo " -k|--hook <hook-url> DDNS update or notifing URL to be hit" echo " -k|--hook <hook-url> DDNS update or notifing URL to be hit"
echo " -r|--request-domain <domain-name> Domain name to request for letsencrypt cert" echo " -r|--request-domain <domain-name> Domain name to request for letsencrypt cert"
echo " -c|--cert-home <cert-home-dir> Reading TLS certs from folder <cert-home-dir>/<domain-name>/" echo " -c|--cert-home <cert-home-dir> Reading TLS certs from folder <cert-home-dir>/<domain-name>/"
echo " -i|--stdin Read config from STDIN instead of auto generation" echo " -i|--stdin Read config from STDIN instead of auto generation"
@@ -38,7 +42,7 @@ usage() {
Jrules='{"rules":[]}' Jrules='{"rules":[]}'
TEMP=`getopt -o u:k:r:c:j:di --long user:,hook:,request-domain:,cert-home:,ip-block:,domain-block:,cn-block,lgp:,lgr:,lgt:,lsp:,lst:,ltr:,ltt:,lwp:,lwt:,mtt:,mwp:,mwt:,ttt:,twp:,twt:,ng-opt:,ng-proxy:,json:,stdin,debug -n "$0" -- $@` TEMP=`getopt -o u:k:r:c:j:di --long lgp:,lgr:,lgt:,lsp:,lst:,ltr:,ltrx:,ltt:,lttx:,lwp:,lwt:,mtt:,mwp:,mwt:,ttt:,twp:,twt:,user:,hook:,request-domain:,cert-home:,ip-block:,domain-block:,cn-block,ng-server:,ng-proxy:,st-port:,st-map:,json:,stdin,debug -n "$0" -- $@`
if [ $? != 0 ] ; then usage; exit 1 ; fi if [ $? != 0 ] ; then usage; exit 1 ; fi
eval set -- "$TEMP" eval set -- "$TEMP"
@@ -77,6 +81,12 @@ while true ; do
SVCMD+=("${DIR}/server-${SVC}.sh $2") SVCMD+=("${DIR}/server-${SVC}.sh $2")
shift 2 shift 2
;; ;;
# Alias options
--ltrx|--lttx)
SVC=`echo $1|tr -d '\-\-'|tr -d x`
SVCMD+=("${DIR}/server-${SVC}.sh $2,xtls")
shift 2
;;
--domain-block) --domain-block)
Jrules=`echo "${Jrules}" | jq --arg blkdomain "$2" \ Jrules=`echo "${Jrules}" | jq --arg blkdomain "$2" \
'.rules += [{"type":"field", "outboundTag":"block", "domain":[$blkdomain]}]'` '.rules += [{"type":"field", "outboundTag":"block", "domain":[$blkdomain]}]'`
@@ -96,14 +106,22 @@ while true ; do
'.rules += [{"type":"field", "outboundTag":"block", "ip":[$ignip]}]'` '.rules += [{"type":"field", "outboundTag":"block", "ip":[$ignip]}]'`
shift 1 shift 1
;; ;;
--ng-opt) --ng-server)
NGOPT+=("$2") NGSVR+=("$2")
shift 2 shift 2
;; ;;
--ng-proxy) --ng-proxy)
NGPROXY+=("$2") NGPROXY+=("$2")
shift 2 shift 2
;; ;;
--st-port)
STPORT="$2"
shift 2
;;
--st-map)
STMAP+=("$2")
shift 2
;;
--) --)
shift shift
break break
@@ -161,59 +179,80 @@ Jrouting='{"routing": {"domainStrategy":"AsIs"}}'
Jrouting=`echo "${Jrouting}" |jq --argjson jrules "${Jrules}" '.routing += $jrules'` Jrouting=`echo "${Jrouting}" |jq --argjson jrules "${Jrules}" '.routing += $jrules'`
cat $XCONF| jq --argjson jrouting "${Jrouting}" '. += $jrouting' | sponge $XCONF cat $XCONF| jq --argjson jrouting "${Jrouting}" '. += $jrouting' | sponge $XCONF
if [ -n "${SVCMD}" ]; then # Run Xray only. Read Xray config from STDIN
for svcmd in "${SVCMD[@]}" if [ "${STDINCONF}" = "1" ]; then
exec /usr/local/bin/xray
fi
if [ -z "${SVCMD}" ]; then
echo "No Xray service creation found. Quit."
exit 1
fi
# Start Nginx if necessary
if [ -n "${STPORT}" ]; then
NGOPT="--st-port ${STPORT}"
for mapopt in "${STMAP[@]}"
do do
svcmd="$svcmd,$xopt" NGOPT="${NGOPT} --st-map $mapopt"
$svcmd
if [[ $? -ne 0 ]]; then
echo
echo "Command failed: $svcmd"
exit 1
fi
done done
fi
if [ "${DEBUG}" = "1" ]; then if [ -n "${NGSVR}" ]; then
cat $XCONF |jq '.log.loglevel |="debug"' |sponge $XCONF for svropt in "${NGSVR[@]}"
do
NGOPT="${NGOPT} --ng-server ${svropt},$xopt"
done
for pxyopt in "${NGPROXY[@]}"
do
NGOPT="${NGOPT} --ng-proxy ${pxyopt}"
done
fi
if [ -n "${NGOPT}" ]; then
ngcmd="${DIR}/server-nginx.sh $NGOPT"
$ngcmd
ret=$?; if [ $ret != 0 ]; then
echo ""
echo "Nginx config generation failed from the following cmd:\n$ngcmd";
echo "Please check log for details"
exit $ret;
fi
killall nginx
nginx;
fi
# Xray service config generation
for svcmd in "${SVCMD[@]}"
do
svcmd="$svcmd,$xopt"
$svcmd
if [[ $? -ne 0 ]]; then
echo echo
fi echo "Service creation command failed: $svcmd"
if [ -n "${NGOPT}" ]; then
ngcmd="${DIR}/server-nginx.sh"
for ngopt in "${NGOPT[@]}"
do
ngcmd="${ngcmd} --ng-opt ${ngopt},$xopt"
done
for ngproxy in "${NGPROXY[@]}"
do
ngcmd="${ngcmd} --ng-proxy ${ngproxy}"
done
$ngcmd
ret=$?; if [ $ret != 0 ] ; then echo "\nNon-zero result $ret from the following cmd:\n$ngcmd"; exit $ret ; fi
nginx;
fi
if [ -n "${INJECT}" ]; then
for JSON_IN in "${INJECT[@]}"
do
echo "${JSON_IN}"|jq -ec >/tmp/merge.json
if [[ $? -ne 0 ]]; then
echo "Invalid json ${JSON_IN}"
exit 1
fi
jq -s '.[0] * .[1]' $XCONF /tmp/merge.json |sponge $XCONF
done
fi
cat $XCONF
echo
exec /usr/local/bin/xray -c $XCONF
else
if [ "${STDINCONF}" = "1" ]; then
exec /usr/local/bin/xray
else
usage
exit 1 exit 1
fi fi
done
if [ "${DEBUG}" = "1" ]; then
cat $XCONF |jq '.log.loglevel |="debug"' |sponge $XCONF
echo
fi
if [ -n "${INJECT}" ]; then
for JSON_IN in "${INJECT[@]}"
do
echo "${JSON_IN}"|jq -ec >/tmp/merge.json
if [[ $? -ne 0 ]]; then
echo "Invalid json ${JSON_IN}"
exit 1
fi
jq -s '.[0] * .[1]' $XCONF /tmp/merge.json |sponge $XCONF
done
fi
cat $XCONF
echo
exec /usr/local/bin/xray -c $XCONF
fi fi

View File

@@ -3,27 +3,39 @@
DIR=`dirname $0` DIR=`dirname $0`
DIR="$(cd $DIR; pwd)" DIR="$(cd $DIR; pwd)"
TPL="site-ssl.conf.tpl" TPL="site-ssl.conf.tpl"
STPL="nginx-stream.tpl"
NGCONF="/etc/nginx/nginx.conf"
usage() { usage() {
echo "server-nginx --ng-opt <c=certhome,d=domain>[,p=443] --ng-proxy <p=xport,l=location,n=grpc|ws|splt>[,h=127.0.0.1]" echo "server-nginx --ng-server <c=certhome,d=domain>[,p=443] --ng-proxy <p=xport,l=location,n=grpc|ws|splt>[,h=127.0.0.1]"
echo " --ng-opt <c=cert-home-dir,d=host-domain>[,p=443]" echo " --ng-server <c=cert-home-dir,d=host-domain>[,p=443]"
echo " --ng-proxy <p=port-backend,l=location-path,n=grpc|ws|splt>[,h=127.0.0.1][,d=host-domain]" echo " --ng-proxy <p=port-backend,l=location-path,n=grpc|ws|splt>[,h=127.0.0.1][,d=host-domain]"
echo " --st-port <port-num>"
echo " --st-map <sni=domain.com,ups=127.0.0.1:8443>"
} }
TEMP=`getopt -o o:x: --long ng-opt:,ng-proxy: -n "$0" -- $@` TEMP=`getopt -o m:p:s:x: --long ng-server:,ng-proxy:,st-map:,st-port: -n "$0" -- $@`
if [ $? != 0 ] ; then usage; exit 1 ; fi if [ $? != 0 ] ; then usage; exit 1 ; fi
eval set -- "$TEMP" eval set -- "$TEMP"
while true ; do while true ; do
case "$1" in case "$1" in
-o|--ng-opt) -m|--st-map)
NGOPT+=("$2") STMAP+=("$2")
shift 2
;;
-p|--st-port)
STPORT="$2"
shift 2
;;
-s|--ng-server)
NGSVR+=("$2")
shift 2 shift 2
;; ;;
-x|--ng-proxy) -x|--ng-proxy)
NGPROXY+=("$2") NGPROXY+=("$2")
shift 2 shift 2
;; ;;
--) --)
shift shift
break break
@@ -36,21 +48,56 @@ while true ; do
esac esac
done done
if [ -z "${NGOPT}" ]; then usage; exit 1; fi if [ -z "${NGSVR}" ] && [ -z "${STPORT}" ]; then echo "No server/stream defined. Quit."; exit 1; fi
if [ -z "${NGPROXY}" ]; then usage; exit 1; fi
# Running as root to enable low port listening. Necessary for Fargate or k8s. # Running as root to enable low port listening. Necessary for Fargate or k8s.
# sed -i 's/^user nginx;$/user root;/g' /etc/nginx/nginx.conf # sed -i 's/^user nginx;$/user root;/g' /etc/nginx/nginx.conf
mkdir -p /run/nginx/ #mkdir -p /run/nginx/
cd /etc/nginx/conf.d/ cd /etc/nginx/conf.d/
if [ -f /etc/nginx/conf.d/default.conf ]; then if [ -f /etc/nginx/conf.d/default.conf ]; then
mv default.conf default.conf.disable mv default.conf default.conf.disable
fi fi
for ngopt in "${NGOPT[@]}" if [ -n "${STPORT}" ]; then
# Remove all lines generated previously after #STSTUB tag.
sed -i '/\#STSTUB/q' /etc/nginx/nginx.conf
# Remove #STSTUB tag
sed -i '/\#STSTUB/d' /etc/nginx/nginx.conf
rm /tmp/map.conf
rm /tmp/ups.conf
# Attach the stream configuration to the tail of nginx.conf
cat ${STPL} >> /etc/nginx/nginx.conf
for stmap in "${STMAP[@]}"
do
options=(`echo $stmap |tr ',' ' '`)
for option in "${options[@]}"
do
kv=(`echo $option |tr '=' ' '`)
case "${kv[0]}" in
sni)
sni="${kv[1]}"
;;
ups|upstream)
upstream="${kv[1]}"
;;
esac
done
upsname=`echo $sni|sed 's/\./_/g'`
echo " $sni $upsname;" >>/tmp/map.conf
echo " upstream $upsname {" >>/tmp/ups.conf
echo " server $upstream;" >>/tmp/ups.conf
echo " }" >>/tmp/ups.conf
done
sed -i '/#MAPSTUB/r /tmp/map.conf' /etc/nginx/nginx.conf
sed -i '/#UPSSTUB/r /tmp/ups.conf' /etc/nginx/nginx.conf
sed -i "s/STPORT/${STPORT}/g" /etc/nginx/nginx.conf
fi
for ngsvr in "${NGSVR[@]}"
do do
unset certhome unset certhome
options=(`echo $ngopt |tr ',' ' '`) options=(`echo $ngsvr |tr ',' ' '`)
for option in "${options[@]}" for option in "${options[@]}"
do do
kv=(`echo $option |tr '=' ' '`) kv=(`echo $option |tr '=' ' '`)
@@ -127,7 +174,7 @@ do
for domain in "${xdomain[@]}" for domain in "${xdomain[@]}"
do do
if ! [ -f "${domain}.conf" ]; then echo "Assigned domain ${domain} not found"; usage; exit 1; fi if ! [ -f "${domain}.conf" ]; then echo "Assigned domain ${domain} not found"; usage; exit 1; fi
# Replace the last(only) single line '}' with specific tpl file, hence insert a new section into the Nginx config file # Replace the last(only) single line '}' with the content of tpl file, hence insert a new section into the Nginx config file
case "${xnetwork}" in case "${xnetwork}" in
ws|websocket) ws|websocket)
sed -i -e "/^\}$/r nginx-ws.tpl" -e "/^\}$/d" ${domain}.conf sed -i -e "/^\}$/r nginx-ws.tpl" -e "/^\}$/d" ${domain}.conf