Skip to content

Commit

Permalink
feat: add network tools commands for NAS and Wake-on-LAN
Browse files Browse the repository at this point in the history
- Introduce a new command `nas` for network-attached storage tools.
- Implement a new `wol` command which allows the sending of Wake-on-LAN packets.
- Add a new `wol` package to handle the construction and sending of magic packets.
- Modify the root command to include the newly created `nas` command.
- Ensure error handling is implemented for MAC address input in the `wol` command.
- Include licensing information in the newly created files.

Signed-off-by: ysicing <i@ysicing.me>
  • Loading branch information
ysicing committed Nov 27, 2024
1 parent b78ca36 commit 0d8db60
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 1 deletion.
23 changes: 23 additions & 0 deletions cmd/nas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) 2024 ysicing(ysicing.me, ysicing@12306.work) All rights reserved.
// Use of this source code is covered by the following dual licenses:
// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0)
// (2) Affero General Public License 3.0 (AGPL 3.0)
// License that can be found in the LICENSE file.

package cmd

import (
"github.com/spf13/cobra"
"github.com/ysicing/tiga/cmd/nas"
"github.com/ysicing/tiga/pkg/factory"
)

func newCmdNas(f factory.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "nas",
Short: "nas tools",
Args: cobra.NoArgs,
}
cmd.AddCommand(nas.WolCmd(f))
return cmd
}
36 changes: 36 additions & 0 deletions cmd/nas/wol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) 2024 ysicing(ysicing.me, ysicing@12306.work) All rights reserved.
// Use of this source code is covered by the following dual licenses:
// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0)
// (2) Affero General Public License 3.0 (AGPL 3.0)
// License that can be found in the LICENSE file.

package nas

import (
"fmt"

"github.com/spf13/cobra"
"github.com/ysicing/tiga/internal/pkg/wol"
"github.com/ysicing/tiga/pkg/factory"
)

func WolCmd(f factory.Factory) *cobra.Command {
var mac string
cmd := &cobra.Command{
Use: "wol",
Short: "wol tools",
RunE: func(_ *cobra.Command, _ []string) error {
if mac == "" {
return fmt.Errorf("mac address is required")
}
if err := wol.Wake(mac); err != nil {
f.GetLog().Warnf("wol %s failed: %v", mac, err)
return nil
}
f.GetLog().Donef("wol %s success", mac)
return nil
},
}
cmd.Flags().StringVarP(&mac, "mac", "m", "", "mac address")
return cmd
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func BuildRoot(f factory.Factory) *cobra.Command {
rootCmd.AddCommand(newCmdRepo(f))
rootCmd.AddCommand(newCmdFake(f))
rootCmd.AddCommand(newCmdCfdTunnel(f))

rootCmd.AddCommand(newCmdNas(f))
rootCmd.AddCommand(newManCmd())

args := os.Args
Expand Down
114 changes: 114 additions & 0 deletions internal/pkg/wol/wol.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright (c) 2024 ysicing(ysicing.me, ysicing@12306.work) All rights reserved.
// Use of this source code is covered by the following dual licenses:
// (1) Y PUBLIC LICENSE 1.0 (YPL 1.0)
// (2) Affero General Public License 3.0 (AGPL 3.0)
// License that can be found in the LICENSE file.

package wol

import (
"bytes"
"encoding/binary"
"fmt"
"net"
"regexp"
)

// think from https://github.com/xiaoxinpro/WolGoWeb

var (
delims = ":-"
reMAC = regexp.MustCompile(`^([0-9a-fA-F]{2}[` + delims + `]){5}([0-9a-fA-F]{2})$`)
)

// MACAddress represents a 6 byte network mac address.
type MACAddress [6]byte

// A MagicPacket is constituted of 6 bytes of 0xFF followed by 16-groups of the
// destination MAC address.
type MagicPacket struct {
header [6]byte
payload [16]MACAddress
}

// New 返回一个基于mac地址字符串的魔法包。
func New(mac string) (*MagicPacket, error) {
var packet MagicPacket
var macAddr MACAddress

hwAddr, err := net.ParseMAC(mac)
if err != nil {
return nil, err
}

// We only support 6 byte MAC addresses since it is much harder to use the
// binary.Write(...) interface when the size of the MagicPacket is dynamic.
if !reMAC.MatchString(mac) {
return nil, fmt.Errorf("%s is not a IEEE 802 MAC-48 address", mac)
}

// Copy bytes from the returned HardwareAddr -> a fixed size MACAddress.
for idx := range macAddr {
macAddr[idx] = hwAddr[idx]
}

// Setup the header which is 6 repetitions of 0xFF.
for idx := range packet.header {
packet.header[idx] = 0xFF
}

// Setup the payload which is 16 repetitions of the MAC addr.
for idx := range packet.payload {
packet.payload[idx] = macAddr
}

return &packet, nil
}

// Marshal 将魔术包结构序列化为一个102字节数组。
func (mp *MagicPacket) Marshal() ([]byte, error) {
var buf bytes.Buffer
if err := binary.Write(&buf, binary.BigEndian, mp); err != nil {
return nil, err
}

return buf.Bytes(), nil
}

// wake 执行唤醒指令
func Wake(macAddr string) error {
udpAddr, err := net.ResolveUDPAddr("udp", "255.255.255.255:9")
if err != nil {
return err
}

// 获取魔术包
mp, err := New(macAddr)
if err != nil {
return err
}

// 魔术包转字节流
bs, err := mp.Marshal()
if err != nil {
return err
}

var localAddr *net.UDPAddr
// 创建UDP链接
conn, err := net.DialUDP("udp", localAddr, udpAddr)
if err != nil {
return err
}
defer conn.Close()

// 开始发送UDP数据
n, err := conn.Write(bs)
if err == nil && n != 102 {
err = fmt.Errorf("sent magic packet %d bytes (should send magic packet 102 bytes)", n)
}
if err != nil {
return err
}
return nil
}

0 comments on commit 0d8db60

Please sign in to comment.