mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 12:33:02 +02:00
Merge pull request #1453 from kernelkit/discovery-docs
mDNS discovery improvements
This commit is contained in:
@@ -8,6 +8,8 @@ on:
|
|||||||
env:
|
env:
|
||||||
PROJECT_NAME: Infix
|
PROJECT_NAME: Infix
|
||||||
CONTACT_EMAIL: troglobit@gmail.com
|
CONTACT_EMAIL: troglobit@gmail.com
|
||||||
|
LIBYANG_VERSION: 4.2.2
|
||||||
|
SYSREPO_VERSION: 4.2.10
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
coverity:
|
coverity:
|
||||||
@@ -59,12 +61,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Build dependencies
|
- name: Build dependencies
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/CESNET/libyang.git
|
git clone -b v${LIBYANG_VERSION} --depth 1 https://github.com/CESNET/libyang.git
|
||||||
|
for p in patches/libyang/${LIBYANG_VERSION}/*.patch; do
|
||||||
|
git -C libyang apply < "$p"
|
||||||
|
done
|
||||||
mkdir libyang/build
|
mkdir libyang/build
|
||||||
(cd libyang/build && cmake .. && make all && sudo make install)
|
(cd libyang/build && cmake .. && make all && sudo make install)
|
||||||
git clone https://github.com/sysrepo/sysrepo.git
|
|
||||||
|
git clone -b v${SYSREPO_VERSION} --depth 1 https://github.com/sysrepo/sysrepo.git
|
||||||
|
for p in patches/sysrepo/${SYSREPO_VERSION}/*.patch; do
|
||||||
|
git -C sysrepo apply < "$p"
|
||||||
|
done
|
||||||
mkdir sysrepo/build
|
mkdir sysrepo/build
|
||||||
(cd sysrepo/build && cmake .. && make all && sudo make install)
|
(cd sysrepo/build && cmake .. && make all && sudo make install)
|
||||||
|
|
||||||
git clone https://github.com/troglobit/libite.git
|
git clone https://github.com/troglobit/libite.git
|
||||||
(cd libite && ./autogen.sh && ./configure && make && sudo make install)
|
(cd libite && ./autogen.sh && ./configure && make && sudo make install)
|
||||||
make dep
|
make dep
|
||||||
|
|||||||
+76
-82
@@ -18,8 +18,7 @@ link-local IPv6 address is then seen in the response. In the following
|
|||||||
example, the PC here uses *tap0* as *if1*, Infix responds with address
|
example, the PC here uses *tap0* as *if1*, Infix responds with address
|
||||||
*fe80::ff:fec0:ffed*.
|
*fe80::ff:fec0:ffed*.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>ping -6 -L -c 3 ff02::1%tap0</b>
|
||||||
linux-pc:# ping -6 -L -c 3 ff02::1%tap0
|
|
||||||
PING ff02::1%tap0(ff02::1%tap0) 56 data bytes
|
PING ff02::1%tap0(ff02::1%tap0) 56 data bytes
|
||||||
64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=1 ttl=64 time=0.558 ms
|
64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=1 ttl=64 time=0.558 ms
|
||||||
64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=2 ttl=64 time=0.419 ms
|
64 bytes from fe80::ff:fec0:ffed%tap0: icmp_seq=2 ttl=64 time=0.419 ms
|
||||||
@@ -29,7 +28,7 @@ PING ff02::1%tap0(ff02::1%tap0) 56 data bytes
|
|||||||
3 packets transmitted, 3 received, 0% packet loss, time 2043ms
|
3 packets transmitted, 3 received, 0% packet loss, time 2043ms
|
||||||
rtt min/avg/max/mdev = 0.389/0.455/0.558/0.073 ms
|
rtt min/avg/max/mdev = 0.389/0.455/0.558/0.073 ms
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> The `-L` option ignores local responses from the PC.
|
> The `-L` option ignores local responses from the PC.
|
||||||
@@ -37,11 +36,10 @@ linux-pc:#
|
|||||||
This address can then be used to connect to the device, e.g., using SSH.
|
This address can then be used to connect to the device, e.g., using SSH.
|
||||||
Notice the syntax `username@address%interface`:
|
Notice the syntax `username@address%interface`:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>ssh admin@fe80::ff:fec0:ffed%tap0</b>
|
||||||
linux-pc:# ssh admin@fe80::ff:fec0:ffed%tap0
|
|
||||||
admin@fe80::ff:fec0:ffed%tap0's password: admin
|
admin@fe80::ff:fec0:ffed%tap0's password: admin
|
||||||
admin@infix-c0-ff-ee:~$
|
admin@infix-c0-ff-ee:~$
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
@@ -52,10 +50,9 @@ no extra software required.
|
|||||||
|
|
||||||
From the command line, use the `.local` hostname directly:
|
From the command line, use the `.local` hostname directly:
|
||||||
|
|
||||||
```cmd
|
<pre class="cli"><code>C:\> <b>ping infix-c0-ff-ee.local</b>
|
||||||
C:\> ping infix-c0-ff-ee.local
|
C:\> <b>ssh admin@infix-c0-ff-ee.local</b>
|
||||||
C:\> ssh admin@infix-c0-ff-ee.local
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> IPv6 multicast ping (`ping ff02::1%if1`) may not display responses on
|
> IPv6 multicast ping (`ping ff02::1%if1`) may not display responses on
|
||||||
@@ -63,9 +60,8 @@ C:\> ssh admin@infix-c0-ff-ee.local
|
|||||||
> confirm connectivity, Wireshark will show the ICMPv6 echo replies
|
> confirm connectivity, Wireshark will show the ICMPv6 echo replies
|
||||||
> arriving. Use mDNS (see [mDNS-SD](#mdns-sd) below) as the reliable
|
> arriving. Use mDNS (see [mDNS-SD](#mdns-sd) below) as the reliable
|
||||||
> alternative.
|
> alternative.
|
||||||
|
>
|
||||||

|
> 
|
||||||
|
|
||||||
|
|
||||||
## LLDP
|
## LLDP
|
||||||
|
|
||||||
@@ -73,8 +69,7 @@ Infix supports LLDP (IEEE 802.1AB). For a device with factory default
|
|||||||
settings, the link-local IPv6 address can be read from the Management
|
settings, the link-local IPv6 address can be read from the Management
|
||||||
Address TLV using *tcpdump* or other sniffing tools[^1]:
|
Address TLV using *tcpdump* or other sniffing tools[^1]:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>tcpdump -i tap0 -Qin -v ether proto 0x88cc</b>
|
||||||
linux-pc:# tcpdump -i tap0 -Qin -v ether proto 0x88cc
|
|
||||||
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
||||||
15:51:52.061071 LLDP, length 193
|
15:51:52.061071 LLDP, length 193
|
||||||
Chassis ID TLV (1), length 7
|
Chassis ID TLV (1), length 7
|
||||||
@@ -104,7 +99,7 @@ tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144
|
|||||||
^C
|
^C
|
||||||
1 packet captured
|
1 packet captured
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
If the device has an IPv4 address assigned, it is shown in an additional
|
If the device has an IPv4 address assigned, it is shown in an additional
|
||||||
Management Address TLV.
|
Management Address TLV.
|
||||||
@@ -116,8 +111,7 @@ Management Address TLV.
|
|||||||
In the example below, the IPv4 address (10.0.1.1) happens to be
|
In the example below, the IPv4 address (10.0.1.1) happens to be
|
||||||
assigned to *eth0*, while the IPv6 address (2001:db8::1) is not.
|
assigned to *eth0*, while the IPv6 address (2001:db8::1) is not.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>sudo tcpdump -i tap0 -Qin -v ether proto 0x88cc</b>
|
||||||
linux-pc:# sudo tcpdump -i tap0 -Qin -v ether proto 0x88cc
|
|
||||||
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
|
||||||
15:46:07.908665 LLDP, length 207
|
15:46:07.908665 LLDP, length 207
|
||||||
Chassis ID TLV (1), length 7
|
Chassis ID TLV (1), length 7
|
||||||
@@ -152,7 +146,7 @@ tcpdump: listening on tap0, link-type EN10MB (Ethernet), snapshot length 262144
|
|||||||
2 packets received by filter
|
2 packets received by filter
|
||||||
0 packets dropped by kernel
|
0 packets dropped by kernel
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.
|
The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.
|
||||||
|
|
||||||
@@ -160,19 +154,17 @@ The following capabilities are available via NETCONF/RESTCONF or the Infix CLI.
|
|||||||
|
|
||||||
The LLDP service can be disabled using the following commands.
|
The LLDP service can be disabled using the following commands.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/> <b>configure</b>
|
||||||
admin@infix-c0-ff-ee:/> configure
|
admin@infix-c0-ff-ee:/config/> <b>no lldp</b>
|
||||||
admin@infix-c0-ff-ee:/config/> no lldp
|
admin@infix-c0-ff-ee:/config/> <b>leave</b>
|
||||||
admin@infix-c0-ff-ee:/config/> leave
|
|
||||||
admin@infix-c0-ff-ee:/>
|
admin@infix-c0-ff-ee:/>
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
To reenable it from the CLI config mode:
|
To reenable it from the CLI config mode:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@test-00-01-00:/config/> <b>set lldp enabled</b>
|
||||||
admin@test-00-01-00:/config/> set lldp enabled
|
admin@test-00-01-00:/config/> <b>leave</b>
|
||||||
admin@test-00-01-00:/config/> leave
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
### LLDP Message Transmission Interval
|
### LLDP Message Transmission Interval
|
||||||
|
|
||||||
@@ -180,10 +172,9 @@ By default, LLDP uses a `message-tx-interval` of 30 seconds, as defined
|
|||||||
by the IEEE standard. Infix allows this value to be customized.
|
by the IEEE standard. Infix allows this value to be customized.
|
||||||
To change it using the CLI:
|
To change it using the CLI:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@test-00-01-00:/config/> <b>set lldp message-tx-interval 1</b>
|
||||||
admin@test-00-01-00:/config/> set lldp message-tx-interval 1
|
admin@test-00-01-00:/config/> <b>leave</b>
|
||||||
admin@test-00-01-00:/config/> leave
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
### LLDP Administrative Status per Interface
|
### LLDP Administrative Status per Interface
|
||||||
|
|
||||||
@@ -197,12 +188,11 @@ also supported:
|
|||||||
|
|
||||||
Example configuration:
|
Example configuration:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@test-00-01-00:/config/> <b>set lldp port e8 dest-mac-address 01:80:C2:00:00:0E admin-status disabled</b>
|
||||||
admin@test-00-01-00:/config/> set lldp port e8 dest-mac-address 01:80:C2:00:00:0E admin-status disabled
|
admin@test-00-01-00:/config/> <b>set lldp port e5 dest-mac-address 01:80:C2:00:00:0E admin-status rx-only</b>
|
||||||
admin@test-00-01-00:/config/> set lldp port e5 dest-mac-address 01:80:C2:00:00:0E admin-status rx-only
|
admin@test-00-01-00:/config/> <b>set lldp port e6 dest-mac-address 01:80:C2:00:00:0E admin-status tx-only</b>
|
||||||
admin@test-00-01-00:/config/> set lldp port e6 dest-mac-address 01:80:C2:00:00:0E admin-status tx-only
|
admin@test-00-01-00:/config/> <b>leave</b>
|
||||||
admin@test-00-01-00:/config/> leave
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> The destination MAC address must be the standard LLDP multicast
|
> The destination MAC address must be the standard LLDP multicast
|
||||||
@@ -213,13 +203,12 @@ admin@test-00-01-00:/config/> leave
|
|||||||
In CLI mode, Infix also provides a convenient `show lldp` command to
|
In CLI mode, Infix also provides a convenient `show lldp` command to
|
||||||
list LLDP neighbors detected on each interface:
|
list LLDP neighbors detected on each interface:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@test-00-01-00:/> <b>show lldp</b>
|
||||||
admin@test-00-01-00:/> show lldp
|
<span class="header">INTERFACE REM-IDX TIME CHASSIS-ID PORT-ID </span>
|
||||||
INTERFACE REM-IDX TIME CHASSIS-ID PORT-ID
|
|
||||||
e5 1 902 00:a0:85:00:04:01 00:a0:85:00:04:07
|
e5 1 902 00:a0:85:00:04:01 00:a0:85:00:04:07
|
||||||
e6 3 897 00:a0:85:00:03:01 00:a0:85:00:03:07
|
e6 3 897 00:a0:85:00:03:01 00:a0:85:00:03:07
|
||||||
e8 2 901 00:a0:85:00:02:01 00:a0:85:00:02:05
|
e8 2 901 00:a0:85:00:02:01 00:a0:85:00:02:05
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
## mDNS-SD
|
## mDNS-SD
|
||||||
|
|
||||||
@@ -227,34 +216,44 @@ DNS-SD/mDNS-SD can be used to discover Infix devices and services. By
|
|||||||
default, Infix use the `.local` domain for advertising services. Some
|
default, Infix use the `.local` domain for advertising services. Some
|
||||||
networks use `.lan` instead, so this configurable:
|
networks use `.lan` instead, so this configurable:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/> <b>configure</b>
|
||||||
admin@infix-c0-ff-ee:/> configure
|
admin@infix-c0-ff-ee:/config/> <b>edit mdns</b>
|
||||||
admin@infix-c0-ff-ee:/config/> edit mdns
|
admin@infix-c0-ff-ee:/config/mdns/> <b>set domain lan</b>
|
||||||
admin@infix-c0-ff-ee:/config/mdns/> set domain lan
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
Other available settings include limiting the interfaces mDNS responder
|
Other available settings include limiting the interfaces mDNS responder
|
||||||
acts on:
|
acts on, `allow`:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/config/> <b>set interfaces allow e1</b>
|
||||||
admin@infix-c0-ff-ee:/config/> set interfaces allow e1
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
or
|
or `deny`. The `allow` and `deny` settings are complementary, `deny` always wins.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/config/> <b>set interfaces deny wan</b>
|
||||||
admin@infix-c0-ff-ee:/config/> set interfaces deny wan
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
The `allow` and `deny` settings are complementary, `deny` always wins.
|
Use `leave` to activate the new settings, then inspect the operational
|
||||||
|
state and any detected neighbors with `show mdns` from admin-exec
|
||||||
|
context:
|
||||||
|
|
||||||
|
<pre class="cli"><code>admin@gateway:/> <b>show mdns</b>
|
||||||
|
Enabled : yes
|
||||||
|
Domain : local
|
||||||
|
Deny : wan
|
||||||
|
|
||||||
|
<span class="header">HOSTNAME ADDRESS LAST SEEN SERVICES </span>
|
||||||
|
Living-Room.local 192.168.0.139 17:28:43 trel(59813) sleep-proxy(61936) raop(7000) srpl-tls(853)
|
||||||
|
firefly-4.local 192.168.0.122 17:28:37 workstation(9)
|
||||||
|
gimli.local 192.168.0.180 17:28:37 smb(445)
|
||||||
|
infix.local 192.168.0.1 17:28:38 https(443) workstation(9) ssh(22) https(443)
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
In Linux, tools such as *avahi-browse* or *mdns-scan*[^2] can be used to
|
In Linux, tools such as *avahi-browse* or *mdns-scan*[^2] can be used to
|
||||||
search for devices advertising their services via mDNS.
|
search for devices advertising their services via mDNS.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>avahi-browse -ar</b>
|
||||||
linux-pc:# avahi-browse -ar
|
|
||||||
+ tap0 IPv6 infix-c0-ff-ee SFTP File Transfer local
|
+ tap0 IPv6 infix-c0-ff-ee SFTP File Transfer local
|
||||||
+ tap0 IPv4 infix-c0-ff-ee SFTP File Transfer local
|
+ tap0 IPv4 infix-c0-ff-ee SFTP File Transfer local
|
||||||
+ tap0 IPv6 infix-c0-ff-ee SSH Remote Terminal local
|
+ tap0 IPv6 infix-c0-ff-ee SSH Remote Terminal local
|
||||||
@@ -281,7 +280,7 @@ linux-pc:# avahi-browse -ar
|
|||||||
txt = []
|
txt = []
|
||||||
^C
|
^C
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> The `-t` option is also very useful, it stops browsing automatically
|
> The `-t` option is also very useful, it stops browsing automatically
|
||||||
@@ -294,17 +293,15 @@ name mappings for IP addresses. By default, it translates from IPv4
|
|||||||
addresses. This function allows users to confirm that addresses are
|
addresses. This function allows users to confirm that addresses are
|
||||||
mapped correctly.
|
mapped correctly.
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>avahi-resolve-host-name infix-c0-ff-ee.local</b>
|
||||||
linux-pc:# avahi-resolve-host-name infix-c0-ff-ee.local
|
|
||||||
infix-c0-ff-ee.local 10.0.1.1
|
infix-c0-ff-ee.local 10.0.1.1
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
Thanks to mDNS we can use the advertised name instead of the IP
|
Thanks to mDNS we can use the advertised name instead of the IP
|
||||||
address for operations like `ping` and `ssh` as shown below:
|
address for operations like `ping` and `ssh` as shown below:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>ping infix-c0-ff-ee.local -c 3</b>
|
||||||
linux-pc:# ping infix-c0-ff-ee.local -c 3
|
|
||||||
PING infix-c0-ff-ee.local (10.0.1.1) 56(84) bytes of data.
|
PING infix-c0-ff-ee.local (10.0.1.1) 56(84) bytes of data.
|
||||||
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.852 ms
|
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.852 ms
|
||||||
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=1.12 ms
|
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=1.12 ms
|
||||||
@@ -314,7 +311,7 @@ PING infix-c0-ff-ee.local (10.0.1.1) 56(84) bytes of data.
|
|||||||
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
|
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
|
||||||
rtt min/avg/max/mdev = 0.852/1.105/1.348/0.202 ms
|
rtt min/avg/max/mdev = 0.852/1.105/1.348/0.202 ms
|
||||||
|
|
||||||
linux-pc:# ssh admin@infix-c0-ff-ee.local
|
linux-pc:# <b>ssh admin@infix-c0-ff-ee.local</b>
|
||||||
(admin@infix-c0-ff-ee.local) Password:
|
(admin@infix-c0-ff-ee.local) Password:
|
||||||
.-------.
|
.-------.
|
||||||
| . . | Infix OS — Immutable.Friendly.Secure
|
| . . | Infix OS — Immutable.Friendly.Secure
|
||||||
@@ -324,27 +321,25 @@ linux-pc:# ssh admin@infix-c0-ff-ee.local
|
|||||||
Run the command 'cli' for interactive OAM
|
Run the command 'cli' for interactive OAM
|
||||||
|
|
||||||
linux-pc:#
|
linux-pc:#
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
To disable mDNS/mDNS-SD, type the commands:
|
To disable mDNS/mDNS-SD, type the commands:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/> <b>configure</b>
|
||||||
admin@infix-c0-ff-ee:/> configure
|
admin@infix-c0-ff-ee:/config/> <b>no mdns</b>
|
||||||
admin@infix-c0-ff-ee:/config/> no mdns
|
admin@infix-c0-ff-ee:/config/> <b>leave</b>
|
||||||
admin@infix-c0-ff-ee:/config/> leave
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
### Human-Friendly Hostname Alias
|
### Human-Friendly Hostname Alias
|
||||||
|
|
||||||
Each Infix deviuce advertise itself as *infix.local*, in addition to its
|
Each Infix device advertises itself as *infix.local*, in addition to its
|
||||||
full hostname (e.g., *infix-c0-ff-ee.local* or *foo.local*). This alias
|
full hostname (e.g., *infix-c0-ff-ee.local* or *foo.local*). This alias
|
||||||
works seamlessly on a network with a single Infix device, and makes it
|
works seamlessly on a network with a single Infix device, and makes it
|
||||||
easy to connect when the exact hostname is not known in advance. The
|
easy to connect when the exact hostname is not known in advance. The
|
||||||
examples below show how the alias can be used for actions such as
|
examples below show how the alias can be used for actions such as
|
||||||
pinging or establishing an SSH connection:
|
pinging or establishing an SSH connection:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>linux-pc:# <b>ping infix.local -c 3</b>
|
||||||
linux-pc:# ping infix.local -c 3
|
|
||||||
PING infix.local (10.0.1.1) 56(84) bytes of data.
|
PING infix.local (10.0.1.1) 56(84) bytes of data.
|
||||||
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.751 ms
|
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.751 ms
|
||||||
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=2.28 ms
|
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=2.28 ms
|
||||||
@@ -354,7 +349,7 @@ PING infix.local (10.0.1.1) 56(84) bytes of data.
|
|||||||
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
|
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
|
||||||
rtt min/avg/max/mdev = 0.751/1.482/2.281/0.626 ms
|
rtt min/avg/max/mdev = 0.751/1.482/2.281/0.626 ms
|
||||||
|
|
||||||
linux-pc:# ssh admin@infix.local
|
linux-pc:# <b>ssh admin@infix.local</b>
|
||||||
(admin@infix.local) Password:
|
(admin@infix.local) Password:
|
||||||
.-------.
|
.-------.
|
||||||
| . . | Infix OS — Immutable.Friendly.Secure
|
| . . | Infix OS — Immutable.Friendly.Secure
|
||||||
@@ -364,7 +359,7 @@ linux-pc:# ssh admin@infix.local
|
|||||||
Run the command 'cli' for interactive OAM
|
Run the command 'cli' for interactive OAM
|
||||||
|
|
||||||
admin@infix-c0-ff-ee:~$
|
admin@infix-c0-ff-ee:~$
|
||||||
```
|
</code></pre>
|
||||||
|
|
||||||
When multiple Infix devices are present on the LAN the alias will not
|
When multiple Infix devices are present on the LAN the alias will not
|
||||||
uniquely identify a device; *infix.local* will refer to any of the
|
uniquely identify a device; *infix.local* will refer to any of the
|
||||||
@@ -390,12 +385,11 @@ portal to access all others, if it goes down another takes its place.
|
|||||||
To disable the netbrowse service, and the *network.local* alias, the
|
To disable the netbrowse service, and the *network.local* alias, the
|
||||||
following commands can be used:
|
following commands can be used:
|
||||||
|
|
||||||
```
|
<pre class="cli"><code>admin@infix-c0-ff-ee:/> <b>configure</b>
|
||||||
admin@infix-c0-ff-ee:/> configure
|
admin@infix-c0-ff-ee:/config/> <b>edit web</b>
|
||||||
admin@infix-c0-ff-ee:/config/> edit web
|
admin@infix-c0-ff-ee:/config/web/> <b>no netbrowse</b>
|
||||||
admin@infix-c0-ff-ee:/config/web/> no netbrowse
|
admin@infix-c0-ff-ee:/config/web/> <b>leave</b>
|
||||||
admin@infix-c0-ff-ee:/config/web/> leave
|
</code></pre>
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
[^1]: E.g., [lldpd](https://github.com/lldp/lldpd) which includes the
|
[^1]: E.g., [lldpd](https://github.com/lldp/lldpd) which includes the
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 258 KiB After Width: | Height: | Size: 73 KiB |
@@ -1,3 +1,3 @@
|
|||||||
# Locally calculated
|
# Locally calculated
|
||||||
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
sha256 9d9d33b873917ca5d0bdcc47a36d2fd385971ab0c045d1472fcadf95ee5bcf5b LICENCE
|
||||||
sha256 cd9bc969350b8b30d9a7a31b0f19fb3218c602f04fe7e6a1d80682a75ed26d18 klish-3ae496c43d90354ffa94d364d7775c089f0e119a-git4.tar.gz
|
sha256 be6548a5a4f8c35906b02ea0ccb64cdd94d48bfe7133801353fbf658aa33d5c0 klish-8dca4da70a7794d5f4e0b047724bfe9e2088ebf3-git4.tar.gz
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
KLISH_VERSION = 3ae496c43d90354ffa94d364d7775c089f0e119a
|
KLISH_VERSION = 8dca4da70a7794d5f4e0b047724bfe9e2088ebf3
|
||||||
KLISH_SITE = https://github.com/kernelkit/klish.git
|
KLISH_SITE = https://github.com/kernelkit/klish.git
|
||||||
#KLISH_VERSION = tags/3.0.0
|
#KLISH_VERSION = tags/3.0.0
|
||||||
#KLISH_SITE = https://src.libcode.org/pkun/klish.git
|
#KLISH_SITE = https://src.libcode.org/pkun/klish.git
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# From GitHub release
|
# From GitHub release
|
||||||
sha256 cf32b3c224325b3b660669a82e6dc30ad8438016d7492433cea591fa3a8a1dd9 mdns-alias-1.1.tar.gz
|
sha256 9f194fa0b6e34fd915054394ef5b820a4f6b1755ace5ed1011bfba6df550accf mdns-alias-1.2.tar.gz
|
||||||
|
|
||||||
# Locally generated
|
# Locally generated
|
||||||
sha256 3d6f910b5e198f3daab48047b8ee6949040f7abee3927daf2e231f265faf7d91 LICENSE
|
sha256 3d6f910b5e198f3daab48047b8ee6949040f7abee3927daf2e231f265faf7d91 LICENSE
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
MDNS_ALIAS_VERSION = 1.1
|
MDNS_ALIAS_VERSION = 1.2
|
||||||
MDNS_ALIAS_SITE = https://github.com/troglobit/mdns-alias/releases/download/v$(MDNS_ALIAS_VERSION)
|
MDNS_ALIAS_SITE = https://github.com/troglobit/mdns-alias/releases/download/v$(MDNS_ALIAS_VERSION)
|
||||||
MDNS_ALIAS_LICENSE = ISC
|
MDNS_ALIAS_LICENSE = ISC
|
||||||
MDNS_ALIAS_LICENSE_FILES = LICENSE
|
MDNS_ALIAS_LICENSE_FILES = LICENSE
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
# Avahi advertises the system default hostname, this service advertises
|
# Avahi advertises the system default hostname, this service advertises
|
||||||
# /etc/hostname (-H) and, optionally, network.local as CNAMEs. Changes
|
# /etc/hostname (-H) and, optionally, network.local as CNAMEs. Changes
|
||||||
# to /etc/default/mdns-alias will cause Finit to restart not reload.
|
# to /etc/default/mdns-alias will cause Finit to restart not reload.
|
||||||
service <pid/mdns> env:-/etc/default/mdns-alias \
|
service [2345] env:-/etc/default/mdns-alias mdns-alias -H $MDNS_ALIAS_ARGS --
|
||||||
[2345] mdns-alias -H $MDNS_ALIAS_ARGS --
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
service <!> name:netbrowse log:prio:daemon.debug,tag:netbrowse \
|
service <!> name:netbrowse log:prio:daemon.debug,tag:netbrowse \
|
||||||
[2345] netbrowse -l 127.0.0.1:8000 -- Network browser
|
[2345] netbrowse -l 127.0.0.1:8000 \
|
||||||
|
-- Network browser
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
service <!pid/confd,usr/bootstrap> name:mdns env:-/etc/default/avahi \
|
service name:mdns pid:!/run/avahi-daemon/pid env:-/etc/default/avahi \
|
||||||
[2345] avahi-daemon -s $AVAHI_ARGS -- Avahi mDNS-SD daemon
|
[2345] <!pid/confd,usr/bootstrap> avahi-daemon -s $AVAHI_ARGS \
|
||||||
|
-- Avahi mDNS-SD daemon
|
||||||
|
|||||||
@@ -1,3 +1,2 @@
|
|||||||
#set DEBUG=1
|
#set DEBUG=1
|
||||||
|
|
||||||
service name:statd [12345] <pid/confd> statd -f -p /run/statd.pid -n -- Status daemon
|
service name:statd [12345] <pid/confd> statd -f -p /run/statd.pid -n -- Status daemon
|
||||||
|
|||||||
+245
-65
@@ -2,6 +2,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -42,6 +44,74 @@ var typeOrder = map[string]int{
|
|||||||
"HTTPS": 1, "HTTP": 2, "SSH": 3, "SFTP": 4,
|
"HTTPS": 1, "HTTP": 2, "SSH": 3, "SFTP": 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// txtMeta holds fields extracted from a set of DNS-SD TXT records.
|
||||||
|
type txtMeta struct {
|
||||||
|
vv1 bool
|
||||||
|
legacy bool
|
||||||
|
path string
|
||||||
|
adminurl string
|
||||||
|
product string
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTxt extracts well-known fields from a slice of individual TXT record
|
||||||
|
// strings. Each element must already be a single record without quoting.
|
||||||
|
// avahi-browse \DDD escape sequences in values are resolved via decode().
|
||||||
|
func parseTxt(records []string) txtMeta {
|
||||||
|
var m txtMeta
|
||||||
|
for _, r := range records {
|
||||||
|
switch {
|
||||||
|
case r == "vv=1":
|
||||||
|
m.vv1 = true
|
||||||
|
case r == "on=Infix":
|
||||||
|
m.legacy = true
|
||||||
|
case m.path == "" && strings.HasPrefix(r, "path="):
|
||||||
|
m.path = decode(r[5:])
|
||||||
|
case m.adminurl == "" && strings.HasPrefix(r, "adminurl="):
|
||||||
|
m.adminurl = decode(r[9:])
|
||||||
|
case m.product == "" && strings.HasPrefix(r, "product="):
|
||||||
|
m.product = decode(r[8:])
|
||||||
|
case m.product == "" && strings.HasPrefix(r, "am="):
|
||||||
|
// RAOP/AirPlay 1 Apple model key
|
||||||
|
m.product = decode(r[3:])
|
||||||
|
case m.product == "" && strings.HasPrefix(r, "model="):
|
||||||
|
// AirPlay 2 Apple model key
|
||||||
|
m.product = decode(r[6:])
|
||||||
|
case m.version == "" && strings.HasPrefix(r, "ov="):
|
||||||
|
m.version = decode(r[3:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildHosts sorts services and assembles the final host map from the
|
||||||
|
// per-host accumulator maps that both scan() and scanOperational() maintain.
|
||||||
|
func buildHosts(svcsMap map[string][]Service, addrMap, productMap, versionMap map[string]string,
|
||||||
|
vvHosts, legHosts, mgmtHosts map[string]bool) map[string]Host {
|
||||||
|
hosts := make(map[string]Host)
|
||||||
|
for link, svcs := range svcsMap {
|
||||||
|
sort.SliceStable(svcs, func(i, j int) bool {
|
||||||
|
oi, oj := typeOrder[svcs[i].Type], typeOrder[svcs[j].Type]
|
||||||
|
if oi == 0 {
|
||||||
|
oi = 999
|
||||||
|
}
|
||||||
|
if oj == 0 {
|
||||||
|
oj = 999
|
||||||
|
}
|
||||||
|
return oi < oj
|
||||||
|
})
|
||||||
|
isInfix := (vvHosts[link] && mgmtHosts[link]) || legHosts[link]
|
||||||
|
hosts[link] = Host{
|
||||||
|
Addr: addrMap[link],
|
||||||
|
Product: productMap[link],
|
||||||
|
Version: versionMap[link],
|
||||||
|
Other: !isInfix,
|
||||||
|
Svcs: svcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hosts
|
||||||
|
}
|
||||||
|
|
||||||
// hasK checks whether avahi-browse supports the -k flag.
|
// hasK checks whether avahi-browse supports the -k flag.
|
||||||
func hasK() bool {
|
func hasK() bool {
|
||||||
out, err := exec.Command("avahi-browse", "--help").CombinedOutput()
|
out, err := exec.Command("avahi-browse", "--help").CombinedOutput()
|
||||||
@@ -82,23 +152,21 @@ func scan() map[string]Host {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
family := parts[2]
|
family := parts[2]
|
||||||
serviceName := parts[3]
|
serviceName := parts[3]
|
||||||
serviceType := parts[4]
|
serviceType := parts[4]
|
||||||
link := parts[6]
|
link := parts[6]
|
||||||
address := parts[7]
|
address := parts[7]
|
||||||
port := parts[8]
|
port := parts[8]
|
||||||
txt := parts[9]
|
txt := strings.Join(parts[9:], ";")
|
||||||
|
|
||||||
if family != "IPv4" && family != "IPv6" {
|
if family != "IPv4" && family != "IPv6" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
info, known := knownServices[serviceType]
|
info, known := knownServices[serviceType]
|
||||||
displayName := info.displayName
|
if known {
|
||||||
urlTemplate := info.urlTemplate
|
mgmtHosts[link] = true
|
||||||
if !known {
|
|
||||||
displayName = serviceType
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// vv=1 is the platform marker set by confd/services.c, survives OS
|
// vv=1 is the platform marker set by confd/services.c, survives OS
|
||||||
@@ -106,9 +174,6 @@ func scan() map[string]Host {
|
|||||||
// (ssh, web, netconf, restconf) to avoid false positives from Apple
|
// (ssh, web, netconf, restconf) to avoid false positives from Apple
|
||||||
// devices, which also use vv=1 in their AirPlay/RAOP TXT records.
|
// devices, which also use vv=1 in their AirPlay/RAOP TXT records.
|
||||||
// on=Infix is kept as a fallback for older firmware predating vv=1.
|
// on=Infix is kept as a fallback for older firmware predating vv=1.
|
||||||
if known {
|
|
||||||
mgmtHosts[link] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prefer real IPv4; skip loopback and link-local.
|
// Prefer real IPv4; skip loopback and link-local.
|
||||||
// Loopback (127.x / ::1) appears when avahi resolves local-machine
|
// Loopback (127.x / ::1) appears when avahi resolves local-machine
|
||||||
@@ -128,42 +193,41 @@ func scan() map[string]Host {
|
|||||||
// "ty=Brother DCP-L3550CDW series" "adminurl=http://..."
|
// "ty=Brother DCP-L3550CDW series" "adminurl=http://..."
|
||||||
// Split on the between-record boundary `" "` (close-quote space
|
// Split on the between-record boundary `" "` (close-quote space
|
||||||
// open-quote) to keep each record intact, then trim outer quotes.
|
// open-quote) to keep each record intact, then trim outer quotes.
|
||||||
var path, adminurl, product, version string
|
var records []string
|
||||||
for _, record := range strings.Split(txt, "\" \"") {
|
for _, rec := range strings.Split(txt, "\" \"") {
|
||||||
stripped := strings.Trim(record, "\"")
|
records = append(records, strings.Trim(rec, "\""))
|
||||||
switch {
|
|
||||||
case stripped == "vv=1":
|
|
||||||
vvHosts[link] = true
|
|
||||||
case stripped == "on=Infix":
|
|
||||||
legHosts[link] = true
|
|
||||||
case path == "" && strings.HasPrefix(stripped, "path="):
|
|
||||||
path = stripped[5:]
|
|
||||||
case adminurl == "" && strings.HasPrefix(stripped, "adminurl="):
|
|
||||||
adminurl = stripped[9:]
|
|
||||||
case product == "" && strings.HasPrefix(stripped, "product="):
|
|
||||||
product = stripped[8:]
|
|
||||||
case version == "" && strings.HasPrefix(stripped, "ov="):
|
|
||||||
version = stripped[3:]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
meta := parseTxt(records)
|
||||||
|
if meta.vv1 {
|
||||||
|
vvHosts[link] = true
|
||||||
|
}
|
||||||
|
if meta.legacy {
|
||||||
|
legHosts[link] = true
|
||||||
|
}
|
||||||
|
|
||||||
// IPP/Bonjour printers encode product as "(Name)" — strip the parens.
|
// IPP/Bonjour printers encode product as "(Name)" — strip the parens.
|
||||||
product = strings.TrimPrefix(strings.TrimSuffix(product, ")"), "(")
|
product := strings.TrimPrefix(strings.TrimSuffix(meta.product, ")"), "(")
|
||||||
if product != "" && productMap[link] == "" {
|
if product != "" && productMap[link] == "" {
|
||||||
productMap[link] = product
|
productMap[link] = product
|
||||||
}
|
}
|
||||||
if version != "" && versionMap[link] == "" {
|
if meta.version != "" && versionMap[link] == "" {
|
||||||
versionMap[link] = version
|
versionMap[link] = meta.version
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := info.displayName
|
||||||
|
if !known {
|
||||||
|
displayName = serviceType
|
||||||
}
|
}
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
if adminurl != "" {
|
if meta.adminurl != "" {
|
||||||
url = adminurl
|
url = meta.adminurl
|
||||||
} else if urlTemplate != "" {
|
} else if info.urlTemplate != "" {
|
||||||
url = strings.NewReplacer(
|
url = strings.NewReplacer(
|
||||||
"{address}", address,
|
"{address}", address,
|
||||||
"{port}", port,
|
"{port}", port,
|
||||||
"{path}", path,
|
"{path}", meta.path,
|
||||||
).Replace(urlTemplate)
|
).Replace(info.urlTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc := Service{
|
svc := Service{
|
||||||
@@ -172,7 +236,9 @@ func scan() map[string]Host {
|
|||||||
URL: url,
|
URL: url,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deduplicate
|
// Deduplicate: avahi-browse reports each service once per
|
||||||
|
// (interface, protocol) combination, so the same entry can appear
|
||||||
|
// for both eth0/IPv4 and eth0/IPv6.
|
||||||
dup := false
|
dup := false
|
||||||
for _, existing := range svcsMap[link] {
|
for _, existing := range svcsMap[link] {
|
||||||
if existing.Type == svc.Type && existing.Name == svc.Name && existing.URL == svc.URL {
|
if existing.Type == svc.Type && existing.Name == svc.Name && existing.URL == svc.URL {
|
||||||
@@ -185,37 +251,151 @@ func scan() map[string]Host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort services per host
|
return buildHosts(svcsMap, addrMap, productMap, versionMap, vvHosts, legHosts, mgmtHosts)
|
||||||
for link := range svcsMap {
|
}
|
||||||
sort.SliceStable(svcsMap[link], func(i, j int) bool {
|
|
||||||
oi := typeOrder[svcsMap[link][i].Type]
|
|
||||||
oj := typeOrder[svcsMap[link][j].Type]
|
|
||||||
if oi == 0 {
|
|
||||||
oi = 999
|
|
||||||
}
|
|
||||||
if oj == 0 {
|
|
||||||
oj = 999
|
|
||||||
}
|
|
||||||
return oi < oj
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build final host map. Default view shows only Infix devices: a host
|
// JSON types for the operational-state backend.
|
||||||
// qualifies if it has vv=1 on a management service (to exclude Apple
|
type opRoot struct {
|
||||||
// AirPlay collisions), or on=Infix for older firmware predating vv=1.
|
MDNS struct {
|
||||||
hosts := make(map[string]Host)
|
Enabled bool `json:"enabled"`
|
||||||
for link, svcs := range svcsMap {
|
Neighbors struct {
|
||||||
isInfix := (vvHosts[link] && mgmtHosts[link]) || legHosts[link]
|
Neighbor []opNeighbor `json:"neighbor"`
|
||||||
hosts[link] = Host{
|
} `json:"neighbors"`
|
||||||
Addr: addrMap[link],
|
} `json:"infix-services:mdns"`
|
||||||
Product: productMap[link],
|
}
|
||||||
Version: versionMap[link],
|
|
||||||
Other: !isInfix,
|
type opNeighbor struct {
|
||||||
Svcs: svcs,
|
Hostname string `json:"hostname"`
|
||||||
|
Addresses []string `json:"address"`
|
||||||
|
Services []opService `json:"service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type opService struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Port uint16 `json:"port"`
|
||||||
|
Txt []string `json:"txt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOperational builds a host map from an already-decoded opRoot.
|
||||||
|
func parseOperational(root *opRoot) map[string]Host {
|
||||||
|
svcsMap := make(map[string][]Service)
|
||||||
|
addrMap := make(map[string]string)
|
||||||
|
productMap := make(map[string]string)
|
||||||
|
versionMap := make(map[string]string)
|
||||||
|
vvHosts := make(map[string]bool)
|
||||||
|
legHosts := make(map[string]bool)
|
||||||
|
mgmtHosts := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, n := range root.MDNS.Neighbors.Neighbor {
|
||||||
|
link := n.Hostname
|
||||||
|
|
||||||
|
// Pick best address: prefer IPv4, skip link-local.
|
||||||
|
// Loopback is already excluded by statd before storing.
|
||||||
|
for _, a := range n.Addresses {
|
||||||
|
if strings.HasPrefix(a, "fe80:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(a, ":") {
|
||||||
|
addrMap[link] = a // IPv4 wins, stop looking
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if addrMap[link] == "" {
|
||||||
|
addrMap[link] = a // IPv6 fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, svc := range n.Services {
|
||||||
|
info, known := knownServices[svc.Type]
|
||||||
|
if known {
|
||||||
|
mgmtHosts[link] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := parseTxt(svc.Txt)
|
||||||
|
if meta.vv1 {
|
||||||
|
vvHosts[link] = true
|
||||||
|
}
|
||||||
|
if meta.legacy {
|
||||||
|
legHosts[link] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPP/Bonjour printers encode product as "(Name)" — strip the parens.
|
||||||
|
product := strings.TrimPrefix(strings.TrimSuffix(meta.product, ")"), "(")
|
||||||
|
if product != "" && productMap[link] == "" {
|
||||||
|
productMap[link] = product
|
||||||
|
}
|
||||||
|
if meta.version != "" && versionMap[link] == "" {
|
||||||
|
versionMap[link] = meta.version
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := info.displayName
|
||||||
|
if !known {
|
||||||
|
displayName = svc.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := addrMap[link]
|
||||||
|
if addr == "" {
|
||||||
|
addr = n.Hostname // fall back to hostname if no usable address
|
||||||
|
}
|
||||||
|
|
||||||
|
var url string
|
||||||
|
if meta.adminurl != "" {
|
||||||
|
url = meta.adminurl
|
||||||
|
} else if info.urlTemplate != "" {
|
||||||
|
url = strings.NewReplacer(
|
||||||
|
"{address}", addr,
|
||||||
|
"{port}", fmt.Sprintf("%d", svc.Port),
|
||||||
|
"{path}", meta.path,
|
||||||
|
).Replace(info.urlTemplate)
|
||||||
|
}
|
||||||
|
|
||||||
|
svcsMap[link] = append(svcsMap[link], Service{
|
||||||
|
Type: displayName,
|
||||||
|
Name: svc.Name,
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hosts
|
return buildHosts(svcsMap, addrMap, productMap, versionMap, vvHosts, legHosts, mgmtHosts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchOpRoot runs `copy operational-state -x /mdns` and decodes the result.
|
||||||
|
// Returns nil on any error (command failure or JSON parse error).
|
||||||
|
func fetchOpRoot() *opRoot {
|
||||||
|
out, err := exec.Command("copy", "operational-state", "-x", "/mdns").Output()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("copy operational-state: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var root opRoot
|
||||||
|
if err := json.Unmarshal(out, &root); err != nil {
|
||||||
|
log.Printf("copy operational-state: json: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &root
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanOperational fetches the mDNS neighbor table from the sysrepo
|
||||||
|
// operational datastore via `copy operational-state -x /mdns` and returns
|
||||||
|
// the same host map as scan().
|
||||||
|
func scanOperational() map[string]Host {
|
||||||
|
root := fetchOpRoot()
|
||||||
|
if root == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return parseOperational(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scanAuto tries the operational backend first. If the `copy` command is
|
||||||
|
// unavailable or mDNS is disabled in the operational state, it falls back
|
||||||
|
// to the avahi-browse backend transparently.
|
||||||
|
func scanAuto() map[string]Host {
|
||||||
|
root := fetchOpRoot()
|
||||||
|
if root == nil || !root.MDNS.Enabled {
|
||||||
|
return scan()
|
||||||
|
}
|
||||||
|
return parseOperational(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode handles avahi's DNS-SD escape sequences in service names:
|
// decode handles avahi's DNS-SD escape sequences in service names:
|
||||||
|
|||||||
@@ -458,7 +458,7 @@
|
|||||||
}
|
}
|
||||||
var parts = [label];
|
var parts = [label];
|
||||||
if (lastTs) {
|
if (lastTs) {
|
||||||
parts.push('updated ' + lastTs.toLocaleTimeString());
|
parts.push('updated ' + lastTs.toLocaleTimeString(undefined, { hour12: false }));
|
||||||
}
|
}
|
||||||
foot.textContent = parts.join(' · ');
|
foot.textContent = parts.join(' · ');
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-9
@@ -25,24 +25,37 @@ func browseHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(browseHTML)
|
w.Write(browseHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dataHandler(w http.ResponseWriter, r *http.Request) {
|
func dataHandler(scanFn func() map[string]Host) http.HandlerFunc {
|
||||||
hosts := scan()
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
hosts := scanFn()
|
||||||
w.Header().Set("Cache-Control", "no-store")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
if err := json.NewEncoder(w).Encode(hosts); err != nil {
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
log.Printf("json: %v", err)
|
if err := json.NewEncoder(w).Encode(hosts); err != nil {
|
||||||
|
log.Printf("json: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
listen := flag.String("l", "127.0.0.1:8000", "listen address:port")
|
listen := flag.String("l", "127.0.0.1:8000", "listen address:port")
|
||||||
|
backend := flag.String("backend", "auto", "discovery backend: auto, avahi, or operational")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
var scanFn func() map[string]Host
|
||||||
|
switch *backend {
|
||||||
|
case "operational":
|
||||||
|
scanFn = scanOperational
|
||||||
|
case "avahi":
|
||||||
|
scanFn = scan
|
||||||
|
default:
|
||||||
|
scanFn = scanAuto
|
||||||
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("/", browseHandler)
|
mux.HandleFunc("/", browseHandler)
|
||||||
mux.HandleFunc("/netbrowse", browseHandler)
|
mux.HandleFunc("/netbrowse", browseHandler)
|
||||||
mux.HandleFunc("/data", dataHandler)
|
mux.HandleFunc("/data", dataHandler(scanFn))
|
||||||
mux.HandleFunc("/netbrowse/data", dataHandler)
|
mux.HandleFunc("/netbrowse/data", dataHandler(scanFn))
|
||||||
|
|
||||||
staticSub, err := fs.Sub(staticFS, "static")
|
staticSub, err := fs.Sub(staticFS, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+129
-19
@@ -380,7 +380,7 @@ static void ds_push_resolver(struct mdns_ctx *ctx, struct avahi_service *svc,
|
|||||||
char val[64];
|
char val[64];
|
||||||
struct avahi_txt *t;
|
struct avahi_txt *t;
|
||||||
char ts[32];
|
char ts[32];
|
||||||
int err = 0;
|
int err;
|
||||||
|
|
||||||
xpath_str(qname, sizeof(qname), svc->name);
|
xpath_str(qname, sizeof(qname), svc->name);
|
||||||
|
|
||||||
@@ -388,7 +388,7 @@ static void ds_push_resolver(struct mdns_ctx *ctx, struct avahi_service *svc,
|
|||||||
* rejects editing list-key leaves directly — set the list entry instead) */
|
* rejects editing list-key leaves directly — set the list entry instead) */
|
||||||
snprintf(xpath, sizeof(xpath),
|
snprintf(xpath, sizeof(xpath),
|
||||||
XPATH_BASE "/neighbor[hostname='%s']", svc->hostname);
|
XPATH_BASE "/neighbor[hostname='%s']", svc->hostname);
|
||||||
err = err ?: sr_setstr(ctx->sr_ses, xpath, NULL);
|
err = sr_setstr(ctx->sr_ses, xpath, NULL);
|
||||||
|
|
||||||
/* address (only if a new one was added) */
|
/* address (only if a new one was added) */
|
||||||
if (new_addr) {
|
if (new_addr) {
|
||||||
@@ -542,16 +542,49 @@ static void resolver_cb(AvahiServiceResolver *r,
|
|||||||
|
|
||||||
svc->port = port;
|
svc->port = port;
|
||||||
|
|
||||||
/* Copy TXT records verbatim */
|
/* Copy TXT records, skipping any that are not valid UTF-8 or contain
|
||||||
|
* bytes that are illegal in XML/YANG strings. Apple devices sometimes
|
||||||
|
* embed raw binary tokens (device keys, protocol blobs) in TXT records;
|
||||||
|
* passing them to sr_set_item_str() would return EINVAL. */
|
||||||
for (s = txtlist; s; s = avahi_string_list_get_next(s)) {
|
for (s = txtlist; s; s = avahi_string_list_get_next(s)) {
|
||||||
uint8_t *data = avahi_string_list_get_text(s);
|
uint8_t *data = avahi_string_list_get_text(s);
|
||||||
size_t len = avahi_string_list_get_size(s);
|
size_t len = avahi_string_list_get_size(s);
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
/* Validate: must be well-formed UTF-8 with no XML-illegal bytes */
|
||||||
|
for (i = 0; i < len; ) {
|
||||||
|
uint8_t b = data[i];
|
||||||
|
int extra;
|
||||||
|
|
||||||
|
if (b < 0x80) {
|
||||||
|
/* ASCII: reject control chars invalid in XML */
|
||||||
|
if ((b < 0x09) || (b > 0x0D && b < 0x20) || b == 0x7F)
|
||||||
|
goto skip;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Multi-byte UTF-8 lead byte */
|
||||||
|
if ((b & 0xE0) == 0xC0) extra = 1;
|
||||||
|
else if ((b & 0xF0) == 0xE0) extra = 2;
|
||||||
|
else if ((b & 0xF8) == 0xF0) extra = 3;
|
||||||
|
else goto skip; /* invalid lead byte */
|
||||||
|
|
||||||
|
i++;
|
||||||
|
for (; extra-- > 0; i++) {
|
||||||
|
if (i >= len || (data[i] & 0xC0) != 0x80)
|
||||||
|
goto skip; /* truncated sequence */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t = calloc(1, sizeof(*t));
|
t = calloc(1, sizeof(*t));
|
||||||
if (!t)
|
if (!t)
|
||||||
break;
|
break;
|
||||||
snprintf(t->val, sizeof(t->val), "%.*s", (int)len, (char *)data);
|
snprintf(t->val, sizeof(t->val), "%.*s", (int)len, (char *)data);
|
||||||
LIST_INSERT_HEAD(&svc->txts, t, link);
|
LIST_INSERT_HEAD(&svc->txts, t, link);
|
||||||
|
continue;
|
||||||
|
skip:
|
||||||
|
DEBUG("mdns: skipping binary TXT record for '%s' (len=%zu)", name, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
ds_push_resolver(ctx, svc, new_addr);
|
ds_push_resolver(ctx, svc, new_addr);
|
||||||
@@ -714,31 +747,59 @@ static bool mdns_is_enabled(struct mdns_ctx *ctx)
|
|||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Retry timer callback: fires 2 s after AVAHI_CLIENT_FAILURE (and repeats up
|
* Reconnect timer: fires MDNS_RECONN_DELAY seconds after AVAHI_CLIENT_FAILURE.
|
||||||
* to 3 times). Only logs ERROR once all retries are exhausted AND mDNS is
|
* Frees the broken client and creates a fresh one. libavahi's own AVAHI_CLIENT_NO_FAIL
|
||||||
* enabled in the running config — this avoids noisy errors when the operator
|
* reconnection can miss D-Bus NameOwnerChanged events; explicit free+recreate is
|
||||||
* has simply disabled the mDNS service.
|
* more reliable (same pattern used by mdns-alias).
|
||||||
*
|
|
||||||
* Example log (mDNS enabled, daemon stays down):
|
|
||||||
* avahi: mDNS daemon not responding (attempt 3/3) — check that the mdns
|
|
||||||
* service is running
|
|
||||||
*/
|
*/
|
||||||
|
#define MDNS_RECONN_DELAY 3.0
|
||||||
|
|
||||||
|
static void reconn_cb(struct ev_loop *loop, ev_timer *w, int revents)
|
||||||
|
{
|
||||||
|
struct mdns_ctx *ctx = (struct mdns_ctx *)
|
||||||
|
((char *)w - offsetof(struct mdns_ctx, reconn_timer));
|
||||||
|
int avahi_err;
|
||||||
|
|
||||||
|
(void)loop;
|
||||||
|
(void)revents;
|
||||||
|
|
||||||
|
if (ctx->client) {
|
||||||
|
avahi_client_free(ctx->client);
|
||||||
|
ctx->client = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->client = avahi_client_new(&ctx->poll_api, AVAHI_CLIENT_NO_FAIL,
|
||||||
|
client_cb, ctx, &avahi_err);
|
||||||
|
if (!ctx->client)
|
||||||
|
ERROR("mdns: failed to recreate avahi client: %s", avahi_strerror(avahi_err));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Log-delay timer: fires MDNS_WARN_DELAY seconds after AVAHI_CLIENT_FAILURE.
|
||||||
|
* Logs a single warning if mDNS is still enabled in the running config —
|
||||||
|
* suppresses noise when the operator has simply disabled the mDNS service or
|
||||||
|
* avahi is just restarting briefly. Reconnection itself is handled by the
|
||||||
|
* libavahi client (AVAHI_CLIENT_NO_FAIL) — we never give up.
|
||||||
|
*
|
||||||
|
* The delay must exceed libavahi's internal reconnect-poll interval (~5 s so
|
||||||
|
* that a normal daemon restart cancels this timer before it fires.
|
||||||
|
*/
|
||||||
|
#define MDNS_WARN_DELAY 10.0
|
||||||
|
|
||||||
static void mdns_retry_cb(struct ev_loop *loop, ev_timer *w, int revents)
|
static void mdns_retry_cb(struct ev_loop *loop, ev_timer *w, int revents)
|
||||||
{
|
{
|
||||||
struct mdns_ctx *ctx = (struct mdns_ctx *)
|
struct mdns_ctx *ctx = (struct mdns_ctx *)
|
||||||
((char *)w - offsetof(struct mdns_ctx, retry_timer));
|
((char *)w - offsetof(struct mdns_ctx, retry_timer));
|
||||||
|
|
||||||
|
(void)loop;
|
||||||
|
(void)revents;
|
||||||
ctx->fail_count++;
|
ctx->fail_count++;
|
||||||
if (ctx->fail_count < 3) {
|
|
||||||
ev_timer_set(w, 2.0, 0.0);
|
|
||||||
ev_timer_start(loop, w);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mdns_is_enabled(ctx))
|
if (mdns_is_enabled(ctx))
|
||||||
ERROR("mdns: mDNS daemon not responding (attempt %d/3) — "
|
WARN("mdns: mDNS daemon not responding, will reconnect automatically");
|
||||||
"check that the mdns service is running", ctx->fail_count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
|
static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
|
||||||
@@ -750,6 +811,7 @@ static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case AVAHI_CLIENT_S_RUNNING:
|
case AVAHI_CLIENT_S_RUNNING:
|
||||||
if (ctx->fail_count > 0) {
|
if (ctx->fail_count > 0) {
|
||||||
|
ev_timer_stop(ctx->loop, &ctx->reconn_timer);
|
||||||
ev_timer_stop(ctx->loop, &ctx->retry_timer);
|
ev_timer_stop(ctx->loop, &ctx->retry_timer);
|
||||||
NOTE("mdns: mDNS daemon reconnected");
|
NOTE("mdns: mDNS daemon reconnected");
|
||||||
ctx->fail_count = 0;
|
ctx->fail_count = 0;
|
||||||
@@ -779,8 +841,12 @@ static void client_cb(AvahiClient *c, AvahiClientState state, void *userdata)
|
|||||||
* will log only if the daemon stays down for 3 attempts (~6 s)
|
* will log only if the daemon stays down for 3 attempts (~6 s)
|
||||||
* and mDNS is enabled in the running config.
|
* and mDNS is enabled in the running config.
|
||||||
*/
|
*/
|
||||||
|
if (!ev_is_active(&ctx->reconn_timer)) {
|
||||||
|
ev_timer_init(&ctx->reconn_timer, reconn_cb, MDNS_RECONN_DELAY, 0.0);
|
||||||
|
ev_timer_start(ctx->loop, &ctx->reconn_timer);
|
||||||
|
}
|
||||||
if (!ev_is_active(&ctx->retry_timer)) {
|
if (!ev_is_active(&ctx->retry_timer)) {
|
||||||
ev_timer_init(&ctx->retry_timer, mdns_retry_cb, 2.0, 0.0);
|
ev_timer_init(&ctx->retry_timer, mdns_retry_cb, MDNS_WARN_DELAY, 0.0);
|
||||||
ev_timer_start(ctx->loop, &ctx->retry_timer);
|
ev_timer_start(ctx->loop, &ctx->retry_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,10 +923,54 @@ int mdns_ctx_init(struct mdns_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *sr_
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mdns_ctx_reconnect(struct mdns_ctx *ctx)
|
||||||
|
{
|
||||||
|
struct avahi_type_entry *te;
|
||||||
|
int avahi_err;
|
||||||
|
|
||||||
|
if (!mdns_is_enabled(ctx)) {
|
||||||
|
NOTE("mdns: mDNS is disabled, ignoring reconnect request");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NOTE("mdns: reconnecting on request");
|
||||||
|
|
||||||
|
ev_timer_stop(ctx->loop, &ctx->reconn_timer);
|
||||||
|
ev_timer_stop(ctx->loop, &ctx->retry_timer);
|
||||||
|
ctx->fail_count = 0;
|
||||||
|
|
||||||
|
/* Clean up browsers before freeing the client */
|
||||||
|
while (!LIST_EMPTY(&ctx->type_entries)) {
|
||||||
|
te = LIST_FIRST(&ctx->type_entries);
|
||||||
|
avahi_service_browser_free(te->browser);
|
||||||
|
LIST_REMOVE(te, link);
|
||||||
|
free(te);
|
||||||
|
}
|
||||||
|
if (ctx->type_browser) {
|
||||||
|
avahi_service_type_browser_free(ctx->type_browser);
|
||||||
|
ctx->type_browser = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free_all(ctx);
|
||||||
|
ds_clear_all(ctx);
|
||||||
|
|
||||||
|
if (ctx->client) {
|
||||||
|
avahi_client_free(ctx->client);
|
||||||
|
ctx->client = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->client = avahi_client_new(&ctx->poll_api, AVAHI_CLIENT_NO_FAIL,
|
||||||
|
client_cb, ctx, &avahi_err);
|
||||||
|
if (!ctx->client)
|
||||||
|
ERROR("mdns: failed to recreate avahi client: %s", avahi_strerror(avahi_err));
|
||||||
|
}
|
||||||
|
|
||||||
void mdns_ctx_exit(struct mdns_ctx *ctx)
|
void mdns_ctx_exit(struct mdns_ctx *ctx)
|
||||||
{
|
{
|
||||||
struct avahi_type_entry *te;
|
struct avahi_type_entry *te;
|
||||||
|
|
||||||
|
if (ev_is_active(&ctx->reconn_timer))
|
||||||
|
ev_timer_stop(ctx->loop, &ctx->reconn_timer);
|
||||||
if (ev_is_active(&ctx->retry_timer))
|
if (ev_is_active(&ctx->retry_timer))
|
||||||
ev_timer_stop(ctx->loop, &ctx->retry_timer);
|
ev_timer_stop(ctx->loop, &ctx->retry_timer);
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -58,14 +58,16 @@ struct mdns_ctx {
|
|||||||
AvahiClient *client;
|
AvahiClient *client;
|
||||||
AvahiServiceTypeBrowser *type_browser;
|
AvahiServiceTypeBrowser *type_browser;
|
||||||
AvahiPoll poll_api; /* libev-backed vtable */
|
AvahiPoll poll_api; /* libev-backed vtable */
|
||||||
unsigned int fail_count; /* Consecutive avahi-daemon connection failures */
|
unsigned int fail_count; /* Non-zero while avahi-daemon is absent */
|
||||||
ev_timer retry_timer; /* Deferred error-log timer */
|
ev_timer reconn_timer; /* Free+recreate client after brief delay */
|
||||||
|
ev_timer retry_timer; /* Deferred warn-log timer */
|
||||||
LIST_HEAD(, avahi_neighbor) neighbors;
|
LIST_HEAD(, avahi_neighbor) neighbors;
|
||||||
LIST_HEAD(, avahi_service) services; /* Flat list; keyed by 5-tuple */
|
LIST_HEAD(, avahi_service) services; /* Flat list; keyed by 5-tuple */
|
||||||
LIST_HEAD(, avahi_type_entry) type_entries;
|
LIST_HEAD(, avahi_type_entry) type_entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
int mdns_ctx_init(struct mdns_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *sr_conn);
|
int mdns_ctx_init(struct mdns_ctx *ctx, struct ev_loop *loop, sr_conn_ctx_t *sr_conn);
|
||||||
|
void mdns_ctx_reconnect(struct mdns_ctx *ctx);
|
||||||
void mdns_ctx_exit(struct mdns_ctx *ctx);
|
void mdns_ctx_exit(struct mdns_ctx *ctx);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+12
-1
@@ -352,6 +352,13 @@ static void sigusr1_cb(struct ev_loop *, struct ev_signal *, int)
|
|||||||
debug ^= 1;
|
debug ^= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sighup_cb(struct ev_loop *, struct ev_signal *w, int)
|
||||||
|
{
|
||||||
|
struct statd *statd = w->data;
|
||||||
|
|
||||||
|
mdns_ctx_reconnect(&statd->mdns);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void sr_event_cb(struct ev_loop *, struct ev_io *w, int)
|
static void sr_event_cb(struct ev_loop *, struct ev_io *w, int)
|
||||||
{
|
{
|
||||||
@@ -455,7 +462,7 @@ static int subscribe_to_all(struct statd *statd)
|
|||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
struct ev_signal sigint_watcher, sigusr1_watcher;
|
struct ev_signal sigint_watcher, sigusr1_watcher, sighup_watcher;
|
||||||
int log_opts = LOG_PID | LOG_NDELAY;
|
int log_opts = LOG_PID | LOG_NDELAY;
|
||||||
struct statd statd = {};
|
struct statd statd = {};
|
||||||
const char *env;
|
const char *env;
|
||||||
@@ -516,6 +523,10 @@ int main(int argc, char *argv[])
|
|||||||
sigusr1_watcher.data = &statd;
|
sigusr1_watcher.data = &statd;
|
||||||
ev_signal_start(statd.ev_loop, &sigusr1_watcher);
|
ev_signal_start(statd.ev_loop, &sigusr1_watcher);
|
||||||
|
|
||||||
|
ev_signal_init(&sighup_watcher, sighup_cb, SIGHUP);
|
||||||
|
sighup_watcher.data = &statd;
|
||||||
|
ev_signal_start(statd.ev_loop, &sighup_watcher);
|
||||||
|
|
||||||
err = journal_start(&statd.journal, statd.sr_query_ses);
|
err = journal_start(&statd.journal, statd.sr_query_ses);
|
||||||
if (err) {
|
if (err) {
|
||||||
sr_session_stop(statd.sr_query_ses);
|
sr_session_stop(statd.sr_query_ses);
|
||||||
|
|||||||
+22
-2
@@ -197,12 +197,31 @@ llscan_mdns()
|
|||||||
{
|
{
|
||||||
local all="$1"
|
local all="$1"
|
||||||
local flags="-tarp"
|
local flags="-tarp"
|
||||||
|
local tmpfile i c pid
|
||||||
|
|
||||||
if avahi-browse --help 2>&1 | grep -q -- '-k'; then
|
if avahi-browse --help 2>&1 | grep -q -- '-k'; then
|
||||||
flags="-tarpk"
|
flags="-tarpk"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
avahi-browse $flags 2>/dev/null | awk -F';' -v show_all="$all" '
|
tmpfile=$(mktemp)
|
||||||
|
avahi-browse $flags 2>/dev/null >"$tmpfile" &
|
||||||
|
pid=$!
|
||||||
|
|
||||||
|
if [ -t 2 ]; then
|
||||||
|
i=0
|
||||||
|
while kill -0 "$pid" 2>/dev/null; do
|
||||||
|
case $((i % 4)) in
|
||||||
|
0) c='-';; 1) c="\\";; 2) c='|';; 3) c='/';;
|
||||||
|
esac
|
||||||
|
printf "\r[%s] Scanning, please wait ..." "$c" >&2
|
||||||
|
i=$((i + 1))
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
printf "\r\033[2K" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
wait "$pid"
|
||||||
|
awk -F';' -v show_all="$all" '
|
||||||
$1 == "=" {
|
$1 == "=" {
|
||||||
host = $7
|
host = $7
|
||||||
proto = $3
|
proto = $3
|
||||||
@@ -279,7 +298,8 @@ llscan_mdns()
|
|||||||
|
|
||||||
printf "\n%d device(s) found.\n", ndevs
|
printf "\n%d device(s) found.\n", ndevs
|
||||||
}
|
}
|
||||||
'
|
' "$tmpfile"
|
||||||
|
rm -f "$tmpfile"
|
||||||
}
|
}
|
||||||
|
|
||||||
llscan_ll()
|
llscan_ll()
|
||||||
|
|||||||
Reference in New Issue
Block a user