1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2025-01-17 23:06:30 -05:00

Resync Hysteria2 Import (#3280)

* Revert "Remove hysteria2"

This reverts commit 1d21d7a077.

* Update Hysteria2

* Rename hysteria2 module

* fix broken hy2 import version

---------

Co-authored-by: JimmyHuang454 <jimmyhuang454@gmail.com>
This commit is contained in:
Xiaokang Wang (Shelikhoo) 2025-01-14 10:06:08 +00:00 committed by GitHub
parent ccacd52b24
commit 54fbeeba6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 2530 additions and 10 deletions

7
go.mod
View File

@ -6,6 +6,7 @@ toolchain go1.22.7
require ( require (
github.com/adrg/xdg v0.5.3 github.com/adrg/xdg v0.5.3
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3 github.com/go-chi/render v1.0.3
github.com/go-playground/validator/v10 v10.22.1 github.com/go-playground/validator/v10 v10.22.1
@ -28,6 +29,7 @@ require (
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848
github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e
github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6 github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6
github.com/vincent-petithory/dataurl v1.0.0 github.com/vincent-petithory/dataurl v1.0.0
@ -65,11 +67,9 @@ require (
github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/compress v1.17.4 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/reedsolomon v1.11.7 // indirect github.com/klauspost/reedsolomon v1.11.7 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
github.com/mustafaturan/monoton v1.0.0 // indirect github.com/mustafaturan/monoton v1.0.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/onsi/ginkgo/v2 v2.17.0 // indirect github.com/onsi/ginkgo/v2 v2.17.0 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/logging v0.2.2 // indirect
@ -77,8 +77,10 @@ require (
github.com/pion/sctp v1.8.7 // indirect github.com/pion/sctp v1.8.7 // indirect
github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect github.com/secure-io/siv-go v0.0.0-20180922214919-5ff40651e2c4 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xtaci/smux v1.5.24 // indirect github.com/xtaci/smux v1.5.24 // indirect
go.uber.org/mock v0.4.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
@ -87,5 +89,4 @@ require (
golang.org/x/time v0.5.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
) )

24
go.sum
View File

@ -25,6 +25,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7 h1:zO38yBOvQ1dLHbSuaU5BFZ8zalnSDQslj+i/9AGOk9s=
github.com/apernet/quic-go v0.48.2-0.20241104191913-cb103fcecfe7/go.mod h1:LoSUY2chVqNQCDyi4IZGqPpXLy1FuCkE37PKwtJvNGg=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
@ -50,7 +52,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -189,10 +190,11 @@ github.com/klauspost/reedsolomon v1.11.7/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg= github.com/lunixbochs/struc v0.0.0-20190916212049-a5c72983bc42/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
@ -223,8 +225,6 @@ github.com/mustafaturan/monoton v1.0.0/go.mod h1:FOnE7NV3s3EWPXb8/7+/OSdiMBbdlkV
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo/v2 v2.17.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI= github.com/onsi/ginkgo/v2 v2.17.0 h1:kdnunFXpBjbzN56hcJHrXZ8M+LOkenKA7NnBzTNigTI=
github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/ginkgo/v2 v2.17.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
@ -273,6 +273,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs=
github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM=
@ -281,6 +283,8 @@ github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
@ -309,6 +313,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -329,6 +335,8 @@ github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08 h1:4Yh46CVE3k/
github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY= github.com/v2fly/BrowserBridge v0.0.0-20210430233438-0570fc1d7d08/go.mod h1:KAuQNm+LWQCOFqdBcUgihPzRpVXRKzGbTNhfEfRZ4wY=
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w= github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848 h1:p1UzXK6VAutXFFQMnre66h7g1BjRKUnLv0HfmmRoz7w=
github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M= github.com/v2fly/VSign v0.0.0-20201108000810-e2adc24bf848/go.mod h1:p80Bv154ZtrGpXMN15slDCqc9UGmfBuUzheDFBYaW/M=
github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab h1:GstVKviVuxRZXxHzeWq0N2M4LG5A5W1HvFX1b7aQ48w=
github.com/v2fly/hysteria/core/v2 v2.0.0-20250113081444-b0a0747ac7ab/go.mod h1:yWDV7zOoL0pPhVlWV6Hqf46gWYenwwT9g4Y+e5yPRz8=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e h1:5QefA066A1tF8gHIiADmOVOV5LS43gt3ONnlEl3xkwI=
github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU= github.com/v2fly/ss-bloomring v0.0.0-20210312155135-28617310f63e/go.mod h1:5t19P9LBIrNamL6AcMQOncg/r10y3Pc01AbHeMhwlpU=
github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6 h1:Qea2jW7g1hvQ9TkYq3aT2h0NDWjPQHtvDfmKXoWgJ9E= github.com/v2fly/struc v0.0.0-20241227015403-8e8fa1badfd6 h1:Qea2jW7g1hvQ9TkYq3aT2h0NDWjPQHtvDfmKXoWgJ9E=
@ -352,6 +360,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8= go.starlark.net v0.0.0-20230612165344-9532f5667272 h1:2/wtqS591wZyD2OsClsVBKRPEvBsQt/Js+fsCiYhwu8=
go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= go.starlark.net v0.0.0-20230612165344-9532f5667272/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
@ -572,8 +582,8 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@ -0,0 +1,78 @@
package v4
import (
"github.com/golang/protobuf/proto"
"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
"github.com/v2fly/v2ray-core/v5/common/protocol"
"github.com/v2fly/v2ray-core/v5/common/serial"
"github.com/v2fly/v2ray-core/v5/infra/conf/cfgcommon"
"github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
)
// Hysteria2ServerTarget is configuration of a single hysteria2 server
type Hysteria2ServerTarget struct {
Address *cfgcommon.Address `json:"address"`
Port uint16 `json:"port"`
Email string `json:"email"`
Level byte `json:"level"`
}
// Hysteria2ClientConfig is configuration of hysteria2 servers
type Hysteria2ClientConfig struct {
Servers []*Hysteria2ServerTarget `json:"servers"`
}
// Build implements Buildable
func (c *Hysteria2ClientConfig) Build() (proto.Message, error) {
config := new(hysteria2.ClientConfig)
if len(c.Servers) == 0 {
return nil, newError("0 Hysteria2 server configured.")
}
serverSpecs := make([]*protocol.ServerEndpoint, len(c.Servers))
for idx, rec := range c.Servers {
if rec.Address == nil {
return nil, newError("Hysteria2 server address is not set.")
}
if rec.Port == 0 {
return nil, newError("Invalid Hysteria2 port.")
}
account := &hysteria2.Account{}
hysteria2 := &protocol.ServerEndpoint{
Address: rec.Address.Build(),
Port: uint32(rec.Port),
User: []*protocol.User{
{
Level: uint32(rec.Level),
Email: rec.Email,
Account: serial.ToTypedMessage(account),
},
},
}
serverSpecs[idx] = hysteria2
}
config.Server = serverSpecs
return config, nil
}
// Hysteria2ServerConfig is Inbound configuration
type Hysteria2ServerConfig struct {
PacketEncoding string `json:"packetEncoding"`
}
// Build implements Buildable
func (c *Hysteria2ServerConfig) Build() (proto.Message, error) {
config := new(hysteria2.ServerConfig)
switch c.PacketEncoding {
case "Packet":
config.PacketEncoding = packetaddr.PacketAddrType_Packet
case "", "None":
config.PacketEncoding = packetaddr.PacketAddrType_None
}
return config, nil
}

View File

@ -16,6 +16,7 @@ import (
"github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket" "github.com/v2fly/v2ray-core/v5/transport/internet/domainsocket"
httpheader "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http" httpheader "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
"github.com/v2fly/v2ray-core/v5/transport/internet/http" "github.com/v2fly/v2ray-core/v5/transport/internet/http"
"github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
"github.com/v2fly/v2ray-core/v5/transport/internet/kcp" "github.com/v2fly/v2ray-core/v5/transport/internet/kcp"
"github.com/v2fly/v2ray-core/v5/transport/internet/quic" "github.com/v2fly/v2ray-core/v5/transport/internet/quic"
"github.com/v2fly/v2ray-core/v5/transport/internet/tcp" "github.com/v2fly/v2ray-core/v5/transport/internet/tcp"
@ -143,6 +144,26 @@ type Hy2ConfigCongestion struct {
DownMbps uint64 `json:"down_mbps"` DownMbps uint64 `json:"down_mbps"`
} }
type Hy2Config struct {
Password string `json:"password"`
Congestion Hy2ConfigCongestion `json:"congestion"`
UseUdpExtension bool `json:"use_udp_extension"`
IgnoreClientBandwidth bool `json:"ignore_client_bandwidth"`
}
// Build implements Buildable.
func (c *Hy2Config) Build() (proto.Message, error) {
return &hysteria2.Config{Password: c.Password,
Congestion: &hysteria2.Congestion{
Type: c.Congestion.Type,
DownMbps: c.Congestion.DownMbps,
UpMbps: c.Congestion.UpMbps,
},
UseUdpExtension: c.UseUdpExtension,
IgnoreClientBandwidth: c.IgnoreClientBandwidth,
}, nil
}
type WebSocketConfig struct { type WebSocketConfig struct {
Path string `json:"path"` Path string `json:"path"`
Headers map[string]string `json:"headers"` Headers map[string]string `json:"headers"`
@ -285,6 +306,8 @@ func (p TransportProtocol) Build() (string, error) {
return "quic", nil return "quic", nil
case "gun", "grpc": case "gun", "grpc":
return "gun", nil return "gun", nil
case "hy2", "hysteria2":
return "hysteria2", nil
default: default:
return "", newError("Config: unknown transport protocol: ", p) return "", newError("Config: unknown transport protocol: ", p)
} }
@ -302,6 +325,7 @@ type StreamConfig struct {
QUICSettings *QUICConfig `json:"quicSettings"` QUICSettings *QUICConfig `json:"quicSettings"`
GunSettings *GunConfig `json:"gunSettings"` GunSettings *GunConfig `json:"gunSettings"`
GRPCSettings *GunConfig `json:"grpcSettings"` GRPCSettings *GunConfig `json:"grpcSettings"`
Hy2Settings *Hy2Config `json:"hy2Settings"`
SocketSettings *socketcfg.SocketConfig `json:"sockopt"` SocketSettings *socketcfg.SocketConfig `json:"sockopt"`
} }
@ -403,6 +427,16 @@ func (c *StreamConfig) Build() (*internet.StreamConfig, error) {
Settings: serial.ToTypedMessage(gs), Settings: serial.ToTypedMessage(gs),
}) })
} }
if c.Hy2Settings != nil {
hy2, err := c.Hy2Settings.Build()
if err != nil {
return nil, newError("Failed to build hy2 config.").Base(err)
}
config.TransportSettings = append(config.TransportSettings, &internet.TransportConfig{
ProtocolName: "hysteria2",
Settings: serial.ToTypedMessage(hy2),
})
}
if c.SocketSettings != nil { if c.SocketSettings != nil {
ss, err := c.SocketSettings.Build() ss, err := c.SocketSettings.Build()
if err != nil { if err != nil {

View File

@ -35,6 +35,7 @@ var (
"vless": func() interface{} { return new(VLessInboundConfig) }, "vless": func() interface{} { return new(VLessInboundConfig) },
"vmess": func() interface{} { return new(VMessInboundConfig) }, "vmess": func() interface{} { return new(VMessInboundConfig) },
"trojan": func() interface{} { return new(TrojanServerConfig) }, "trojan": func() interface{} { return new(TrojanServerConfig) },
"hysteria2": func() interface{} { return new(Hysteria2ServerConfig) },
}, "protocol", "settings") }, "protocol", "settings")
outboundConfigLoader = loader.NewJSONConfigLoader(loader.ConfigCreatorCache{ outboundConfigLoader = loader.NewJSONConfigLoader(loader.ConfigCreatorCache{
@ -46,6 +47,7 @@ var (
"vless": func() interface{} { return new(VLessOutboundConfig) }, "vless": func() interface{} { return new(VLessOutboundConfig) },
"vmess": func() interface{} { return new(VMessOutboundConfig) }, "vmess": func() interface{} { return new(VMessOutboundConfig) },
"trojan": func() interface{} { return new(TrojanClientConfig) }, "trojan": func() interface{} { return new(TrojanClientConfig) },
"hysteria2": func() interface{} { return new(Hysteria2ClientConfig) },
"dns": func() interface{} { return new(DNSOutboundConfig) }, "dns": func() interface{} { return new(DNSOutboundConfig) },
"loopback": func() interface{} { return new(LoopbackConfig) }, "loopback": func() interface{} { return new(LoopbackConfig) },
}, "protocol", "settings") }, "protocol", "settings")

View File

@ -53,6 +53,7 @@ import (
_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound" _ "github.com/v2fly/v2ray-core/v5/proxy/vlite/inbound"
_ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound" _ "github.com/v2fly/v2ray-core/v5/proxy/vlite/outbound"
_ "github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
_ "github.com/v2fly/v2ray-core/v5/proxy/shadowsocks2022" _ "github.com/v2fly/v2ray-core/v5/proxy/shadowsocks2022"
// Transports // Transports
@ -82,6 +83,8 @@ import (
_ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade" _ "github.com/v2fly/v2ray-core/v5/transport/internet/httpupgrade"
_ "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
// Transport headers // Transport headers
_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http" _ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
_ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/noop" _ "github.com/v2fly/v2ray-core/v5/transport/internet/headers/noop"

216
proxy/hysteria2/client.go Normal file
View File

@ -0,0 +1,216 @@
package hysteria2
import (
"context"
hyProtocol "github.com/v2fly/hysteria/core/v2/international/protocol"
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/buf"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
"github.com/v2fly/v2ray-core/v5/common/protocol"
"github.com/v2fly/v2ray-core/v5/common/retry"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/common/signal"
"github.com/v2fly/v2ray-core/v5/common/task"
"github.com/v2fly/v2ray-core/v5/features/policy"
"github.com/v2fly/v2ray-core/v5/proxy"
"github.com/v2fly/v2ray-core/v5/transport"
"github.com/v2fly/v2ray-core/v5/transport/internet"
hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
"github.com/v2fly/v2ray-core/v5/transport/internet/udp"
)
// Client is an inbound handler
type Client struct {
serverPicker protocol.ServerPicker
policyManager policy.Manager
}
// NewClient create a new client.
func NewClient(ctx context.Context, config *ClientConfig) (*Client, error) {
serverList := protocol.NewServerList()
for _, rec := range config.Server {
s, err := protocol.NewServerSpecFromPB(rec)
if err != nil {
return nil, newError("failed to parse server spec").Base(err)
}
serverList.AddServer(s)
}
if serverList.Size() == 0 {
return nil, newError("0 server")
}
v := core.MustFromContext(ctx)
client := &Client{
serverPicker: protocol.NewRoundRobinServerPicker(serverList),
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
}
return client, nil
}
// Process implements OutboundHandler.Process().
func (c *Client) Process(ctx context.Context, link *transport.Link, dialer internet.Dialer) error {
outbound := session.OutboundFromContext(ctx)
if outbound == nil || !outbound.Target.IsValid() {
return newError("target not specified")
}
destination := outbound.Target
network := destination.Network
var server *protocol.ServerSpec
var conn internet.Connection
err := retry.ExponentialBackoff(5, 100).On(func() error {
server = c.serverPicker.PickServer()
rawConn, err := dialer.Dial(ctx, server.Destination())
if err != nil {
return err
}
conn = rawConn
return nil
})
if err != nil {
return newError("failed to find an available destination").AtWarning().Base(err)
}
newError("tunneling request to ", destination, " via ", server.Destination().NetAddr()).WriteToLog(session.ExportIDToError(ctx))
defer conn.Close()
iConn := conn
if statConn, ok := conn.(*internet.StatCouterConnection); ok {
iConn = statConn.Connection // will not count the UDP traffic.
}
hyConn, IsHy2Transport := iConn.(*hyTransport.HyConn)
if !IsHy2Transport && network == net.Network_UDP {
// hysteria2 need to use udp extension to proxy UDP.
return newError(hyTransport.CanNotUseUdpExtension)
}
user := server.PickUser()
userLevel := uint32(0)
if user != nil {
userLevel = user.Level
}
sessionPolicy := c.policyManager.ForLevel(userLevel)
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
if packetConn, err := packetaddr.ToPacketAddrConn(link, destination); err == nil {
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
var buffer [2048]byte
n, addr, err := packetConn.ReadFrom(buffer[:])
if err != nil {
return newError("failed to read a packet").Base(err)
}
dest := net.DestinationFromAddr(addr)
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
connWriter := &ConnWriter{Writer: bufferWriter, Target: dest}
packetWriter := &PacketWriter{Writer: connWriter, Target: dest, HyConn: hyConn}
// write some request payload to buffer
if _, err := packetWriter.WriteTo(buffer[:n], addr); err != nil {
return newError("failed to write a request payload").Base(err)
}
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err = bufferWriter.SetBuffered(false); err != nil {
return newError("failed to flush payload").Base(err).AtWarning()
}
return udp.CopyPacketConn(packetWriter, packetConn, udp.UpdateActivity(timer))
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
packetReader := &PacketReader{Reader: conn, HyConn: hyConn}
packetConnectionReader := &PacketConnectionReader{reader: packetReader}
return udp.CopyPacketConn(packetConn, packetConnectionReader, udp.UpdateActivity(timer))
}
responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer))
if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
postRequest := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
var bodyWriter buf.Writer
bufferWriter := buf.NewBufferedWriter(buf.NewWriter(conn))
connWriter := &ConnWriter{Writer: bufferWriter, Target: destination}
bodyWriter = connWriter
if network == net.Network_UDP {
bodyWriter = &PacketWriter{Writer: connWriter, Target: destination, HyConn: hyConn}
} else {
// write some request payload to buffer
err = buf.CopyOnceTimeout(link.Reader, bodyWriter, proxy.FirstPayloadTimeout)
switch err {
case buf.ErrNotTimeoutReader, buf.ErrReadTimeout:
if err := connWriter.WriteTCPHeader(); err != nil {
return newError("failed to write request header").Base(err).AtWarning()
}
case nil:
default:
return newError("failed to write a request payload").Base(err).AtWarning()
}
// Flush; bufferWriter.WriteMultiBuffer now is bufferWriter.writer.WriteMultiBuffer
if err = bufferWriter.SetBuffered(false); err != nil {
return newError("failed to flush payload").Base(err).AtWarning()
}
}
if err = buf.Copy(link.Reader, bodyWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer request payload").Base(err).AtInfo()
}
return nil
}
getResponse := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
var reader buf.Reader
if network == net.Network_UDP {
reader = &PacketReader{
Reader: conn, HyConn: hyConn,
}
} else {
ok, msg, err := hyProtocol.ReadTCPResponse(conn)
if err != nil {
return err
}
if !ok {
return newError(msg)
}
reader = buf.NewReader(conn)
}
return buf.Copy(reader, link.Writer, buf.UpdateActivity(timer))
}
responseDoneAndCloseWriter := task.OnSuccess(getResponse, task.Close(link.Writer))
if err := task.Run(ctx, postRequest, responseDoneAndCloseWriter); err != nil {
return newError("connection ends").Base(err)
}
return nil
}
func init() {
common.Must(common.RegisterConfig((*ClientConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewClient(ctx, config.(*ClientConfig))
}))
}

