mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-09-09 13:04:06 +08:00
start implementing wsc server
This commit is contained in:
parent
8fb71028e2
commit
6b783e6674
1
go.mod
1
go.mod
@ -97,6 +97,7 @@ require (
|
||||
github.com/vishvananda/netns v0.0.4 // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
goftp.io/server/v2 v2.0.1
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
|
30
go.sum
30
go.sum
@ -19,6 +19,7 @@ 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/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU=
|
||||
github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||
@ -48,6 +49,7 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
|
||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@ -57,14 +59,17 @@ github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1
|
||||
github.com/itsabgr/ge v0.0.0-20241202140951-7f5c5d99dde6 h1:Kf3FJPViCGIastIRn0xQiO+cm+duif+tUjFGRdwTFxU=
|
||||
github.com/itsabgr/ge v0.0.0-20241202140951-7f5c5d99dde6/go.mod h1:mFdngiWlpvIU5FFzKpjyS3V48itqlZzdLtAwABysrp8=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jlaffaye/ftp v0.0.0-20190624084859-c1312a7102bf/go.mod h1:lli8NYPQOFy3O++YmYbqVgOcQ1JPCwdOy+5zSjKJ9qY=
|
||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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=
|
||||
@ -88,6 +93,9 @@ github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
|
||||
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
|
||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
||||
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
|
||||
@ -156,11 +164,16 @@ github.com/sagernet/wireguard-go v0.0.1-beta.5 h1:aBEsxJUMEONwOZqKPIkuAcv4zJV5p6
|
||||
github.com/sagernet/wireguard-go v0.0.1-beta.5/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@ -184,7 +197,12 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
|
||||
goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
@ -192,13 +210,21 @@ golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -211,6 +237,8 @@ golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
@ -218,6 +246,7 @@ golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
@ -235,6 +264,7 @@ google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
103
inbound/wsc.go
103
inbound/wsc.go
@ -2,24 +2,47 @@ package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/tls"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/transport/wsc"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
var _ adapter.Inbound = &WSC{}
|
||||
var _ adapter.InjectableInbound = &WSC{}
|
||||
|
||||
var _ adapter.WSCServerTransportHandler = &wscTransportHandler{}
|
||||
|
||||
var _ wsc.Authenticator = &CustomAuthenticator{}
|
||||
|
||||
type WSC struct {
|
||||
myInboundAdapter
|
||||
server adapter.WSCServerTransport
|
||||
tlsConfig tls.ServerConfig
|
||||
}
|
||||
|
||||
type wscTransportHandler WSC
|
||||
|
||||
type CustomAuthenticator struct {
|
||||
id uint64
|
||||
logger logger.ContextLogger
|
||||
}
|
||||
|
||||
func NewWSC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WSCInboundOptions) (*WSC, error) {
|
||||
wsc := &WSC{
|
||||
inbound := &WSC{
|
||||
myInboundAdapter: myInboundAdapter{
|
||||
protocol: C.TypeWSC,
|
||||
network: []string{network.NetworkTCP},
|
||||
@ -30,27 +53,57 @@ func NewWSC(ctx context.Context, router adapter.Router, logger log.ContextLogger
|
||||
listenOptions: options.ListenOptions,
|
||||
},
|
||||
}
|
||||
wsc.connHandler = wsc
|
||||
return wsc, nil
|
||||
server, err := wsc.NewServer(wsc.ServerConfig{
|
||||
Ctx: ctx,
|
||||
Logger: logger,
|
||||
Handler: (*wscTransportHandler)(inbound),
|
||||
Authenticator: &CustomAuthenticator{
|
||||
id: 0,
|
||||
logger: logger,
|
||||
},
|
||||
Router: router,
|
||||
MaxConnectionPerUser: options.MaxConnectionPerUser,
|
||||
UsageReportTrafficInterval: options.UsageTraffic.Traffic,
|
||||
UsageReportTimeInterval: time.Duration(options.UsageTraffic.Time),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.TLS != nil {
|
||||
inbound.tlsConfig, err = tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
inbound.server = server
|
||||
return inbound, nil
|
||||
}
|
||||
|
||||
func (wsc *WSC) Close() error {
|
||||
return wsc.myInboundAdapter.Close()
|
||||
return common.Close(&wsc.myInboundAdapter, wsc.tlsConfig, wsc.server)
|
||||
}
|
||||
|
||||
func (wsc *WSC) Start() error {
|
||||
return wsc.myInboundAdapter.Start()
|
||||
tcpListener, err := wsc.ListenTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
sErr := wsc.server.Serve(tcpListener)
|
||||
if sErr != nil && !exceptions.IsClosedOrCanceled(sErr) && !errors.Is(sErr, http.ErrServerClosed) {
|
||||
wsc.logger.Error("wsc server serve error: ", sErr)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *WSC) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||
wsc.injectTCP(conn, metadata)
|
||||
wsc.routeTCP(ctx, conn, metadata)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wsc *WSC) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata adapter.InboundContext) error {
|
||||
wsc.myInboundAdapter.NewError(ctx, network.ErrUnknownNetwork)
|
||||
conn.Close()
|
||||
return network.ErrUnknownNetwork
|
||||
return wsc.myInboundAdapter.newPacketConnection(ctx, conn, metadata)
|
||||
}
|
||||
|
||||
func (wsc *WSC) Inject(conn net.Conn, metadata adapter.InboundContext) error {
|
||||
@ -61,3 +114,35 @@ func (wsc *WSC) Inject(conn net.Conn, metadata adapter.InboundContext) error {
|
||||
func (wsc *WSC) NewError(ctx context.Context, err error) {
|
||||
wsc.myInboundAdapter.NewError(ctx, err)
|
||||
}
|
||||
|
||||
func (handler *wscTransportHandler) NewConnection(ctx context.Context, conn net.Conn, metadata metadata.Metadata) error {
|
||||
return (*WSC)(handler).NewConnection(ctx, conn, adapter.InboundContext{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
})
|
||||
}
|
||||
|
||||
func (handler *wscTransportHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata metadata.Metadata) error {
|
||||
return (*WSC)(handler).NewPacketConnection(ctx, conn, adapter.InboundContext{
|
||||
Source: metadata.Source,
|
||||
Destination: metadata.Destination,
|
||||
})
|
||||
}
|
||||
|
||||
func (handler *wscTransportHandler) NewError(ctx context.Context, err error) {
|
||||
(*WSC)(handler).NewError(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *CustomAuthenticator) Authenticate(ctx context.Context, params wsc.AuthenticateParams) (wsc.AuthenticateResult, error) {
|
||||
auth.id++
|
||||
return wsc.AuthenticateResult{
|
||||
ID: int64(auth.id),
|
||||
Rate: math.MaxInt64,
|
||||
MaxConn: 60,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (auth *CustomAuthenticator) ReportUsage(ctx context.Context, params wsc.ReportUsageParams) (wsc.ReportUsageResult, error) {
|
||||
auth.logger.Debug("Reporting usage : ", params.ID, " | ", params.UsedTraffic)
|
||||
return wsc.ReportUsageResult{}, nil
|
||||
}
|
||||
|
25
transport/wsc/authenticator.go
Normal file
25
transport/wsc/authenticator.go
Normal file
@ -0,0 +1,25 @@
|
||||
package wsc
|
||||
|
||||
import "context"
|
||||
|
||||
type AuthenticateParams struct {
|
||||
Auth string
|
||||
}
|
||||
|
||||
type AuthenticateResult struct {
|
||||
ID int64
|
||||
Rate int64
|
||||
MaxConn int
|
||||
}
|
||||
|
||||
type ReportUsageParams struct {
|
||||
ID int64
|
||||
UsedTraffic int64
|
||||
}
|
||||
|
||||
type ReportUsageResult struct{}
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate(ctx context.Context, params AuthenticateParams) (AuthenticateResult, error)
|
||||
ReportUsage(ctx context.Context, params ReportUsageParams) (ReportUsageResult, error)
|
||||
}
|
187
transport/wsc/server.go
Normal file
187
transport/wsc/server.go
Normal file
@ -0,0 +1,187 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/ws"
|
||||
)
|
||||
|
||||
var _ adapter.WSCServerTransport = &Server{}
|
||||
var _ http.Handler = &Server{}
|
||||
|
||||
type Server struct {
|
||||
ctx context.Context
|
||||
handler adapter.WSCServerTransportHandler
|
||||
httpServer *http.Server
|
||||
logger logger.ContextLogger
|
||||
authenticator Authenticator
|
||||
userManager *wscUserManager
|
||||
router adapter.Router
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Ctx context.Context
|
||||
Logger logger.ContextLogger
|
||||
Handler adapter.WSCServerTransportHandler
|
||||
Authenticator Authenticator
|
||||
Router adapter.Router
|
||||
MaxConnectionPerUser int
|
||||
UsageReportTrafficInterval int64
|
||||
UsageReportTimeInterval time.Duration
|
||||
}
|
||||
|
||||
/*TODO: Support TLS servers*/
|
||||
/*TODO: Pipe conn and packetConn*/
|
||||
func NewServer(config ServerConfig) (*Server, error) {
|
||||
if config.Authenticator == nil {
|
||||
return nil, errors.New("authenticator required")
|
||||
}
|
||||
server := &Server{
|
||||
ctx: config.Ctx,
|
||||
handler: config.Handler,
|
||||
logger: config.Logger,
|
||||
authenticator: config.Authenticator,
|
||||
router: config.Router,
|
||||
userManager: &wscUserManager{
|
||||
users: map[int64]*wscUser{},
|
||||
authenticator: config.Authenticator,
|
||||
maxConnPerUser: config.MaxConnectionPerUser,
|
||||
usageReportTrafficInterval: config.UsageReportTrafficInterval,
|
||||
usageReportTimeInterval: config.UsageReportTimeInterval,
|
||||
},
|
||||
}
|
||||
server.httpServer = &http.Server{
|
||||
Handler: server,
|
||||
ReadHeaderTimeout: constant.TCPTimeout,
|
||||
MaxHeaderBytes: http.DefaultMaxHeaderBytes,
|
||||
BaseContext: func(l net.Listener) context.Context {
|
||||
return config.Ctx
|
||||
},
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (server *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
|
||||
auth := req.URL.Query().Get("auth")
|
||||
if auth == "" {
|
||||
server.failRequest(res, req, "Authentication required", http.StatusBadRequest, 0, "", metadata.Socksaddr{})
|
||||
return
|
||||
}
|
||||
|
||||
account, err := server.authenticator.Authenticate(ctx, AuthenticateParams{
|
||||
Auth: auth,
|
||||
})
|
||||
if err != nil {
|
||||
if account.ID != 0 {
|
||||
if err := server.userManager.cleanupUser(ctx, account.ID, false); err != nil {
|
||||
server.logger.Debug("Request failed. Couldn't cleanup user: ", err.Error(), " (Client: ", req.RemoteAddr, ", User-ID: ", account.ID, ")")
|
||||
}
|
||||
}
|
||||
server.failRequest(res, req, "Authentication failed: "+err.Error(), http.StatusBadRequest, account.ID, "", metadata.Socksaddr{})
|
||||
return
|
||||
}
|
||||
|
||||
if req.Method == http.MethodPost && req.URL.Path == "/cleanup" {
|
||||
if err := server.userManager.cleanupUser(ctx, account.ID, true); err != nil {
|
||||
server.failRequest(res, req, "Failed to cleanup user: "+err.Error(), http.StatusInternalServerError, account.ID, "", metadata.Socksaddr{})
|
||||
return
|
||||
}
|
||||
res.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
user := server.userManager.findOrCreateUser(ctx, account.ID, account.Rate)
|
||||
|
||||
netW := req.URL.Query().Get("net")
|
||||
if netW == "" {
|
||||
netW = network.NetworkTCP
|
||||
}
|
||||
|
||||
endpoint := req.URL.Query().Get("ep")
|
||||
addr, err := server.resolveDestination(ctx, metadata.ParseSocksaddr(endpoint))
|
||||
if err != nil {
|
||||
server.failRequest(res, req, "Failed to parse and resolve endpoint: "+err.Error(), http.StatusBadRequest, account.ID, netW, addr)
|
||||
return
|
||||
}
|
||||
|
||||
server.logger.Debug("New request (Client: ", req.RemoteAddr, ", Auth: ", auth, ", User-ID: ", account.ID, ", ", netW+"-Addr: ", addr.String(), ")")
|
||||
|
||||
conn, _, _, err := ws.UpgradeHTTP(req, res)
|
||||
if err != nil {
|
||||
server.failRequest(res, req, "Websocket upgrade failed: "+err.Error(), http.StatusBadRequest, account.ID, netW, addr)
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := server.userManager.cleanupUserConn(ctx, user, conn); err != nil {
|
||||
server.logger.Error("Failed to cleanup user connection: "+err.Error(), "(Client: ", req.RemoteAddr, ", User-ID: ", account.ID, ")")
|
||||
}
|
||||
if err := conn.Close(); err != nil {
|
||||
server.logger.Debug("Failed to close connection: "+err.Error(), "(Client: ", req.RemoteAddr, ", User-ID: ", account.ID, ")")
|
||||
}
|
||||
}()
|
||||
|
||||
server.logger.Info("serve http called: ", req.URL.String(), " | ", req.RemoteAddr, " | ", endpoint, " | ", addr)
|
||||
res.Write([]byte("endpoint is : " + endpoint))
|
||||
res.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (server *Server) Close() error {
|
||||
return common.Close(common.PtrOrNil(server.httpServer))
|
||||
}
|
||||
|
||||
func (server *Server) Network() []string {
|
||||
return []string{network.NetworkTCP}
|
||||
}
|
||||
|
||||
func (server *Server) Serve(listener net.Listener) error {
|
||||
return server.httpServer.Serve(listener)
|
||||
}
|
||||
|
||||
func (server *Server) ServePacket(listener net.PacketConn) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func (server *Server) resolveDestination(ctx context.Context, dest metadata.Socksaddr) (metadata.Socksaddr, error) {
|
||||
if dest.IsFqdn() {
|
||||
addrs, err := server.router.LookupDefault(ctx, dest.Fqdn)
|
||||
if err != nil {
|
||||
return metadata.Socksaddr{}, err
|
||||
}
|
||||
if len(addrs) == 0 {
|
||||
return metadata.Socksaddr{}, exceptions.New("no addresses found for endpoint domina: ", dest.Fqdn)
|
||||
}
|
||||
return metadata.Socksaddr{
|
||||
Addr: addrs[0],
|
||||
Port: dest.Port,
|
||||
}, nil
|
||||
}
|
||||
return dest, nil
|
||||
}
|
||||
|
||||
func (server *Server) failRequest(res http.ResponseWriter, request *http.Request, msg string, code int, uid int64, network string, addr metadata.Socksaddr) {
|
||||
http.Error(res, msg, code)
|
||||
|
||||
info := "(Client: " + request.RemoteAddr
|
||||
info += ", User-ID: " + strconv.Itoa(int(uid))
|
||||
info += ", Network: " + network
|
||||
info += ", " + network + "-Address: " + addr.String()
|
||||
info += ")"
|
||||
|
||||
server.logger.Debug(msg, " ", info)
|
||||
}
|
170
transport/wsc/user.go
Normal file
170
transport/wsc/user.go
Normal file
@ -0,0 +1,170 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"goftp.io/server/v2/ratelimit"
|
||||
)
|
||||
|
||||
const connReadSize = 2048
|
||||
|
||||
type connData struct {
|
||||
time int64
|
||||
id int
|
||||
reader io.Reader
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type wscUser struct {
|
||||
mu sync.Mutex
|
||||
id int64
|
||||
usedTrafficBytes atomic.Int64
|
||||
reportedTrafficBytes atomic.Int64
|
||||
lastTrafficUpdateTick atomic.Int64
|
||||
conns map[net.Conn]connData
|
||||
heap []byte
|
||||
rateLimit int64
|
||||
maxConnCount int
|
||||
usedIds []bool
|
||||
}
|
||||
|
||||
func (manager *wscUserManager) newUser(id int64, usedTrafficBytes int64, maxConnCount int, rateLimit int64) *wscUser {
|
||||
user := &wscUser{
|
||||
id: id,
|
||||
conns: make(map[net.Conn]connData, maxConnCount),
|
||||
heap: make([]byte, connReadSize*2*maxConnCount),
|
||||
rateLimit: rateLimit,
|
||||
usedIds: make([]bool, maxConnCount),
|
||||
maxConnCount: maxConnCount,
|
||||
}
|
||||
user.usedTrafficBytes.Store(usedTrafficBytes)
|
||||
user.reportedTrafficBytes.Store(0)
|
||||
user.lastTrafficUpdateTick.Store(0)
|
||||
return user
|
||||
}
|
||||
|
||||
func (user *wscUser) outBuffer(conn net.Conn) []byte {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
|
||||
if user.maxConnCount < 1 {
|
||||
return make([]byte, connReadSize)
|
||||
}
|
||||
|
||||
if d, found := user.conns[conn]; found {
|
||||
bufStart := (connReadSize*2)*(d.id+1) - connReadSize
|
||||
bufEnd := bufStart + connReadSize
|
||||
return user.heap[bufStart:bufEnd]
|
||||
}
|
||||
|
||||
return make([]byte, connReadSize)
|
||||
}
|
||||
|
||||
func (user *wscUser) inBuffer(conn net.Conn) []byte {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
|
||||
if user.maxConnCount < 1 {
|
||||
return make([]byte, connReadSize)
|
||||
}
|
||||
|
||||
if d, found := user.conns[conn]; found {
|
||||
bufStart := (connReadSize * 2) * d.id
|
||||
bufEnd := bufStart + connReadSize
|
||||
return user.heap[bufStart:bufEnd]
|
||||
}
|
||||
|
||||
return make([]byte, connReadSize)
|
||||
}
|
||||
|
||||
func (user *wscUser) connReader(conn net.Conn) (io.Reader, error) {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
if d, found := user.conns[conn]; found {
|
||||
return d.reader, nil
|
||||
}
|
||||
return nil, errors.New("connection doesn't exist")
|
||||
}
|
||||
|
||||
func (user *wscUser) connWriter(conn net.Conn) (io.Writer, error) {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
if d, found := user.conns[conn]; found {
|
||||
return d.writer, nil
|
||||
}
|
||||
return nil, errors.New("connection doesn't exist")
|
||||
}
|
||||
|
||||
func (user *wscUser) connCount() int {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
return len(user.conns)
|
||||
}
|
||||
|
||||
func (user *wscUser) addConn(conn net.Conn) (net.Conn, error) {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
|
||||
if _, exists := user.conns[conn]; exists {
|
||||
return nil, errors.New("connection already exists")
|
||||
}
|
||||
|
||||
var selectedConn net.Conn = nil
|
||||
selectedConnId := 0
|
||||
if user.maxConnCount > 0 {
|
||||
if len(user.conns) >= user.maxConnCount {
|
||||
minTime := int64(math.MaxInt64)
|
||||
for c, d := range user.conns {
|
||||
if d.time < minTime {
|
||||
minTime = d.time
|
||||
selectedConn = c
|
||||
selectedConnId = d.id
|
||||
}
|
||||
}
|
||||
if selectedConn != nil {
|
||||
delete(user.conns, selectedConn)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < user.maxConnCount; i++ {
|
||||
if !user.usedIds[i] {
|
||||
selectedConnId = i
|
||||
user.usedIds[i] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
user.conns[conn] = connData{
|
||||
time: nowns(),
|
||||
id: selectedConnId,
|
||||
reader: ratelimit.Reader(conn, ratelimit.New(user.rateLimit)),
|
||||
writer: ratelimit.Writer(conn, ratelimit.New(user.rateLimit)),
|
||||
}
|
||||
|
||||
return selectedConn, nil
|
||||
}
|
||||
|
||||
func (user *wscUser) removeConn(conn net.Conn) error {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
if d, exists := user.conns[conn]; exists {
|
||||
user.usedIds[d.id] = false
|
||||
delete(user.conns, conn)
|
||||
return nil
|
||||
}
|
||||
return errors.New("connection doesn't exist")
|
||||
}
|
||||
|
||||
func (user *wscUser) cleanup() {
|
||||
user.mu.Lock()
|
||||
defer user.mu.Unlock()
|
||||
for conn := range user.conns {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
86
transport/wsc/user_manager.go
Normal file
86
transport/wsc/user_manager.go
Normal file
@ -0,0 +1,86 @@
|
||||
package wsc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type wscUserManager struct {
|
||||
mu sync.Mutex
|
||||
users map[int64]*wscUser
|
||||
maxConnPerUser int
|
||||
usageReportTrafficInterval int64
|
||||
usageReportTimeInterval time.Duration
|
||||
authenticator Authenticator
|
||||
}
|
||||
|
||||
func (manager *wscUserManager) findOrCreateUser(ctx context.Context, uid int64, rateLimit int64) *wscUser {
|
||||
manager.mu.Lock()
|
||||
defer manager.mu.Unlock()
|
||||
if user, exists := manager.users[uid]; exists {
|
||||
manager.reportUser(ctx, user, false)
|
||||
return user
|
||||
}
|
||||
user := manager.newUser(uid, 0, manager.maxConnPerUser, rateLimit)
|
||||
manager.users[uid] = user
|
||||
return user
|
||||
}
|
||||
|
||||
func (manager *wscUserManager) reportUser(ctx context.Context, user *wscUser, force bool) bool {
|
||||
usedTraffic := user.usedTrafficBytes.Load()
|
||||
reportedTraffic := user.reportedTrafficBytes.Load()
|
||||
trafficResult := usedTraffic - reportedTraffic
|
||||
now := nowns()
|
||||
|
||||
if !force {
|
||||
if trafficResult == 0 {
|
||||
return false
|
||||
}
|
||||
if trafficResult < manager.usageReportTrafficInterval && time.Duration(now-user.lastTrafficUpdateTick.Load()) < manager.usageReportTimeInterval {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
_, err := manager.authenticator.ReportUsage(ctx, ReportUsageParams{
|
||||
ID: user.id,
|
||||
UsedTraffic: trafficResult,
|
||||
})
|
||||
if err == nil {
|
||||
user.reportedTrafficBytes.Store(usedTraffic)
|
||||
user.lastTrafficUpdateTick.Store(nowns())
|
||||
}
|
||||
}()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (manager *wscUserManager) cleanupUserConn(ctx context.Context, user *wscUser, conn net.Conn) error {
|
||||
manager.mu.Lock()
|
||||
defer manager.mu.Unlock()
|
||||
sent := manager.reportUser(ctx, user, false)
|
||||
err := user.removeConn(conn)
|
||||
if user.connCount() == 0 {
|
||||
if !sent {
|
||||
manager.reportUser(ctx, user, true)
|
||||
}
|
||||
delete(manager.users, user.id)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (manager *wscUserManager) cleanupUser(ctx context.Context, uid int64, forceReport bool) error {
|
||||
manager.mu.Lock()
|
||||
defer manager.mu.Unlock()
|
||||
if user, exists := manager.users[uid]; !exists {
|
||||
return errors.New("user doesn't exist")
|
||||
} else {
|
||||
manager.reportUser(ctx, user, forceReport)
|
||||
user.cleanup()
|
||||
delete(manager.users, uid)
|
||||
return nil
|
||||
}
|
||||
}
|
7
transport/wsc/utils.go
Normal file
7
transport/wsc/utils.go
Normal file
@ -0,0 +1,7 @@
|
||||
package wsc
|
||||
|
||||
import "time"
|
||||
|
||||
func nowns() int64 {
|
||||
return time.Now().UnixNano()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user