From 0f83efa5b5db7f1461a8d39cfd4d6db4fe9e743d Mon Sep 17 00:00:00 2001 From: Samuel Huang Date: Thu, 12 Sep 2024 15:21:19 +1000 Subject: [PATCH] Initial Nginx SNI stream map support --- Dockerfile | 15 ++--- nginx-stream.tpl | 15 +++++ run.sh | 161 +++++++++++++++++++++++++++++------------------ server-nginx.sh | 71 +++++++++++++++++---- 4 files changed, 182 insertions(+), 80 deletions(-) create mode 100644 nginx-stream.tpl diff --git a/Dockerfile b/Dockerfile index 875d9ca..dd8d268 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,15 +2,15 @@ FROM golang:1.23-alpine3.20 AS builder 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 RUN git clone https://github.com/XTLS/Xray-core.git . && \ git checkout ${XRAYVER} && \ 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; 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/geoip.dat +RUN cd /tmp; curl -O https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat 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 setcap CAP_NET_BIND_SERVICE=+eip /usr/sbin/nginx -COPY site-ssl.conf.tpl /etc/nginx/conf.d/ -COPY nginx-proxy.tpl /etc/nginx/conf.d/ -COPY nginx-grpc.tpl /etc/nginx/conf.d/ -COPY nginx-ws.tpl /etc/nginx/conf.d/ +COPY site-ssl.conf.tpl /etc/nginx/conf.d/ +COPY nginx-stream.tpl /etc/nginx/conf.d/ +COPY nginx-proxy.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-lgr.sh /server-lgr.sh diff --git a/nginx-stream.tpl b/nginx-stream.tpl new file mode 100644 index 0000000..70ce746 --- /dev/null +++ b/nginx-stream.tpl @@ -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; + } +} diff --git a/run.sh b/run.sh index 539b6ee..a082d78 100755 --- a/run.sh +++ b/run.sh @@ -8,12 +8,14 @@ XCONF=/tmp/server-xray.json usage() { echo "server-xray " echo " --lgp p=11443,u=id1,u=id2...,s=svcname" - echo " --lgr p=12443,u=id1,u=id2...,s=svcname,d=dest.com,pub=xx,prv=yy[,shortId=zz]" + echo " --lgr p=12443,u=id1,u=id2...,s=svcname,d=dest.com,pub=xx,prv=yy[,shortId=ab]" echo " --lgt p=13443,u=id1,u=id2...,s=svcname,d=domain.com" echo " --lsp p=14443,u=id1,u=id2...,w=/webpath" echo " --lst p=16443,u=id1,u=id2...,w=/webpath,d=domain.com" - echo " --ltr p=17443,u=id1,u=id2...,[xtls],d=dest.com,pub=xx,prv=yy[,shortId=zz]" - echo " --ltt p=18443,u=id1,u=id2...,[xtls],d=domain.com" + echo " --ltr p=17443,u=id1,u=id2...,d=dest.com,pub=xx,prv=yy[,shortId=ab],[xtls]" + echo " --ltrx p=17443,u=id1,u=id2...,d=dest.com,pub=xx,prv=yy[,shortId=ab]" + echo " --ltt p=18443,u=id1,u=id2...,d=domain.com,[xtls]" + echo " --lttx p=18443,u=id1,u=id2...,d=domain.com" echo " --lwp p=19443,u=id1,u=id2...,w=/wskpath" echo " --lwt p=22443,u=id1,u=id2...,w=/wskpath,d=domain.com" echo " --mtt p=23443,u=id1,u=id2...,d=domain.com" @@ -22,13 +24,15 @@ usage() { echo " --ttt p=26443,u=pw1,u=pw2...,d=domain.com" echo " --twp p=27443,u=pw1,u=pw2...,w=/wskpath" echo " --twt p=28443,u=pw1,u=pw2...,w=/wskpath,d=domain.com" - echo " --ng-opt p=443,d=domain0.com,d=domain1.com..." - echo " --ng-proxy d=domain0.com,d=domain1.com,p=port-backend,l=location,n=ws|grpc|splt" - echo " --domain-block Add a domain rule for routing block, like geosite:category-ads-all" - echo " --ip-block Add a ip-addr rule for routing block, like geoip:private" + echo " --ng-server p=8443,d=domain0.com,d=domain1.com..." + echo " --ng-proxy d=domain0.com,d=domain1.com,p=port-backend,l=location,n=ws|grpc|splt" + echo " --st-port 443" + echo " --st-map sni=domain.com,ups=127.0.0.1:8443" + echo " --domain-block Add a domain rule for routing-server block, like geosite:category-ads-all" + echo " --ip-block 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 " -u|--user u=id0,u=id1..." - echo " -k|--hook DDNS update or notifing URL to be hit" + echo " -u|--user u=id0,u=id1..." + echo " -k|--hook DDNS update or notifing URL to be hit" echo " -r|--request-domain Domain name to request for letsencrypt cert" echo " -c|--cert-home Reading TLS certs from folder //" echo " -i|--stdin Read config from STDIN instead of auto generation" @@ -38,7 +42,7 @@ usage() { 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 eval set -- "$TEMP" @@ -77,6 +81,12 @@ while true ; do SVCMD+=("${DIR}/server-${SVC}.sh $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) Jrules=`echo "${Jrules}" | jq --arg blkdomain "$2" \ '.rules += [{"type":"field", "outboundTag":"block", "domain":[$blkdomain]}]'` @@ -96,14 +106,22 @@ while true ; do '.rules += [{"type":"field", "outboundTag":"block", "ip":[$ignip]}]'` shift 1 ;; - --ng-opt) - NGOPT+=("$2") + --ng-server) + NGSVR+=("$2") shift 2 ;; --ng-proxy) NGPROXY+=("$2") shift 2 ;; + --st-port) + STPORT="$2" + shift 2 + ;; + --st-map) + STMAP+=("$2") + shift 2 + ;; --) shift break @@ -161,59 +179,80 @@ Jrouting='{"routing": {"domainStrategy":"AsIs"}}' Jrouting=`echo "${Jrouting}" |jq --argjson jrules "${Jrules}" '.routing += $jrules'` cat $XCONF| jq --argjson jrouting "${Jrouting}" '. += $jrouting' | sponge $XCONF -if [ -n "${SVCMD}" ]; then - for svcmd in "${SVCMD[@]}" +# Run Xray only. Read Xray config from STDIN +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 - svcmd="$svcmd,$xopt" - $svcmd - if [[ $? -ne 0 ]]; then - echo - echo "Command failed: $svcmd" - exit 1 - fi + NGOPT="${NGOPT} --st-map $mapopt" done +fi - if [ "${DEBUG}" = "1" ]; then - cat $XCONF |jq '.log.loglevel |="debug"' |sponge $XCONF +if [ -n "${NGSVR}" ]; then + 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 - fi - - 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 + echo "Service creation command failed: $svcmd" exit 1 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 diff --git a/server-nginx.sh b/server-nginx.sh index 113e9bb..8819230 100755 --- a/server-nginx.sh +++ b/server-nginx.sh @@ -3,27 +3,39 @@ DIR=`dirname $0` DIR="$(cd $DIR; pwd)" TPL="site-ssl.conf.tpl" +STPL="nginx-stream.tpl" +NGCONF="/etc/nginx/nginx.conf" usage() { - echo "server-nginx --ng-opt [,p=443] --ng-proxy [,h=127.0.0.1]" - echo " --ng-opt [,p=443]" + echo "server-nginx --ng-server [,p=443] --ng-proxy [,h=127.0.0.1]" + echo " --ng-server [,p=443]" echo " --ng-proxy [,h=127.0.0.1][,d=host-domain]" + echo " --st-port " + echo " --st-map " } -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 eval set -- "$TEMP" while true ; do case "$1" in - -o|--ng-opt) - NGOPT+=("$2") + -m|--st-map) + STMAP+=("$2") + shift 2 + ;; + -p|--st-port) + STPORT="$2" + shift 2 + ;; + -s|--ng-server) + NGSVR+=("$2") shift 2 ;; -x|--ng-proxy) NGPROXY+=("$2") shift 2 - ;; + ;; --) shift break @@ -36,21 +48,56 @@ while true ; do esac done -if [ -z "${NGOPT}" ]; then usage; exit 1; fi -if [ -z "${NGPROXY}" ]; then usage; exit 1; fi +if [ -z "${NGSVR}" ] && [ -z "${STPORT}" ]; then echo "No server/stream defined. Quit."; exit 1; fi # 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 -mkdir -p /run/nginx/ +#mkdir -p /run/nginx/ + cd /etc/nginx/conf.d/ if [ -f /etc/nginx/conf.d/default.conf ]; then mv default.conf default.conf.disable 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 unset certhome - options=(`echo $ngopt |tr ',' ' '`) + options=(`echo $ngsvr |tr ',' ' '`) for option in "${options[@]}" do kv=(`echo $option |tr '=' ' '`) @@ -127,7 +174,7 @@ do for domain in "${xdomain[@]}" do 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 ws|websocket) sed -i -e "/^\}$/r nginx-ws.tpl" -e "/^\}$/d" ${domain}.conf