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"`