Skip to content

Commit

Permalink
Refactor join-server configuration. Add server option.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
brocaar committed May 11, 2020
1 parent e454a9a commit 8b02116
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 49 deletions.
17 changes: 12 additions & 5 deletions cmd/chirpstack-network-server/cmd/configfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }}"
Expand All @@ -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 }}"
Expand Down
12 changes: 9 additions & 3 deletions docs/content/install/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
17 changes: 9 additions & 8 deletions internal/backend/joinserver/joinserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
}

Expand All @@ -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
Expand Down
79 changes: 48 additions & 31 deletions internal/backend/joinserver/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -39,52 +43,55 @@ 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()
if ok {
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) {
Expand Down Expand Up @@ -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{}
}
31 changes: 31 additions & 0 deletions internal/backend/joinserver/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
5 changes: 3 additions & 2 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down

0 comments on commit 8b02116

Please sign in to comment.