18
proxy/hysteria2/config.go Normal file
View File

@ -0,0 +1,18 @@
package hysteria2
import (
"github.com/v2fly/v2ray-core/v5/common/protocol"
)
// MemoryAccount is an account type converted from Account.
type MemoryAccount struct{}
// AsAccount implements protocol.AsAccount.
func (a *Account) AsAccount() (protocol.Account, error) {
return &MemoryAccount{}, nil
}
// Equals implements protocol.Account.Equals().
func (a *MemoryAccount) Equals(another protocol.Account) bool {
return false
}

View File

@ -0,0 +1,282 @@
package hysteria2
import (
packetaddr "github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
protocol "github.com/v2fly/v2ray-core/v5/common/protocol"
_ "github.com/v2fly/v2ray-core/v5/common/protoext"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Account struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
}
func (x *Account) Reset() {
*x = Account{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_hysteria2_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Account) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Account) ProtoMessage() {}
func (x *Account) ProtoReflect() protoreflect.Message {
mi := &file_proxy_hysteria2_config_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Account.ProtoReflect.Descriptor instead.
func (*Account) Descriptor() ([]byte, []int) {
return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{0}
}
type ClientConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Server []*protocol.ServerEndpoint `protobuf:"bytes,1,rep,name=server,proto3" json:"server,omitempty"`
}
func (x *ClientConfig) Reset() {
*x = ClientConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_hysteria2_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ClientConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ClientConfig) ProtoMessage() {}
func (x *ClientConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_hysteria2_config_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ClientConfig.ProtoReflect.Descriptor instead.
func (*ClientConfig) Descriptor() ([]byte, []int) {
return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{1}
}
func (x *ClientConfig) GetServer() []*protocol.ServerEndpoint {
if x != nil {
return x.Server
}
return nil
}
type ServerConfig struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
PacketEncoding packetaddr.PacketAddrType `protobuf:"varint,1,opt,name=packet_encoding,json=packetEncoding,proto3,enum=v2ray.core.net.packetaddr.PacketAddrType" json:"packet_encoding,omitempty"`
}
func (x *ServerConfig) Reset() {
*x = ServerConfig{}
if protoimpl.UnsafeEnabled {
mi := &file_proxy_hysteria2_config_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ServerConfig) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ServerConfig) ProtoMessage() {}
func (x *ServerConfig) ProtoReflect() protoreflect.Message {
mi := &file_proxy_hysteria2_config_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ServerConfig.ProtoReflect.Descriptor instead.
func (*ServerConfig) Descriptor() ([]byte, []int) {
return file_proxy_hysteria2_config_proto_rawDescGZIP(), []int{2}
}
func (x *ServerConfig) GetPacketEncoding() packetaddr.PacketAddrType {
if x != nil {
return x.PacketEncoding
}
return packetaddr.PacketAddrType(0)
}
var File_proxy_hysteria2_config_proto protoreflect.FileDescriptor
var file_proxy_hysteria2_config_proto_rawDesc = []byte{
0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61,
0x32, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a,
0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
0x2e, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x1a, 0x22, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64, 0x64,
0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21,
0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f,
0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x65,
0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x22, 0x09, 0x0a, 0x07, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x6d,
0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x42,
0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a,
0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76,
0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76,
0x65, 0x72, 0x3a, 0x19, 0x82, 0xb5, 0x18, 0x15, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75,
0x6e, 0x64, 0x12, 0x09, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x22, 0x7c, 0x0a,
0x0c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x52, 0x0a,
0x0f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67,
0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63,
0x6f, 0x72, 0x65, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x61, 0x64,
0x64, 0x72, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x54, 0x79, 0x70,
0x65, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e,
0x67, 0x3a, 0x18, 0x82, 0xb5, 0x18, 0x14, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64,
0x12, 0x09, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x42, 0x6f, 0x0a, 0x1e, 0x63,
0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x78, 0x79, 0x2e, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x50, 0x01, 0x5a,
0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c,
0x79, 0x2f, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f,
0x70, 0x72, 0x6f, 0x78, 0x79, 0x2f, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0xaa,
0x02, 0x1a, 0x56, 0x32, 0x52, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f,
0x78, 0x79, 0x2e, 0x48, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_proxy_hysteria2_config_proto_rawDescOnce sync.Once
file_proxy_hysteria2_config_proto_rawDescData = file_proxy_hysteria2_config_proto_rawDesc
)
func file_proxy_hysteria2_config_proto_rawDescGZIP() []byte {
file_proxy_hysteria2_config_proto_rawDescOnce.Do(func() {
file_proxy_hysteria2_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_proxy_hysteria2_config_proto_rawDescData)
})
return file_proxy_hysteria2_config_proto_rawDescData
}
var file_proxy_hysteria2_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_proxy_hysteria2_config_proto_goTypes = []any{
(*Account)(nil), // 0: v2ray.core.proxy.hysteria2.Account
(*ClientConfig)(nil), // 1: v2ray.core.proxy.hysteria2.ClientConfig
(*ServerConfig)(nil), // 2: v2ray.core.proxy.hysteria2.ServerConfig
(*protocol.ServerEndpoint)(nil), // 3: v2ray.core.common.protocol.ServerEndpoint
(packetaddr.PacketAddrType)(0), // 4: v2ray.core.net.packetaddr.PacketAddrType
}
var file_proxy_hysteria2_config_proto_depIdxs = []int32{
3, // 0: v2ray.core.proxy.hysteria2.ClientConfig.server:type_name -> v2ray.core.common.protocol.ServerEndpoint
4, // 1: v2ray.core.proxy.hysteria2.ServerConfig.packet_encoding:type_name -> v2ray.core.net.packetaddr.PacketAddrType
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_proxy_hysteria2_config_proto_init() }
func file_proxy_hysteria2_config_proto_init() {
if File_proxy_hysteria2_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_proxy_hysteria2_config_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Account); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_hysteria2_config_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*ClientConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_proxy_hysteria2_config_proto_msgTypes[2].Exporter = func(v any, i int) any {
switch v := v.(*ServerConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_proxy_hysteria2_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_proxy_hysteria2_config_proto_goTypes,
DependencyIndexes: file_proxy_hysteria2_config_proto_depIdxs,
MessageInfos: file_proxy_hysteria2_config_proto_msgTypes,
}.Build()
File_proxy_hysteria2_config_proto = out.File
file_proxy_hysteria2_config_proto_rawDesc = nil
file_proxy_hysteria2_config_proto_goTypes = nil
file_proxy_hysteria2_config_proto_depIdxs = nil
}

