From 8b02116a7b2a62682498c67945d51540a559b541 Mon Sep 17 00:00:00 2001 From: Orne Brocaar Date: Mon, 11 May 2020 12:01:00 +0200 Subject: [PATCH] Refactor join-server configuration. Add server option. This renames the certificates config section to servers. When server is set, DNS resolving is disabled and the JoinEUI matches, ChirpStack Network Server will use the matching server for handling the join-request. --- .../cmd/configfile.go | 17 ++-- docs/content/install/config.md | 12 ++- internal/backend/joinserver/joinserver.go | 17 ++-- internal/backend/joinserver/pool.go | 79 +++++++++++-------- internal/backend/joinserver/pool_test.go | 31 ++++++++ internal/config/config.go | 5 +- 6 files changed, 112 insertions(+), 49 deletions(-) diff --git a/cmd/chirpstack-network-server/cmd/configfile.go b/cmd/chirpstack-network-server/cmd/configfile.go index 91d7f65e..ecdde5c4 100644 --- a/cmd/chirpstack-network-server/cmd/configfile.go +++ b/cmd/chirpstack-network-server/cmd/configfile.go @@ -694,15 +694,21 @@ resolve_join_eui={{ .JoinServer.ResolveJoinEUI }} resolve_domain_suffix="{{ .JoinServer.ResolveDomainSuffix }}" - # Join-server certificates. + # Per Join Server configuration. # # Example: - # [[join_server.certificates]] + # [[join_server.servers]] # # JoinEUI. # # # # The JoinEUI of the joinserver to to use the certificates for. # join_eui="0102030405060708" + # # Server (optional). + # # + # # The endpoint to the Join Server. If set, the DNS lookup will not be used + # # for the JoinEUI associated with this server. + # server="https://example.com:1234/join/endpoint" + # # CA certificate (optional). # # # # Set this to validate the join-server server certificate (e.g. when the @@ -718,8 +724,9 @@ resolve_domain_suffix="{{ .JoinServer.ResolveDomainSuffix }}" # # # # Set this to enable client-certificate authentication with the join-server. # tls_key="/path/to/tls_key.pem" - {{ range $index, $element := .JoinServer.Certificates }} - [[join_server.certificates]] + {{ range $index, $element := .JoinServer.Servers }} + [[join_server.servers]] + server="{{ $element.Server }}" join_eui="{{ $element.JoinEUI }}" ca_cert="{{ $element.CACert }}" tls_cert="{{ $element.TLSCert }}" @@ -731,7 +738,7 @@ resolve_domain_suffix="{{ .JoinServer.ResolveDomainSuffix }}" # This join-server will be used when resolving the JoinEUI is set to false # or as a fallback when resolving the JoinEUI fails. [join_server.default] - # hostname:port of the default join-server + # Default server endpoint. # # This API is provided by ChirpStack Application Server. server="{{ .JoinServer.Default.Server }}" diff --git a/docs/content/install/config.md b/docs/content/install/config.md index 66e83371..75b9c156 100644 --- a/docs/content/install/config.md +++ b/docs/content/install/config.md @@ -744,15 +744,21 @@ resolve_join_eui=false resolve_domain_suffix=".joineuis.lora-alliance.org" - # Join-server certificates. + # Per Join Server configuration. # # Example: - # [[join_server.certificates]] + # [[join_server.servers]] # # JoinEUI. # # # # The JoinEUI of the joinserver to to use the certificates for. # join_eui="0102030405060708" + # # Server (optional). + # # + # # The endpoint to the Join Server. If set, the DNS lookup will not be used + # # for the JoinEUI associated with this server. + # server="https://example.com:1234/join/endpoint" + # # CA certificate (optional). # # # # Set this to validate the join-server server certificate (e.g. when the @@ -775,7 +781,7 @@ resolve_domain_suffix=".joineuis.lora-alliance.org" # This join-server will be used when resolving the JoinEUI is set to false # or as a fallback when resolving the JoinEUI fails. [join_server.default] - # hostname:port of the default join-server + # Default server endpoint. # # This API is provided by ChirpStack Application Server. server="http://localhost:8003" diff --git a/internal/backend/joinserver/joinserver.go b/internal/backend/joinserver/joinserver.go index 0f57fc2a..742dd989 100644 --- a/internal/backend/joinserver/joinserver.go +++ b/internal/backend/joinserver/joinserver.go @@ -23,18 +23,19 @@ func Setup(c config.Config) error { return errors.Wrap(err, "joinserver: create default client error") } - var certificates []certificate - for _, cert := range conf.Certificates { + var servers []server + for _, s := range conf.Servers { var eui lorawan.EUI64 - if err := eui.UnmarshalText([]byte(cert.JoinEUI)); err != nil { + if err := eui.UnmarshalText([]byte(s.JoinEUI)); err != nil { return errors.Wrap(err, "joinserver: unmarshal JoinEUI error") } - certificates = append(certificates, certificate{ + servers = append(servers, server{ + server: s.Server, joinEUI: eui, - caCert: cert.CACert, - tlsCert: cert.TLSCert, - tlsKey: cert.TLSKey, + caCert: s.CACert, + tlsCert: s.TLSCert, + tlsKey: s.TLSKey, }) } @@ -43,7 +44,7 @@ func Setup(c config.Config) error { resolveJoinEUI: conf.ResolveJoinEUI, resolveDomainSuffix: conf.ResolveDomainSuffix, clients: make(map[lorawan.EUI64]poolClient), - certificates: certificates, + servers: servers, } return nil diff --git a/internal/backend/joinserver/pool.go b/internal/backend/joinserver/pool.go index b8880292..c921b867 100644 --- a/internal/backend/joinserver/pool.go +++ b/internal/backend/joinserver/pool.go @@ -12,6 +12,9 @@ import ( "github.com/brocaar/lorawan" ) +// ErrServerNotFound indicates that no server is configured for the given JoinEUI. +var ErrServerNotFound = errors.New("server not found") + // Pool defines the join-server client pool. type Pool interface { Get(joinEUI lorawan.EUI64) (Client, error) @@ -26,11 +29,12 @@ type pool struct { defaultClient Client resolveJoinEUI bool clients map[lorawan.EUI64]poolClient - certificates []certificate + servers []server resolveDomainSuffix string } -type certificate struct { +type server struct { + server string joinEUI lorawan.EUI64 caCert string tlsCert string @@ -39,10 +43,7 @@ type certificate struct { // Get returns the join-server client for the given joinEUI. func (p *pool) Get(joinEUI lorawan.EUI64) (Client, error) { - if !p.resolveJoinEUI { - return p.defaultClient, nil - } - + // return from cache p.RLock() pc, ok := p.clients[joinEUI] p.RUnlock() @@ -50,41 +51,47 @@ func (p *pool) Get(joinEUI lorawan.EUI64) (Client, error) { return pc.client, nil } - client, err := p.resolveJoinServer(joinEUI) - if err != nil { - log.WithField("join_eui", joinEUI).WithError(err).Warning("resolving JoinEUI failed, using default join-server") - return p.defaultClient, nil + client := p.getClient(joinEUI) + if client != p.defaultClient { + p.Lock() + p.clients[joinEUI] = poolClient{client: client} + p.Unlock() } - p.Lock() - p.clients[joinEUI] = poolClient{client: client} - p.Unlock() - return client, nil } -func (p *pool) resolveJoinServer(joinEUI lorawan.EUI64) (Client, error) { - // resolve the join-server EUI to an url (using DNS) - server, err := p.resolveJoinEUIToJoinServerURL(joinEUI) - if err != nil { - return nil, errors.Wrap(err, "resolve joineui to join-server url error") +func (p *pool) getClient(joinEUI lorawan.EUI64) Client { + var err error + s := p.getServer(joinEUI) + + // if the server endpoint is empty and resolve JoinEUI is enabled, try to get it from DNS + if s.server == "" && p.resolveJoinEUI { + s.server, err = p.resolveJoinEUIToJoinServerURL(joinEUI) + if err != nil { + log.WithFields(log.Fields{ + "join_eui": joinEUI, + }).WithError(err).Warning("resolving JoinEUI failed, returning default join-server client") + return p.defaultClient + } } - log.WithFields(log.Fields{ - "join_eui": joinEUI, - "server": server, - }).Debug("resolved joineui to join-server") - - var caCert, tlsCert, tlsKey string - for _, cert := range p.certificates { - if cert.joinEUI == joinEUI { - caCert = cert.caCert - tlsCert = cert.tlsCert - tlsKey = cert.tlsKey + // if the server endpoint is set, return the client + if s.server != "" { + c, err := NewClient(s.server, s.caCert, s.tlsCert, s.tlsKey) + if err != nil { + log.WithFields(log.Fields{ + "join_eui": joinEUI, + "server": s.server, + }).WithError(err).Error("creating join-server client failed, returning default join-server client") + return p.defaultClient } + + return c } - return NewClient(server, caCert, tlsCert, tlsKey) + // in any other case, return the default client + return p.defaultClient } func (p *pool) resolveJoinEUIToJoinServerURL(joinEUI lorawan.EUI64) (string, error) { @@ -118,3 +125,13 @@ func (p *pool) joinEUIToServer(joinEUI lorawan.EUI64) string { return strings.Join(nibbles, ".") + p.resolveDomainSuffix } + +func (p *pool) getServer(joinEUI lorawan.EUI64) server { + for _, s := range p.servers { + if s.joinEUI == joinEUI { + return s + } + } + + return server{} +} diff --git a/internal/backend/joinserver/pool_test.go b/internal/backend/joinserver/pool_test.go index ce6867eb..65e5e414 100644 --- a/internal/backend/joinserver/pool_test.go +++ b/internal/backend/joinserver/pool_test.go @@ -65,6 +65,37 @@ func (ts *PoolTestSuite) TestAToServer() { } } +func (ts *PoolTestSuite) TestGetClient() { + pool := GetPool().(*pool) + pool.resolveJoinEUI = false + pool.defaultClient = &client{} + + joinEUI := lorawan.EUI64{1, 2, 3, 4, 5, 6, 7, 8} + + ts.T().Run("Return default client", func(t *testing.T) { + assert := require.New(t) + + c := pool.getClient(joinEUI) + assert.Equal(pool.defaultClient, c) + }) + + ts.T().Run("Return pre-configured server", func(t *testing.T) { + assert := require.New(t) + + pool.servers = []server{ + { + server: "http://localhost:12345/foo", + joinEUI: joinEUI, + }, + } + + c := pool.getClient(joinEUI) + assert.NotEqual(pool.defaultClient, c) + + assert.Equal("http://localhost:12345/foo", c.(*client).server) + }) +} + func TestPool(t *testing.T) { suite.Run(t, new(PoolTestSuite)) } diff --git a/internal/config/config.go b/internal/config/config.go index e390f867..f5118e04 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -157,12 +157,13 @@ type Config struct { ResolveJoinEUI bool `mapstructure:"resolve_join_eui"` ResolveDomainSuffix string `mapstructure:"resolve_domain_suffix"` - Certificates []struct { + Servers []struct { + Server string `mapstructure:"server"` JoinEUI string `mapstructure:"join_eui"` CACert string `mapstructure:"ca_cert"` TLSCert string `mapstructure:"tls_cert"` TLSKey string `mapstructure:"tls_key"` - } `mapstructure:"certificates"` + } `mapstructure:"servers"` Default struct { Server string `mapstructure:"server"`