NetworkManager is a popular high-level interface for configuring networks in Linux systems, in particular Ubuntu and other Debian-based flavours. This artifact lists the NetworkManager state, all configured connections and their settings, as well as when the connections were last activated. A list of BSSIDs per connection is also retrieved.
All the information is retrieved from NetworkManager configuration files and other state files. Connection information is stored in the /etc/NetworkManager/system-connections as long as the “keyfile” plugin is selected in /etc/NetworkManager/NetworkManager.conf (this is the default). Note that by default, NetworkManager doesn’t manage connections defined in /etc/network/interfaces.
Whether the connections are currently active is not stored in file and must be queried using using nmcli or through dbus. This artifact runs nmcli as an external program to retrieve this information. Information such as IP addresses, routes, DNS servers, available Wi-Fi networks and other settings will also be collected through nmcli.
This artifact also exports two functions, parse_ini(filename) and parse_ini_as_dict(filename), which may be useful to parse INI files in other artifacts.
name: Linux.Network.NM.Connections
author: 'Andreas Misje - @misje'
description: |
NetworkManager is a popular high-level interface for configuring
networks in Linux systems, in particular Ubuntu and other Debian-based
flavours. This artifact lists the NetworkManager state, all configured
connections and their settings, as well as when the connections were
last activated. A list of BSSIDs per connection is also retrieved.
All the information is retrieved from NetworkManager configuration
files and other state files. Connection information is stored in
the /etc/NetworkManager/system-connections as long as the "keyfile"
plugin is selected in /etc/NetworkManager/NetworkManager.conf (this
is the default). Note that by default, NetworkManager doesn't manage
connections defined in /etc/network/interfaces.
Whether the connections are currently active is not stored in file
and must be queried using using nmcli or through dbus. This artifact
runs nmcli as an external program to retrieve this information.
Information such as IP addresses, routes, DNS servers, available Wi-Fi
networks and other settings will also be collected through nmcli.
This artifact also exports two functions, parse_ini(filename) and
parse_ini_as_dict(filename), which may be useful to parse INI files
in other artifacts.
reference:
- https://developer-old.gnome.org/NetworkManager/stable/nm-settings-keyfile.html
- https://developer-old.gnome.org/NetworkManager/stable/settings-connection.html
- https://developer-old.gnome.org/NetworkManager/stable/settings-802-11-wireless.html
type: CLIENT
required_permissions:
- EXECVE
parameters:
- name: RedactSecrets
default: true
type: bool
description: |
Replace Wi-FI PSKs (wifi-security/psk) with "\<REDACTED\>".
export: |
/* Parse an INI config file and return Section (the part enclosed in '[]' on
lines of their own), Key and Value. */
LET parse_ini(filename) = SELECT * FROM foreach(row={
SELECT * FROM parse_records_with_regex(file=filename,
regex='''(?m)\[\s*(?P<Section>[^\]]+)\s*\](?P<Contents>[^\[]*)''')
}, query={
SELECT Section, Key, Value
FROM parse_records_with_regex(file=Contents,
accessor='data',
regex='^[\s\n]*(?P<Key>[^=]+)=(?P<Value>.*)')
})
/* Parse an INI config file and return a single column, Contents, with
the contents. Section names are prepended to keys, separated by '/'. */
LET parse_ini_as_dict(filename) = SELECT to_dict(item={
SELECT lowcase(string=Section + '/' + Key) AS _key, Value AS _value
FROM parse_ini(filename=filename)
}) AS Contents
FROM scope()
column_types:
- name: LastActivated
type: timestamp
description: |
When the connection was last fully successfully activated. This
timestamp may be updated periodically while the connection is active.
precondition: |
SELECT OS FROM info() WHERE OS = 'linux'
sources:
- name: State
description: |
NetworkManager have three states that may be toggled:
NetworkingEnabled, which disables all networking (managed by
NetworkManager); WirelessEnabled, which disables wireless networking;
and WWANEnabled, which disables mobile data connections.
query: |
LET State_ = SELECT parse_string_with_regex(string=Data,
regex=('''NetworkingEnabled=(?P<NetworkingEnabled>\S+)''',
'''WirelessEnabled=(?P<WirelessEnabled>\S+)''',
'''WWANEnabled=(?P<WWANEnabled>\S+)''')) AS Fields
FROM read_file(filenames='/var/lib/NetworkManager/NetworkManager.state')
LET State = SELECT Fields.NetworkingEnabled AS NetworkingEnabled,
Fields.WirelessEnabled AS WirelessEnabled,
Fields.WWANEnabled AS WWANEnabled
FROM State_
SELECT * FROM State
- name: ConnectionConfigs
description: |
All connections configured in NetworkManager. Columns returned are
OSPath and a dict with the connection configuration.
query: |
LET ConfiguredConnections <= SELECT * FROM foreach(row={
SELECT OSPath FROM glob(globs='/etc/NetworkManager/system-connections/*.nmconnection')
}, query=if(condition=RedactSecrets, then={
SELECT OSPath,
Contents + dict(`wifi-security/psk`='<REDACTED>')
AS Contents
FROM parse_ini_as_dict(filename=OSPath)
}, else={
SELECT *, OSPath FROM parse_ini_as_dict(filename=OSPath)
})
)
SELECT OSPath, Contents FROM ConfiguredConnections
- name: Connections
description: |
Return a handful of useful properties from ConnectionConfigs in a
more readable table with individual column names: Name, UUID, Type,
Device and LastActivated. LastActivated are fetched from another
state file and combined with the results from ConnectionConfigs.
query: |
LET Timestamps = SELECT UUID, if(condition=parse_float(string=Timestamp),
then=timestamp(epoch=Timestamp), else=null) AS Timestamp
FROM parse_records_with_regex(file='/var/lib/NetworkManager/timestamps',
regex='''(?P<UUID>[-A-Fa-f0-9]+)+=(?P<Timestamp>\S+)''')
LET Connections <= SELECT Name, _UUID AS UUID, Type, Device, LastActivated
FROM foreach(row={
SELECT * FROM ConfiguredConnections
}, query={
SELECT Contents.`connection/id` AS Name,
Contents.`connection/uuid` AS _UUID,
Contents.`connection/type` AS Type,
Contents.`connection/interface-name` AS Device,
Timestamp AS LastActivated
FROM Timestamps
WHERE _UUID=UUID
})
SELECT * FROM Connections
- name: ActiveConnections
description: |
Return connections from Connections that are currently active,
by asking the NetworkManager daemon through the utility "nmcli".
query: |
LET nmcli = SELECT Stdout
FROM execve(argv=['nmcli', '-t', '-f', 'uuid', 'connection', 'show', '--active'])
LET ActiveConnections = SELECT * FROM foreach(row={
SELECT * FROM parse_lines(accessor='data',
filename=nmcli.Stdout)
}, query={
SELECT * FROM Connections WHERE UUID=Line
})
SELECT * FROM ActiveConnections
- name: DeviceStatus
description: |
Ask NetworkManager through "nmcli" about the status of all network
interfaces, managed as well unmanaged, with detailed information such
as IP addresses, routes, MTU and DNS settings.
query: |
LET nmcli = SELECT Stdout
FROM execve(argv=['nmcli', '-t', 'device', 'show'])
LET DeviceStatus = SELECT * FROM foreach(row=split(sep='\n\n',
string=nmcli.Stdout), query={
SELECT parse_string_with_regex(string=_value,
regex='''GENERAL.DEVICE:(?P<Device>.+)''').Device AS Device,
to_dict(item={
SELECT Key AS _key, Value AS _value
FROM parse_records_with_regex(file=_value, accessor='data',
regex='^\n?(?P<Key>[^:]+):(?P<Value>.*)')
}) AS Status
FROM scope()
})
/* We're pretty much done now, but the output could be a lot nicer to
work with. */
LET S = scope()
LET Status <= SELECT * FROM foreach(row={SELECT * FROM DeviceStatus},
column='Status')
LET to_array(col, dev) = filter(list=array(a={
SELECT * FROM column_filter(include=col, query={
SELECT * FROM Status WHERE `GENERAL.DEVICE` = dev
})}), condition='x=>x')
LET prettify_route(col, dev) = SELECT *
FROM foreach(row=to_array(dev=dev, col=col), query={
SELECT S.Dest AS Dest, S.NextHop AS NextHop, int(int=S.Metric) AS Metric
FROM foreach(row={
SELECT parse_string_with_regex(string=S._value, regex=(
'''dst\s*=\s*(?P<Dest>[^,]+)''',
'''nh\s*=\s*(?P<NextHop>[^,]+)''',
'''mt\s*=\s*(?P<Metric>\d+)''')) AS R
FROM scope()
}, column='R')
})
WHERE Dest
SELECT `GENERAL.DEVICE` AS Device,
S.`GENERAL.TYPE` AS Type,
S.`GENERAL.CONNECTION` AS Connection,
S.`GENERAL.STATE` AS State,
S.`GENERAL.HWADDR` AS Mac,
S.`GENERAL.MTU` AS MTU,
to_array(dev=`GENERAL.DEVICE`, col='IP4.ADDRESS') AS Addresses,
prettify_route(dev=`GENERAL.DEVICE`, col='IP4.ROUTE') AS Routes,
S.`IP4.GATEWAY` AS Gateway,
to_array(dev=`GENERAL.DEVICE`, col='IP4.DNS') AS DNSServers,
to_array(dev=`GENERAL.DEVICE`, col='IP4.DOMAIN') AS DNSDomains,
to_array(dev=`GENERAL.DEVICE`, col='IP4.SEARCHES') AS DNSSearches,
to_array(dev=`GENERAL.DEVICE`, col='IP6.ADDRESS') AS _IPv6Addresses,
prettify_route(dev=`GENERAL.DEVICE`, col='IP6.ROUTE') AS _IPv6Routes,
S.`IP6.GATEWAY` AS _IPv6Gateway,
to_array(dev=`GENERAL.DEVICE`, col='IP6.DNS') AS _IPv6DNSServers,
to_array(dev=`GENERAL.DEVICE`, col='IP6.DOMAIN') AS _IPv6DNSDomains,
to_array(dev=`GENERAL.DEVICE`, col='IP6.SEARCHES') AS _IPv6DNSSearches
FROM Status
- name: AvailableAccessPoints
description: |
Ask NetworkManager through "nmcli" about details about all available
Wi-Fi access points
query: |
LET nmcli = SELECT Stdout
FROM execve(argv=['nmcli', '-t', '-m', 'multiline', '-f',
'ssid,bssid,mode,chan,freq,rate,signal,security,wpa-flags,rsn-flags,device,active,in-use',
'device', 'wifi', 'list'])
LET AccessPoints = SELECT * FROM foreach(row=filter(list=split(sep='\x01\x02',
/* Separate sections of key–values by injecting a blob and then
split on that string. nmcli also has a "tabular" output mode,
but ":", the separator, is also part of the output and is
"escaped" by "/", making the parsing difficult. */
string=regex_replace(source=nmcli.Stdout, re='(?m)^IN-USE:.*$',
replace='$0\x01\x02')), regex='.'), query={
SELECT to_dict(item={
SELECT Key AS _key, Value as _value FROM parse_records_with_regex(
file=_value, accessor='data', regex='^\n?(?P<Key>[^:]+):(?P<Value>.*)')
}) AS Contents FROM scope() WHERE _value
})
WHERE Contents
SELECT DEVICE AS Device, SSID, BSSID, MODE AS Mode, CHAN AS Chan,
FREQ AS Freq, RATE AS Rate, SIGNAL AS Signal, SECURITY AS Security,
ACTIVE='yes' AS Active, `WPA-FLAGS` AS WPAFlags, `RSN-FLAGS` AS RSNFlags,
`IN-USE`='*' AS InUse
FROM foreach(row=AccessPoints, column='Contents')
- name: SeenBSSIDs
description: |
A list of BSSIDs (each BSSID formatted as a MAC address like
"00:11:22:33:44:55") that have been detected as part of the Wi-Fi
network. NetworkManager internally tracks previously seen BSSIDs.
query: |
LET SeenBSSIDs <= SELECT UUID AS _UUID, filter(list=split(sep_string=',', string=BSSIDs),
regex='.+') AS BSSIDs
FROM parse_records_with_regex(file='/var/lib/NetworkManager/seen-bssids',
regex='''(?P<UUID>[-A-Fa-f0-9]+)+=(?P<BSSIDs>\S+)''')
SELECT * FROM foreach(row=SeenBSSIDs, query={
SELECT Name, UUID, Device, BSSIDs FROM Connections
WHERE _UUID=UUID
})