Skip to content

Commit 02beef4

Browse files
committed
Make NetID / JoinEUI filtering global.
Closes #115.
1 parent 18e1c2a commit 02beef4

File tree

10 files changed

+496
-32
lines changed

10 files changed

+496
-32
lines changed

cmd/lora-gateway-bridge/cmd/configfile.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,42 @@ const configTemplate = `[general]
1515
log_level = {{ .General.LogLevel }}
1616
1717
18+
# Filters.
19+
#
20+
# These can be used to filter LoRaWAN frames to reduce bandwith usage between
21+
# the gateway and LoRa Gateway Bride. Depending the used backend, filtering
22+
# will be performed by the Packet Forwarder or LoRa Gateway Bridge.
23+
[filters]
24+
25+
# NetIDs filters.
26+
#
27+
# The configured NetIDs will be used to filter uplink data frames.
28+
# When left blank, no filtering will be performed on NetIDs.
29+
#
30+
# Example:
31+
# net_ids=[
32+
# "000000",
33+
# "000001",
34+
# ]
35+
net_ids=[{{ range $index, $elm := .Filters.NetIDs }}
36+
"{{ $elm }}",{{ end }}
37+
]
38+
39+
# JoinEUI filters.
40+
#
41+
# The configured JoinEUI ranges will be used to filter join-requests.
42+
# When left blank, no filtering will be performed on JoinEUIs.
43+
#
44+
# Example:
45+
# join_euis=[
46+
# ["0000000000000000", "00000000000000ff"],
47+
# ["000000000000ff00", "000000000000ffff"],
48+
# ]
49+
join_euis=[{{ range $index, $elm := .Filters.JoinEUIs }}
50+
["{{ index $elm 0 }}", "{{ index $elm 1 }}"],{{ end }}
51+
]
52+
53+
1854
# Gateway backend configuration.
1955
[backend]
2056
@@ -99,19 +135,6 @@ type="{{ .Backend.Type }}"
99135
# Maximum frequency (Hz).
100136
frequency_max={{ .Backend.BasicStation.FrequencyMax }}
101137
102-
# Filters.
103-
[backend.basic_station.filters]
104-
105-
# NetIDs to filter on when receiving uplinks.
106-
net_ids=[{{ range $index, $elm := .Backend.BasicStation.Filters.NetIDs }}
107-
"{{ $elm }}",{{ end }}
108-
]
109-
110-
# JoinEUIs to filter on when receiving join-requests.
111-
join_euis=[{{ range $index, $elm := .Backend.BasicStation.Filters.JoinEUIs }}
112-
["{{ index $elm 0 }}", "{{ index $elm 1 }}"],{{ end }}
113-
]
114-
115138
116139
# Integration configuration.
117140
[integration]

cmd/lora-gateway-bridge/cmd/root.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ func initConfig() {
108108
if err := viper.Unmarshal(&config.C); err != nil {
109109
log.WithError(err).Fatal("unmarshal config error")
110110
}
111+
112+
// backwards compatibility when BasicStation filters have been configured.
113+
if config.C.Backend.Type == "basic_station" && (len(config.C.Backend.BasicStation.Filters.NetIDs) != 0 || len(config.C.Backend.BasicStation.Filters.JoinEUIs) != 0) {
114+
config.C.Filters.NetIDs = config.C.Backend.BasicStation.Filters.NetIDs
115+
config.C.Filters.JoinEUIs = config.C.Backend.BasicStation.Filters.JoinEUIs
116+
}
111117
}
112118

113119
func viperBindEnvs(iface interface{}, parts ...string) {

cmd/lora-gateway-bridge/cmd/root_run.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/brocaar/lora-gateway-bridge/internal/backend"
1313
"github.com/brocaar/lora-gateway-bridge/internal/config"
14+
"github.com/brocaar/lora-gateway-bridge/internal/filters"
1415
"github.com/brocaar/lora-gateway-bridge/internal/forwarder"
1516
"github.com/brocaar/lora-gateway-bridge/internal/integration"
1617
"github.com/brocaar/lora-gateway-bridge/internal/metadata"
@@ -22,6 +23,7 @@ func run(cmd *cobra.Command, args []string) error {
2223
tasks := []func() error{
2324
setLogLevel,
2425
printStartMessage,
26+
setupFilters,
2527
setupBackend,
2628
setupIntegration,
2729
setupForwarder,
@@ -90,3 +92,10 @@ func setupMetaData() error {
9092
}
9193
return nil
9294
}
95+
96+
func setupFilters() error {
97+
if err := filters.Setup(config.C); err != nil {
98+
return errors.Wrap(err, "setup filters error")
99+
}
100+
return nil
101+
}

docs/content/install/config.md

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,40 @@ Example configuration file:
6767
log_level = 4
6868

6969

70+
# Filters.
71+
#
72+
# These can be used to filter LoRaWAN frames to reduce bandwith usage between
73+
# the gateway and LoRa Gateway Bride. Depending the used backend, filtering
74+
# will be performed by the Packet Forwarder or LoRa Gateway Bridge.
75+
[filters]
76+
77+
# NetIDs filters.
78+
#
79+
# The configured NetIDs will be used to filter uplink data frames.
80+
# When left blank, no filtering will be performed on NetIDs.
81+
#
82+
# Example:
83+
# net_ids=[
84+
# "000000",
85+
# "000001",
86+
# ]
87+
net_ids=[
88+
]
89+
90+
# JoinEUI filters.
91+
#
92+
# The configured JoinEUI ranges will be used to filter join-requests.
93+
# When left blank, no filtering will be performed on JoinEUIs.
94+
#
95+
# Example:
96+
# join_euis=[
97+
# ["0000000000000000", "00000000000000ff"],
98+
# ["000000000000ff00", "000000000000ffff"],
99+
# ]
100+
join_euis=[
101+
]
102+
103+
70104
# Gateway backend configuration.
71105
[backend]
72106

@@ -145,19 +179,6 @@ type="semtech_udp"
145179
# Maximum frequency (Hz).
146180
frequency_max=870000000
147181

148-
# Filters.
149-
[backend.basic_station.filters]
150-
151-
# NetIDs to filter on when receiving uplinks.
152-
net_ids=[
153-
"000000",
154-
]
155-
156-
# JoinEUIs to filter on when receiving join-requests.
157-
join_euis=[
158-
["0000000000000000", "ffffffffffffffff"],
159-
]
160-
161182

162183
# Integration configuration.
163184
[integration]

internal/backend/basicstation/backend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,15 @@ func NewBackend(conf config.Config) (*Backend, error) {
8181
frequencyMax: conf.Backend.BasicStation.FrequencyMax,
8282
}
8383

84-
for _, n := range conf.Backend.BasicStation.Filters.NetIDs {
84+
for _, n := range conf.Filters.NetIDs {
8585
var netID lorawan.NetID
8686
if err := netID.UnmarshalText([]byte(n)); err != nil {
8787
return nil, errors.Wrap(err, "unmarshal netid error")
8888
}
8989
b.netIDs = append(b.netIDs, netID)
9090
}
9191

92-
for _, set := range conf.Backend.BasicStation.Filters.JoinEUIs {
92+
for _, set := range conf.Filters.JoinEUIs {
9393
var joinEUIs [2]lorawan.EUI64
9494
for i, s := range set {
9595
var eui lorawan.EUI64

internal/backend/basicstation/backend_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ func (ts *BackendTestSuite) SetupTest() {
3737
var conf config.Config
3838
conf.Backend.Type = "basic_station"
3939
conf.Backend.BasicStation.Bind = "127.0.0.1:0"
40-
conf.Backend.BasicStation.Filters.NetIDs = []string{"010203"}
41-
conf.Backend.BasicStation.Filters.JoinEUIs = [][2]string{{"0000000000000000", "0102030405060708"}}
40+
conf.Filters.NetIDs = []string{"010203"}
41+
conf.Filters.JoinEUIs = [][2]string{{"0000000000000000", "0102030405060708"}}
4242
conf.Backend.BasicStation.Region = "EU868"
4343
conf.Backend.BasicStation.FrequencyMin = 867000000
4444
conf.Backend.BasicStation.FrequencyMax = 869000000

internal/backend/semtechudp/backend.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"github.com/brocaar/lora-gateway-bridge/internal/backend/semtechudp/packets"
1616
"github.com/brocaar/lora-gateway-bridge/internal/config"
17+
"github.com/brocaar/lora-gateway-bridge/internal/filters"
1718
"github.com/brocaar/loraserver/api/gw"
1819
"github.com/brocaar/lorawan"
1920
)
@@ -478,7 +479,13 @@ func (b *Backend) handleStats(gatewayID lorawan.EUI64, stats gw.GatewayStats) {
478479

479480
func (b *Backend) handleUplinkFrames(uplinkFrames []gw.UplinkFrame) error {
480481
for i := range uplinkFrames {
481-
b.uplinkFrameChan <- uplinkFrames[i]
482+
if filters.MatchFilters(uplinkFrames[i].PhyPayload) {
483+
b.uplinkFrameChan <- uplinkFrames[i]
484+
} else {
485+
log.WithFields(log.Fields{
486+
"data_base64": base64.StdEncoding.EncodeToString(uplinkFrames[i].PhyPayload),
487+
}).Debug("backend/semtechudp: frame dropped because of configured filters")
488+
}
482489
}
483490

484491
return nil

internal/config/config.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ type Config struct {
1010
LogLevel int `mapstructure:"log_level"`
1111
}
1212

13+
Filters struct {
14+
NetIDs []string `mapstructure:"net_ids"`
15+
JoinEUIs [][2]string `mapstructure:"join_euis"`
16+
} `mapstructure:"filters"`
17+
1318
Backend struct {
1419
Type string `mapstructure:"type"`
1520

@@ -33,7 +38,8 @@ type Config struct {
3338
PingInterval time.Duration `mapstructure:"ping_interval"`
3439
ReadTimeout time.Duration `mapstructure:"read_timeout"`
3540
WriteTimeout time.Duration `mapstructure:"write_timeout"`
36-
Filters struct {
41+
// TODO: remove Filters in the next major release, use global filters instead
42+
Filters struct {
3743
NetIDs []string `mapstructure:"net_ids"`
3844
JoinEUIs [][2]string `mapstructure:"join_euis"`
3945
} `mapstructure:"filters"`

internal/filters/filters.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package filters
2+
3+
import (
4+
"encoding/binary"
5+
6+
"github.com/pkg/errors"
7+
log "github.com/sirupsen/logrus"
8+
9+
"github.com/brocaar/lora-gateway-bridge/internal/config"
10+
"github.com/brocaar/lorawan"
11+
)
12+
13+
var netIDs []lorawan.NetID
14+
var joinEUIs [][2]lorawan.EUI64
15+
16+
func Setup(conf config.Config) error {
17+
for _, netIDStr := range conf.Filters.NetIDs {
18+
var netID lorawan.NetID
19+
if err := netID.UnmarshalText([]byte(netIDStr)); err != nil {
20+
return errors.Wrap(err, "unmarshal NetID error")
21+
}
22+
23+
netIDs = append(netIDs, netID)
24+
log.WithFields(log.Fields{
25+
"net_id": netID,
26+
}).Info("filters: NetID filter configured")
27+
}
28+
29+
for _, set := range conf.Filters.JoinEUIs {
30+
var joinEUISet [2]lorawan.EUI64
31+
32+
for i, s := range set {
33+
var joinEUI lorawan.EUI64
34+
if err := joinEUI.UnmarshalText([]byte(s)); err != nil {
35+
return errors.Wrap(err, "unmarshal JoinEUI error")
36+
}
37+
38+
joinEUISet[i] = joinEUI
39+
}
40+
41+
joinEUIs = append(joinEUIs, joinEUISet)
42+
43+
log.WithFields(log.Fields{
44+
"join_eui_from": joinEUISet[0],
45+
"join_eui_to": joinEUISet[1],
46+
}).Info("filters: JoinEUI range configured")
47+
}
48+
49+
return nil
50+
}
51+
52+
// MatchFilters will match the given LoRaWAN frame against the configured
53+
// filters. This function returns true in the following cases:
54+
// * If the PHYPayload matches the configured filters
55+
// * If no filters are configured
56+
// * In case the PHYPayload is not a valid LoRaWAN frame
57+
func MatchFilters(b []byte) bool {
58+
// return true when no filters are configured
59+
if len(netIDs) == 0 && len(joinEUIs) == 0 {
60+
return true
61+
}
62+
63+
// return true when we can't decode the LoRaWAN frame
64+
var phy lorawan.PHYPayload
65+
if err := phy.UnmarshalBinary(b); err != nil {
66+
log.WithError(err).Error("filters: unmarshal phypayload error")
67+
return true
68+
}
69+
70+
switch phy.MHDR.MType {
71+
case lorawan.UnconfirmedDataUp, lorawan.ConfirmedDataUp:
72+
return filterDevAddr(phy)
73+
case lorawan.JoinRequest:
74+
return filterJoinRequest(phy)
75+
case lorawan.RejoinRequest:
76+
return filterRejoinRequest(phy)
77+
default:
78+
return true
79+
}
80+
}
81+
82+
func matchNetIDFilter(netID lorawan.NetID) bool {
83+
if len(netIDs) == 0 {
84+
return true
85+
}
86+
87+
for _, n := range netIDs {
88+
if n == netID {
89+
return true
90+
}
91+
}
92+
93+
return false
94+
}
95+
96+
func matchNetIDFilterForDevAddr(devAddr lorawan.DevAddr) bool {
97+
if len(netIDs) == 0 {
98+
return true
99+
}
100+
101+
for _, netID := range netIDs {
102+
if devAddr.IsNetID(netID) {
103+
return true
104+
}
105+
}
106+
107+
return false
108+
}
109+
110+
func matchJoinEUIFilter(joinEUI lorawan.EUI64) bool {
111+
if len(joinEUIs) == 0 {
112+
return true
113+
}
114+
115+
joinEUIInt := binary.BigEndian.Uint64(joinEUI[:])
116+
117+
for _, pair := range joinEUIs {
118+
min := binary.BigEndian.Uint64(pair[0][:])
119+
max := binary.BigEndian.Uint64(pair[1][:])
120+
121+
if joinEUIInt >= min && joinEUIInt <= max {
122+
return true
123+
}
124+
}
125+
126+
return false
127+
}
128+
129+
func filterDevAddr(phy lorawan.PHYPayload) bool {
130+
mac, ok := phy.MACPayload.(*lorawan.MACPayload)
131+
if !ok {
132+
return true
133+
}
134+
135+
return matchNetIDFilterForDevAddr(mac.FHDR.DevAddr)
136+
}
137+
138+
func filterJoinRequest(phy lorawan.PHYPayload) bool {
139+
jr, ok := phy.MACPayload.(*lorawan.JoinRequestPayload)
140+
if !ok {
141+
return true
142+
}
143+
144+
return matchJoinEUIFilter(jr.JoinEUI)
145+
}
146+
147+
func filterRejoinRequest(phy lorawan.PHYPayload) bool {
148+
switch v := phy.MACPayload.(type) {
149+
case *lorawan.RejoinRequestType02Payload:
150+
return matchNetIDFilter(v.NetID)
151+
case *lorawan.RejoinRequestType1Payload:
152+
return matchJoinEUIFilter(v.JoinEUI)
153+
default:
154+
return true
155+
}
156+
}

0 commit comments

Comments
 (0)