Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78bf5ddc2b |
4
go.mod
4
go.mod
@@ -8,7 +8,7 @@ require (
|
|||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/klauspost/compress v1.17.11
|
github.com/klauspost/compress v1.17.11
|
||||||
github.com/klauspost/pgzip v1.2.6
|
github.com/klauspost/pgzip v1.2.6
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3
|
github.com/pelletier/go-toml/v2 v2.2.2
|
||||||
github.com/qdm12/dns/v2 v2.0.0-rc8
|
github.com/qdm12/dns/v2 v2.0.0-rc8
|
||||||
github.com/qdm12/gosettings v0.4.3
|
github.com/qdm12/gosettings v0.4.3
|
||||||
github.com/qdm12/goshutdown v0.3.0
|
github.com/qdm12/goshutdown v0.3.0
|
||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c
|
||||||
golang.org/x/net v0.30.0
|
golang.org/x/net v0.30.0
|
||||||
golang.org/x/sys v0.27.0
|
golang.org/x/sys v0.26.0
|
||||||
golang.org/x/text v0.19.0
|
golang.org/x/text v0.19.0
|
||||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
|
|||||||
17
go.sum
17
go.sum
@@ -4,6 +4,7 @@ github.com/breml/rootcerts v0.2.18 h1:KjZaNT7AX/akUjzpStuwTMQs42YHlPyc6NmdwShVba
|
|||||||
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
github.com/breml/rootcerts v0.2.18/go.mod h1:S/PKh+4d1HUn4HQovEB8hPJZO6pUZYrIhmXBhsegfXw=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
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=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
@@ -41,8 +42,8 @@ github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE9
|
|||||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
@@ -73,6 +74,13 @@ 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/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||||
@@ -112,8 +120,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.26.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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -142,6 +150,7 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
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=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
|
|||||||
@@ -22,14 +22,15 @@ func (l *Loop) SetSettings(ctx context.Context, settings settings.DNS) (
|
|||||||
return l.state.SetSettings(ctx, settings)
|
return l.state.SetSettings(ctx, settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDoTSettings(settings settings.DNS,
|
func buildServerSettings(settings settings.DNS,
|
||||||
filter *mapfilter.Filter, logger Logger) (
|
filter *mapfilter.Filter, logger Logger) (
|
||||||
serverSettings server.Settings, err error,
|
serverSettings server.Settings, err error,
|
||||||
) {
|
) {
|
||||||
serverSettings.Logger = logger
|
serverSettings.Logger = logger
|
||||||
|
providersData := provider.NewProviders()
|
||||||
|
|
||||||
var dotSettings dot.Settings
|
var dotSettings dot.Settings
|
||||||
providersData := provider.NewProviders()
|
dotSettings.Warner = logger
|
||||||
dotSettings.UpstreamResolvers = make([]provider.Provider, len(settings.DoT.Providers))
|
dotSettings.UpstreamResolvers = make([]provider.Provider, len(settings.DoT.Providers))
|
||||||
for i := range settings.DoT.Providers {
|
for i := range settings.DoT.Providers {
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ func (l *Loop) setupServer(ctx context.Context) (runError <-chan error, err erro
|
|||||||
|
|
||||||
settings := l.GetSettings()
|
settings := l.GetSettings()
|
||||||
|
|
||||||
dotSettings, err := buildDoTSettings(settings, l.filter, l.logger)
|
dotSettings, err := buildServerSettings(settings, l.filter, l.logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("building DoT settings: %w", err)
|
return nil, fmt.Errorf("building DoT settings: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "Canada", City: "Montreal", Hostname: "canada-montreal-ca-version-2.expressnetw.com"},
|
{Country: "Canada", City: "Montreal", Hostname: "canada-montreal-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-2-ca-version-2.expressnetw.com"},
|
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-2-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-ca-version-2.expressnetw.com"},
|
{Country: "Canada", City: "Toronto", Hostname: "canada-toronto-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Canada", City: "Vancouver", Hostname: "canada-vancouver-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Chile", Hostname: "chile-ca-version-2.expressnetw.com"},
|
{Country: "Chile", Hostname: "chile-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Colombia", Hostname: "colombia-ca-version-2.expressnetw.com"},
|
{Country: "Colombia", Hostname: "colombia-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Costa Rica", City: "Costa Rica", Hostname: "costarica-ca-version-2.expressnetw.com"},
|
{Country: "Costa Rica", City: "Costa Rica", Hostname: "costarica-ca-version-2.expressnetw.com"},
|
||||||
@@ -49,17 +48,13 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "France", City: "Strasbourg", Hostname: "france-strasbourg-ca-version-2.expressnetw.com"},
|
{Country: "France", City: "Strasbourg", Hostname: "france-strasbourg-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Georgia", Hostname: "georgia-ca-version-2.expressnetw.com"},
|
{Country: "Georgia", Hostname: "georgia-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Germany", City: "Frankfurt", Hostname: "germany-frankfurt-1-ca-version-2.expressnetw.com"},
|
{Country: "Germany", City: "Frankfurt", Hostname: "germany-frankfurt-1-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Germany", City: "Frankfurt", Hostname: "germany-frankfurt-2-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Germany", City: "Frankfurt", Hostname: "germany-darmstadt-ca-version-2.expressnetw.com"},
|
{Country: "Germany", City: "Frankfurt", Hostname: "germany-darmstadt-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Germany", City: "Nuremberg", Hostname: "germany-nuremberg-ca-version-2.expressnetw.com"},
|
{Country: "Germany", City: "Nuremberg", Hostname: "germany-nuremberg-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Greece", Hostname: "greece-ca-version-2.expressnetw.com"},
|
{Country: "Greece", Hostname: "greece-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Guatemala", Hostname: "guatemala-ca-version-2.expressnetw.com"},
|
{Country: "Guatemala", Hostname: "guatemala-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hongkong-2-ca-version-2.expressnetw.com"},
|
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hongkong-2-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Hong Kong", City: "Hong Kong", Hostname: "hongkong4-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Hungary", Hostname: "hungary-ca-version-2.expressnetw.com"},
|
{Country: "Hungary", Hostname: "hungary-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Iceland", Hostname: "iceland-ca-version-2.expressnetw.com"},
|
{Country: "Iceland", Hostname: "iceland-ca-version-2.expressnetw.com"},
|
||||||
{Country: "India", City: "Chennai", Hostname: "india-chennai-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "India", City: "Mumbai", Hostname: "india-mumbai-1-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Indonesia", Hostname: "indonesia-ca-version-2.expressnetw.com"},
|
{Country: "Indonesia", Hostname: "indonesia-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Ireland", Hostname: "ireland-ca-version-2.expressnetw.com"},
|
{Country: "Ireland", Hostname: "ireland-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Isle Of Man", City: "Isle Of Man", Hostname: "isleofman-ca-version-2.expressnetw.com"},
|
{Country: "Isle Of Man", City: "Isle Of Man", Hostname: "isleofman-ca-version-2.expressnetw.com"},
|
||||||
@@ -72,7 +67,6 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "Jersey", Hostname: "jersey-ca-version-2.expressnetw.com"},
|
{Country: "Jersey", Hostname: "jersey-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Kazakhstan", Hostname: "kazakhstan-ca-version-2.expressnetw.com"},
|
{Country: "Kazakhstan", Hostname: "kazakhstan-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Kenya", Hostname: "kenya-ca-version-2.expressnetw.com"},
|
{Country: "Kenya", Hostname: "kenya-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Kyrgyzstan", Hostname: "kyrgyzstan-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Laos", Hostname: "laos-ca-version-2.expressnetw.com"},
|
{Country: "Laos", Hostname: "laos-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Latvia", Hostname: "latvia-ca-version-2.expressnetw.com"},
|
{Country: "Latvia", Hostname: "latvia-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Liechtenstein", Hostname: "liechtenstein-ca-version-2.expressnetw.com"},
|
{Country: "Liechtenstein", Hostname: "liechtenstein-ca-version-2.expressnetw.com"},
|
||||||
@@ -88,7 +82,6 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "Montenegro", Hostname: "montenegro-ca-version-2.expressnetw.com"},
|
{Country: "Montenegro", Hostname: "montenegro-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Myanmar", Hostname: "myanmar-ca-version-2.expressnetw.com"},
|
{Country: "Myanmar", Hostname: "myanmar-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Nepal", Hostname: "nepal-ca-version-2.expressnetw.com"},
|
{Country: "Nepal", Hostname: "nepal-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Netherlands", City: "Amsterdam", Hostname: "netherlands-amsterdam-2-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "Netherlands", City: "Amsterdam", Hostname: "netherlands-amsterdam-ca-version-2.expressnetw.com"},
|
{Country: "Netherlands", City: "Amsterdam", Hostname: "netherlands-amsterdam-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Netherlands", City: "Rotterdam", Hostname: "netherlands-rotterdam-ca-version-2.expressnetw.com"},
|
{Country: "Netherlands", City: "Rotterdam", Hostname: "netherlands-rotterdam-ca-version-2.expressnetw.com"},
|
||||||
{Country: "Netherlands", City: "The Hague", Hostname: "netherlands-thehague-ca-version-2.expressnetw.com"},
|
{Country: "Netherlands", City: "The Hague", Hostname: "netherlands-thehague-ca-version-2.expressnetw.com"},
|
||||||
@@ -129,7 +122,6 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-2-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-2-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Dallas", Hostname: "usa-dallas-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Denver", Hostname: "usa-denver-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Denver", Hostname: "usa-denver-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-1-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-2-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-2-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-3-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles-3-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles5-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Los Angeles", Hostname: "usa-losangeles5-ca-version-2.expressnetw.com"},
|
||||||
@@ -138,7 +130,6 @@ func hardcodedServers() (servers []models.Server) {
|
|||||||
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-1-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-1-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey2-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey2-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-3-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "New Jersey", Hostname: "usa-newjersey-3-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "New York", Hostname: "us-new-york-2-ca-version-2.expressnetw.com"},
|
|
||||||
{Country: "USA", City: "New York", Hostname: "usa-newyork-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "New York", Hostname: "usa-newyork-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "Salt Lake City", Hostname: "usa-saltlakecity-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "Salt Lake City", Hostname: "usa-saltlakecity-ca-version-2.expressnetw.com"},
|
||||||
{Country: "USA", City: "San Francisco", Hostname: "usa-sanfrancisco-ca-version-2.expressnetw.com"},
|
{Country: "USA", City: "San Francisco", Hostname: "usa-sanfrancisco-ca-version-2.expressnetw.com"},
|
||||||
|
|||||||
@@ -3,11 +3,7 @@ package api
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,8 +16,6 @@ const (
|
|||||||
IP2Location Provider = "ip2location"
|
IP2Location Provider = "ip2location"
|
||||||
)
|
)
|
||||||
|
|
||||||
const echoipPrefix = "echoip#"
|
|
||||||
|
|
||||||
type NameToken struct {
|
type NameToken struct {
|
||||||
Name string
|
Name string
|
||||||
Token string
|
Token string
|
||||||
@@ -36,19 +30,15 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing API name: %w", err)
|
return nil, fmt.Errorf("parsing API name: %w", err)
|
||||||
}
|
}
|
||||||
switch {
|
switch provider {
|
||||||
case provider == Cloudflare:
|
case Cloudflare:
|
||||||
fetchers[i] = newCloudflare(client)
|
fetchers[i] = newCloudflare(client)
|
||||||
case provider == IfConfigCo:
|
case IfConfigCo:
|
||||||
const ifConfigCoURL = "https://ifconfig.co"
|
fetchers[i] = newIfConfigCo(client)
|
||||||
fetchers[i] = newEchoip(client, ifConfigCoURL)
|
case IPInfo:
|
||||||
case provider == IPInfo:
|
|
||||||
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
|
fetchers[i] = newIPInfo(client, nameTokenPair.Token)
|
||||||
case provider == IP2Location:
|
case IP2Location:
|
||||||
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
|
fetchers[i] = newIP2Location(client, nameTokenPair.Token)
|
||||||
case strings.HasPrefix(string(provider), echoipPrefix):
|
|
||||||
url := strings.TrimPrefix(string(provider), echoipPrefix)
|
|
||||||
fetchers[i] = newEchoip(client, url)
|
|
||||||
default:
|
default:
|
||||||
panic("provider not valid: " + provider)
|
panic("provider not valid: " + provider)
|
||||||
}
|
}
|
||||||
@@ -56,88 +46,20 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
|
|||||||
return fetchers, nil
|
return fetchers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexEchoipURL = regexp.MustCompile(`^http(s|):\/\/.+$`)
|
|
||||||
|
|
||||||
var ErrProviderNotValid = errors.New("API name is not valid")
|
var ErrProviderNotValid = errors.New("API name is not valid")
|
||||||
|
|
||||||
func ParseProvider(s string) (provider Provider, err error) {
|
func ParseProvider(s string) (provider Provider, err error) {
|
||||||
possibleProviders := []Provider{
|
switch strings.ToLower(s) {
|
||||||
Cloudflare,
|
case "cloudflare":
|
||||||
IfConfigCo,
|
return Cloudflare, nil
|
||||||
IP2Location,
|
case string(IfConfigCo):
|
||||||
IPInfo,
|
return IfConfigCo, nil
|
||||||
|
case "ipinfo":
|
||||||
|
return IPInfo, nil
|
||||||
|
case "ip2location":
|
||||||
|
return IP2Location, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf(`%w: %q can only be "cloudflare", "ifconfigco", "ip2location" or "ipinfo"`,
|
||||||
|
ErrProviderNotValid, s)
|
||||||
}
|
}
|
||||||
stringToProvider := make(map[string]Provider, len(possibleProviders))
|
|
||||||
for _, provider := range possibleProviders {
|
|
||||||
stringToProvider[string(provider)] = provider
|
|
||||||
}
|
|
||||||
provider, ok := stringToProvider[strings.ToLower(s)]
|
|
||||||
if ok {
|
|
||||||
return provider, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
customPrefixToURLRegex := map[string]*regexp.Regexp{
|
|
||||||
echoipPrefix: regexEchoipURL,
|
|
||||||
}
|
|
||||||
for prefix, urlRegex := range customPrefixToURLRegex {
|
|
||||||
match, err := checkCustomURL(s, prefix, urlRegex)
|
|
||||||
if !match {
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return Provider(s), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
providerStrings := make([]string, 0, len(stringToProvider)+len(customPrefixToURLRegex))
|
|
||||||
for _, providerString := range slices.Sorted(maps.Keys(stringToProvider)) {
|
|
||||||
providerStrings = append(providerStrings, `"`+providerString+`"`)
|
|
||||||
}
|
|
||||||
for _, prefix := range slices.Sorted(maps.Keys(customPrefixToURLRegex)) {
|
|
||||||
providerStrings = append(providerStrings, "a custom "+prefix+" url")
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf(`%w: %q can only be %s`,
|
|
||||||
ErrProviderNotValid, s, orStrings(providerStrings))
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrCustomURLNotValid = errors.New("custom URL is not valid")
|
|
||||||
|
|
||||||
func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool, err error) {
|
|
||||||
if !strings.HasPrefix(s, prefix) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
s = strings.TrimPrefix(s, prefix)
|
|
||||||
_, err = url.Parse(s)
|
|
||||||
if err != nil {
|
|
||||||
return true, fmt.Errorf("%s %w: %w", prefix, ErrCustomURLNotValid, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if regex.MatchString(s) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, fmt.Errorf("%s %w: %q does not match regular expression: %s",
|
|
||||||
prefix, ErrCustomURLNotValid, s, regex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func orStrings(strings []string) (result string) {
|
|
||||||
return joinStrings(strings, "or")
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinStrings(strings []string, lastJoin string) (result string) {
|
|
||||||
if len(strings) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
result = strings[0]
|
|
||||||
for i := 1; i < len(strings); i++ {
|
|
||||||
if i < len(strings)-1 {
|
|
||||||
result += ", " + strings[i]
|
|
||||||
} else {
|
|
||||||
result += " " + lastJoin + " " + strings[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_ParseProvider(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
s string
|
|
||||||
provider Provider
|
|
||||||
errWrapped error
|
|
||||||
errMessage string
|
|
||||||
}{
|
|
||||||
"empty": {
|
|
||||||
errWrapped: ErrProviderNotValid,
|
|
||||||
errMessage: `API name is not valid: "" can only be ` +
|
|
||||||
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
|
|
||||||
},
|
|
||||||
"invalid": {
|
|
||||||
s: "xyz",
|
|
||||||
errWrapped: ErrProviderNotValid,
|
|
||||||
errMessage: `API name is not valid: "xyz" can only be ` +
|
|
||||||
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
|
|
||||||
},
|
|
||||||
"ipinfo": {
|
|
||||||
s: "ipinfo",
|
|
||||||
provider: IPInfo,
|
|
||||||
},
|
|
||||||
"IpInfo": {
|
|
||||||
s: "IpInfo",
|
|
||||||
provider: IPInfo,
|
|
||||||
},
|
|
||||||
"echoip_url_empty": {
|
|
||||||
s: "echoip#",
|
|
||||||
errWrapped: ErrCustomURLNotValid,
|
|
||||||
errMessage: `echoip# custom URL is not valid: "" ` +
|
|
||||||
`does not match regular expression: ^http(s|):\/\/.+$`,
|
|
||||||
},
|
|
||||||
"echoip_url_invalid": {
|
|
||||||
s: "echoip#postgres://localhost:3451",
|
|
||||||
errWrapped: ErrCustomURLNotValid,
|
|
||||||
errMessage: `echoip# custom URL is not valid: "postgres://localhost:3451" ` +
|
|
||||||
`does not match regular expression: ^http(s|):\/\/.+$`,
|
|
||||||
},
|
|
||||||
"echoip_url_valid": {
|
|
||||||
s: "echoip#http://localhost:3451",
|
|
||||||
provider: Provider("echoip#http://localhost:3451"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, testCase := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
provider, err := ParseProvider(testCase.s)
|
|
||||||
|
|
||||||
assert.Equal(t, testCase.provider, provider)
|
|
||||||
assert.ErrorIs(t, err, testCase.errWrapped)
|
|
||||||
if testCase.errWrapped != nil {
|
|
||||||
assert.EqualError(t, err, testCase.errMessage)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,45 +6,39 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/qdm12/gluetun/internal/models"
|
"github.com/qdm12/gluetun/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type echoip struct {
|
type ifConfigCo struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
url string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEchoip(client *http.Client, url string) *echoip {
|
func newIfConfigCo(client *http.Client) *ifConfigCo {
|
||||||
return &echoip{
|
return &ifConfigCo{
|
||||||
client: client,
|
client: client,
|
||||||
url: url,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) String() string {
|
func (i *ifConfigCo) String() string {
|
||||||
s := e.url
|
return string(IfConfigCo)
|
||||||
s = strings.TrimPrefix(s, "http://")
|
|
||||||
s = strings.TrimPrefix(s, "https://")
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) CanFetchAnyIP() bool {
|
func (i *ifConfigCo) CanFetchAnyIP() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *echoip) Token() string {
|
func (i *ifConfigCo) Token() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchInfo obtains information on the ip address provided
|
// FetchInfo obtains information on the ip address provided
|
||||||
// using the echoip API at the url given. If the ip is the zero value,
|
// using the ifconfig.co/json API. If the ip is the zero value,
|
||||||
// the public IP address of the machine is used as the IP.
|
// the public IP address of the machine is used as the IP.
|
||||||
func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
|
func (i *ifConfigCo) FetchInfo(ctx context.Context, ip netip.Addr) (
|
||||||
result models.PublicIP, err error,
|
result models.PublicIP, err error,
|
||||||
) {
|
) {
|
||||||
url := e.url + "/json"
|
url := "https://ifconfig.co/json"
|
||||||
if ip.IsValid() {
|
if ip.IsValid() {
|
||||||
url += "?ip=" + ip.String()
|
url += "?ip=" + ip.String()
|
||||||
}
|
}
|
||||||
@@ -54,7 +48,7 @@ func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := e.client.Do(request)
|
response, err := i.client.Do(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-3
|
|
||||||
type authMethod byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
authNotRequired authMethod = 0
|
|
||||||
authGssapi authMethod = 1
|
|
||||||
authUsernamePassword authMethod = 2
|
|
||||||
authNotAcceptable authMethod = 255
|
|
||||||
)
|
|
||||||
|
|
||||||
func (a authMethod) String() string {
|
|
||||||
switch a {
|
|
||||||
case authNotRequired:
|
|
||||||
return "no authentication required"
|
|
||||||
case authGssapi:
|
|
||||||
return "GSSAPI"
|
|
||||||
case authUsernamePassword:
|
|
||||||
return "username/password"
|
|
||||||
case authNotAcceptable:
|
|
||||||
return "no acceptable methods"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown method (%d)", a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subnegotiation version
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
|
||||||
const (
|
|
||||||
authUsernamePasswordSubNegotiation1 byte = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// SOCKS versions.
|
|
||||||
const (
|
|
||||||
socks5Version byte = 5
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4
|
|
||||||
type cmdType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
connect cmdType = 1
|
|
||||||
bind cmdType = 2
|
|
||||||
udpAssociate cmdType = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c cmdType) String() string {
|
|
||||||
switch c {
|
|
||||||
case connect:
|
|
||||||
return "connect"
|
|
||||||
case bind:
|
|
||||||
return "bind"
|
|
||||||
case udpAssociate:
|
|
||||||
return "UDP associate"
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("unknown command (%d)", c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4 and
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc1928#section-5
|
|
||||||
type addrType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ipv4 addrType = 1
|
|
||||||
domainName addrType = 3
|
|
||||||
ipv6 addrType = 4
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
|
||||||
type replyCode byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
succeeded replyCode = iota
|
|
||||||
generalServerFailure
|
|
||||||
connectionNotAllowedByRuleset
|
|
||||||
networkUnreachable
|
|
||||||
hostUnreachable
|
|
||||||
connectionRefused
|
|
||||||
ttlExpired
|
|
||||||
commandNotSupported
|
|
||||||
addressTypeNotSupported
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Infof(format string, a ...interface{})
|
|
||||||
Warnf(format string, a ...interface{})
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
|
||||||
func (c *socksConn) encodeFailedResponse(writer io.Writer, socksVersion byte, reply replyCode) {
|
|
||||||
_, err := writer.Write([]byte{
|
|
||||||
socksVersion,
|
|
||||||
byte(reply),
|
|
||||||
0, // RSV byte
|
|
||||||
// TODO do we need to set the bind addr type to 0??
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warnf("failed writing failed response: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-6
|
|
||||||
func (c *socksConn) encodeSuccessResponse(writer io.Writer, socksVersion byte,
|
|
||||||
reply replyCode, bindAddrType addrType, bindAddress string,
|
|
||||||
bindPort uint16) (err error) {
|
|
||||||
bindData, err := encodeBindData(bindAddrType, bindAddress, bindPort)
|
|
||||||
if err != nil { // TODO encode with below block if this changes
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialPacketLength = 3
|
|
||||||
capacity := initialPacketLength + len(bindData)
|
|
||||||
packet := make([]byte, initialPacketLength, capacity)
|
|
||||||
packet[0] = socksVersion
|
|
||||||
packet[1] = byte(reply)
|
|
||||||
packet[2] = 0 // RSV byte
|
|
||||||
packet = append(packet, bindData...)
|
|
||||||
|
|
||||||
_, err = writer.Write(packet)
|
|
||||||
if err != nil {
|
|
||||||
c.logger.Warnf("failed writing success response: %s", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrIPVersionUnexpected = errors.New("ip version is unexpected")
|
|
||||||
ErrDomainNameTooLong = errors.New("domain name is too long")
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeBindData(addrType addrType, address string, port uint16) (
|
|
||||||
data []byte, err error) {
|
|
||||||
capacity := bindDataLength(addrType, address)
|
|
||||||
data = make([]byte, 0, capacity)
|
|
||||||
|
|
||||||
data = append(data, byte(addrType))
|
|
||||||
switch addrType {
|
|
||||||
case ipv4, ipv6:
|
|
||||||
ip, err := netip.ParseAddr(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("parsing IP address: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case addrType == ipv4 && !ip.Is4():
|
|
||||||
return nil, fmt.Errorf("%w: expected IPv4 for %s", ErrIPVersionUnexpected, ip)
|
|
||||||
case addrType == ipv6 && !ip.Is6():
|
|
||||||
return nil, fmt.Errorf("%w: expected IPv6 for %s", ErrIPVersionUnexpected, ip)
|
|
||||||
}
|
|
||||||
data = append(data, ip.AsSlice()...)
|
|
||||||
case domainName:
|
|
||||||
const maxDomainNameLength = 255
|
|
||||||
if len(address) > maxDomainNameLength {
|
|
||||||
return nil, fmt.Errorf("%w: %s", ErrDomainNameTooLong, address)
|
|
||||||
}
|
|
||||||
data = append(data, byte(len(address)))
|
|
||||||
data = append(data, []byte(address)...)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unsupported address type %d", addrType))
|
|
||||||
}
|
|
||||||
data = binary.BigEndian.AppendUint16(data, port)
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bindDataLength(addrType addrType, address string) (maxLength int) {
|
|
||||||
maxLength++ // address type
|
|
||||||
switch addrType {
|
|
||||||
case ipv4:
|
|
||||||
maxLength += net.IPv4len
|
|
||||||
case domainName:
|
|
||||||
maxLength++ // domain name length
|
|
||||||
maxLength += len([]byte(address))
|
|
||||||
case ipv6:
|
|
||||||
maxLength += net.IPv6len
|
|
||||||
default:
|
|
||||||
panic("unsupported address type: " + fmt.Sprint(addrType))
|
|
||||||
}
|
|
||||||
maxLength += 2 // port
|
|
||||||
return maxLength
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
address string
|
|
||||||
logger Logger
|
|
||||||
|
|
||||||
// internal fields
|
|
||||||
listener net.Listener
|
|
||||||
listening atomic.Bool
|
|
||||||
socksConnCtx context.Context //nolint:containedctx
|
|
||||||
socksConnCancel context.CancelFunc
|
|
||||||
done <-chan struct{}
|
|
||||||
stopping atomic.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(settings Settings) *Server {
|
|
||||||
return &Server{
|
|
||||||
username: settings.Username,
|
|
||||||
password: settings.Password,
|
|
||||||
address: settings.Address,
|
|
||||||
logger: settings.Logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Start(_ context.Context) (runErr <-chan error, err error) {
|
|
||||||
s.listener, err = net.Listen("tcp", s.address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("listening on %s: %w", s.address, err)
|
|
||||||
}
|
|
||||||
s.listening.Store(true)
|
|
||||||
|
|
||||||
s.socksConnCtx, s.socksConnCancel = context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
ready := make(chan struct{})
|
|
||||||
runErrCh := make(chan error)
|
|
||||||
runErr = runErrCh
|
|
||||||
done := make(chan struct{})
|
|
||||||
s.done = done
|
|
||||||
go s.runServer(ready, runErrCh, done)
|
|
||||||
<-ready
|
|
||||||
return runErr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) runServer(ready chan<- struct{},
|
|
||||||
runErrCh chan<- error, done chan<- struct{}) {
|
|
||||||
close(ready)
|
|
||||||
defer close(done)
|
|
||||||
wg := new(sync.WaitGroup)
|
|
||||||
defer wg.Wait()
|
|
||||||
|
|
||||||
dialer := &net.Dialer{}
|
|
||||||
for {
|
|
||||||
connection, err := s.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
if !s.stopping.Load() {
|
|
||||||
_ = s.Stop()
|
|
||||||
runErrCh <- fmt.Errorf("accepting connection: %w", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
wg.Add(1)
|
|
||||||
go func(ctx context.Context, connection net.Conn,
|
|
||||||
dialer *net.Dialer, wg *sync.WaitGroup) {
|
|
||||||
defer wg.Done()
|
|
||||||
socksConn := &socksConn{
|
|
||||||
dialer: dialer,
|
|
||||||
username: s.username,
|
|
||||||
password: s.password,
|
|
||||||
clientConn: connection,
|
|
||||||
logger: s.logger,
|
|
||||||
}
|
|
||||||
err := socksConn.run(ctx)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Infof("running socks connection: %s", err)
|
|
||||||
}
|
|
||||||
}(s.socksConnCtx, connection, dialer, wg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) Stop() (err error) {
|
|
||||||
s.stopping.Store(true)
|
|
||||||
s.listening.Store(false)
|
|
||||||
err = s.listener.Close()
|
|
||||||
s.socksConnCancel() // stop ongoing socks connections
|
|
||||||
<-s.done // wait for run goroutine to finish
|
|
||||||
s.stopping.Store(false)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) listeningAddress() net.Addr {
|
|
||||||
if s.listening.Load() {
|
|
||||||
return s.listener.Addr()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
type Settings struct {
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Address string
|
|
||||||
Logger Logger
|
|
||||||
}
|
|
||||||
@@ -1,283 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type socksConn struct {
|
|
||||||
// Injected fields
|
|
||||||
dialer *net.Dialer
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
clientConn net.Conn
|
|
||||||
logger Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socksConn) closeClientConn(ctxErr error) {
|
|
||||||
err := c.clientConn.Close()
|
|
||||||
if err != nil && ctxErr == nil {
|
|
||||||
c.logger.Warnf("closing client connection: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *socksConn) run(ctx context.Context) error {
|
|
||||||
authMethod := authNotRequired
|
|
||||||
if c.username != "" || c.password != "" {
|
|
||||||
authMethod = authUsernamePassword
|
|
||||||
}
|
|
||||||
|
|
||||||
err := verifyFirstNegotiation(c.clientConn, authMethod)
|
|
||||||
if err != nil {
|
|
||||||
replyMethod := authMethod
|
|
||||||
if errors.Is(err, ErrNoMethodIdentifiers) || errors.Is(err, ErrNoValidMethodIdentifier) {
|
|
||||||
replyMethod = authNotAcceptable
|
|
||||||
}
|
|
||||||
_, writeErr := c.clientConn.Write([]byte{socks5Version, byte(replyMethod)})
|
|
||||||
if writeErr != nil {
|
|
||||||
c.logger.Warnf("failed writing first negotiation reply: %s", writeErr)
|
|
||||||
}
|
|
||||||
c.closeClientConn(ctx.Err())
|
|
||||||
return fmt.Errorf("verifying first negotiation: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.clientConn.Write([]byte{socks5Version, byte(authMethod)})
|
|
||||||
if err != nil {
|
|
||||||
c.closeClientConn(ctx.Err())
|
|
||||||
return fmt.Errorf("writing first negotiation reply: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch authMethod {
|
|
||||||
case authNotRequired, authNotAcceptable:
|
|
||||||
case authGssapi:
|
|
||||||
panic("not implemented")
|
|
||||||
// TODO
|
|
||||||
case authUsernamePassword:
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
|
||||||
err = usernamePasswordSubnegotiate(c.clientConn, c.username, c.password)
|
|
||||||
if err != nil {
|
|
||||||
// If the server returns a `failure' (STATUS value other than X'00') status,
|
|
||||||
// it MUST close the connection.
|
|
||||||
c.closeClientConn(ctx.Err())
|
|
||||||
return fmt.Errorf("subnegotiating username and password: %w", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("unimplemented auth method %d", authMethod))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.handleRequest(ctx)
|
|
||||||
c.closeClientConn(ctx.Err())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("handling request: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrCommandNotSupported = errors.New("command not supported")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *socksConn) handleRequest(ctx context.Context) error {
|
|
||||||
const socksVersion = socks5Version
|
|
||||||
request, err := decodeRequest(c.clientConn, socksVersion)
|
|
||||||
if err != nil {
|
|
||||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if request.command != connect {
|
|
||||||
c.encodeFailedResponse(c.clientConn, socksVersion, commandNotSupported)
|
|
||||||
return fmt.Errorf("%w: %s", ErrCommandNotSupported, request.command)
|
|
||||||
}
|
|
||||||
|
|
||||||
destinationAddress := net.JoinHostPort(request.destination, fmt.Sprint(request.port))
|
|
||||||
destinationConn, err := c.dialer.DialContext(ctx, "tcp", destinationAddress)
|
|
||||||
if err != nil {
|
|
||||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer destinationConn.Close()
|
|
||||||
|
|
||||||
destinationServerAddress := destinationConn.LocalAddr().String()
|
|
||||||
destinationAddr, destinationPortStr, err := net.SplitHostPort(destinationServerAddress)
|
|
||||||
fmt.Println("===", destinationServerAddress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
destinationPort, err := strconv.Atoi(destinationPortStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var bindAddrType addrType
|
|
||||||
if ip := net.ParseIP(destinationAddr); ip != nil {
|
|
||||||
if ip.To4() != nil {
|
|
||||||
bindAddrType = ipv4
|
|
||||||
} else {
|
|
||||||
bindAddrType = ipv6
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bindAddrType = domainName
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.encodeSuccessResponse(c.clientConn, socksVersion, succeeded, bindAddrType,
|
|
||||||
destinationAddr, uint16(destinationPort))
|
|
||||||
if err != nil {
|
|
||||||
c.encodeFailedResponse(c.clientConn, socksVersion, generalServerFailure)
|
|
||||||
return fmt.Errorf("writing successful %s response: %w", request.command, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
errc := make(chan error)
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(c.clientConn, destinationConn)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("from backend to client: %w", err)
|
|
||||||
}
|
|
||||||
errc <- err
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
_, err := io.Copy(destinationConn, c.clientConn)
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("from client to backend: %w", err)
|
|
||||||
}
|
|
||||||
errc <- err
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case err := <-errc:
|
|
||||||
return err
|
|
||||||
case <-ctx.Done():
|
|
||||||
_ = destinationConn.Close()
|
|
||||||
_ = c.clientConn.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrVersionNotSupported = errors.New("version not supported")
|
|
||||||
ErrNoMethodIdentifiers = errors.New("no method identifiers")
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-3
|
|
||||||
func verifyFirstNegotiation(reader io.Reader, requiredMethod authMethod) error {
|
|
||||||
const headerLength = 2 // version + nMethods bytes
|
|
||||||
header := make([]byte, headerLength)
|
|
||||||
_, err := io.ReadFull(reader, header[:])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if header[0] != socks5Version {
|
|
||||||
return fmt.Errorf("%w: %d", ErrVersionNotSupported, header[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
nMethods := header[1]
|
|
||||||
if nMethods == 0 {
|
|
||||||
return fmt.Errorf("%w", ErrNoMethodIdentifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
methodIdentifiers := make([]byte, nMethods)
|
|
||||||
_, err = io.ReadFull(reader, methodIdentifiers)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("reading method identifiers: %w", err)
|
|
||||||
}
|
|
||||||
for _, methodIdentifier := range methodIdentifiers {
|
|
||||||
if methodIdentifier == byte(requiredMethod) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return makeNoAcceptableMethodError(requiredMethod, methodIdentifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrNoValidMethodIdentifier = errors.New("no valid method identifier")
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeNoAcceptableMethodError(requiredAuthMethod authMethod, methodIdentifiers []byte) error {
|
|
||||||
methodNames := make([]string, len(methodIdentifiers))
|
|
||||||
for i, methodIdentifier := range methodIdentifiers {
|
|
||||||
methodNames[i] = fmt.Sprintf("%q", authMethod(methodIdentifier))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("%w: none of %s matches %s",
|
|
||||||
ErrNoValidMethodIdentifier, strings.Join(methodNames, ", "),
|
|
||||||
requiredAuthMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1928#section-4
|
|
||||||
type request struct {
|
|
||||||
command cmdType
|
|
||||||
destination string
|
|
||||||
port uint16
|
|
||||||
addressType addrType
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrRequestSocksVersionMismatch = errors.New("request SOCKS version mismatch")
|
|
||||||
ErrAddressTypeNotSupported = errors.New("address type not supported")
|
|
||||||
)
|
|
||||||
|
|
||||||
func decodeRequest(reader io.Reader, expectedVersion byte) (req request, err error) {
|
|
||||||
const headerLength = 4
|
|
||||||
header := [headerLength]byte{}
|
|
||||||
_, err = io.ReadFull(reader, header[:])
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
version := header[0]
|
|
||||||
if header[0] != expectedVersion {
|
|
||||||
return request{}, fmt.Errorf("%w: expected %d and got %d",
|
|
||||||
ErrRequestSocksVersionMismatch, expectedVersion, version)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.command = cmdType(header[1])
|
|
||||||
// header[2] is RSV byte
|
|
||||||
req.addressType = addrType(header[3])
|
|
||||||
|
|
||||||
switch req.addressType {
|
|
||||||
case ipv4:
|
|
||||||
var ip [4]byte
|
|
||||||
_, err = io.ReadFull(reader, ip[:])
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading IPv4 address: %w", err)
|
|
||||||
}
|
|
||||||
req.destination = netip.AddrFrom4(ip).String()
|
|
||||||
case ipv6:
|
|
||||||
var ip [16]byte
|
|
||||||
_, err = io.ReadFull(reader, ip[:])
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading IPv6 address: %w", err)
|
|
||||||
}
|
|
||||||
req.destination = netip.AddrFrom16(ip).String()
|
|
||||||
case domainName:
|
|
||||||
var header [1]byte
|
|
||||||
_, err = io.ReadFull(reader, header[:])
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading domain name header: %w", err)
|
|
||||||
}
|
|
||||||
domainName := make([]byte, header[0])
|
|
||||||
_, err = io.ReadFull(reader, domainName)
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading domain name bytes: %w", err)
|
|
||||||
}
|
|
||||||
req.destination = string(domainName)
|
|
||||||
default:
|
|
||||||
return request{}, fmt.Errorf("%w: %d", ErrAddressTypeNotSupported, req.addressType)
|
|
||||||
}
|
|
||||||
|
|
||||||
var portBytes [2]byte
|
|
||||||
_, err = io.ReadFull(reader, portBytes[:])
|
|
||||||
if err != nil {
|
|
||||||
return request{}, fmt.Errorf("reading port: %w", err)
|
|
||||||
}
|
|
||||||
req.port = binary.BigEndian.Uint16(portBytes[:])
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/qdm12/log"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"golang.org/x/net/proxy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
server := New(Settings{
|
|
||||||
Username: "test",
|
|
||||||
Password: "test",
|
|
||||||
Address: ":8000",
|
|
||||||
Logger: log.New(),
|
|
||||||
})
|
|
||||||
|
|
||||||
runErr, startErr := server.Start(context.Background())
|
|
||||||
require.NoError(t, startErr)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-runErr:
|
|
||||||
require.NoError(t, err)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("SlEEPING")
|
|
||||||
time.Sleep(15 * time.Second)
|
|
||||||
t.Log("Done sleeping")
|
|
||||||
|
|
||||||
err := server.Stop()
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func backendServer(listener net.Listener) {
|
|
||||||
conn, err := listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
conn.Write([]byte("Test"))
|
|
||||||
conn.Close()
|
|
||||||
listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRead(t *testing.T) {
|
|
||||||
// backend server which we'll use SOCKS5 to connect to
|
|
||||||
listener, err := net.Listen("tcp", ":0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
backendServerPort := listener.Addr().(*net.TCPAddr).Port
|
|
||||||
go backendServer(listener)
|
|
||||||
|
|
||||||
// SOCKS5 server
|
|
||||||
server := New(Settings{
|
|
||||||
Address: ":0",
|
|
||||||
})
|
|
||||||
_, err = server.Start(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = server.Stop()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
socks5Port := server.listeningAddress().(*net.TCPAddr).Port
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("localhost:%d", socks5Port)
|
|
||||||
socksDialer, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = fmt.Sprintf("localhost:%d", backendServerPort)
|
|
||||||
conn, err := socksDialer.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
_, err = io.ReadFull(conn, buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(buf) != "Test" {
|
|
||||||
t.Fatalf("got: %q want: Test", buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = conn.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadPassword(t *testing.T) {
|
|
||||||
// backend server which we'll use SOCKS5 to connect to
|
|
||||||
ln, err := net.Listen("tcp", ":0")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
backendServerPort := ln.Addr().(*net.TCPAddr).Port
|
|
||||||
go backendServer(ln)
|
|
||||||
|
|
||||||
auth := &proxy.Auth{User: "foo", Password: "bar"}
|
|
||||||
|
|
||||||
server := Server{
|
|
||||||
logger: log.New(),
|
|
||||||
username: auth.User,
|
|
||||||
password: auth.Password,
|
|
||||||
address: ":0",
|
|
||||||
}
|
|
||||||
_, err = server.Start(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
err = server.Stop()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("localhost:%d", server.listeningAddress().(*net.TCPAddr).Port)
|
|
||||||
|
|
||||||
if d, err := proxy.SOCKS5("tcp", addr, nil, proxy.Direct); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if _, err := d.Dial("tcp", addr); err == nil {
|
|
||||||
t.Fatal("expected no-auth dial error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
badPwd := &proxy.Auth{User: "foo", Password: "not right"}
|
|
||||||
if d, err := proxy.SOCKS5("tcp", addr, badPwd, proxy.Direct); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if _, err := d.Dial("tcp", addr); err == nil {
|
|
||||||
t.Fatal("expected bad password dial error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
badUsr := &proxy.Auth{User: "not right", Password: "bar"}
|
|
||||||
if d, err := proxy.SOCKS5("tcp", addr, badUsr, proxy.Direct); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
} else {
|
|
||||||
if _, err := d.Dial("tcp", addr); err == nil {
|
|
||||||
t.Fatal("expected bad username dial error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socksDialer, err := proxy.SOCKS5("tcp", addr, auth, proxy.Direct)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
addr = fmt.Sprintf("localhost:%d", backendServerPort)
|
|
||||||
conn, err := socksDialer.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, 4)
|
|
||||||
if _, err := io.ReadFull(conn, buf); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if string(buf) != "Test" {
|
|
||||||
t.Fatalf("got: %q want: Test", buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conn.Close(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
package socks5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrSubnegotiationVersionNotSupported = errors.New("subnegotiation version not supported")
|
|
||||||
ErrUsernameNotValid = errors.New("username not valid")
|
|
||||||
ErrPasswordNotValid = errors.New("password not valid")
|
|
||||||
)
|
|
||||||
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc1929#section-2
|
|
||||||
func usernamePasswordSubnegotiate(conn io.ReadWriter, username, password string) (err error) {
|
|
||||||
status := byte(1)
|
|
||||||
const defaultVersion = byte(1)
|
|
||||||
|
|
||||||
const headerLength = 2
|
|
||||||
var header [headerLength]byte
|
|
||||||
_, err = io.ReadFull(conn, header[:])
|
|
||||||
if err != nil {
|
|
||||||
_, _ = conn.Write([]byte{defaultVersion, status})
|
|
||||||
return fmt.Errorf("reading header: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if header[0] != authUsernamePasswordSubNegotiation1 {
|
|
||||||
_, _ = conn.Write([]byte{defaultVersion, status})
|
|
||||||
return fmt.Errorf("%w: %d", ErrSubnegotiationVersionNotSupported, header[0])
|
|
||||||
}
|
|
||||||
version := header[0]
|
|
||||||
|
|
||||||
usernameBytes := make([]byte, header[1])
|
|
||||||
_, err = io.ReadFull(conn, usernameBytes)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = conn.Write([]byte{version, status})
|
|
||||||
return fmt.Errorf("reading username bytes: %w", err)
|
|
||||||
} else if username != string(usernameBytes) {
|
|
||||||
_, _ = conn.Write([]byte{version, status})
|
|
||||||
return fmt.Errorf("%w: %s", ErrUsernameNotValid, string(usernameBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
const passwordHeaderLength = 1
|
|
||||||
passwordHeader := make([]byte, passwordHeaderLength)
|
|
||||||
_, err = io.ReadFull(conn, passwordHeader[:])
|
|
||||||
if err != nil {
|
|
||||||
_, _ = conn.Write([]byte{version, status})
|
|
||||||
return fmt.Errorf("reading password length: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
passwordBytes := make([]byte, passwordHeader[0])
|
|
||||||
_, err = io.ReadFull(conn, passwordBytes)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = conn.Write([]byte{version, status})
|
|
||||||
return fmt.Errorf("reading password bytes: %w", err)
|
|
||||||
} else if password != string(passwordBytes) {
|
|
||||||
_, _ = conn.Write([]byte{version, status})
|
|
||||||
return fmt.Errorf("%w: %s", ErrPasswordNotValid, string(passwordBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
status = 0
|
|
||||||
_, err = conn.Write([]byte{version, status})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("writing success status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -5,10 +5,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Parallel struct {
|
type Parallel struct {
|
||||||
repeatResolver *Repeat
|
lastRequestTime time.Time
|
||||||
|
repeatResolver *Repeat
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewParallelResolver(resolverAddress string) *Parallel {
|
func NewParallelResolver(resolverAddress string) *Parallel {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Maintenance
|
# Maintenance
|
||||||
|
|
||||||
- Rename `UNBLOCK` to `DNS_HOSTNAMES_UNBLOCKED`
|
<https://github.com/macvk/dnsleaktest/blob/master/dnsleaktest.go>
|
||||||
|
|
||||||
|
- Rename `UNBLOCK` to `DNS_ALLOWED_HOSTNAMES`
|
||||||
- Change `Run` methods to `Start`+`Stop`, returning channels rather than injecting them
|
- Change `Run` methods to `Start`+`Stop`, returning channels rather than injecting them
|
||||||
- Go 1.18
|
- Go 1.18
|
||||||
- gofumpt
|
- gofumpt
|
||||||
|
|||||||
Reference in New Issue
Block a user