From 950ad8457f07aa738e7cb09d3c27689067e7cb42 Mon Sep 17 00:00:00 2001 From: Martin Geno Date: Wed, 17 Jan 2018 20:20:35 +0100 Subject: [PATCH 1/2] [TASK] handle domain_code (with filters) (#119) --- cmd/config_test.go | 4 ++ cmd/import.go | 8 ++- cmd/query.go | 4 +- cmd/serve.go | 2 +- config_example.toml | 17 ++++- data/nodeinfo.go | 3 +- database/all/connection.go | 4 +- database/database.go | 2 +- database/graphite/global.go | 9 ++- database/influxdb/global.go | 25 ++++--- database/influxdb/global_test.go | 70 +++++++++++++------ database/influxdb/node.go | 3 + database/influxdb/node_test.go | 6 +- database/logging/file.go | 4 +- database/logging/file_test.go | 2 +- database/respondd/main.go | 2 +- docs/docs_configuration.md | 59 +++++++++++++--- output/all/filter.go | 2 + .../domainappendsite/domainappendsite.go | 50 +++++++++++++ .../domainappendsite/domainappendsite_test.go | 43 ++++++++++++ output/filter/domainassite/domainassite.go | 50 +++++++++++++ .../filter/domainassite/domainassite_test.go | 43 ++++++++++++ output/meshviewer-ffrgb/struct.go | 2 + respond/collector.go | 36 +++++----- respond/collector_test.go | 7 +- respond/config.go | 24 +++++-- respond/config_test.go | 24 +++++++ runtime/stats.go | 30 +++++--- runtime/stats_test.go | 68 ++++++++++++------ 29 files changed, 491 insertions(+), 112 deletions(-) create mode 100644 output/filter/domainappendsite/domainappendsite.go create mode 100644 output/filter/domainappendsite/domainappendsite_test.go create mode 100644 output/filter/domainassite/domainassite.go create mode 100644 output/filter/domainassite/domainassite_test.go create mode 100644 respond/config_test.go diff --git a/cmd/config_test.go b/cmd/config_test.go index 6a854030..eaa95f11 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -20,6 +20,10 @@ func TestReadConfig(t *testing.T) { assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration) assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration) + assert.Len(config.Respondd.Sites, 1) + assert.Contains(config.Respondd.Sites, "ffhb") + assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city") + // Test output plugins assert.Len(config.Nodes.Output, 3) outputs := config.Nodes.Output["meshviewer"].([]interface{}) diff --git a/cmd/import.go b/cmd/import.go index 643fa18e..a1103763 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -11,13 +11,14 @@ import ( // importCmd represents the import command var importCmd = &cobra.Command{ - Use: "import ", + Use: "import ", Short: "Imports global statistics from the given RRD files, requires InfluxDB", - Example: "yanic import --config /etc/yanic.toml olddata.rrd global", - Args: cobra.ExactArgs(2), + Example: "yanic import --config /etc/yanic.toml olddata.rrd global global", + Args: cobra.ExactArgs(3), Run: func(cmd *cobra.Command, args []string) { path := args[0] site := args[1] + domain := args[2] config := loadConfig() err := allDatabase.Start(config.Database) @@ -36,6 +37,7 @@ var importCmd = &cobra.Command{ }, ds.Time, site, + domain, ) } }, diff --git a/cmd/query.go b/cmd/query.go index ea81708e..9ab3785a 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -26,7 +26,9 @@ var queryCmd = &cobra.Command{ nodes := runtime.NewNodes(&runtime.NodesConfig{}) - collector := respond.NewCollector(nil, nodes, []string{}, []string{iface}, 0) + sitesDomains := make(map[string][]string) + + collector := respond.NewCollector(nil, nodes, sitesDomains, []string{iface}, 0) defer collector.Close() collector.SendPacket(dstAddress) diff --git a/cmd/serve.go b/cmd/serve.go index 8fe5b5fe..36edaf89 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -54,7 +54,7 @@ var serveCmd = &cobra.Command{ time.Sleep(delay) } - collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.Sites, config.Respondd.Interfaces, config.Respondd.Port) + collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces, config.Respondd.Port) collector.Start(config.Respondd.CollectInterval.Duration) defer collector.Close() } diff --git a/config_example.toml b/config_example.toml index 993f9fc1..bc766301 100644 --- a/config_example.toml +++ b/config_example.toml @@ -11,12 +11,17 @@ synchronize = "1m" collect_interval = "1m" # interface that has an IP in your mesh network interfaces = ["br-ffhb"] -# list of sites to save stats for (empty for global only) -sites = [] # define a port to listen # if not set or set to 0 the kernel will use a random free port at its own #port = 10001 +# table of a site to save stats for (not exists for global only) +#[respondd.sites.example] +## list of domains on this site to save stats for (empty for global only) +#domains = [] +## example +[respondd.sites.ffhb] +domains = ["city"] # A little build-in webserver, which statically serves a directory. # This is useful for testing purposes or for a little standalone installation. @@ -56,6 +61,14 @@ offline_after = "10m" # List of site_codes of nodes that should be included in the output #sites = ["ffhb"] # +# replace the site_code with the domain_code in this output +# e.g. site_code='ffhb',domain_code='city' => site_code='city', domain_code='' +#domain_as_site = true +# +# append on the site_code the domain_code with a '.' in this output +# e.g. site_code='ffhb',domain_code='city' => site_code='ffhb.city', domain_code='' +#domain_append_site = true +# # set has_location to true if you want to include only nodes that have geo-coordinates set # (setting this to false has no sensible effect, unless you'd want to hide nodes that have coordinates) #has_location = true diff --git a/data/nodeinfo.go b/data/nodeinfo.go index 6a402a21..3f9fb29c 100644 --- a/data/nodeinfo.go +++ b/data/nodeinfo.go @@ -43,7 +43,8 @@ type Owner struct { // System struct type System struct { - SiteCode string `json:"site_code,omitempty"` + SiteCode string `json:"site_code,omitempty"` + DomainCode string `json:"domain_code,omitempty"` } // Location struct diff --git a/database/all/connection.go b/database/all/connection.go index fcdc3a20..2c698283 100644 --- a/database/all/connection.go +++ b/database/all/connection.go @@ -60,9 +60,9 @@ func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) { } } -func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { +func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { for _, item := range conn.list { - item.InsertGlobals(stats, time, site) + item.InsertGlobals(stats, time, site, domain) } } diff --git a/database/database.go b/database/database.go index c40768d1..096a2b57 100644 --- a/database/database.go +++ b/database/database.go @@ -15,7 +15,7 @@ type Connection interface { InsertLink(*runtime.Link, time.Time) // InsertGlobals stores global statistics - InsertGlobals(*runtime.GlobalStats, time.Time, string) + InsertGlobals(*runtime.GlobalStats, time.Time, string, string) // PruneNodes prunes historical per-node data PruneNodes(deleteAfter time.Duration) diff --git a/database/graphite/global.go b/database/graphite/global.go index acdd056e..62fff662 100644 --- a/database/graphite/global.go +++ b/database/graphite/global.go @@ -7,7 +7,7 @@ import ( "github.com/fgrosse/graphigo" ) -func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { +func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { measurementGlobal := MeasurementGlobal counterMeasurementModel := CounterMeasurementModel counterMeasurementFirmware := CounterMeasurementFirmware @@ -20,6 +20,13 @@ func (c *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, s counterMeasurementAutoupdater += "_" + site } + if domain != runtime.GLOBAL_DOMAIN { + measurementGlobal += "_" + domain + counterMeasurementModel += "_" + domain + counterMeasurementFirmware += "_" + domain + counterMeasurementAutoupdater += "_" + domain + } + c.addPoint(GlobalStatsFields(measurementGlobal, stats)) c.addCounterMap(counterMeasurementModel, stats.Models, time) c.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time) diff --git a/database/influxdb/global.go b/database/influxdb/global.go index f49bc6e1..c99e699d 100644 --- a/database/influxdb/global.go +++ b/database/influxdb/global.go @@ -8,8 +8,8 @@ import ( ) // InsertGlobals implementation of database -func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { - var tags models.Tags +func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { + tags := models.Tags{} measurementGlobal := MeasurementGlobal counterMeasurementModel := CounterMeasurementModel @@ -17,20 +17,26 @@ func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time counterMeasurementAutoupdater := CounterMeasurementAutoupdater if site != runtime.GLOBAL_SITE { - tags = models.Tags{ - models.Tag{Key: []byte("site"), Value: []byte(site)}, - } + tags.Set([]byte("site"), []byte(site)) measurementGlobal += "_site" counterMeasurementModel += "_site" counterMeasurementFirmware += "_site" counterMeasurementAutoupdater += "_site" } + if domain != runtime.GLOBAL_DOMAIN { + tags.Set([]byte("domain"), []byte(domain)) + + measurementGlobal += "_domain" + counterMeasurementModel += "_domain" + counterMeasurementFirmware += "_domain" + counterMeasurementAutoupdater += "_domain" + } conn.addPoint(measurementGlobal, tags, GlobalStatsFields(stats), time) - conn.addCounterMap(counterMeasurementModel, stats.Models, time, site) - conn.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time, site) - conn.addCounterMap(counterMeasurementAutoupdater, stats.Autoupdater, time, site) + conn.addCounterMap(counterMeasurementModel, stats.Models, time, site, domain) + conn.addCounterMap(counterMeasurementFirmware, stats.Firmwares, time, site, domain) + conn.addCounterMap(counterMeasurementAutoupdater, stats.Autoupdater, time, site, domain) } // GlobalStatsFields returns fields for InfluxDB @@ -48,13 +54,14 @@ func GlobalStatsFields(stats *runtime.GlobalStats) map[string]interface{} { // Saves the values of a CounterMap in the database. // The key are used as 'value' tag. // The value is used as 'counter' field. -func (conn *Connection) addCounterMap(name string, m runtime.CounterMap, t time.Time, site string) { +func (conn *Connection) addCounterMap(name string, m runtime.CounterMap, t time.Time, site string, domain string) { for key, count := range m { conn.addPoint( name, models.Tags{ models.Tag{Key: []byte("value"), Value: []byte(key)}, models.Tag{Key: []byte("site"), Value: []byte(site)}, + models.Tag{Key: []byte("domain"), Value: []byte(domain)}, }, models.Fields{"count": count}, t, diff --git a/database/influxdb/global_test.go b/database/influxdb/global_test.go index f990b5e6..65c3320d 100644 --- a/database/influxdb/global_test.go +++ b/database/influxdb/global_test.go @@ -12,18 +12,24 @@ import ( "github.com/FreifunkBremen/yanic/runtime" ) -const TEST_SITE = "ffxx" +const ( + TEST_SITE = "ffhb" + TEST_DOMAIN = "city" +) func TestGlobalStats(t *testing.T) { - stats := runtime.NewGlobalStats(createTestNodes(), []string{TEST_SITE}) + stats := runtime.NewGlobalStats(createTestNodes(), map[string][]string{TEST_SITE: {TEST_DOMAIN}}) assert := assert.New(t) // check SITE_GLOBAL fields - fields := GlobalStatsFields(stats[runtime.GLOBAL_SITE]) + fields := GlobalStatsFields(stats[runtime.GLOBAL_SITE][runtime.GLOBAL_DOMAIN]) assert.EqualValues(3, fields["nodes"]) - fields = GlobalStatsFields(stats[TEST_SITE]) + fields = GlobalStatsFields(stats[TEST_SITE][runtime.GLOBAL_DOMAIN]) + assert.EqualValues(2, fields["nodes"]) + fields = GlobalStatsFields(stats[TEST_SITE][TEST_DOMAIN]) + assert.EqualValues(1, fields["nodes"]) conn := &Connection{ @@ -32,59 +38,80 @@ func TestGlobalStats(t *testing.T) { global := 0 globalSite := 0 + globalDomain := 0 + model := 0 modelSite := 0 + modelDomain := 0 + firmware := 0 firmwareSite := 0 + firmwareDomain := 0 + autoupdater := 0 autoupdaterSite := 0 + autoupdaterDomain := 0 + wg := sync.WaitGroup{} - wg.Add(9) + wg.Add(15) go func() { for p := range conn.points { switch p.Name() { case MeasurementGlobal: global++ - break case "global_site": globalSite++ - break + case "global_site_domain": + globalDomain++ + case CounterMeasurementModel: model++ - break case "model_site": modelSite++ - break + case "model_site_domain": + modelDomain++ + case CounterMeasurementFirmware: firmware++ - break case "firmware_site": firmwareSite++ - break + case "firmware_site_domain": + firmwareDomain++ + case CounterMeasurementAutoupdater: autoupdater++ - break case "autoupdater_site": autoupdaterSite++ - break + case "autoupdater_site_domain": + autoupdaterDomain++ + default: assert.Equal("invalid p.Name found", p.Name()) } wg.Done() } }() - for site, stat := range stats { - conn.InsertGlobals(stat, time.Now(), site) + for site, domains := range stats { + for domain, stat := range domains { + conn.InsertGlobals(stat, time.Now(), site, domain) + } } wg.Wait() assert.Equal(1, global) assert.Equal(1, globalSite) + assert.Equal(1, globalDomain) + assert.Equal(2, model) - assert.Equal(1, modelSite) + assert.Equal(2, modelSite) + assert.Equal(1, modelDomain) + assert.Equal(1, firmware) - assert.Equal(0, firmwareSite) + assert.Equal(1, firmwareSite) + assert.Equal(0, firmwareDomain) + assert.Equal(2, autoupdater) - assert.Equal(1, autoupdaterSite) + assert.Equal(2, autoupdaterSite) + assert.Equal(1, autoupdaterDomain) } func createTestNodes() *runtime.Nodes { @@ -102,7 +129,9 @@ func createTestNodes() *runtime.Nodes { Hardware: data.Hardware{ Model: "TP-Link 841", }, - System: data.System{}, + System: data.System{ + SiteCode: TEST_SITE, + }, }, } nodeData.Nodeinfo.Software.Firmware.Release = "2016.1.6+entenhausen1" @@ -134,7 +163,8 @@ func createTestNodes() *runtime.Nodes { Model: "Xeon Multi-Core", }, System: data.System{ - SiteCode: TEST_SITE, + SiteCode: TEST_SITE, + DomainCode: TEST_DOMAIN, }, }, }) diff --git a/database/influxdb/node.go b/database/influxdb/node.go index f54db7dd..17acc134 100644 --- a/database/influxdb/node.go +++ b/database/influxdb/node.go @@ -52,6 +52,9 @@ func (conn *Connection) InsertNode(node *runtime.Node) { if nodeinfo.System.SiteCode != "" { tags.SetString("site", nodeinfo.System.SiteCode) } + if nodeinfo.System.DomainCode != "" { + tags.SetString("domain", nodeinfo.System.DomainCode) + } if owner := nodeinfo.Owner; owner != nil { tags.SetString("owner", owner.Contact) } diff --git a/database/influxdb/node_test.go b/database/influxdb/node_test.go index 70d310a3..47cb5fc0 100644 --- a/database/influxdb/node_test.go +++ b/database/influxdb/node_test.go @@ -52,7 +52,8 @@ func TestToInflux(t *testing.T) { Contact: "nobody", }, System: data.System{ - SiteCode: "ffxx", + SiteCode: "ffhb", + DomainCode: "city", }, Wireless: &data.Wireless{ TxPower24: 3, @@ -133,7 +134,8 @@ func TestToInflux(t *testing.T) { assert.EqualValues("deadbeef", tags["nodeid"]) assert.EqualValues("nobody", tags["owner"]) assert.EqualValues("testing", tags["autoupdater"]) - assert.EqualValues("ffxx", tags["site"]) + assert.EqualValues("ffhb", tags["site"]) + assert.EqualValues("city", tags["domain"]) assert.EqualValues(0.5, fields["load"]) assert.EqualValues(0, fields["neighbours.lldp"]) assert.EqualValues(1, fields["neighbours.batadv"]) diff --git a/database/logging/file.go b/database/logging/file.go index 328e307f..e086d0fc 100644 --- a/database/logging/file.go +++ b/database/logging/file.go @@ -50,8 +50,8 @@ func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) { conn.log("InsertLink: ", link) } -func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { - conn.log("InsertGlobals: [", time.String(), "] site: ", site, ", nodes: ", stats.Nodes, ", clients: ", stats.Clients, " models: ", len(stats.Models)) +func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { + conn.log("InsertGlobals: [", time.String(), "] site: ", site, " domain: ", domain, ", nodes: ", stats.Nodes, ", clients: ", stats.Clients, " models: ", len(stats.Models)) } func (conn *Connection) PruneNodes(deleteAfter time.Duration) { diff --git a/database/logging/file_test.go b/database/logging/file_test.go index 50b59acf..7c01be23 100644 --- a/database/logging/file_test.go +++ b/database/logging/file_test.go @@ -43,7 +43,7 @@ func TestStart(t *testing.T) { assert.Contains(string(dat), "InsertLink") assert.NotContains(string(dat), "InsertGlobals") - conn.InsertGlobals(&runtime.GlobalStats{}, time.Now(), runtime.GLOBAL_SITE) + conn.InsertGlobals(&runtime.GlobalStats{}, time.Now(), runtime.GLOBAL_SITE, runtime.GLOBAL_DOMAIN) dat, _ = ioutil.ReadFile(path) assert.Contains(string(dat), "InsertGlobals") diff --git a/database/respondd/main.go b/database/respondd/main.go index 998eec70..34f1115c 100644 --- a/database/respondd/main.go +++ b/database/respondd/main.go @@ -85,7 +85,7 @@ func (conn *Connection) InsertNode(node *runtime.Node) { func (conn *Connection) InsertLink(link *runtime.Link, time time.Time) { } -func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string) { +func (conn *Connection) InsertGlobals(stats *runtime.GlobalStats, time time.Time, site string, domain string) { } func (conn *Connection) PruneNodes(deleteAfter time.Duration) { diff --git a/docs/docs_configuration.md b/docs/docs_configuration.md index 5c67c164..fce86d14 100644 --- a/docs/docs_configuration.md +++ b/docs/docs_configuration.md @@ -15,8 +15,9 @@ enable = true # synchronize = "1m" collect_interval = "1m" interfaces = ["br-ffhb"] -sites = ["ffhb"] #port = 10001 +#[respondd.sites.example] +#domains = ["city"] ``` {% endmethod %} @@ -64,23 +65,32 @@ interfaces = ["br-ffhb"] {% endmethod %} -### sites +### port {% method %} -List of sites to save stats for (empty for global only) +Define a port to listen and send the respondd packages. +If not set or set to 0 the kernel will use a random free port at its own. {% sample lang="toml" %} ```toml -sites = ["ffhb"] +port = 10001 ``` {% endmethod %} - -### port +### [respondd.sites.example] {% method %} -Define a port to listen and send the respondd packages. -If not set or set to 0 the kernel will use a random free port at its own. +Tables of sites to save stats for (not exists for global only). +Here is the site _ffhb_. {% sample lang="toml" %} ```toml -port = 10001 +[respondd.sites.ffhb] +domains = ["city"] +``` +{% endmethod %} +#### domains +{% method %} +list of domains on this site to save stats for (empty for global only) +{% sample lang="toml" %} +```toml +domains = ["city"] ``` {% endmethod %} @@ -197,6 +207,8 @@ enable = true no_owner = true blacklist = ["00112233445566", "1337f0badead"] sites = ["ffhb"] +domain_as_site = true +domain_append_site = true has_location = true [nodes.output.example.filter.in_area] latitude_min = 34.30 @@ -258,6 +270,35 @@ blacklist = ["00112233445566", "1337f0badead"] {% endmethod %} +### sites +{% method %} +List of site_codes of nodes that should be included in output +{% sample lang="toml" %} +```toml +sites = ["ffhb"] +``` +{% endmethod %} + +### domain_as_site +{% method %} +Replace the `site_code` with the `domain_code` in this output. +e.g. `site_code='ffhb',domain_code='city'` becomes `site_code='city', domain_code=''` +{% sample lang="toml" %} +```toml +domain_as_site = true +``` +{% endmethod %} + +### domain_append_site +{% method %} +Append on the `site_code` the `domain_code` with a `.` in this output. +e.g. `site_code='ffhb',domain_code='city'` becomes `site_code='ffhb.city', domain_code=''` +{% sample lang="toml" %} +```toml +domain_append_site = true +``` +{% endmethod %} + ### sites {% method %} List of site_codes of nodes that should be included in output diff --git a/output/all/filter.go b/output/all/filter.go index 1c95ef59..72b114cc 100644 --- a/output/all/filter.go +++ b/output/all/filter.go @@ -2,6 +2,8 @@ package all import ( _ "github.com/FreifunkBremen/yanic/output/filter/blacklist" + _ "github.com/FreifunkBremen/yanic/output/filter/domainappendsite" + _ "github.com/FreifunkBremen/yanic/output/filter/domainassite" _ "github.com/FreifunkBremen/yanic/output/filter/haslocation" _ "github.com/FreifunkBremen/yanic/output/filter/inarea" _ "github.com/FreifunkBremen/yanic/output/filter/noowner" diff --git a/output/filter/domainappendsite/domainappendsite.go b/output/filter/domainappendsite/domainappendsite.go new file mode 100644 index 00000000..17d3e89b --- /dev/null +++ b/output/filter/domainappendsite/domainappendsite.go @@ -0,0 +1,50 @@ +package domainappendsite + +import ( + "errors" + + "github.com/FreifunkBremen/yanic/data" + "github.com/FreifunkBremen/yanic/output/filter" + "github.com/FreifunkBremen/yanic/runtime" +) + +type domainAppendSite struct{ set bool } + +func init() { + filter.Register("domain_append_site", build) +} + +func build(config interface{}) (filter.Filter, error) { + if value, ok := config.(bool); ok { + return &domainAppendSite{set: value}, nil + } + return nil, errors.New("invalid configuration, boolean expected") +} + +func (config *domainAppendSite) Apply(node *runtime.Node) *runtime.Node { + if nodeinfo := node.Nodeinfo; nodeinfo != nil && config.set && nodeinfo.System.DomainCode != "" { + node = &runtime.Node{ + Address: node.Address, + Firstseen: node.Firstseen, + Lastseen: node.Lastseen, + Online: node.Online, + Statistics: node.Statistics, + Nodeinfo: &data.NodeInfo{ + NodeID: nodeinfo.NodeID, + Network: nodeinfo.Network, + System: data.System{ + SiteCode: nodeinfo.System.SiteCode + "." + nodeinfo.System.DomainCode, + }, + Owner: nodeinfo.Owner, + Hostname: nodeinfo.Hostname, + Location: nodeinfo.Location, + Software: nodeinfo.Software, + Hardware: nodeinfo.Hardware, + VPN: nodeinfo.VPN, + Wireless: nodeinfo.Wireless, + }, + Neighbours: node.Neighbours, + } + } + return node +} diff --git a/output/filter/domainappendsite/domainappendsite_test.go b/output/filter/domainappendsite/domainappendsite_test.go new file mode 100644 index 00000000..78ca86e9 --- /dev/null +++ b/output/filter/domainappendsite/domainappendsite_test.go @@ -0,0 +1,43 @@ +package domainappendsite + +import ( + "testing" + + "github.com/FreifunkBremen/yanic/data" + "github.com/FreifunkBremen/yanic/runtime" + "github.com/stretchr/testify/assert" +) + +func TestFilter(t *testing.T) { + assert := assert.New(t) + + // invalid config + filter, err := build("nope") + assert.Error(err) + + // delete owner by configuration + filter, _ = build(true) + n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{ + System: data.System{ + SiteCode: "ffhb", + DomainCode: "city", + }, + }}) + + assert.NotNil(n) + assert.Equal("ffhb.city", n.Nodeinfo.System.SiteCode) + assert.Equal("", n.Nodeinfo.System.DomainCode) + + // keep owner configuration + filter, _ = build(false) + n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{ + System: data.System{ + SiteCode: "ffhb", + DomainCode: "city", + }, + }}) + + assert.NotNil(n) + assert.Equal("ffhb", n.Nodeinfo.System.SiteCode) + assert.Equal("city", n.Nodeinfo.System.DomainCode) +} diff --git a/output/filter/domainassite/domainassite.go b/output/filter/domainassite/domainassite.go new file mode 100644 index 00000000..7146d19b --- /dev/null +++ b/output/filter/domainassite/domainassite.go @@ -0,0 +1,50 @@ +package domainassite + +import ( + "errors" + + "github.com/FreifunkBremen/yanic/data" + "github.com/FreifunkBremen/yanic/output/filter" + "github.com/FreifunkBremen/yanic/runtime" +) + +type domainAsSite struct{ set bool } + +func init() { + filter.Register("domain_as_site", build) +} + +func build(config interface{}) (filter.Filter, error) { + if value, ok := config.(bool); ok { + return &domainAsSite{set: value}, nil + } + return nil, errors.New("invalid configuration, boolean expected") +} + +func (config *domainAsSite) Apply(node *runtime.Node) *runtime.Node { + if nodeinfo := node.Nodeinfo; nodeinfo != nil && config.set && nodeinfo.System.DomainCode != "" { + node = &runtime.Node{ + Address: node.Address, + Firstseen: node.Firstseen, + Lastseen: node.Lastseen, + Online: node.Online, + Statistics: node.Statistics, + Nodeinfo: &data.NodeInfo{ + NodeID: nodeinfo.NodeID, + Network: nodeinfo.Network, + System: data.System{ + SiteCode: nodeinfo.System.DomainCode, + }, + Owner: nodeinfo.Owner, + Hostname: nodeinfo.Hostname, + Location: nodeinfo.Location, + Software: nodeinfo.Software, + Hardware: nodeinfo.Hardware, + VPN: nodeinfo.VPN, + Wireless: nodeinfo.Wireless, + }, + Neighbours: node.Neighbours, + } + } + return node +} diff --git a/output/filter/domainassite/domainassite_test.go b/output/filter/domainassite/domainassite_test.go new file mode 100644 index 00000000..504500c0 --- /dev/null +++ b/output/filter/domainassite/domainassite_test.go @@ -0,0 +1,43 @@ +package domainassite + +import ( + "testing" + + "github.com/FreifunkBremen/yanic/data" + "github.com/FreifunkBremen/yanic/runtime" + "github.com/stretchr/testify/assert" +) + +func TestFilter(t *testing.T) { + assert := assert.New(t) + + // invalid config + filter, err := build("nope") + assert.Error(err) + + // delete owner by configuration + filter, _ = build(true) + n := filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{ + System: data.System{ + SiteCode: "ffhb", + DomainCode: "city", + }, + }}) + + assert.NotNil(n) + assert.Equal("city", n.Nodeinfo.System.SiteCode) + assert.Equal("", n.Nodeinfo.System.DomainCode) + + // keep owner configuration + filter, _ = build(false) + n = filter.Apply(&runtime.Node{Nodeinfo: &data.NodeInfo{ + System: data.System{ + SiteCode: "ffhb", + DomainCode: "city", + }, + }}) + + assert.NotNil(n) + assert.Equal("ffhb", n.Nodeinfo.System.SiteCode) + assert.Equal("city", n.Nodeinfo.System.DomainCode) +} diff --git a/output/meshviewer-ffrgb/struct.go b/output/meshviewer-ffrgb/struct.go index 3c3fb827..e39e7757 100644 --- a/output/meshviewer-ffrgb/struct.go +++ b/output/meshviewer-ffrgb/struct.go @@ -33,6 +33,7 @@ type Node struct { MAC string `json:"mac"` Addresses []string `json:"addresses"` SiteCode string `json:"site_code,omitempty"` + DomainCode string `json:"-"` Hostname string `json:"hostname"` Owner string `json:"owner,omitempty"` Location *Location `json:"location,omitempty"` @@ -85,6 +86,7 @@ func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node { node.MAC = nodeinfo.Network.Mac node.Addresses = nodeinfo.Network.Addresses node.SiteCode = nodeinfo.System.SiteCode + node.DomainCode = nodeinfo.System.DomainCode node.Hostname = nodeinfo.Hostname if owner := nodeinfo.Owner; owner != nil { node.Owner = owner.Contact diff --git a/respond/collector.go b/respond/collector.go index 5fed2008..b2581581 100644 --- a/respond/collector.go +++ b/respond/collector.go @@ -21,25 +21,25 @@ type Collector struct { ifaceToConn map[string]*net.UDPConn // map from interface name to UDP socket port int - queue chan *Response // received responses - db database.Connection - nodes *runtime.Nodes - sites []string - interval time.Duration // Interval for multicast packets - stop chan interface{} + queue chan *Response // received responses + db database.Connection + nodes *runtime.Nodes + sitesDomains map[string][]string + interval time.Duration // Interval for multicast packets + stop chan interface{} } // NewCollector creates a Collector struct -func NewCollector(db database.Connection, nodes *runtime.Nodes, sites []string, ifaces []string, port int) *Collector { +func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []string, port int) *Collector { coll := &Collector{ - db: db, - nodes: nodes, - sites: sites, - port: port, - queue: make(chan *Response, 400), - stop: make(chan interface{}), - ifaceToConn: make(map[string]*net.UDPConn), + db: db, + nodes: nodes, + sitesDomains: sitesDomains, + port: port, + queue: make(chan *Response, 400), + stop: make(chan interface{}), + ifaceToConn: make(map[string]*net.UDPConn), } for _, iface := range ifaces { @@ -302,9 +302,11 @@ func (coll *Collector) globalStatsWorker() { // saves global statistics func (coll *Collector) saveGlobalStats() { - stats := runtime.NewGlobalStats(coll.nodes, coll.sites) + stats := runtime.NewGlobalStats(coll.nodes, coll.sitesDomains) - for site, stat := range stats { - coll.db.InsertGlobals(stat, time.Now(), site) + for site, domains := range stats { + for domain, stat := range domains { + coll.db.InsertGlobals(stat, time.Now(), site, domain) + } } } diff --git a/respond/collector_test.go b/respond/collector_test.go index d6e5bd99..82dca235 100644 --- a/respond/collector_test.go +++ b/respond/collector_test.go @@ -9,12 +9,15 @@ import ( "github.com/stretchr/testify/assert" ) -const SITE_TEST = "ffxx" +const ( + SITE_TEST = "ffhb" + DOMAIN_TEST = "city" +) func TestCollector(t *testing.T) { nodes := runtime.NewNodes(&runtime.NodesConfig{}) - collector := NewCollector(nil, nodes, []string{SITE_TEST}, []string{}, 10001) + collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []string{}, 10001) collector.Start(time.Millisecond) time.Sleep(time.Millisecond * 10) collector.Close() diff --git a/respond/config.go b/respond/config.go index b221fa15..1a523134 100644 --- a/respond/config.go +++ b/respond/config.go @@ -3,10 +3,22 @@ package respond import "github.com/FreifunkBremen/yanic/lib/duration" type Config struct { - Enable bool `toml:"enable"` - Synchronize duration.Duration `toml:"synchronize"` - Interfaces []string `toml:"interfaces"` - Sites []string `toml:"sites"` - Port int `toml:"port"` - CollectInterval duration.Duration `toml:"collect_interval"` + Enable bool `toml:"enable"` + Synchronize duration.Duration `toml:"synchronize"` + Interfaces []string `toml:"interfaces"` + Sites map[string]SiteConfig `toml:"sites"` + Port int `toml:"port"` + CollectInterval duration.Duration `toml:"collect_interval"` +} + +func (c *Config) SitesDomains() (result map[string][]string) { + result = make(map[string][]string) + for site, siteConfig := range c.Sites { + result[site] = siteConfig.Domains + } + return +} + +type SiteConfig struct { + Domains []string `toml:"domains"` } diff --git a/respond/config_test.go b/respond/config_test.go new file mode 100644 index 00000000..69e4fc9d --- /dev/null +++ b/respond/config_test.go @@ -0,0 +1,24 @@ +package respond + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSitesDomainsConfigTransform(t *testing.T) { + assert := assert.New(t) + c := Config{ + Sites: map[string]SiteConfig{ + "ffhb": {Domains: []string{"city"}}, + }, + } + result := c.SitesDomains() + assert.Len(result, 1) + assert.Contains(result, "ffhb") + + domains := result["ffhb"] + + assert.Len(domains, 1) + assert.Equal("city", domains[0]) +} diff --git a/runtime/stats.go b/runtime/stats.go index 506a0f9f..145d8bb2 100644 --- a/runtime/stats.go +++ b/runtime/stats.go @@ -3,6 +3,7 @@ package runtime const ( DISABLED_AUTOUPDATER = "disabled" GLOBAL_SITE = "global" + GLOBAL_DOMAIN = "global" ) // CounterMap to manage multiple values @@ -23,32 +24,45 @@ type GlobalStats struct { } //NewGlobalStats returns global statistics for InfluxDB -func NewGlobalStats(nodes *Nodes, sites []string) (result map[string]*GlobalStats) { - result = make(map[string]*GlobalStats) +func NewGlobalStats(nodes *Nodes, sitesDomains map[string][]string) (result map[string]map[string]*GlobalStats) { + result = make(map[string]map[string]*GlobalStats) - result[GLOBAL_SITE] = &GlobalStats{ + result[GLOBAL_SITE] = make(map[string]*GlobalStats) + result[GLOBAL_SITE][GLOBAL_DOMAIN] = &GlobalStats{ Firmwares: make(CounterMap), Models: make(CounterMap), Autoupdater: make(CounterMap), } - for _, site := range sites { - result[site] = &GlobalStats{ + for site, domains := range sitesDomains { + result[site] = make(map[string]*GlobalStats) + result[site][GLOBAL_DOMAIN] = &GlobalStats{ Firmwares: make(CounterMap), Models: make(CounterMap), Autoupdater: make(CounterMap), } + for _, domain := range domains { + result[site][domain] = &GlobalStats{ + Firmwares: make(CounterMap), + Models: make(CounterMap), + Autoupdater: make(CounterMap), + } + } } nodes.RLock() for _, node := range nodes.List { if node.Online { - result[GLOBAL_SITE].Add(node) + result[GLOBAL_SITE][GLOBAL_DOMAIN].Add(node) if info := node.Nodeinfo; info != nil { site := info.System.SiteCode - if _, exist := result[site]; exist { - result[site].Add(node) + domain := info.System.DomainCode + if _, ok := result[site]; ok { + result[site][GLOBAL_DOMAIN].Add(node) + if _, ok := result[site][domain]; ok { + result[site][domain].Add(node) + } } } } diff --git a/runtime/stats_test.go b/runtime/stats_test.go index e25ac626..93363e86 100644 --- a/runtime/stats_test.go +++ b/runtime/stats_test.go @@ -8,49 +8,70 @@ import ( "github.com/FreifunkBremen/yanic/data" ) -const TEST_SITE = "ffxx" +const ( + TEST_SITE = "ffhb" + TEST_DOMAIN = "city" +) func TestGlobalStats(t *testing.T) { - stats := NewGlobalStats(createTestNodes(), []string{TEST_SITE}) + stats := NewGlobalStats(createTestNodes(), map[string][]string{TEST_SITE: {TEST_DOMAIN}}) assert := assert.New(t) assert.Len(stats, 2) //check GLOBAL_SITE stats - assert.EqualValues(1, stats[GLOBAL_SITE].Gateways) - assert.EqualValues(3, stats[GLOBAL_SITE].Nodes) - assert.EqualValues(25, stats[GLOBAL_SITE].Clients) + assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Gateways) + assert.EqualValues(3, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Nodes) + assert.EqualValues(25, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Clients) // check models - assert.Len(stats[GLOBAL_SITE].Models, 2) - assert.EqualValues(2, stats[GLOBAL_SITE].Models["TP-Link 841"]) - assert.EqualValues(1, stats[GLOBAL_SITE].Models["Xeon Multi-Core"]) + assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models, 2) + assert.EqualValues(2, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models["TP-Link 841"]) + assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Models["Xeon Multi-Core"]) // check firmwares - assert.Len(stats[GLOBAL_SITE].Firmwares, 1) - assert.EqualValues(1, stats[GLOBAL_SITE].Firmwares["2016.1.6+entenhausen1"]) + assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Firmwares, 1) + assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Firmwares["2016.1.6+entenhausen1"]) // check autoupdater - assert.Len(stats[GLOBAL_SITE].Autoupdater, 2) - assert.EqualValues(1, stats[GLOBAL_SITE].Autoupdater["stable"]) + assert.Len(stats[GLOBAL_SITE][GLOBAL_DOMAIN].Autoupdater, 2) + assert.EqualValues(1, stats[GLOBAL_SITE][GLOBAL_DOMAIN].Autoupdater["stable"]) // check TEST_SITE stats - assert.EqualValues(1, stats[TEST_SITE].Gateways) - assert.EqualValues(2, stats[TEST_SITE].Nodes) - assert.EqualValues(23, stats[TEST_SITE].Clients) + assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Gateways) + assert.EqualValues(2, stats[TEST_SITE][GLOBAL_DOMAIN].Nodes) + assert.EqualValues(23, stats[TEST_SITE][GLOBAL_DOMAIN].Clients) // check models - assert.Len(stats[TEST_SITE].Models, 2) - assert.EqualValues(1, stats[TEST_SITE].Models["TP-Link 841"]) - assert.EqualValues(1, stats[TEST_SITE].Models["Xeon Multi-Core"]) + assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Models, 2) + assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Models["TP-Link 841"]) + assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Models["Xeon Multi-Core"]) // check firmwares - assert.Len(stats[TEST_SITE].Firmwares, 1) - assert.EqualValues(1, stats[TEST_SITE].Firmwares["2016.1.6+entenhausen1"]) + assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Firmwares, 1) + assert.EqualValues(1, stats[TEST_SITE][GLOBAL_DOMAIN].Firmwares["2016.1.6+entenhausen1"]) // check autoupdater - assert.Len(stats[TEST_SITE].Autoupdater, 1) - assert.EqualValues(0, stats[TEST_SITE].Autoupdater["stable"]) + assert.Len(stats[TEST_SITE][GLOBAL_DOMAIN].Autoupdater, 1) + assert.EqualValues(0, stats[TEST_SITE][GLOBAL_DOMAIN].Autoupdater["stable"]) + + // check TEST_DOMAIN stats + assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Gateways) + assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Nodes) + assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Clients) + + // check models + assert.Len(stats[TEST_SITE][TEST_DOMAIN].Models, 1) + assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Models["TP-Link 841"]) + assert.EqualValues(1, stats[TEST_SITE][TEST_DOMAIN].Models["Xeon Multi-Core"]) + + // check firmwares + assert.Len(stats[TEST_SITE][TEST_DOMAIN].Firmwares, 0) + assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Firmwares["2016.1.6+entenhausen1"]) + + // check autoupdater + assert.Len(stats[TEST_SITE][TEST_DOMAIN].Autoupdater, 1) + assert.EqualValues(0, stats[TEST_SITE][TEST_DOMAIN].Autoupdater["stable"]) } func createTestNodes() *Nodes { @@ -109,7 +130,8 @@ func createTestNodes() *Nodes { Model: "Xeon Multi-Core", }, System: data.System{ - SiteCode: TEST_SITE, + SiteCode: TEST_SITE, + DomainCode: TEST_DOMAIN, }, }, }) From c660e175f0259b06d1683f81316d838f69a63bc4 Mon Sep 17 00:00:00 2001 From: Martin Geno Date: Tue, 5 Dec 2017 23:17:49 +0100 Subject: [PATCH 2/2] [TASK] make yanic babel compatible (#104) --- cmd/config_test.go | 2 +- cmd/query.go | 43 ++++++++-- cmd/serve.go | 2 +- config_example.toml | 19 +++-- data/neighbours.go | 17 ++++ data/nodeinfo.go | 18 +++-- data/nodeinfo_test.go | 4 +- database/influxdb/link.go | 6 +- database/influxdb/node_test.go | 8 +- docs/docs_configuration.md | 82 +++++++++++++++---- docs/docs_quick_conf.md | 2 +- docs/home_about.md | 2 +- output/meshviewer-ffrgb/meshviewer.go | 90 +++++++++++++-------- output/meshviewer-ffrgb/meshviewer_test.go | 18 ++--- output/meshviewer-ffrgb/struct.go | 20 ++--- respond/collector.go | 94 +++++++++++++++------- respond/collector_test.go | 2 +- respond/config.go | 10 ++- respond/respond.go | 6 +- runtime/node.go | 10 +-- runtime/nodes.go | 41 ++++++---- runtime/nodes_test.go | 10 +-- 22 files changed, 344 insertions(+), 162 deletions(-) diff --git a/cmd/config_test.go b/cmd/config_test.go index eaa95f11..ce6cd61a 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -15,7 +15,7 @@ func TestReadConfig(t *testing.T) { assert.NotNil(config) assert.True(config.Respondd.Enable) - assert.Equal([]string{"br-ffhb"}, config.Respondd.Interfaces) + assert.Equal("br-ffhb", config.Respondd.Interfaces[0].InterfaceName) assert.Equal(time.Minute, config.Respondd.CollectInterval.Duration) assert.Equal(time.Hour*24*7, config.Nodes.PruneAfter.Duration) assert.Equal(time.Hour*24*7, config.Database.DeleteAfter.Duration) diff --git a/cmd/query.go b/cmd/query.go index 9ab3785a..7478d63c 100644 --- a/cmd/query.go +++ b/cmd/query.go @@ -1,8 +1,10 @@ package cmd import ( + "encoding/json" "log" "net" + "strings" "time" "github.com/FreifunkBremen/yanic/respond" @@ -10,32 +12,55 @@ import ( "github.com/spf13/cobra" ) -var wait int +var ( + wait int + port int + ipAddress string +) // queryCmd represents the query command var queryCmd = &cobra.Command{ - Use: "query ", + Use: "query ", Short: "Sends a query on the interface to the destination and waits for a response", - Example: `yanic query wlan0 "fe80::eade:27ff:dead:beef"`, + Example: `yanic query "eth0,wlan0" "fe80::eade:27ff:dead:beef"`, Args: cobra.ExactArgs(2), Run: func(cmd *cobra.Command, args []string) { - iface := args[0] + ifaces := strings.Split(args[0], ",") dstAddress := net.ParseIP(args[1]) - log.Printf("Sending request address=%s iface=%s", dstAddress, iface) + log.Printf("Sending request address=%s ifaces=%s", dstAddress, ifaces) + + var ifacesConfigs []respond.InterfaceConfig + for _, iface := range ifaces { + ifaceConfig := respond.InterfaceConfig{ + InterfaceName: iface, + Port: port, + IPAddress: ipAddress, + } + ifacesConfigs = append(ifacesConfigs, ifaceConfig) + } nodes := runtime.NewNodes(&runtime.NodesConfig{}) sitesDomains := make(map[string][]string) - - collector := respond.NewCollector(nil, nodes, sitesDomains, []string{iface}, 0) + collector := respond.NewCollector(nil, nodes, sitesDomains, ifacesConfigs) defer collector.Close() collector.SendPacket(dstAddress) time.Sleep(time.Second * time.Duration(wait)) for id, data := range nodes.List { - log.Printf("%s: %+v", id, data) + jq, err := json.Marshal(data) + if err != nil { + log.Printf("%s: %+v", id, data) + } else { + jqNeighbours, err := json.Marshal(data.Neighbours) + if err != nil { + log.Printf("%s: %s neighbours: %+v", id, string(jq), data.Neighbours) + } else { + log.Printf("%s: %s neighbours: %s", id, string(jq), string(jqNeighbours)) + } + } } }, } @@ -43,4 +68,6 @@ var queryCmd = &cobra.Command{ func init() { RootCmd.AddCommand(queryCmd) queryCmd.Flags().IntVar(&wait, "wait", 1, "Seconds to wait for a response") + queryCmd.Flags().IntVar(&port, "port", 0, "define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)") + queryCmd.Flags().StringVar(&ipAddress, "ip", "", "ip address which is used for sending (optional - without definition used the link-local address)") } diff --git a/cmd/serve.go b/cmd/serve.go index 36edaf89..d81eae80 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -54,7 +54,7 @@ var serveCmd = &cobra.Command{ time.Sleep(delay) } - collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces, config.Respondd.Port) + collector = respond.NewCollector(allDatabase.Conn, nodes, config.Respondd.SitesDomains(), config.Respondd.Interfaces) collector.Start(config.Respondd.CollectInterval.Duration) defer collector.Close() } diff --git a/config_example.toml b/config_example.toml index bc766301..e80203b5 100644 --- a/config_example.toml +++ b/config_example.toml @@ -9,11 +9,6 @@ enable = true synchronize = "1m" # how often request per multicast collect_interval = "1m" -# interface that has an IP in your mesh network -interfaces = ["br-ffhb"] -# define a port to listen -# if not set or set to 0 the kernel will use a random free port at its own -#port = 10001 # table of a site to save stats for (not exists for global only) #[respondd.sites.example] @@ -23,6 +18,20 @@ interfaces = ["br-ffhb"] [respondd.sites.ffhb] domains = ["city"] +# interface that has an IP in your mesh network +[[respondd.interfaces]] +# name of interface on which this collector is running +ifname = "br-ffhb" +# ip address which is used for sending +# (optional - without definition used a address of ifname) +ip_address = "fd2f:5119:f2d::5" +# multicast address to destination of respondd +# (optional - without definition used batman default ff02::2:1001) +multicast_address = "ff05::2:1001" +# define a port to listen +# if not set or set to 0 the kernel will use a random free port at its own +#port = 10001 + # A little build-in webserver, which statically serves a directory. # This is useful for testing purposes or for a little standalone installation. [webserver] diff --git a/data/neighbours.go b/data/neighbours.go index 08fd0d23..b45aea47 100644 --- a/data/neighbours.go +++ b/data/neighbours.go @@ -3,6 +3,7 @@ package data // Neighbours struct type Neighbours struct { Batadv map[string]BatadvNeighbours `json:"batadv"` + Babel map[string]BabelNeighbours `json:"babel"` LLDP map[string]LLDPNeighbours `json:"lldp"` //WifiNeighbours map[string]WifiNeighbours `json:"wifi"` NodeID string `json:"node_id"` @@ -27,11 +28,27 @@ type LLDPLink struct { Description string `json:"descr"` } +// BabelLink struct +type BabelLink struct { + // How need this: + RXCost int `json:"rxcost"` + TXCost int `json:"txcost"` + Cost int `json:"cost"` + Reachability int `json:"reachability"` +} + // BatadvNeighbours struct type BatadvNeighbours struct { Neighbours map[string]BatmanLink `json:"neighbours"` } +// BabelNeighbours struct +type BabelNeighbours struct { + Protocol string `json:"protocol"` + LinkLocalAddress string `json:"ll-addr"` + Neighbours map[string]BabelLink `json:"neighbours"` +} + // WifiNeighbours struct type WifiNeighbours struct { Neighbours map[string]WifiLink `json:"neighbours"` diff --git a/data/nodeinfo.go b/data/nodeinfo.go index 3f9fb29c..1b8e0913 100644 --- a/data/nodeinfo.go +++ b/data/nodeinfo.go @@ -14,8 +14,8 @@ type NodeInfo struct { Wireless *Wireless `json:"wireless,omitempty"` } -// BatInterface struct -type BatInterface struct { +// NetworkInterface struct +type NetworkInterface struct { Interfaces struct { Wireless []string `json:"wireless,omitempty"` Other []string `json:"other,omitempty"` @@ -24,16 +24,17 @@ type BatInterface struct { } // Addresses returns a flat list of all MAC addresses -func (iface *BatInterface) Addresses() []string { +func (iface *NetworkInterface) Addresses() []string { return append(append(iface.Interfaces.Other, iface.Interfaces.Tunnel...), iface.Interfaces.Wireless...) } // Network struct type Network struct { - Mac string `json:"mac"` - Addresses []string `json:"addresses"` - Mesh map[string]*BatInterface `json:"mesh"` - MeshInterfaces []string `json:"mesh_interfaces"` + Mac string `json:"mac"` + Addresses []string `json:"addresses"` + Mesh map[string]*NetworkInterface `json:"mesh"` + // still used in gluon? + MeshInterfaces []string `json:"mesh_interfaces"` } // Owner struct @@ -64,6 +65,9 @@ type Software struct { Version string `json:"version,omitempty"` Compat int `json:"compat,omitempty"` } `json:"batman-adv,omitempty"` + Babeld struct { + Version string `json:"version,omitempty"` + } `json:"babeld,omitempty"` Fastd struct { Enabled bool `json:"enabled,omitempty"` Version string `json:"version,omitempty"` diff --git a/data/nodeinfo_test.go b/data/nodeinfo_test.go index 40f78897..b32974b6 100644 --- a/data/nodeinfo_test.go +++ b/data/nodeinfo_test.go @@ -8,7 +8,7 @@ import ( func TestNodeinfoBatAddresses(t *testing.T) { assert := assert.New(t) - batIface := &BatInterface{ + iface := &NetworkInterface{ Interfaces: struct { Wireless []string `json:"wireless,omitempty"` Other []string `json:"other,omitempty"` @@ -20,7 +20,7 @@ func TestNodeinfoBatAddresses(t *testing.T) { }, } - addr := batIface.Addresses() + addr := iface.Addresses() assert.NotNil(addr) assert.Equal([]string{"aa:aa:aa:aa:aa", "aa:aa:aa:aa:ab"}, addr) } diff --git a/database/influxdb/link.go b/database/influxdb/link.go index 3d504685..9e543037 100644 --- a/database/influxdb/link.go +++ b/database/influxdb/link.go @@ -11,9 +11,9 @@ import ( func (conn *Connection) InsertLink(link *runtime.Link, t time.Time) { tags := models.Tags{} tags.SetString("source.id", link.SourceID) - tags.SetString("source.mac", link.SourceMAC) + tags.SetString("source.addr", link.SourceAddress) tags.SetString("target.id", link.TargetID) - tags.SetString("target.mac", link.TargetMAC) + tags.SetString("target.addr", link.TargetAddress) - conn.addPoint(MeasurementLink, tags, models.Fields{"tq": float32(link.TQ) / 2.55}, t) + conn.addPoint(MeasurementLink, tags, models.Fields{"tq": link.TQ * 100}, t) } diff --git a/database/influxdb/node_test.go b/database/influxdb/node_test.go index 47cb5fc0..58ba6941 100644 --- a/database/influxdb/node_test.go +++ b/database/influxdb/node_test.go @@ -158,10 +158,10 @@ func TestToInflux(t *testing.T) { fields, _ = nPoint.Fields() assert.EqualValues("link", nPoint.Name()) assert.EqualValues(map[string]string{ - "source.id": "deadbeef", - "source.mac": "a-interface", - "target.id": "foobar", - "target.mac": "BAFF1E5", + "source.id": "deadbeef", + "source.addr": "a-interface", + "target.id": "foobar", + "target.addr": "BAFF1E5", }, tags) assert.EqualValues(80, fields["tq"]) diff --git a/docs/docs_configuration.md b/docs/docs_configuration.md index fce86d14..1fcfecf1 100644 --- a/docs/docs_configuration.md +++ b/docs/docs_configuration.md @@ -14,10 +14,15 @@ Group for configuration of respondd request. enable = true # synchronize = "1m" collect_interval = "1m" -interfaces = ["br-ffhb"] -#port = 10001 + #[respondd.sites.example] #domains = ["city"] + +[[respondd.interfaces]] +ifname = "br-ffhb" +#ip_address = "fe80::..." +#multicast_address = "ff02::2:1001" +#port = 10001 ``` {% endmethod %} @@ -46,7 +51,7 @@ synchronize = "1m" {% method %} How often send request per respondd. -It will send UDP packets with multicast group `ff02::2:1001` and port `1001`. +It will send UDP packets with multicast address `ff02::2:1001` and port `1001`. If a node does not answer after the half time, it will request with the last know address under the port `1001`. {% sample lang="toml" %} ```toml @@ -55,26 +60,16 @@ collect_interval = "1m" {% endmethod %} -### interfaces +### sites {% method %} -Interface that has an IP in your mesh network +List of sites to save stats for (empty for global only) {% sample lang="toml" %} ```toml -interfaces = ["br-ffhb"] +sites = ["ffhb"] ``` {% endmethod %} -### port -{% method %} -Define a port to listen and send the respondd packages. -If not set or set to 0 the kernel will use a random free port at its own. -{% sample lang="toml" %} -```toml -port = 10001 -``` -{% endmethod %} - ### [respondd.sites.example] {% method %} Tables of sites to save stats for (not exists for global only). @@ -85,6 +80,7 @@ Here is the site _ffhb_. domains = ["city"] ``` {% endmethod %} + #### domains {% method %} list of domains on this site to save stats for (empty for global only) @@ -95,6 +91,60 @@ domains = ["city"] {% endmethod %} +### [[respondd.interfaces]] +{% method %} +Interface that has an ip address in your mesh network. +It is possible to have multiple interfaces, just add this group again with new parameters (see toml [[array of table]]). +{% sample lang="toml" %} +```toml +[[respondd.interfaces]] +ifname = "br-ffhb" +#ip_address = "fe80::..." +#multicast_address = "ff02::2:1001" +#port = 10001 +``` +{% endmethod %} + +### ifname +{% method %} +name of interface on which this collector is running. +{% sample lang="toml" %} +```toml +ifname = "br-ffhb" +``` +{% endmethod %} + +### ip_address +{% method %} +ip address is the own address which is used for sending. +If not set or set with empty string it will take an address of ifname. +{% sample lang="toml" %} +```toml +ip_address = "fe80::..." +``` +{% endmethod %} + +### multicast_address +{% method %} +Multicast address to destination of respondd. +If not set or set with empty string it will take the batman default multicast address `ff02::2:1001` +(Needed in babel for a mesh-network wide routeable multicast addreess `ff05::2:1001`) +{% sample lang="toml" %} +```toml +multicast_address = "ff02::2:1001" +``` +{% endmethod %} + +### port +{% method %} +Define a port to listen and send the respondd packages. +If not set or set to 0 the kernel will use a random free port at its own. +{% sample lang="toml" %} +```toml +port = 10001 +``` +{% endmethod %} + ## [webserver] {% method %} diff --git a/docs/docs_quick_conf.md b/docs/docs_quick_conf.md index 4fbb45bf..eb83b1c3 100644 --- a/docs/docs_quick_conf.md +++ b/docs/docs_quick_conf.md @@ -5,7 +5,7 @@ cp /opt/go/src/github.com/FreifunkBremen/yanic/config_example.toml /etc/yanic.co ``` # Quick configuration -For an easy startup you only need to edit the `interfaces` in section +For an easy startup you only need to edit the `[[respondd.interfaces]]` in section `[respondd]` in file `/etc/yanic.conf`. Then create the following files and folders: diff --git a/docs/home_about.md b/docs/home_about.md index 6ed79711..86bcea1a 100644 --- a/docs/home_about.md +++ b/docs/home_about.md @@ -7,7 +7,7 @@ A little overview of yanic in connection with other software: It sends the `gluon-neighbour-info` request and collects the answers. -It will send UDP packets with multicast group `ff02:0:0:0:0:0:2:1001` and port `1001`. +It will send UDP packets with multicast address `ff02:0:0:0:0:0:2:1001` and port `1001`. If a node does not answer, it will request with the last know address under the port `1001`. diff --git a/output/meshviewer-ffrgb/meshviewer.go b/output/meshviewer-ffrgb/meshviewer.go index 15d26206..cf9d01cd 100644 --- a/output/meshviewer-ffrgb/meshviewer.go +++ b/output/meshviewer-ffrgb/meshviewer.go @@ -9,6 +9,12 @@ import ( "github.com/FreifunkBremen/yanic/runtime" ) +const ( + LINK_TYPE_WIRELESS = "wifi" + LINK_TYPE_TUNNEL = "vpn" + LINK_TYPE_FALLBACK = "other" +) + func transform(nodes *runtime.Nodes) *Meshviewer { meshviewer := &Meshviewer{ @@ -34,11 +40,11 @@ func transform(nodes *runtime.Nodes) *Meshviewer { if nodeinfo := nodeOrigin.Nodeinfo; nodeinfo != nil { if meshes := nodeinfo.Network.Mesh; meshes != nil { for _, mesh := range meshes { - for _, mac := range mesh.Interfaces.Wireless { - typeList[mac] = "wifi" + for _, addr := range mesh.Interfaces.Wireless { + typeList[addr] = LINK_TYPE_WIRELESS } - for _, mac := range mesh.Interfaces.Tunnel { - typeList[mac] = "vpn" + for _, addr := range mesh.Interfaces.Tunnel { + typeList[addr] = LINK_TYPE_TUNNEL } } } @@ -47,52 +53,70 @@ func transform(nodes *runtime.Nodes) *Meshviewer { for _, linkOrigin := range nodes.NodeLinks(nodeOrigin) { var key string // keep source and target in the same order - switchSourceTarget := strings.Compare(linkOrigin.SourceMAC, linkOrigin.TargetMAC) > 0 + switchSourceTarget := strings.Compare(linkOrigin.SourceAddress, linkOrigin.TargetAddress) > 0 if switchSourceTarget { - key = fmt.Sprintf("%s-%s", linkOrigin.SourceMAC, linkOrigin.TargetMAC) + key = fmt.Sprintf("%s-%s", linkOrigin.SourceAddress, linkOrigin.TargetAddress) } else { - key = fmt.Sprintf("%s-%s", linkOrigin.TargetMAC, linkOrigin.SourceMAC) + key = fmt.Sprintf("%s-%s", linkOrigin.TargetAddress, linkOrigin.SourceAddress) } + if link := links[key]; link != nil { + linkType, linkTypeFound := typeList[linkOrigin.SourceAddress] + if !linkTypeFound { + linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] + } + if switchSourceTarget { - link.TargetTQ = float32(linkOrigin.TQ) / 255.0 - if link.Type == "other" { - link.Type = typeList[linkOrigin.TargetMAC] - } else if link.Type != typeList[linkOrigin.TargetMAC] { - log.Printf("different linktypes %s:%s current: %s source: %s target: %s", linkOrigin.SourceMAC, linkOrigin.TargetMAC, link.Type, typeList[linkOrigin.SourceMAC], typeList[linkOrigin.TargetMAC]) + link.TargetTQ = linkOrigin.TQ + + linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] + if !linkTypeFound { + linkType, linkTypeFound = typeList[linkOrigin.SourceAddress] } } else { - link.SourceTQ = float32(linkOrigin.TQ) / 255.0 - if link.Type == "other" { - link.Type = typeList[linkOrigin.SourceMAC] - } else if link.Type != typeList[linkOrigin.SourceMAC] { - log.Printf("different linktypes %s:%s current: %s source: %s target: %s", linkOrigin.SourceMAC, linkOrigin.TargetMAC, link.Type, typeList[linkOrigin.SourceMAC], typeList[linkOrigin.TargetMAC]) - } + link.SourceTQ = linkOrigin.TQ } - if link.Type == "" { - link.Type = "other" + + if linkTypeFound && linkType != link.Type { + if link.Type == LINK_TYPE_FALLBACK { + link.Type = linkType + } else { + log.Printf("different linktypes for '%s' - '%s' prev: '%s' new: '%s' source: '%s' target: '%s'", linkOrigin.SourceAddress, linkOrigin.TargetAddress, link.Type, linkType, typeList[linkOrigin.SourceAddress], typeList[linkOrigin.TargetAddress]) + } } + continue } - tq := float32(linkOrigin.TQ) / 255.0 link := &Link{ - Type: typeList[linkOrigin.SourceMAC], - Source: linkOrigin.SourceID, - SourceMAC: linkOrigin.SourceMAC, - Target: linkOrigin.TargetID, - TargetMAC: linkOrigin.TargetMAC, - SourceTQ: tq, - TargetTQ: tq, + Source: linkOrigin.SourceID, + SourceAddress: linkOrigin.SourceAddress, + Target: linkOrigin.TargetID, + TargetAddress: linkOrigin.TargetAddress, + SourceTQ: linkOrigin.TQ, + TargetTQ: linkOrigin.TQ, + } + + linkType, linkTypeFound := typeList[linkOrigin.SourceAddress] + if !linkTypeFound { + linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] } + if switchSourceTarget { - link.Type = typeList[linkOrigin.TargetMAC] link.Source = linkOrigin.TargetID - link.SourceMAC = linkOrigin.TargetMAC + link.SourceAddress = linkOrigin.TargetAddress link.Target = linkOrigin.SourceID - link.TargetMAC = linkOrigin.SourceMAC + link.TargetAddress = linkOrigin.SourceAddress + + linkType, linkTypeFound = typeList[linkOrigin.TargetAddress] + if !linkTypeFound { + linkType, linkTypeFound = typeList[linkOrigin.SourceAddress] + } } - if link.Type == "" { - link.Type = "other" + + if linkTypeFound { + link.Type = linkType + } else { + link.Type = LINK_TYPE_FALLBACK } links[key] = link meshviewer.Links = append(meshviewer.Links, link) diff --git a/output/meshviewer-ffrgb/meshviewer_test.go b/output/meshviewer-ffrgb/meshviewer_test.go index 80f80836..7fcde4af 100644 --- a/output/meshviewer-ffrgb/meshviewer_test.go +++ b/output/meshviewer-ffrgb/meshviewer_test.go @@ -18,7 +18,7 @@ func TestTransform(t *testing.T) { NodeID: "node_a", Network: data.Network{ Mac: "node:a:mac", - Mesh: map[string]*data.BatInterface{ + Mesh: map[string]*data.NetworkInterface{ "bat0": { Interfaces: struct { Wireless []string `json:"wireless,omitempty"` @@ -55,7 +55,7 @@ func TestTransform(t *testing.T) { NodeID: "node_c", Network: data.Network{ Mac: "node:c:mac", - Mesh: map[string]*data.BatInterface{ + Mesh: map[string]*data.NetworkInterface{ "bat0": { Interfaces: struct { Wireless []string `json:"wireless,omitempty"` @@ -85,7 +85,7 @@ func TestTransform(t *testing.T) { NodeID: "node_b", Network: data.Network{ Mac: "node:b:mac", - Mesh: map[string]*data.BatInterface{ + Mesh: map[string]*data.NetworkInterface{ "bat0": { Interfaces: struct { Wireless []string `json:"wireless,omitempty"` @@ -121,7 +121,7 @@ func TestTransform(t *testing.T) { NodeID: "node_d", Network: data.Network{ Mac: "node:d:mac", - Mesh: map[string]*data.BatInterface{ + Mesh: map[string]*data.NetworkInterface{ "bat0": { Interfaces: struct { Wireless []string `json:"wireless,omitempty"` @@ -159,27 +159,27 @@ func TestTransform(t *testing.T) { assert.Len(links, 3) for _, link := range links { - switch link.SourceMAC { + switch link.SourceAddress { case "node:a:mac:lan": assert.Equal("other", link.Type) - assert.Equal("node:b:mac:lan", link.TargetMAC) + assert.Equal("node:b:mac:lan", link.TargetAddress) assert.Equal(float32(0.2), link.SourceTQ) assert.Equal(float32(0.2), link.TargetTQ) break case "node:a:mac:wifi": assert.Equal("wifi", link.Type) - assert.Equal("node:b:mac:wifi", link.TargetMAC) + assert.Equal("node:b:mac:wifi", link.TargetAddress) assert.Equal(float32(0.6), link.SourceTQ) assert.Equal(float32(0.8), link.TargetTQ) case "node:b:mac:lan": assert.Equal("other", link.Type) - assert.Equal("node:c:mac:lan", link.TargetMAC) + assert.Equal("node:c:mac:lan", link.TargetAddress) assert.Equal(float32(0.8), link.SourceTQ) assert.Equal(float32(0.4), link.TargetTQ) break default: - assert.False(true, "invalid link.SourceMAC found") + assert.False(true, "invalid link.SourceAddress found") } } } diff --git a/output/meshviewer-ffrgb/struct.go b/output/meshviewer-ffrgb/struct.go index e39e7757..f8975bcf 100644 --- a/output/meshviewer-ffrgb/struct.go +++ b/output/meshviewer-ffrgb/struct.go @@ -64,13 +64,13 @@ type Location struct { // Link type Link struct { - Type string `json:"type"` - Source string `json:"source"` - Target string `json:"target"` - SourceTQ float32 `json:"source_tq"` - TargetTQ float32 `json:"target_tq"` - SourceMAC string `json:"source_mac"` - TargetMAC string `json:"target_mac"` + Type string `json:"type"` + Source string `json:"source"` + Target string `json:"target"` + SourceTQ float32 `json:"source_tq"` + TargetTQ float32 `json:"target_tq"` + SourceAddress string `json:"source_addr"` + TargetAddress string `json:"target_addr"` } func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node { @@ -134,15 +134,15 @@ func NewNode(nodes *runtime.Nodes, n *runtime.Node) *Node { } node.Uptime = jsontime.Now().Add(time.Duration(statistic.Uptime) * -time.Second) - node.GatewayNexthop = nodes.GetNodeIDbyMAC(statistic.GatewayNexthop) + node.GatewayNexthop = nodes.GetNodeIDbyAddress(statistic.GatewayNexthop) if node.GatewayNexthop == "" { node.GatewayNexthop = statistic.GatewayNexthop } - node.GatewayIPv4 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv4) + node.GatewayIPv4 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv4) if node.GatewayIPv4 == "" { node.GatewayIPv4 = statistic.GatewayIPv4 } - node.GatewayIPv6 = nodes.GetNodeIDbyMAC(statistic.GatewayIPv6) + node.GatewayIPv6 = nodes.GetNodeIDbyAddress(statistic.GatewayIPv6) if node.GatewayIPv6 == "" { node.GatewayIPv6 = statistic.GatewayIPv6 } diff --git a/respond/collector.go b/respond/collector.go index b2581581..1727bab7 100644 --- a/respond/collector.go +++ b/respond/collector.go @@ -17,8 +17,7 @@ import ( // Collector for a specificle respond messages type Collector struct { - connections []*net.UDPConn // UDP sockets - ifaceToConn map[string]*net.UDPConn // map from interface name to UDP socket + connections []multicastConn // UDP sockets port int queue chan *Response // received responses @@ -29,17 +28,20 @@ type Collector struct { stop chan interface{} } +type multicastConn struct { + Conn *net.UDPConn + MulticastAddress net.IP +} + // NewCollector creates a Collector struct -func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []string, port int) *Collector { +func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map[string][]string, ifaces []InterfaceConfig) *Collector { coll := &Collector{ db: db, nodes: nodes, sitesDomains: sitesDomains, - port: port, queue: make(chan *Response, 400), stop: make(chan interface{}), - ifaceToConn: make(map[string]*net.UDPConn), } for _, iface := range ifaces { @@ -55,35 +57,47 @@ func NewCollector(db database.Connection, nodes *runtime.Nodes, sitesDomains map return coll } -func (coll *Collector) listenUDP(iface string) { - if _, found := coll.ifaceToConn[iface]; found { - log.Panicf("can not listen twice on %s", iface) +func (coll *Collector) listenUDP(iface InterfaceConfig) { + + var addr net.IP + + var err error + if iface.IPAddress != "" { + addr = net.ParseIP(iface.IPAddress) + } else { + addr, err = getUnicastAddr(iface.InterfaceName) + if err != nil { + log.Panic(err) + } } - linkLocalAddr, err := getLinkLocalAddr(iface) - if err != nil { - log.Panic(err) + + multicastAddress := multicastAddressDefault + if iface.MulticastAddress != "" { + multicastAddress = iface.MulticastAddress } // Open socket conn, err := net.ListenUDP("udp", &net.UDPAddr{ - IP: linkLocalAddr, - Port: coll.port, - Zone: iface, + IP: addr, + Port: iface.Port, + Zone: iface.InterfaceName, }) if err != nil { log.Panic(err) } conn.SetReadBuffer(maxDataGramSize) - coll.ifaceToConn[iface] = conn - coll.connections = append(coll.connections, conn) + coll.connections = append(coll.connections, multicastConn{ + Conn: conn, + MulticastAddress: net.ParseIP(multicastAddress), + }) // Start receiver go coll.receiver(conn) } -// Returns the first link local unicast address for the given interface name -func getLinkLocalAddr(ifname string) (net.IP, error) { +// Returns a unicast address of given interface (prefer global unicast address over link local address) +func getUnicastAddr(ifname string) (net.IP, error) { iface, err := net.InterfaceByName(ifname) if err != nil { return nil, err @@ -93,13 +107,23 @@ func getLinkLocalAddr(ifname string) (net.IP, error) { if err != nil { return nil, err } + var ip net.IP for _, addr := range addresses { - if ipnet := addr.(*net.IPNet); ipnet.IP.IsLinkLocalUnicast() { - return ipnet.IP, nil + ipnet, ok := addr.(*net.IPNet) + if !ok { + continue } + if ipnet.IP.IsGlobalUnicast() { + ip = ipnet.IP + } else if ipnet.IP.IsLinkLocalUnicast() && ip == nil { + ip = ipnet.IP + } + } + if ip != nil { + return ip, nil } - return nil, fmt.Errorf("unable to find link local unicast address for %s", ifname) + return nil, fmt.Errorf("unable to find a unicast address for %s", ifname) } // Start Collector @@ -122,7 +146,7 @@ func (coll *Collector) Start(interval time.Duration) { func (coll *Collector) Close() { close(coll.stop) for _, conn := range coll.connections { - conn.Close() + conn.Conn.Close() } close(coll.queue) } @@ -139,7 +163,7 @@ func (coll *Collector) sendOnce() { func (coll *Collector) sendMulticast() { log.Println("sending multicasts") for _, conn := range coll.connections { - coll.sendPacket(conn, multiCastGroup) + coll.sendPacket(conn.Conn, conn.MulticastAddress) } } @@ -153,21 +177,29 @@ func (coll *Collector) sendUnicasts(seenBefore jsontime.Time) { }) // Send unicast packets - log.Printf("sending unicast to %d nodes", len(nodes)) + count := 0 for _, node := range nodes { - conn := coll.ifaceToConn[node.Address.Zone] - if conn == nil { + send := 0 + for _, conn := range coll.connections { + if node.Address.Zone != "" && conn.Conn.LocalAddr().(*net.UDPAddr).Zone != node.Address.Zone { + continue + } + coll.sendPacket(conn.Conn, node.Address.IP) + send++ + } + if send == 0 { log.Printf("unable to find connection for %s", node.Address.Zone) - continue + } else { + time.Sleep(10 * time.Millisecond) + count += send } - coll.sendPacket(conn, node.Address.IP) - time.Sleep(10 * time.Millisecond) } + log.Printf("sending %d unicast pkg for %d nodes", count, len(nodes)) } // SendPacket sends a UDP request to the given unicast or multicast address on the first UDP socket func (coll *Collector) SendPacket(destination net.IP) { - coll.sendPacket(coll.connections[0], destination) + coll.sendPacket(coll.connections[0].Conn, destination) } // sendPacket sends a UDP request to the given unicast or multicast address on the given UDP socket @@ -201,7 +233,7 @@ func (coll *Collector) sender() { func (coll *Collector) parser() { for obj := range coll.queue { if data, err := obj.parse(); err != nil { - log.Println("unable to decode response from", obj.Address.String(), err, "\n", string(obj.Raw)) + log.Println("unable to decode response from", obj.Address.String(), err) } else { coll.saveResponse(obj.Address, data) } diff --git a/respond/collector_test.go b/respond/collector_test.go index 82dca235..c16fdfb5 100644 --- a/respond/collector_test.go +++ b/respond/collector_test.go @@ -17,7 +17,7 @@ const ( func TestCollector(t *testing.T) { nodes := runtime.NewNodes(&runtime.NodesConfig{}) - collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []string{}, 10001) + collector := NewCollector(nil, nodes, map[string][]string{SITE_TEST: {DOMAIN_TEST}}, []InterfaceConfig{}) collector.Start(time.Millisecond) time.Sleep(time.Millisecond * 10) collector.Close() diff --git a/respond/config.go b/respond/config.go index 1a523134..1d381ef5 100644 --- a/respond/config.go +++ b/respond/config.go @@ -5,9 +5,8 @@ import "github.com/FreifunkBremen/yanic/lib/duration" type Config struct { Enable bool `toml:"enable"` Synchronize duration.Duration `toml:"synchronize"` - Interfaces []string `toml:"interfaces"` + Interfaces []InterfaceConfig `toml:"interfaces"` Sites map[string]SiteConfig `toml:"sites"` - Port int `toml:"port"` CollectInterval duration.Duration `toml:"collect_interval"` } @@ -22,3 +21,10 @@ func (c *Config) SitesDomains() (result map[string][]string) { type SiteConfig struct { Domains []string `toml:"domains"` } + +type InterfaceConfig struct { + InterfaceName string `toml:"ifname"` + IPAddress string `toml:"ip_address"` + MulticastAddress string `toml:"multicast_address"` + Port int `toml:"port"` +} diff --git a/respond/respond.go b/respond/respond.go index 068a6d64..9564b8e6 100644 --- a/respond/respond.go +++ b/respond/respond.go @@ -4,10 +4,10 @@ import ( "net" ) -// default multicast group used by announced -var multiCastGroup = net.ParseIP("ff02:0:0:0:0:0:2:1001") - const ( + // default multicast group used by announced + multicastAddressDefault = "ff02:0:0:0:0:0:2:1001" + // default udp port used by announced port = 1001 diff --git a/runtime/node.go b/runtime/node.go index f49807b4..3e2059cb 100644 --- a/runtime/node.go +++ b/runtime/node.go @@ -20,11 +20,11 @@ type Node struct { // Link represents a link between two nodes type Link struct { - SourceID string - SourceMAC string - TargetID string - TargetMAC string - TQ int + SourceID string + SourceAddress string + TargetID string + TargetAddress string + TQ float32 } // IsGateway returns whether the node is a gateway diff --git a/runtime/nodes.go b/runtime/nodes.go index 99be5db9..17fd26d5 100644 --- a/runtime/nodes.go +++ b/runtime/nodes.go @@ -100,8 +100,8 @@ func (nodes *Nodes) Select(f func(*Node) bool) []*Node { return result } -func (nodes *Nodes) GetNodeIDbyMAC(mac string) string { - return nodes.ifaceToNodeID[mac] +func (nodes *Nodes) GetNodeIDbyAddress(addr string) string { + return nodes.ifaceToNodeID[addr] } // NodeLinks returns a list of links to known neighbours @@ -116,11 +116,24 @@ func (nodes *Nodes) NodeLinks(node *Node) (result []Link) { for neighbourMAC, link := range batadv.Neighbours { if neighbourID := nodes.ifaceToNodeID[neighbourMAC]; neighbourID != "" { result = append(result, Link{ - SourceID: neighbours.NodeID, - SourceMAC: sourceMAC, - TargetID: neighbourID, - TargetMAC: neighbourMAC, - TQ: link.Tq, + SourceID: neighbours.NodeID, + SourceAddress: sourceMAC, + TargetID: neighbourID, + TargetAddress: neighbourMAC, + TQ: float32(link.Tq) / 255.0, + }) + } + } + } + for _, iface := range neighbours.Babel { + for neighbourIP, link := range iface.Neighbours { + if neighbourID := nodes.ifaceToNodeID[neighbourIP]; neighbourID != "" { + result = append(result, Link{ + SourceID: neighbours.NodeID, + SourceAddress: iface.LinkLocalAddress, + TargetID: neighbourID, + TargetAddress: neighbourIP, + TQ: 1.0 - (float32(link.Cost) / 65535.0), }) } } @@ -179,19 +192,19 @@ func (nodes *Nodes) readIfaces(nodeinfo *data.NodeInfo) { addresses := []string{network.Mac} - for _, batinterface := range network.Mesh { - addresses = append(addresses, batinterface.Addresses()...) + for _, iface := range network.Mesh { + addresses = append(addresses, iface.Addresses()...) } - for _, mac := range addresses { - if mac == "" { + for _, addr := range addresses { + if addr == "" { continue } - if oldNodeID, _ := nodes.ifaceToNodeID[mac]; oldNodeID != nodeID { + if oldNodeID, _ := nodes.ifaceToNodeID[addr]; oldNodeID != nodeID { if oldNodeID != "" { - log.Printf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, mac) + log.Printf("override nodeID from %s to %s on MAC address %s", oldNodeID, nodeID, addr) } - nodes.ifaceToNodeID[mac] = nodeID + nodes.ifaceToNodeID[addr] = nodeID } } } diff --git a/runtime/nodes_test.go b/runtime/nodes_test.go index 05401da1..7283f734 100644 --- a/runtime/nodes_test.go +++ b/runtime/nodes_test.go @@ -179,7 +179,7 @@ func TestLinksNodes(t *testing.T) { "f4:f2:6d:d7:a3:0b": { Neighbours: map[string]data.BatmanLink{ "f4:f2:6d:d7:a3:0a": { - Tq: 200, Lastseen: 0.42, + Tq: 204, Lastseen: 0.42, }, }, }, @@ -198,11 +198,11 @@ func TestLinksNodes(t *testing.T) { assert.Len(links, 1) link := links[0] assert.Equal(link.SourceID, "f4f26dd7a30b") - assert.Equal(link.SourceMAC, "f4:f2:6d:d7:a3:0b") + assert.Equal(link.SourceAddress, "f4:f2:6d:d7:a3:0b") assert.Equal(link.TargetID, "f4f26dd7a30a") - assert.Equal(link.TargetMAC, "f4:f2:6d:d7:a3:0a") - assert.Equal(link.TQ, 200) + assert.Equal(link.TargetAddress, "f4:f2:6d:d7:a3:0a") + assert.Equal(link.TQ, float32(0.8)) - nodeid := nodes.GetNodeIDbyMAC("f4:f2:6d:d7:a3:0a") + nodeid := nodes.GetNodeIDbyAddress("f4:f2:6d:d7:a3:0a") assert.Equal("f4f26dd7a30a", nodeid) }