View File

@ -0,0 +1,28 @@
syntax = "proto3";
package v2ray.core.proxy.hysteria2;
option csharp_namespace = "V2Ray.Core.Proxy.Hysteria2";
option go_package = "github.com/v2fly/v2ray-core/v5/proxy/hysteria2";
option java_package = "com.v2ray.core.proxy.hysteria2";
option java_multiple_files = true;
import "common/net/packetaddr/config.proto";
import "common/protocol/server_spec.proto";
import "common/protoext/extensions.proto";
message Account {
}
message ClientConfig {
option (v2ray.core.common.protoext.message_opt).type = "outbound";
option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
repeated v2ray.core.common.protocol.ServerEndpoint server = 1;
}
message ServerConfig {
option (v2ray.core.common.protoext.message_opt).type = "inbound";
option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
v2ray.core.net.packetaddr.PacketAddrType packet_encoding = 1;
}

View File

@ -0,0 +1,9 @@
package hysteria2
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1 @@
package hysteria2

210
proxy/hysteria2/protocol.go Normal file
View File

@ -0,0 +1,210 @@
package hysteria2
import (
"io"
"math/rand"
hyProtocol "github.com/v2fly/hysteria/core/v2/international/protocol"
"github.com/apernet/quic-go/quicvarint"
"github.com/v2fly/v2ray-core/v5/common/buf"
"github.com/v2fly/v2ray-core/v5/common/net"
hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
)
const (
paddingChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
)
// ConnWriter is TCP Connection Writer Wrapper
type ConnWriter struct {
io.Writer
Target net.Destination
TCPHeaderSent bool
}
// Write implements io.Writer
func (c *ConnWriter) Write(p []byte) (n int, err error) {
if !c.TCPHeaderSent {
if err := c.writeTCPHeader(); err != nil {
return 0, newError("failed to write request header").Base(err)
}
}
return c.Writer.Write(p)
}
// WriteMultiBuffer implements buf.Writer
func (c *ConnWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
defer buf.ReleaseMulti(mb)
for _, b := range mb {
if !b.IsEmpty() {
if _, err := c.Write(b.Bytes()); err != nil {
return err
}
}
}
return nil
}
func (c *ConnWriter) WriteTCPHeader() error {
if !c.TCPHeaderSent {
if err := c.writeTCPHeader(); err != nil {
return err
}
}
return nil
}
func QuicLen(s int) int {
return int(quicvarint.Len(uint64(s)))
}
func (c *ConnWriter) writeTCPHeader() error {
c.TCPHeaderSent = true
paddingLen := 64 + rand.Intn(512-64)
padding := make([]byte, paddingLen)
for i := range padding {
padding[i] = paddingChars[rand.Intn(len(paddingChars))]
}
addressAndPort := c.Target.NetAddr()
addressLen := len(addressAndPort)
if addressLen > hyProtocol.MaxAddressLength {
return newError("address length too large: ", addressLen)
}
size := QuicLen(addressLen) + addressLen + QuicLen(paddingLen) + paddingLen
buf := make([]byte, size)
i := hyProtocol.VarintPut(buf, uint64(addressLen))
i += copy(buf[i:], addressAndPort)
i += hyProtocol.VarintPut(buf[i:], uint64(paddingLen))
copy(buf[i:], padding)
_, err := c.Writer.Write(buf)
return err
}
// PacketWriter UDP Connection Writer Wrapper
type PacketWriter struct {
io.Writer
HyConn *hyTransport.HyConn
Target net.Destination
}
// WriteMultiBuffer implements buf.Writer
func (w *PacketWriter) WriteMultiBuffer(mb buf.MultiBuffer) error {
for _, b := range mb {
if b.IsEmpty() {
continue
}
if _, err := w.writePacket(b.Bytes(), w.Target); err != nil {
buf.ReleaseMulti(mb)
return err
}
}
return nil
}
// WriteMultiBufferWithMetadata writes udp packet with destination specified
func (w *PacketWriter) WriteMultiBufferWithMetadata(mb buf.MultiBuffer, dest net.Destination) error {
for _, b := range mb {
if b.IsEmpty() {
continue
}
if _, err := w.writePacket(b.Bytes(), dest); err != nil {
buf.ReleaseMulti(mb)
return err
}
}
return nil
}
func (w *PacketWriter) WriteTo(payload []byte, addr net.Addr) (int, error) {
dest := net.DestinationFromAddr(addr)
return w.writePacket(payload, dest)
}
func (w *PacketWriter) writePacket(payload []byte, dest net.Destination) (int, error) {
return w.HyConn.WritePacket(payload, dest)
}
// ConnReader is TCP Connection Reader Wrapper
type ConnReader struct {
io.Reader
Target net.Destination
}
// Read implements io.Reader
func (c *ConnReader) Read(p []byte) (int, error) {
return c.Reader.Read(p)
}
// ReadMultiBuffer implements buf.Reader
func (c *ConnReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
b := buf.New()
_, err := b.ReadFrom(c)
if err != nil {
return nil, err
}
return buf.MultiBuffer{b}, nil
}
// PacketPayload combines udp payload and destination
type PacketPayload struct {
Target net.Destination
Buffer buf.MultiBuffer
}
// PacketReader is UDP Connection Reader Wrapper
type PacketReader struct {
io.Reader
HyConn *hyTransport.HyConn
}
// ReadMultiBuffer implements buf.Reader
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
p, err := r.ReadMultiBufferWithMetadata()
if p != nil {
return p.Buffer, err
}
return nil, err
}
// ReadMultiBufferWithMetadata reads udp packet with destination
func (r *PacketReader) ReadMultiBufferWithMetadata() (*PacketPayload, error) {
_, data, dest, err := r.HyConn.ReadPacket()
if err != nil {
return nil, err
}
b := buf.FromBytes(data)
return &PacketPayload{Target: *dest, Buffer: buf.MultiBuffer{b}}, nil
}
type PacketConnectionReader struct {
reader *PacketReader
payload *PacketPayload
}
func (r *PacketConnectionReader) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
if r.payload == nil || r.payload.Buffer.IsEmpty() {
r.payload, err = r.reader.ReadMultiBufferWithMetadata()
if err != nil {
return
}
}
addr = &net.UDPAddr{
IP: r.payload.Target.Address.IP(),
Port: int(r.payload.Target.Port),
}
r.payload.Buffer, n = buf.SplitFirstBytes(r.payload.Buffer, p)
return
}

