Browse Source

Resync Hysteria2 Import (#3280)

* Revert "Remove hysteria2"

This reverts commit 1d21d7a07761af3b40ecfbf418b0849eb1c3918b.

* Update Hysteria2

* Rename hysteria2 module

* fix broken hy2 import version

---------

Co-authored-by: JimmyHuang454 <jimmyhuang454@gmail.com>
Xiaokang Wang (Shelikhoo) 10 months ago
parent
commit
54fbeeba6d

+ 4 - 3
go.mod

@@ -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
 )
 )

+ 17 - 7
go.sum

@@ -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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+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=

+ 78 - 0
infra/conf/v4/hysteria2.go

@@ -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
+}

+ 34 - 0
infra/conf/v4/transport_internet.go

@@ -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 {

+ 2 - 0
infra/conf/v4/v2ray.go

@@ -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")

+ 3 - 0
main/distro/all/all.go

@@ -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 - 0
proxy/hysteria2/client.go

@@ -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 - 0
proxy/hysteria2/config.go

@@ -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
+}

+ 282 - 0
proxy/hysteria2/config.pb.go

@@ -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
+}

+ 28 - 0
proxy/hysteria2/config.proto

@@ -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;
+}

+ 9 - 0
proxy/hysteria2/errors.generated.go

@@ -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{})
+}

+ 1 - 0
proxy/hysteria2/hysteria2.go

@@ -0,0 +1 @@
+package hysteria2

+ 210 - 0
proxy/hysteria2/protocol.go

@@ -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 - 0
proxy/hysteria2/server.go

@@ -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)
+			}
+		}
+	}
+}

+ 468 - 0
testing/scenarios/hy2_test.go

@@ -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)
+	}
+}

+ 271 - 0
transport/internet/hysteria2/config.pb.go

@@ -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
+}

+ 26 - 0
transport/internet/hysteria2/config.proto

@@ -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;
+}

+ 130 - 0
transport/internet/hysteria2/conn.go

@@ -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)
+}

+ 207 - 0
transport/internet/hysteria2/dialer.go

@@ -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))
+}

+ 9 - 0
transport/internet/hysteria2/errors.generated.go

@@ -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{})
+}

+ 128 - 0
transport/internet/hysteria2/hub.go

@@ -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))
+}

+ 155 - 0
transport/internet/hysteria2/hy2_transport_test.go

@@ -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)
+	}
+}

+ 18 - 0
transport/internet/hysteria2/hysteria2.go

@@ -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)
+	}))
+}