mirror of
https://github.com/seriousm4x/UpSnap.git
synced 2026-07-28 00:12:56 -04:00
use wol package and support wol passwords
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user