216
proxy/hysteria2/server.go Normal file
View File

@ -0,0 +1,216 @@
package hysteria2
import (
"context"
"io"
"time"
hyProtocol "github.com/v2fly/hysteria/core/v2/international/protocol"
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/buf"
"github.com/v2fly/v2ray-core/v5/common/errors"
"github.com/v2fly/v2ray-core/v5/common/log"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/net/packetaddr"
udp_proto "github.com/v2fly/v2ray-core/v5/common/protocol/udp"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/common/signal"
"github.com/v2fly/v2ray-core/v5/common/task"
"github.com/v2fly/v2ray-core/v5/features/policy"
"github.com/v2fly/v2ray-core/v5/features/routing"
"github.com/v2fly/v2ray-core/v5/transport/internet"
hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
"github.com/v2fly/v2ray-core/v5/transport/internet/udp"
)
func init() {
common.Must(common.RegisterConfig((*ServerConfig)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
return NewServer(ctx, config.(*ServerConfig))
}))
}
// Server is an inbound connection handler that handles messages in protocol.
type Server struct {
policyManager policy.Manager
packetEncoding packetaddr.PacketAddrType
}
// NewServer creates a new inbound handler.
func NewServer(ctx context.Context, config *ServerConfig) (*Server, error) {
v := core.MustFromContext(ctx)
server := &Server{
policyManager: v.GetFeature(policy.ManagerType()).(policy.Manager),
packetEncoding: config.PacketEncoding,
}
return server, nil
}
// Network implements proxy.Inbound.Network().
func (s *Server) Network() []net.Network {
return []net.Network{net.Network_TCP, net.Network_UNIX}
}
// Process implements proxy.Inbound.Process().
func (s *Server) Process(ctx context.Context, network net.Network, conn internet.Connection, dispatcher routing.Dispatcher) error {
sid := session.ExportIDToError(ctx)
iConn := conn
if statConn, ok := conn.(*internet.StatCouterConnection); ok {
iConn = statConn.Connection // will not count the UDP traffic.
}
hyConn, IsHy2Transport := iConn.(*hyTransport.HyConn)
if IsHy2Transport && hyConn.IsUDPExtension {
network = net.Network_UDP
}
if !IsHy2Transport && network == net.Network_UDP {
return newError(hyTransport.CanNotUseUdpExtension)
}
sessionPolicy := s.policyManager.ForLevel(0)
if err := conn.SetReadDeadline(time.Now().Add(sessionPolicy.Timeouts.Handshake)); err != nil {
return newError("unable to set read deadline").Base(err).AtWarning()
}
bufferedReader := &buf.BufferedReader{
Reader: buf.NewReader(conn),
}
clientReader := &ConnReader{Reader: bufferedReader}
if err := conn.SetReadDeadline(time.Time{}); err != nil {
return newError("unable to set read deadline").Base(err).AtWarning()
}
if network == net.Network_UDP { // handle udp request
return s.handleUDPPayload(ctx,
&PacketReader{Reader: clientReader, HyConn: hyConn},
&PacketWriter{Writer: conn, HyConn: hyConn}, dispatcher)
}
var reqAddr string
var err error
reqAddr, err = hyProtocol.ReadTCPRequest(conn)
if err != nil {
return newError("failed to parse header").Base(err)
}
err = hyProtocol.WriteTCPResponse(conn, true, "")
if err != nil {
return newError("failed to send response").Base(err)
}
address, stringPort, err := net.SplitHostPort(reqAddr)
if err != nil {
return err
}
port, err := net.PortFromString(stringPort)
if err != nil {
return err
}
destination := net.Destination{Network: network, Address: net.ParseAddress(address), Port: port}
inbound := session.InboundFromContext(ctx)
if inbound == nil {
panic("no inbound metadata")
}
sessionPolicy = s.policyManager.ForLevel(0)
ctx = log.ContextWithAccessMessage(ctx, &log.AccessMessage{
From: conn.RemoteAddr(),
To: destination,
Status: log.AccessAccepted,
Reason: "",
})
newError("received request for ", destination).WriteToLog(sid)
return s.handleConnection(ctx, sessionPolicy, destination, clientReader, buf.NewWriter(conn), dispatcher)
}
func (s *Server) handleConnection(ctx context.Context, sessionPolicy policy.Session,
destination net.Destination,
clientReader buf.Reader,
clientWriter buf.Writer, dispatcher routing.Dispatcher,
) error {
ctx, cancel := context.WithCancel(ctx)
timer := signal.CancelAfterInactivity(ctx, cancel, sessionPolicy.Timeouts.ConnectionIdle)
ctx = policy.ContextWithBufferPolicy(ctx, sessionPolicy.Buffer)
link, err := dispatcher.Dispatch(ctx, destination)
if err != nil {
return newError("failed to dispatch request to ", destination).Base(err)
}
requestDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.DownlinkOnly)
if err := buf.Copy(clientReader, link.Writer, buf.UpdateActivity(timer)); err != nil {
return newError("failed to transfer request").Base(err)
}
return nil
}
responseDone := func() error {
defer timer.SetTimeout(sessionPolicy.Timeouts.UplinkOnly)
if err := buf.Copy(link.Reader, clientWriter, buf.UpdateActivity(timer)); err != nil {
return newError("failed to write response").Base(err)
}
return nil
}
requestDonePost := task.OnSuccess(requestDone, task.Close(link.Writer))
if err := task.Run(ctx, requestDonePost, responseDone); err != nil {
common.Must(common.Interrupt(link.Reader))
common.Must(common.Interrupt(link.Writer))
return newError("connection ends").Base(err)
}
return nil
}
func (s *Server) handleUDPPayload(ctx context.Context, clientReader *PacketReader, clientWriter *PacketWriter, dispatcher routing.Dispatcher) error {
udpDispatcherConstructor := udp.NewSplitDispatcher
switch s.packetEncoding {
case packetaddr.PacketAddrType_None:
case packetaddr.PacketAddrType_Packet:
packetAddrDispatcherFactory := udp.NewPacketAddrDispatcherCreator(ctx)
udpDispatcherConstructor = packetAddrDispatcherFactory.NewPacketAddrDispatcher
}
udpServer := udpDispatcherConstructor(dispatcher, func(ctx context.Context, packet *udp_proto.Packet) {
if err := clientWriter.WriteMultiBufferWithMetadata(buf.MultiBuffer{packet.Payload}, packet.Source); err != nil {
newError("failed to write response").Base(err).AtWarning().WriteToLog(session.ExportIDToError(ctx))
}
})
inbound := session.InboundFromContext(ctx)
for {
select {
case <-ctx.Done():
return nil
default:
p, err := clientReader.ReadMultiBufferWithMetadata()
if err != nil {
if errors.Cause(err) != io.EOF {
return newError("unexpected EOF").Base(err)
}
return nil
}
currentPacketCtx := ctx
currentPacketCtx = log.ContextWithAccessMessage(currentPacketCtx, &log.AccessMessage{
From: inbound.Source,
To: p.Target,
Status: log.AccessAccepted,
Reason: "",
})
newError("tunnelling request to ", p.Target).WriteToLog(session.ExportIDToError(ctx))
for _, b := range p.Buffer {
udpServer.Dispatch(currentPacketCtx, p.Target, b)
}
}
}
}

