Files
akvorado/console/query.go
Vincent Bernat 689497aa13 console: add SrcNetPrefix and DstNetPrefix as dimensions
This is not added to filtering as I fail to see how it would be useful.
One can still filter on SrcAddr and DstAddr.

Fix #218
2022-11-26 15:49:26 +01:00

142 lines
4.2 KiB
Go

// SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only
package console
import (
"errors"
"fmt"
"strings"
"akvorado/common/helpers"
"akvorado/console/filter"
)
type queryColumn int
func (qc queryColumn) MarshalText() ([]byte, error) {
got, ok := queryColumnMap.LoadValue(qc)
if ok {
return []byte(got), nil
}
return nil, errors.New("unknown field")
}
func (qc queryColumn) String() string {
got, _ := queryColumnMap.LoadValue(qc)
return got
}
func (qc *queryColumn) UnmarshalText(input []byte) error {
got, ok := queryColumnMap.LoadKey(string(input))
if ok {
*qc = got
return nil
}
return errors.New("unknown field")
}
// queryColumnsRequiringMainTable lists query columns only present in
// the main table. Also check filter/parser.peg.
var queryColumnsRequiringMainTable = map[queryColumn]struct{}{
queryColumnSrcAddr: {},
queryColumnDstAddr: {},
queryColumnSrcNetPrefix: {},
queryColumnDstNetPrefix: {},
queryColumnSrcPort: {},
queryColumnDstPort: {},
queryColumnDstASPath: {},
queryColumnDstCommunities: {},
}
func requireMainTable(qcs []queryColumn, qf queryFilter) bool {
if qf.MainTableRequired {
return true
}
for _, qc := range qcs {
if _, ok := queryColumnsRequiringMainTable[qc]; ok {
return true
}
}
return false
}
type queryFilter struct {
Filter string
ReverseFilter string
MainTableRequired bool
}
func (qf queryFilter) String() string {
return qf.Filter
}
func (qf queryFilter) MarshalText() ([]byte, error) {
return []byte(qf.Filter), nil
}
func (qf *queryFilter) UnmarshalText(input []byte) error {
if strings.TrimSpace(string(input)) == "" {
*qf = queryFilter{}
return nil
}
meta := &filter.Meta{}
direct, err := filter.Parse("", input, filter.GlobalStore("meta", meta))
if err != nil {
return fmt.Errorf("cannot parse filter: %s", filter.HumanError(err))
}
meta = &filter.Meta{ReverseDirection: true}
reverse, err := filter.Parse("", input, filter.GlobalStore("meta", meta))
if err != nil {
return fmt.Errorf("cannot parse reverse filter: %s", filter.HumanError(err))
}
*qf = queryFilter{
Filter: direct.(string),
ReverseFilter: reverse.(string),
MainTableRequired: meta.MainTableRequired,
}
return nil
}
// toSQLSelect transforms a column into an expression to use in SELECT
func (qc queryColumn) toSQLSelect() string {
var strValue string
switch qc {
case queryColumnExporterAddress, queryColumnSrcAddr, queryColumnDstAddr:
strValue = fmt.Sprintf("replaceRegexpOne(IPv6NumToString(%s), '^::ffff:', '')", qc)
case queryColumnSrcAS, queryColumnDstAS, queryColumnDst1stAS, queryColumnDst2ndAS, queryColumnDst3rdAS:
strValue = fmt.Sprintf(`concat(toString(%s), ': ', dictGetOrDefault('asns', 'name', %s, '???'))`,
qc, qc)
case queryColumnEType:
strValue = fmt.Sprintf(`if(EType = %d, 'IPv4', if(EType = %d, 'IPv6', '???'))`,
helpers.ETypeIPv4, helpers.ETypeIPv6)
case queryColumnProto:
strValue = `dictGetOrDefault('protocols', 'name', Proto, '???')`
case queryColumnInIfSpeed, queryColumnOutIfSpeed, queryColumnSrcPort, queryColumnDstPort, queryColumnForwardingStatus, queryColumnInIfBoundary, queryColumnOutIfBoundary:
strValue = fmt.Sprintf("toString(%s)", qc)
case queryColumnDstASPath:
strValue = `arrayStringConcat(DstASPath, ' ')`
case queryColumnDstCommunities:
strValue = `arrayStringConcat(arrayConcat(arrayMap(c -> concat(toString(bitShiftRight(c, 16)), ':', toString(bitAnd(c, 0xffff))), DstCommunities), arrayMap(c -> concat(toString(bitAnd(bitShiftRight(c, 64), 0xffffffff)), ':', toString(bitAnd(bitShiftRight(c, 32), 0xffffffff)), ':', toString(bitAnd(c, 0xffffffff))), DstLargeCommunities)), ' ')`
default:
strValue = qc.String()
}
return strValue
}
// reverseDirection reverse the direction of a column (src/dst, in/out)
func (qc queryColumn) reverseDirection() queryColumn {
value, ok := queryColumnMap.LoadKey(filter.ReverseColumnDirection(qc.String()))
if !ok {
panic("unknown reverse column")
}
return value
}
// fixQueryColumnName fix capitalization of the provided column name
func fixQueryColumnName(name string) string {
name = strings.ToLower(name)
for _, target := range queryColumnMap.Values() {
if strings.ToLower(target) == name {
return target
}
}
return ""
}