use wol package and support wol passwords

This commit is contained in:
Maxi Quoß
2023-01-26 18:27:49 +01:00
parent a3b1413fbb
commit e39d626119
6 changed files with 146 additions and 89 deletions

View File

@@ -40,11 +40,11 @@ Open up [http://localhost:5173/](http://localhost:5173/)
- [x] ~~make sure ping works~~
- [ ] make sure arp works
- [ ] make sure wake works
- [x] make sure wake works
- [ ] remove nmap?
- [ ] add shutdown command
- [ ] add scheduled wake
- [ ] [#34 Add support for WOL passwords](https://github.com/seriousm4x/UpSnap/issues/34)
- [x] [#34 Add support for WOL passwords](https://github.com/seriousm4x/UpSnap/issues/34)
- [x] [~~#33 Seemingly High Ram usage~~](https://github.com/seriousm4x/UpSnap/issues/33)
- [x] [~~#32 API available?~~](https://github.com/seriousm4x/UpSnap/issues/32)

View File

@@ -5,6 +5,7 @@ go 1.19
require (
github.com/go-ping/ping v1.1.0
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198
github.com/mdlayher/wol v0.0.0-20220221231636-b763a792253a
github.com/pocketbase/dbx v1.8.0
github.com/pocketbase/pocketbase v0.11.2
github.com/robfig/cron/v3 v3.0.1
@@ -48,10 +49,14 @@ require (
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966 // indirect
github.com/mdlayher/packet v1.0.0 // indirect
github.com/mdlayher/socket v0.2.1 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/spf13/cast v1.5.0 // indirect

View File

@@ -1293,6 +1293,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -1397,6 +1399,15 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966 h1:O3p5UmisBhl3V6lgs4Vdfg8HpjzbWJPyOfGLdwVJSmI=
github.com/mdlayher/ethernet v0.0.0-20190313224307-5b5fc417d966/go.mod h1:5s5p/sMJ6sNsFl6uCh85lkFGV8kLuIYJCRJLavVJwvg=
github.com/mdlayher/packet v1.0.0 h1:InhZJbdShQYt6XV2GPj5XHxChzOfhJJOMbvnGAmOfQ8=
github.com/mdlayher/packet v1.0.0/go.mod h1:eE7/ctqDhoiRhQ44ko5JZU2zxB88g+JH/6jmnjzPjOU=
github.com/mdlayher/raw v0.0.0-20190313224157-43dbcdd7739d/go.mod h1:r1fbeITl2xL/zLbVnNHFyOzQJTgr/3fpf1lJX/cjzR8=
github.com/mdlayher/socket v0.2.1 h1:F2aaOwb53VsBE+ebRS9bLd7yPOfYUMC8lOODdCBDY6w=
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/wol v0.0.0-20220221231636-b763a792253a h1:fpQi42PM6HzbHwNg6a7OvLbh5l49Rgr7GCARozjHwio=
github.com/mdlayher/wol v0.0.0-20220221231636-b763a792253a/go.mod h1:XX6nvyQpGc8E4KaQm62FyexOYd36ilpyfYf8wjgMDV8=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -1980,6 +1991,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=

View File

@@ -1,45 +1,74 @@
package networking
import (
"bytes"
"encoding/hex"
"encoding/binary"
"errors"
"fmt"
"net"
"strings"
"github.com/mdlayher/wol"
"github.com/pocketbase/pocketbase/models"
)
func SendMagicPacket(mac, netmask string) error {
macAddr, err := hex.DecodeString(strings.ReplaceAll(mac, ":", ""))
func SendMagicPacket(device *models.Record) error {
ip := device.GetString("ip")
mac := device.GetString("mac")
netmask := device.GetString("netmask")
password := device.GetString("password")
// parse inputs
parsedIP := net.ParseIP(ip)
if parsedIP == nil {
return fmt.Errorf("error: invalid ip address")
}
parsedMac, err := net.ParseMAC(mac)
if err != nil {
return fmt.Errorf("error decoding mac address: %v", err)
return err
}
parsedNetmask := net.ParseIP(netmask)
if parsedNetmask == nil {
return fmt.Errorf("error: invalid netmask")
}
var bytePassword []byte
if len(password) == 0 || len(password) == 4 || len(password) == 6 {
bytePassword = []byte(password)
} else {
return fmt.Errorf("error: password must be 0, 4 or 6 characters long")
}
// Create an address to listen on
address := net.UDPAddr{
IP: net.ParseIP(netmask),
Port: 7,
// get target addr
ipNet := &net.IPNet{
IP: parsedIP,
Mask: parsedNetmask.DefaultMask(),
}
// create a new UDP packet conn
conn, err := net.DialUDP("udp", nil, &address)
broadcastIp, err := getBroadcastIp(ipNet)
if err != nil {
return fmt.Errorf("error creating UDP packet conn: %v", err)
return err
}
defer conn.Close()
targetAddr := fmt.Sprintf("%s:%d", broadcastIp, 9)
// create a magic packet
var magicPacket bytes.Buffer
for i := 0; i < 6; i++ {
magicPacket.WriteByte(0xff)
}
for i := 0; i < 16; i++ {
magicPacket.Write(macAddr)
// send wake via udp port 9
if err := wakeUDP(targetAddr, parsedMac, bytePassword); err != nil {
return err
}
// send the magic packet
_, err = conn.Write(magicPacket.Bytes())
if err != nil {
return fmt.Errorf("error sending magic packet: %v", err)
}
return nil
}
func wakeUDP(addr string, target net.HardwareAddr, password []byte) error {
c, err := wol.NewClient()
if err != nil {
return err
}
defer c.Close()
return c.WakePassword(addr, target, password)
}
func getBroadcastIp(n *net.IPNet) (net.IP, error) {
if n.IP.To4() == nil {
return net.IP{}, errors.New("does not support IPv6 addresses")
}
ip := make(net.IP, len(n.IP.To4()))
binary.BigEndian.PutUint32(ip, binary.BigEndian.Uint32(n.IP.To4())|^binary.BigEndian.Uint32(net.IP(n.Mask).To4()))
return ip, nil
}

View File

@@ -8,7 +8,7 @@ import (
)
func WakeDevice(device *models.Record) bool {
err := SendMagicPacket(device.GetString("mac"), device.GetString("netmask"))
err := SendMagicPacket(device)
if err != nil {
logger.Error.Println(err)
return false

View File

@@ -48,6 +48,13 @@
async function addDevice() {
buttons.add_device.state = 'waiting';
try {
if (
newDevice.password.length != 0 &&
newDevice.password.length != 4 &&
newDevice.password.length != 6
) {
throw 'Password must be 0, 4 or 6 characters long';
}
await pb.collection('devices').create(newDevice, { $autoCancel: false });
setTimeout(() => {
buttons.add_device.state = 'none';
@@ -274,6 +281,7 @@
aria-describedby="addon-wrapping"
type={passwordType}
value={newDevice.password}
maxlength="6"
on:input={onPasswordInput}
/>
<button
@@ -316,74 +324,77 @@
</li>
</ul>
<p class="m-0">
(3) Some network cards have the option to set a password for magic packets.
(3) Some network cards have the option to set a password for magic packets, also called
"SecureON". Password can only be 0, 4 or 6 characters in length.
</p>
</div>
</div>
</div>
</section>
<section class="m-0 mt-4 p-4 shadow-sm">
<h3 class="mb-3">Ping interval</h3>
<div class="row">
<div class="col-md-6">
<p>Sets the interval in which the devices are pinged.</p>
<div class="input-group mb-3">
<span class="input-group-text">Cron</span>
<input
class="form-control"
placeholder="e.g. '@every 5s' or '@every 1m'"
aria-label="Interval"
aria-describedby="addon-wrapping"
type="text"
bind:value={settings.interval}
/>
<form on:submit|preventDefault={saveSettings}>
<h3 class="mb-3">Ping interval</h3>
<div class="row">
<div class="col-md-6">
<p>Sets the interval in which the devices are pinged.</p>
<div class="input-group mb-3">
<span class="input-group-text">Cron</span>
<input
class="form-control"
placeholder="e.g. '@every 5s' or '@every 1m'"
aria-label="Interval"
aria-describedby="addon-wrapping"
type="text"
bind:value={settings.interval}
/>
</div>
</div>
<div class="col-md-6">
<div class="callout callout-info m-0">
<p class="m-0">
Leave blank to use default value of <span class="fw-bold">"@every 3s"</span>.
</p>
<p class="m-0">
Read more about valid cron syntax at <a
target="_blank"
rel="noreferrer"
href="https://pkg.go.dev/github.com/robfig/cron"
>pkg.go.dev/github.com/robfig/cron</a
>
</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="callout callout-info m-0">
<p class="m-0">
Leave blank to use default value of <span class="fw-bold">"@every 3s"</span>.
</p>
<p class="m-0">
Read more about valid cron syntax at <a
target="_blank"
rel="noreferrer"
href="https://pkg.go.dev/github.com/robfig/cron">pkg.go.dev/github.com/robfig/cron</a
>
</p>
</div>
<h3 class="my-3">Notifications</h3>
<p>Show toast notifications in the bottom right corner.</p>
<div class="form-check form-switch">
<label class="form-check-label" for="settings-notifications">Enable notifications</label>
<input
class="form-check-input"
type="checkbox"
id="settings-notifications"
role="switch"
bind:checked={settings.notifications}
/>
</div>
</div>
<h3 class="my-3">Notifications</h3>
<p>Show toast notifications in the bottom right corner.</p>
<div class="form-check form-switch">
<label class="form-check-label" for="settings-notifications">Enable notifications</label>
<input
class="form-check-input"
type="checkbox"
id="settings-notifications"
role="switch"
bind:checked={settings.notifications}
/>
</div>
<button
type="button"
class="btn btn-secondary mt-3"
class:btn-success={buttons.saved.state === 'success' ? true : false}
class:btn-warning={buttons.saved.state === 'waiting' ? true : false}
class:btn-danger={buttons.saved.state === 'failed' ? true : false}
on:click={() => saveSettings()}
>
{#if buttons.saved.state === 'none'}
Save
{:else if buttons.saved.state === 'success'}
Saved
{:else if buttons.saved.state === 'waiting'}
Waiting
{:else if buttons.saved.state === 'failed'}
Failed: {buttons.add_device.error}
{/if}
</button>
<button
type="submit"
class="btn btn-secondary mt-3"
class:btn-success={buttons.saved.state === 'success' ? true : false}
class:btn-warning={buttons.saved.state === 'waiting' ? true : false}
class:btn-danger={buttons.saved.state === 'failed' ? true : false}
>
{#if buttons.saved.state === 'none'}
Save
{:else if buttons.saved.state === 'success'}
Saved
{:else if buttons.saved.state === 'waiting'}
Waiting
{:else if buttons.saved.state === 'failed'}
Failed: {buttons.add_device.error}
{/if}
</button>
</form>
</section>
<section class="m-0 my-4 p-4 shadow-sm">
<h3 class="mb-3">Restore</h3>