View File

@ -0,0 +1,468 @@
package scenarios
import (
"testing"
"time"
"golang.org/x/sync/errgroup"
"google.golang.org/protobuf/types/known/anypb"
core "github.com/v2fly/v2ray-core/v5"
"github.com/v2fly/v2ray-core/v5/app/log"
"github.com/v2fly/v2ray-core/v5/app/proxyman"
"github.com/v2fly/v2ray-core/v5/common"
clog "github.com/v2fly/v2ray-core/v5/common/log"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/protocol"
"github.com/v2fly/v2ray-core/v5/common/protocol/tls/cert"
"github.com/v2fly/v2ray-core/v5/common/serial"
"github.com/v2fly/v2ray-core/v5/common/uuid"
"github.com/v2fly/v2ray-core/v5/proxy/dokodemo"
"github.com/v2fly/v2ray-core/v5/proxy/freedom"
"github.com/v2fly/v2ray-core/v5/proxy/hysteria2"
"github.com/v2fly/v2ray-core/v5/proxy/vmess"
"github.com/v2fly/v2ray-core/v5/proxy/vmess/inbound"
"github.com/v2fly/v2ray-core/v5/proxy/vmess/outbound"
"github.com/v2fly/v2ray-core/v5/testing/servers/tcp"
"github.com/v2fly/v2ray-core/v5/testing/servers/udp"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/headers/http"
hyTransport "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
tcpTransport "github.com/v2fly/v2ray-core/v5/transport/internet/tcp"
"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
)
func TestVMessHysteria2Congestion(t *testing.T) {
for _, v := range []string{"bbr", "brutal"} {
testVMessHysteria2(t, v)
}
}
func testVMessHysteria2(t *testing.T, congestionType string) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
userID := protocol.NewID(uuid.New())
serverPort := udp.PickPort()
serverConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
StreamSettings: &internet.StreamConfig{
ProtocolName: "hysteria2",
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
},
),
},
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "hysteria2",
Settings: serial.ToTypedMessage(&hyTransport.Config{
Congestion: &hyTransport.Congestion{Type: congestionType, UpMbps: 100, DownMbps: 100},
Password: "password",
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&inbound.Config{
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
AlterId: 0,
}),
},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(clientPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(dest.Address),
Port: uint32(dest.Port),
NetworkList: &net.NetworkList{
Network: []net.Network{net.Network_TCP},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "hysteria2",
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
ServerName: "www.v2fly.org",
AllowInsecure: true,
},
),
},
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "hysteria2",
Settings: serial.ToTypedMessage(&hyTransport.Config{
Congestion: &hyTransport.Congestion{Type: congestionType, UpMbps: 100, DownMbps: 100},
Password: "password",
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&outbound.Config{
Receiver: []*protocol.ServerEndpoint{
{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&vmess.Account{
Id: userID.String(),
AlterId: 0,
SecuritySettings: &protocol.SecurityConfig{
Type: protocol.SecurityType_NONE,
},
}),
},
},
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
if err != nil {
t.Fatal("Failed to initialize all servers: ", err.Error())
}
defer CloseAllServers(servers)
var errg errgroup.Group
for i := 0; i < 10; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
}
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
func TestHysteria2Offical(t *testing.T) {
for _, v := range []bool{true, false} {
testHysteria2Offical(t, v)
}
}
func testHysteria2Offical(t *testing.T, isUDP bool) {
var dest net.Destination
var err error
if isUDP {
udpServer := udp.Server{
MsgProcessor: xor,
}
dest, err = udpServer.Start()
common.Must(err)
defer udpServer.Close()
} else {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err = tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
}
serverPort := udp.PickPort()
serverConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
StreamSettings: &internet.StreamConfig{
ProtocolName: "hysteria2",
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
},
),
},
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "hysteria2",
Settings: serial.ToTypedMessage(&hyTransport.Config{
Congestion: &hyTransport.Congestion{Type: "brutal", UpMbps: 100, DownMbps: 100},
UseUdpExtension: true,
Password: "password",
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&hysteria2.ServerConfig{}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(clientPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(dest.Address),
Port: uint32(dest.Port),
NetworkList: &net.NetworkList{
Network: []net.Network{net.Network_TCP, net.Network_UDP},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
ProtocolName: "hysteria2",
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
ServerName: "www.v2fly.org",
AllowInsecure: true,
},
),
},
TransportSettings: []*internet.TransportConfig{
{
ProtocolName: "hysteria2",
Settings: serial.ToTypedMessage(&hyTransport.Config{
Congestion: &hyTransport.Congestion{Type: "brutal", UpMbps: 100, DownMbps: 100},
UseUdpExtension: true,
Password: "password",
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&hysteria2.ClientConfig{
Server: []*protocol.ServerEndpoint{
{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&hysteria2.Account{}),
},
},
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
if err != nil {
t.Fatal("Failed to initialize all servers: ", err.Error())
}
defer CloseAllServers(servers)
var errg errgroup.Group
for i := 0; i < 10; i++ {
if isUDP {
errg.Go(testUDPConn(clientPort, 1500, time.Second*4))
} else {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
}
}
if err := errg.Wait(); err != nil {
t.Error(err)
}
}
func TestHysteria2OnTCP(t *testing.T) {
tcpServer := tcp.Server{
MsgProcessor: xor,
}
dest, err := tcpServer.Start()
common.Must(err)
defer tcpServer.Close()
serverPort := udp.PickPort()
serverConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(serverPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
StreamSettings: &internet.StreamConfig{
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
Certificate: []*tls.Certificate{tls.ParseCertificate(cert.MustGenerate(nil))},
},
),
},
TransportSettings: []*internet.TransportConfig{
{
Protocol: internet.TransportProtocol_TCP,
Settings: serial.ToTypedMessage(&tcpTransport.Config{
HeaderSettings: serial.ToTypedMessage(&http.Config{}),
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&hysteria2.ServerConfig{}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
ProxySettings: serial.ToTypedMessage(&freedom.Config{}),
},
},
}
clientPort := tcp.PickPort()
clientConfig := &core.Config{
App: []*anypb.Any{
serial.ToTypedMessage(&log.Config{
Error: &log.LogSpecification{Level: clog.Severity_Debug, Type: log.LogType_Console},
}),
},
Inbound: []*core.InboundHandlerConfig{
{
ReceiverSettings: serial.ToTypedMessage(&proxyman.ReceiverConfig{
PortRange: net.SinglePortRange(clientPort),
Listen: net.NewIPOrDomain(net.LocalHostIP),
}),
ProxySettings: serial.ToTypedMessage(&dokodemo.Config{
Address: net.NewIPOrDomain(dest.Address),
Port: uint32(dest.Port),
NetworkList: &net.NetworkList{
Network: []net.Network{net.Network_TCP},
},
}),
},
},
Outbound: []*core.OutboundHandlerConfig{
{
SenderSettings: serial.ToTypedMessage(&proxyman.SenderConfig{
StreamSettings: &internet.StreamConfig{
SecurityType: serial.GetMessageType(&tls.Config{}),
SecuritySettings: []*anypb.Any{
serial.ToTypedMessage(
&tls.Config{
ServerName: "www.v2fly.org",
AllowInsecure: true,
},
),
},
TransportSettings: []*internet.TransportConfig{
{
Protocol: internet.TransportProtocol_TCP,
Settings: serial.ToTypedMessage(&tcpTransport.Config{
HeaderSettings: serial.ToTypedMessage(&http.Config{}),
}),
},
},
},
}),
ProxySettings: serial.ToTypedMessage(&hysteria2.ClientConfig{
Server: []*protocol.ServerEndpoint{
{
Address: net.NewIPOrDomain(net.LocalHostIP),
Port: uint32(serverPort),
User: []*protocol.User{
{
Account: serial.ToTypedMessage(&hysteria2.Account{}),
},
},
},
},
}),
},
},
}
servers, err := InitializeServerConfigs(serverConfig, clientConfig)
if err != nil {
t.Fatal("Failed to initialize all servers: ", err.Error())
}
defer CloseAllServers(servers)
var errg errgroup.Group
for i := 0; i < 1; i++ {
errg.Go(testTCPConn(clientPort, 10240*1024, time.Second*40))
}
if err := errg.Wait(); err != nil {
t.Error(err)
}
}

View File

@ -0,0 +1,271 @@
package hysteria2
import (
_ "github.com/v2fly/v2ray-core/v5/common/protoext"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Congestion struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
UpMbps uint64 `protobuf:"varint,2,opt,name=up_mbps,json=upMbps,proto3" json:"up_mbps,omitempty"`
DownMbps uint64 `protobuf:"varint,3,opt,name=down_mbps,json=downMbps,proto3" json:"down_mbps,omitempty"`
}
func (x *Congestion) Reset() {
*x = Congestion{}
if protoimpl.UnsafeEnabled {
mi := &file_transport_internet_hysteria2_config_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Congestion) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Congestion) ProtoMessage() {}
func (x *Congestion) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_hysteria2_config_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Congestion.ProtoReflect.Descriptor instead.
func (*Congestion) Descriptor() ([]byte, []int) {
return file_transport_internet_hysteria2_config_proto_rawDescGZIP(), []int{0}
}
func (x *Congestion) GetType() string {
if x != nil {
return x.Type
}
return ""
}
func (x *Congestion) GetUpMbps() uint64 {
if x != nil {
return x.UpMbps
}
return 0
}
func (x *Congestion) GetDownMbps() uint64 {
if x != nil {
return x.DownMbps
}
return 0
}
type Config struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
Congestion *Congestion `protobuf:"bytes,4,opt,name=congestion,proto3" json:"congestion,omitempty"`
IgnoreClientBandwidth bool `protobuf:"varint,5,opt,name=ignore_client_bandwidth,json=ignoreClientBandwidth,proto3" json:"ignore_client_bandwidth,omitempty"`
UseUdpExtension bool `protobuf:"varint,6,opt,name=use_udp_extension,json=useUdpExtension,proto3" json:"use_udp_extension,omitempty"`
}
func (x *Config) Reset() {
*x = Config{}
if protoimpl.UnsafeEnabled {
mi := &file_transport_internet_hysteria2_config_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Config) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Config) ProtoMessage() {}
func (x *Config) ProtoReflect() protoreflect.Message {
mi := &file_transport_internet_hysteria2_config_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Config.ProtoReflect.Descriptor instead.
func (*Config) Descriptor() ([]byte, []int) {
return file_transport_internet_hysteria2_config_proto_rawDescGZIP(), []int{1}
}
func (x *Config) GetPassword() string {
if x != nil {
return x.Password
}
return ""
}
func (x *Config) GetCongestion() *Congestion {
if x != nil {
return x.Congestion
}
return nil
}
func (x *Config) GetIgnoreClientBandwidth() bool {
if x != nil {
return x.IgnoreClientBandwidth
}
return false
}
func (x *Config) GetUseUdpExtension() bool {
if x != nil {
return x.UseUdpExtension
}
return false
}
var File_transport_internet_hysteria2_config_proto protoreflect.FileDescriptor
var file_transport_internet_hysteria2_config_proto_rawDesc = []byte{
0x0a, 0x29, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x65, 0x74, 0x2f, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x2f, 0x63,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x76, 0x32, 0x72,
0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72,
0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x79, 0x73, 0x74, 0x65,
0x72, 0x69, 0x61, 0x32, 0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x65, 0x78, 0x74, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x0a, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73,
0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01,
0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x70, 0x5f, 0x6d,
0x62, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x75, 0x70, 0x4d, 0x62, 0x70,
0x73, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x6d, 0x62, 0x70, 0x73, 0x18, 0x03,
0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x6f, 0x77, 0x6e, 0x4d, 0x62, 0x70, 0x73, 0x22, 0xf9,
0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x53, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74,
0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x76, 0x32, 0x72, 0x61,
0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72,
0x69, 0x61, 0x32, 0x2e, 0x43, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a,
0x63, 0x6f, 0x6e, 0x67, 0x65, 0x73, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x17, 0x69, 0x67,
0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x62, 0x61, 0x6e, 0x64,
0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x69, 0x67, 0x6e,
0x6f, 0x72, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64,
0x74, 0x68, 0x12, 0x2a, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x5f, 0x75, 0x64, 0x70, 0x5f, 0x65, 0x78,
0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x75,
0x73, 0x65, 0x55, 0x64, 0x70, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x1a,
0x82, 0xb5, 0x18, 0x16, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12,
0x09, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x42, 0x96, 0x01, 0x0a, 0x2b, 0x63,
0x6f, 0x6d, 0x2e, 0x76, 0x32, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x74, 0x72,
0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
0x2e, 0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0x50, 0x01, 0x5a, 0x3b, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x76, 0x32, 0x66, 0x6c, 0x79, 0x2f, 0x76,
0x32, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x35, 0x2f, 0x74, 0x72, 0x61,
0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f,
0x68, 0x79, 0x73, 0x74, 0x65, 0x72, 0x69, 0x61, 0x32, 0xaa, 0x02, 0x27, 0x56, 0x32, 0x52, 0x61,
0x79, 0x2e, 0x43, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x48, 0x79, 0x73, 0x74, 0x65, 0x72,
0x69, 0x61, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_transport_internet_hysteria2_config_proto_rawDescOnce sync.Once
file_transport_internet_hysteria2_config_proto_rawDescData = file_transport_internet_hysteria2_config_proto_rawDesc
)
func file_transport_internet_hysteria2_config_proto_rawDescGZIP() []byte {
file_transport_internet_hysteria2_config_proto_rawDescOnce.Do(func() {
file_transport_internet_hysteria2_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_transport_internet_hysteria2_config_proto_rawDescData)
})
return file_transport_internet_hysteria2_config_proto_rawDescData
}
var file_transport_internet_hysteria2_config_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_transport_internet_hysteria2_config_proto_goTypes = []any{
(*Congestion)(nil), // 0: v2ray.core.transport.internet.hysteria2.Congestion
(*Config)(nil), // 1: v2ray.core.transport.internet.hysteria2.Config
}
var file_transport_internet_hysteria2_config_proto_depIdxs = []int32{
0, // 0: v2ray.core.transport.internet.hysteria2.Config.congestion:type_name -> v2ray.core.transport.internet.hysteria2.Congestion
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_transport_internet_hysteria2_config_proto_init() }
func file_transport_internet_hysteria2_config_proto_init() {
if File_transport_internet_hysteria2_config_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_transport_internet_hysteria2_config_proto_msgTypes[0].Exporter = func(v any, i int) any {
switch v := v.(*Congestion); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_transport_internet_hysteria2_config_proto_msgTypes[1].Exporter = func(v any, i int) any {
switch v := v.(*Config); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_transport_internet_hysteria2_config_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_transport_internet_hysteria2_config_proto_goTypes,
DependencyIndexes: file_transport_internet_hysteria2_config_proto_depIdxs,
MessageInfos: file_transport_internet_hysteria2_config_proto_msgTypes,
}.Build()
File_transport_internet_hysteria2_config_proto = out.File
file_transport_internet_hysteria2_config_proto_rawDesc = nil
file_transport_internet_hysteria2_config_proto_goTypes = nil
file_transport_internet_hysteria2_config_proto_depIdxs = nil
}

View File

@ -0,0 +1,26 @@
syntax = "proto3";
package v2ray.core.transport.internet.hysteria2;
option csharp_namespace = "V2Ray.Core.Transport.Internet.Hysteria2";
option go_package = "github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2";
option java_package = "com.v2ray.core.transport.internet.hysteria2";
option java_multiple_files = true;
import "common/protoext/extensions.proto";
message Congestion{
string type = 1;
uint64 up_mbps = 2;
uint64 down_mbps = 3;
}
message Config {
option (v2ray.core.common.protoext.message_opt).type = "transport";
option (v2ray.core.common.protoext.message_opt).short_name = "hysteria2";
string password = 3;
Congestion congestion = 4;
bool ignore_client_bandwidth = 5;
bool use_udp_extension = 6;
}

View File

@ -0,0 +1,130 @@
package hysteria2
import (
"time"
hyClient "github.com/v2fly/hysteria/core/v2/client"
"github.com/v2fly/hysteria/core/v2/international/protocol"
hyServer "github.com/v2fly/hysteria/core/v2/server"
"github.com/apernet/quic-go"
"github.com/v2fly/v2ray-core/v5/common/net"
)
const CanNotUseUdpExtension = "Only hysteria2 proxy protocol can use udpExtension."
const Hy2MustNeedTLS = "Hysteria2 based on QUIC that requires TLS."
type HyConn struct {
IsUDPExtension bool
IsServer bool
ClientUDPSession hyClient.HyUDPConn
ServerUDPSession *hyServer.UdpSessionEntry
stream quic.Stream
local net.Addr
remote net.Addr
}
func (c *HyConn) Read(b []byte) (int, error) {
if c.IsUDPExtension {
n, data, _, err := c.ReadPacket()
copy(b, data)
return n, err
}
return c.stream.Read(b)
}
func (c *HyConn) Write(b []byte) (int, error) {
if c.IsUDPExtension {
dest, _ := net.ParseDestination("udp:v2fly.org:6666")
return c.WritePacket(b, dest)
}
return c.stream.Write(b)
}
func (c *HyConn) WritePacket(b []byte, dest net.Destination) (int, error) {
if !c.IsUDPExtension {
return 0, newError(CanNotUseUdpExtension)
}
if c.IsServer {
msg := &protocol.UDPMessage{
SessionID: c.ServerUDPSession.ID,
PacketID: 0,
FragID: 0,
FragCount: 1,
Addr: dest.NetAddr(),
Data: b,
}
c.ServerUDPSession.SendCh <- msg
return len(b), nil
}
return len(b), c.ClientUDPSession.Send(b, dest.NetAddr())
}
func (c *HyConn) ReadPacket() (int, []byte, *net.Destination, error) {
if !c.IsUDPExtension {
return 0, nil, nil, newError(CanNotUseUdpExtension)
}
if c.IsServer {
msg, ok := <-c.ServerUDPSession.ReceiveCh
if !ok {
return 0, nil, nil, newError("UDP session receive channel closed")
}
dest, err := net.ParseDestination("udp:" + msg.Addr)
return len(msg.Data), msg.Data, &dest, err
}
data, address, err := c.ClientUDPSession.Receive()
if err != nil {
return 0, nil, nil, err
}
dest, err := net.ParseDestination("udp:" + address)
if err != nil {
return 0, nil, nil, err
}
return len(data), data, &dest, nil
}
func (c *HyConn) Close() error {
if c.IsUDPExtension {
if !c.IsServer && c.ClientUDPSession == nil || (c.IsServer && c.ServerUDPSession == nil) {
return newError(CanNotUseUdpExtension)
}
if c.IsServer {
c.ServerUDPSession.CloseWithErr(nil)
return nil
}
return c.ClientUDPSession.Close()
}
return c.stream.Close()
}
func (c *HyConn) LocalAddr() net.Addr {
return c.local
}
func (c *HyConn) RemoteAddr() net.Addr {
return c.remote
}
func (c *HyConn) SetDeadline(t time.Time) error {
if c.IsUDPExtension {
return nil
}
return c.stream.SetDeadline(t)
}
func (c *HyConn) SetReadDeadline(t time.Time) error {
if c.IsUDPExtension {
return nil
}
return c.stream.SetReadDeadline(t)
}
func (c *HyConn) SetWriteDeadline(t time.Time) error {
if c.IsUDPExtension {
return nil
}
return c.stream.SetWriteDeadline(t)
}

View File

@ -0,0 +1,207 @@
package hysteria2
import (
"context"
"sync"
hyClient "github.com/v2fly/hysteria/core/v2/client"
hyProtocol "github.com/v2fly/hysteria/core/v2/international/protocol"
"github.com/apernet/quic-go/quicvarint"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
)
type dialerConf struct {
net.Destination
*internet.MemoryStreamConfig
}
var RunningClient map[dialerConf](hyClient.Client)
var ClientMutex sync.Mutex
var MBps uint64 = 1000000 / 8 // MByte
func GetClientTLSConfig(dest net.Destination, streamSettings *internet.MemoryStreamConfig) (*hyClient.TLSConfig, error) {
config := tls.ConfigFromStreamSettings(streamSettings)
if config == nil {
return nil, newError(Hy2MustNeedTLS)
}
tlsConfig := config.GetTLSConfig(tls.WithDestination(dest))
return &hyClient.TLSConfig{
RootCAs: tlsConfig.RootCAs,
ServerName: tlsConfig.ServerName,
InsecureSkipVerify: tlsConfig.InsecureSkipVerify,
VerifyPeerCertificate: tlsConfig.VerifyPeerCertificate,
}, nil
}
func ResolveAddress(dest net.Destination) (net.Addr, error) {
var destAddr *net.UDPAddr
if dest.Address.Family().IsIP() {
destAddr = &net.UDPAddr{
IP: dest.Address.IP(),
Port: int(dest.Port),
}
} else {
addr, err := net.ResolveUDPAddr("udp", dest.NetAddr())
if err != nil {
return nil, err
}
destAddr = addr
}
return destAddr, nil
}
type connFactory struct {
hyClient.ConnFactory
NewFunc func(addr net.Addr) (net.PacketConn, error)
}
func (f *connFactory) New(addr net.Addr) (net.PacketConn, error) {
return f.NewFunc(addr)
}
func NewHyClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) (hyClient.Client, error) {
tlsConfig, err := GetClientTLSConfig(dest, streamSettings)
if err != nil {
return nil, err
}
serverAddr, err := ResolveAddress(dest)
if err != nil {
return nil, err
}
config := streamSettings.ProtocolSettings.(*Config)
client, _, err := hyClient.NewClient(&hyClient.Config{
Auth: config.GetPassword(),
TLSConfig: *tlsConfig,
ServerAddr: serverAddr,
ConnFactory: &connFactory{
NewFunc: func(addr net.Addr) (net.PacketConn, error) {
rawConn, err := internet.ListenSystemPacket(context.Background(), &net.UDPAddr{
IP: []byte{0, 0, 0, 0},
Port: 0,
}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
return rawConn.(*net.UDPConn), nil
},
},
BandwidthConfig: hyClient.BandwidthConfig{MaxTx: config.Congestion.GetUpMbps() * MBps, MaxRx: config.GetCongestion().GetDownMbps() * MBps},
})
if err != nil {
return nil, err
}
return client, nil
}
func CloseHyClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) error {
ClientMutex.Lock()
defer ClientMutex.Unlock()
client, found := RunningClient[dialerConf{dest, streamSettings}]
if found {
delete(RunningClient, dialerConf{dest, streamSettings})
return client.Close()
}
return nil
}
func GetHyClient(dest net.Destination, streamSettings *internet.MemoryStreamConfig) (hyClient.Client, error) {
var err error
var client hyClient.Client
ClientMutex.Lock()
client, found := RunningClient[dialerConf{dest, streamSettings}]
ClientMutex.Unlock()
if !found || !CheckHyClientHealthy(client) {
if found {
// retry
CloseHyClient(dest, streamSettings)
}
client, err = NewHyClient(dest, streamSettings)
if err != nil {
return nil, err
}
ClientMutex.Lock()
RunningClient[dialerConf{dest, streamSettings}] = client
ClientMutex.Unlock()
}
return client, nil
}
func CheckHyClientHealthy(client hyClient.Client) bool {
quicConn := client.GetQuicConn()
if quicConn == nil {
return false
}
select {
case <-quicConn.Context().Done():
return false
default:
}
return true
}
func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (internet.Connection, error) {
config := streamSettings.ProtocolSettings.(*Config)
client, err := GetHyClient(dest, streamSettings)
if err != nil {
CloseHyClient(dest, streamSettings)
return nil, err
}
quicConn := client.GetQuicConn()
conn := &HyConn{
local: quicConn.LocalAddr(),
remote: quicConn.RemoteAddr(),
}
outbound := session.OutboundFromContext(ctx)
network := net.Network_TCP
if outbound != nil {
network = outbound.Target.Network
}
if network == net.Network_UDP && config.GetUseUdpExtension() { // only hysteria2 can use udpExtension
conn.IsUDPExtension = true
conn.IsServer = false
conn.ClientUDPSession, err = client.UDP()
if err != nil {
CloseHyClient(dest, streamSettings)
return nil, err
}
return conn, nil
}
conn.stream, err = client.OpenStream()
if err != nil {
CloseHyClient(dest, streamSettings)
return nil, err
}
// write TCP frame type
frameSize := int(quicvarint.Len(hyProtocol.FrameTypeTCPRequest))
buf := make([]byte, frameSize)
hyProtocol.VarintPut(buf, hyProtocol.FrameTypeTCPRequest)
_, err = conn.stream.Write(buf)
if err != nil {
CloseHyClient(dest, streamSettings)
return nil, err
}
return conn, nil
}
func init() {
RunningClient = make(map[dialerConf]hyClient.Client)
common.Must(internet.RegisterTransportDialer(protocolName, Dial))
}

View File

@ -0,0 +1,9 @@
package hysteria2
import "github.com/v2fly/v2ray-core/v5/common/errors"
type errPathObjHolder struct{}
func newError(values ...interface{}) *errors.Error {
return errors.New(values...).WithPathObj(errPathObjHolder{})
}

View File

@ -0,0 +1,128 @@
package hysteria2
import (
"context"
hyServer "github.com/v2fly/hysteria/core/v2/server"
"github.com/apernet/quic-go"
"github.com/apernet/quic-go/http3"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
)
// Listener is an internet.Listener that listens for TCP connections.
type Listener struct {
hyServer hyServer.Server
rawConn net.PacketConn
addConn internet.ConnHandler
}
// Addr implements internet.Listener.Addr.
func (l *Listener) Addr() net.Addr {
return l.rawConn.LocalAddr()
}
// Close implements internet.Listener.Close.
func (l *Listener) Close() error {
return l.hyServer.Close()
}
func (l *Listener) StreamHijacker(ft http3.FrameType, conn quic.Connection, stream quic.Stream, err error) (bool, error) {
// err always == nil
tcpConn := &HyConn{
stream: stream,
local: conn.LocalAddr(),
remote: conn.RemoteAddr(),
}
l.addConn(tcpConn)
return true, nil
}
func (l *Listener) UdpHijacker(entry *hyServer.UdpSessionEntry, originalAddr string) {
addr, err := net.ResolveUDPAddr("udp", originalAddr)
if err != nil {
return
}
udpConn := &HyConn{
IsUDPExtension: true,
IsServer: true,
ServerUDPSession: entry,
remote: addr,
local: l.rawConn.LocalAddr(),
}
l.addConn(udpConn)
}
// Listen creates a new Listener based on configurations.
func Listen(ctx context.Context, address net.Address, port net.Port, streamSettings *internet.MemoryStreamConfig, handler internet.ConnHandler) (internet.Listener, error) {
tlsConfig, err := GetServerTLSConfig(streamSettings)
if err != nil {
return nil, err
}
if address.Family().IsDomain() {
return nil, nil
}
config := streamSettings.ProtocolSettings.(*Config)
rawConn, err := internet.ListenSystemPacket(context.Background(),
&net.UDPAddr{
IP: address.IP(),
Port: int(port),
}, streamSettings.SocketSettings)
if err != nil {
return nil, err
}
listener := &Listener{
rawConn: rawConn,
addConn: handler,
}
hyServer, err := hyServer.NewServer(&hyServer.Config{
Conn: rawConn,
TLSConfig: *tlsConfig,
DisableUDP: !config.GetUseUdpExtension(),
Authenticator: &Authenticator{Password: config.GetPassword()},
StreamHijacker: listener.StreamHijacker, // acceptStreams
BandwidthConfig: hyServer.BandwidthConfig{MaxTx: config.Congestion.GetUpMbps() * MBps, MaxRx: config.GetCongestion().GetDownMbps() * MBps},
UdpSessionHijacker: listener.UdpHijacker, // acceptUDPSession
IgnoreClientBandwidth: config.GetIgnoreClientBandwidth(),
})
if err != nil {
return nil, err
}
listener.hyServer = hyServer
go hyServer.Serve()
return listener, nil
}
func GetServerTLSConfig(streamSettings *internet.MemoryStreamConfig) (*hyServer.TLSConfig, error) {
config := tls.ConfigFromStreamSettings(streamSettings)
if config == nil {
return nil, newError(Hy2MustNeedTLS)
}
tlsConfig := config.GetTLSConfig()
return &hyServer.TLSConfig{Certificates: tlsConfig.Certificates, GetCertificate: tlsConfig.GetCertificate}, nil
}
type Authenticator struct {
Password string
}
func (a *Authenticator) Authenticate(addr net.Addr, auth string, tx uint64) (ok bool, id string) {
if auth == a.Password || a.Password == "" {
return true, "user"
}
return false, ""
}
func init() {
common.Must(internet.RegisterTransportListener(protocolName, Listen))
}

View File

@ -0,0 +1,155 @@
package hysteria2_test
import (
"context"
"crypto/rand"
"fmt"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/common/buf"
"github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/protocol/tls/cert"
"github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/testing/servers/udp"
"github.com/v2fly/v2ray-core/v5/transport/internet"
"github.com/v2fly/v2ray-core/v5/transport/internet/hysteria2"
"github.com/v2fly/v2ray-core/v5/transport/internet/tls"
)
func TestTCP(t *testing.T) {
port := udp.PickPort()
listener, err := hysteria2.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
ProtocolName: "hysteria2",
ProtocolSettings: &hysteria2.Config{Password: "123"},
SecurityType: "tls",
SecuritySettings: &tls.Config{
Certificate: []*tls.Certificate{
tls.ParseCertificate(
cert.MustGenerate(nil,
cert.DNSNames("www.v2fly.org"),
),
),
},
},
}, func(conn internet.Connection) {
go func() {
defer conn.Close()
b := buf.New()
defer b.Release()
for {
b.Clear()
if _, err := b.ReadFrom(conn); err != nil {
fmt.Println(err)
return
}
common.Must2(conn.Write(b.Bytes()))
}
}()
})
common.Must(err)
defer listener.Close()
time.Sleep(time.Second)
dctx := context.Background()
conn, err := hysteria2.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
ProtocolName: "hysteria2",
ProtocolSettings: &hysteria2.Config{Password: "123"},
SecurityType: "tls",
SecuritySettings: &tls.Config{
ServerName: "www.v2fly.org",
AllowInsecure: true,
},
})
common.Must(err)
defer conn.Close()
const N = 1000
b1 := make([]byte, N)
common.Must2(rand.Read(b1))
b2 := buf.New()
common.Must2(conn.Write(b1))
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
if r := cmp.Diff(b2.Bytes(), b1); r != "" {
t.Error(r)
}
}
func TestUDP(t *testing.T) {
port := udp.PickPort()
listener, err := hysteria2.Listen(context.Background(), net.LocalHostIP, port, &internet.MemoryStreamConfig{
ProtocolName: "hysteria2",
ProtocolSettings: &hysteria2.Config{Password: "123", UseUdpExtension: true},
SecurityType: "tls",
SecuritySettings: &tls.Config{
Certificate: []*tls.Certificate{
tls.ParseCertificate(
cert.MustGenerate(nil,
cert.DNSNames("www.v2fly.org"),
),
),
},
},
}, func(conn internet.Connection) {
fmt.Println("incoming")
go func() {
defer conn.Close()
b := buf.New()
defer b.Release()
for {
b.Clear()
if _, err := b.ReadFrom(conn); err != nil {
fmt.Println(err)
return
}
common.Must2(conn.Write(b.Bytes()))
}
}()
})
common.Must(err)
defer listener.Close()
time.Sleep(time.Second)
address, err := net.ParseDestination("udp:127.0.0.1:1180")
dctx := session.ContextWithOutbound(context.Background(), &session.Outbound{Target: address})
conn, err := hysteria2.Dial(dctx, net.TCPDestination(net.LocalHostIP, port), &internet.MemoryStreamConfig{
ProtocolName: "hysteria2",
ProtocolSettings: &hysteria2.Config{Password: "123", UseUdpExtension: true},
SecurityType: "tls",
SecuritySettings: &tls.Config{
ServerName: "www.v2fly.org",
AllowInsecure: true,
},
})
common.Must(err)
defer conn.Close()
const N = 1000
b1 := make([]byte, N)
common.Must2(rand.Read(b1))
common.Must2(conn.Write(b1))
b2 := buf.New()
b2.Clear()
common.Must2(b2.ReadFullFrom(conn, N))
if r := cmp.Diff(b2.Bytes(), b1); r != "" {
t.Error(r)
}
}

View File

@ -0,0 +1,18 @@
package hysteria2
import (
"github.com/v2fly/v2ray-core/v5/common"
"github.com/v2fly/v2ray-core/v5/transport/internet"
)
//go:generate go run github.com/v2fly/v2ray-core/v5/common/errors/errorgen
const (
protocolName = "hysteria2"
)
func init() {
common.Must(internet.RegisterProtocolConfigCreator(protocolName, func() interface{} {
return new(Config)
}))
}