mirror of https://github.com/jetkvm/kvm.git
Compare commits
7 Commits
def37141c8
...
513b305606
Author | SHA1 | Date |
---|---|---|
|
513b305606 | |
|
2a99c2db9d | |
|
0b5033f798 | |
|
d07bedb323 | |
|
aa0f38bc0b | |
|
dd1189e8ad | |
|
c380c702c7 |
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
name: golangci-lint
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "go.sum"
|
||||||
|
- "go.mod"
|
||||||
|
- "**.go"
|
||||||
|
- ".github/workflows/golangci-lint.yml"
|
||||||
|
- ".golangci.yml"
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions: # added using https://github.com/step-security/secure-repo
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
permissions:
|
||||||
|
contents: read # for actions/checkout to fetch code
|
||||||
|
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0
|
||||||
|
with:
|
||||||
|
go-version: 1.23.x
|
||||||
|
- name: Lint
|
||||||
|
uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1
|
||||||
|
with:
|
||||||
|
args: --verbose
|
||||||
|
version: v1.62.0
|
|
@ -0,0 +1,12 @@
|
||||||
|
---
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
# - goimports
|
||||||
|
# - misspell
|
||||||
|
# - revive
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
2
cloud.go
2
cloud.go
|
@ -130,7 +130,7 @@ func runWebsocketClient() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer c.CloseNow()
|
defer c.CloseNow() //nolint:errcheck
|
||||||
logger.Infof("WS connected to %v", wsURL.String())
|
logger.Infof("WS connected to %v", wsURL.String())
|
||||||
runCtx, cancelRun := context.WithCancel(context.Background())
|
runCtx, cancelRun := context.WithCancel(context.Background())
|
||||||
defer cancelRun()
|
defer cancelRun()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"kvm"
|
"github.com/jetkvm/kvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
@ -22,6 +22,7 @@ type Config struct {
|
||||||
LocalAuthToken string `json:"local_auth_token"`
|
LocalAuthToken string `json:"local_auth_token"`
|
||||||
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
|
||||||
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
|
||||||
|
EdidString string `json:"hdmi_edid_string"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const configPath = "/userdata/kvm_config.json"
|
const configPath = "/userdata/kvm_config.json"
|
||||||
|
|
4
hw.go
4
hw.go
|
@ -14,7 +14,7 @@ func extractSerialNumber() (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := regexp.Compile("Serial\\s*:\\s*(\\S+)")
|
r, err := regexp.Compile(`Serial\s*:\s*(\S+)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to compile regex: %w", err)
|
return "", fmt.Errorf("failed to compile regex: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ func extractSerialNumber() (string, error) {
|
||||||
return matches[1], nil
|
return matches[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readOtpEntropy() ([]byte, error) {
|
func readOtpEntropy() ([]byte, error) { //nolint:unused
|
||||||
content, err := os.ReadFile("/sys/bus/nvmem/devices/rockchip-otp0/nvmem")
|
content, err := os.ReadFile("/sys/bus/nvmem/devices/rockchip-otp0/nvmem")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -183,6 +183,12 @@ func rpcSetEDID(edid string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save EDID to config, allowing it to be restored on reboot.
|
||||||
|
LoadConfig()
|
||||||
|
config.EdidString = edid
|
||||||
|
SaveConfig()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
main.go
2
main.go
|
@ -48,7 +48,7 @@ func Main() {
|
||||||
time.Sleep(15 * time.Minute)
|
time.Sleep(15 * time.Minute)
|
||||||
for {
|
for {
|
||||||
logger.Debugf("UPDATING - Auto update enabled: %v", config.AutoUpdateEnabled)
|
logger.Debugf("UPDATING - Auto update enabled: %v", config.AutoUpdateEnabled)
|
||||||
if config.AutoUpdateEnabled == false {
|
if !config.AutoUpdateEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if currentSession != nil {
|
if currentSession != nil {
|
||||||
|
|
25
native.go
25
native.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"kvm/resource"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
@ -14,6 +13,8 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/resource"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v4/pkg/media"
|
"github.com/pion/webrtc/v4/pkg/media"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,8 +92,8 @@ func WriteCtrlMessage(message []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var nativeCtrlSocketListener net.Listener
|
var nativeCtrlSocketListener net.Listener //nolint:unused
|
||||||
var nativeVideoSocketListener net.Listener
|
var nativeVideoSocketListener net.Listener //nolint:unused
|
||||||
|
|
||||||
var ctrlClientConnected = make(chan struct{})
|
var ctrlClientConnected = make(chan struct{})
|
||||||
|
|
||||||
|
@ -152,6 +153,9 @@ func handleCtrlClient(conn net.Conn) {
|
||||||
|
|
||||||
ctrlSocketConn = conn
|
ctrlSocketConn = conn
|
||||||
|
|
||||||
|
// Restore HDMI EDID if applicable
|
||||||
|
go restoreHdmiEdid()
|
||||||
|
|
||||||
readBuf := make([]byte, 4096)
|
readBuf := make([]byte, 4096)
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(readBuf)
|
n, err := conn.Read(readBuf)
|
||||||
|
@ -192,7 +196,7 @@ func handleVideoClient(conn net.Conn) {
|
||||||
for {
|
for {
|
||||||
n, err := conn.Read(inboundPacket)
|
n, err := conn.Read(inboundPacket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("error during read: %s", err)
|
log.Printf("error during read: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -304,3 +308,16 @@ func ensureBinaryUpdated(destPath string) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore the HDMI EDID value from the config.
|
||||||
|
// Called after successful connection to jetkvm_native.
|
||||||
|
func restoreHdmiEdid() {
|
||||||
|
LoadConfig()
|
||||||
|
if config.EdidString != "" {
|
||||||
|
logger.Infof("Restoring HDMI EDID to %v", config.EdidString)
|
||||||
|
_, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": config.EdidString})
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to restore HDMI EDID: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
45
network.go
45
network.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"golang.org/x/net/ipv4"
|
"golang.org/x/net/ipv4"
|
||||||
"golang.org/x/net/ipv6"
|
"golang.org/x/net/ipv6"
|
||||||
"net"
|
"net"
|
||||||
|
"os/exec"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
|
@ -25,10 +26,27 @@ type LocalIpInfo struct {
|
||||||
MAC string
|
MAC string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setDhcpClientState sends signals to udhcpc to change it's current mode
|
||||||
|
// of operation. Setting active to true will force udhcpc to renew the DHCP lease.
|
||||||
|
// Setting active to false will put udhcpc into idle mode.
|
||||||
|
func setDhcpClientState(active bool) {
|
||||||
|
var signal string;
|
||||||
|
if active {
|
||||||
|
signal = "-SIGUSR1"
|
||||||
|
} else {
|
||||||
|
signal = "-SIGUSR2"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("/usr/bin/killall", signal, "udhcpc");
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
fmt.Printf("network: setDhcpClientState: failed to change udhcpc state: %s\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func checkNetworkState() {
|
func checkNetworkState() {
|
||||||
iface, err := netlink.LinkByName("eth0")
|
iface, err := netlink.LinkByName("eth0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to get eth0 interface: %v\n", err)
|
fmt.Printf("failed to get eth0 interface: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +62,29 @@ func checkNetworkState() {
|
||||||
|
|
||||||
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
|
addrs, err := netlink.AddrList(iface, nl.FAMILY_ALL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to get addresses for eth0: %v\n", err)
|
fmt.Printf("failed to get addresses for eth0: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the link is going down, put udhcpc into idle mode.
|
||||||
|
// If the link is coming back up, activate udhcpc and force it to renew the lease.
|
||||||
|
if newState.Up != networkState.Up {
|
||||||
|
setDhcpClientState(newState.Up)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, addr := range addrs {
|
for _, addr := range addrs {
|
||||||
if addr.IP.To4() != nil {
|
if addr.IP.To4() != nil {
|
||||||
newState.IPv4 = addr.IP.String()
|
if !newState.Up && networkState.Up {
|
||||||
|
// If the network is going down, remove all IPv4 addresses from the interface.
|
||||||
|
fmt.Printf("network: state transitioned to down, removing IPv4 address %s\n", addr.IP.String())
|
||||||
|
err := netlink.AddrDel(iface, &addr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("network: failed to delete %s", addr.IP.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
newState.IPv4 = "..."
|
||||||
|
} else {
|
||||||
|
newState.IPv4 = addr.IP.String()
|
||||||
|
}
|
||||||
} else if addr.IP.To16() != nil && newState.IPv6 == "" {
|
} else if addr.IP.To16() != nil && newState.IPv6 == "" {
|
||||||
newState.IPv6 = addr.IP.String()
|
newState.IPv6 = addr.IP.String()
|
||||||
}
|
}
|
||||||
|
@ -98,7 +133,7 @@ func init() {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
|
|
||||||
if err := netlink.LinkSubscribe(updates, done); err != nil {
|
if err := netlink.LinkSubscribe(updates, done); err != nil {
|
||||||
fmt.Println("failed to subscribe to link updates: %v", err)
|
fmt.Printf("failed to subscribe to link updates: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +160,6 @@ func init() {
|
||||||
fmt.Println("Starting mDNS server")
|
fmt.Println("Starting mDNS server")
|
||||||
err := startMDNS()
|
err := startMDNS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("failed to run mDNS: %v", err)
|
fmt.Printf("failed to run mDNS: %v\n", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
ntp.go
2
ntp.go
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/beevik/ntp"
|
"github.com/beevik/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var timeSynced = false
|
var timeSynced = false //nolint:unused
|
||||||
|
|
||||||
func TimeSyncLoop() {
|
func TimeSyncLoop() {
|
||||||
for {
|
for {
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (w *WebRTCDiskReader) Read(ctx context.Context, offset int64, size int64) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
buf := make([]byte, 0)
|
var buf []byte
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case data := <-diskReadChan:
|
case data := <-diskReadChan:
|
||||||
|
|
|
@ -55,11 +55,13 @@ func handleTerminalChannel(d *webrtc.DataChannel) {
|
||||||
var size TerminalSize
|
var size TerminalSize
|
||||||
err := json.Unmarshal([]byte(msg.Data), &size)
|
err := json.Unmarshal([]byte(msg.Data), &size)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
pty.Setsize(ptmx, &pty.Winsize{
|
err = pty.Setsize(ptmx, &pty.Winsize{
|
||||||
Rows: uint16(size.Rows),
|
Rows: uint16(size.Rows),
|
||||||
Cols: uint16(size.Cols),
|
Cols: uint16(size.Cols),
|
||||||
})
|
})
|
||||||
return
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.Errorf("Failed to parse terminal size: %v", err)
|
logger.Errorf("Failed to parse terminal size: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -74,7 +76,7 @@ func handleTerminalChannel(d *webrtc.DataChannel) {
|
||||||
ptmx.Close()
|
ptmx.Close()
|
||||||
}
|
}
|
||||||
if cmd != nil && cmd.Process != nil {
|
if cmd != nil && cmd.Process != nil {
|
||||||
cmd.Process.Kill()
|
_ = cmd.Process.Kill()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { InputFieldWithLabel } from "./InputField";
|
||||||
import DebianIcon from "@/assets/debian-icon.png";
|
import DebianIcon from "@/assets/debian-icon.png";
|
||||||
import UbuntuIcon from "@/assets/ubuntu-icon.png";
|
import UbuntuIcon from "@/assets/ubuntu-icon.png";
|
||||||
import FedoraIcon from "@/assets/fedora-icon.png";
|
import FedoraIcon from "@/assets/fedora-icon.png";
|
||||||
|
import OpenSUSEIcon from "@/assets/opensuse-icon.png";
|
||||||
import ArchIcon from "@/assets/arch-icon.png";
|
import ArchIcon from "@/assets/arch-icon.png";
|
||||||
import NetBootIcon from "@/assets/netboot-icon.svg";
|
import NetBootIcon from "@/assets/netboot-icon.svg";
|
||||||
import { TrashIcon } from "@heroicons/react/16/solid";
|
import { TrashIcon } from "@heroicons/react/16/solid";
|
||||||
|
@ -542,6 +543,16 @@ function UrlView({
|
||||||
url: "https://download.fedoraproject.org/pub/fedora/linux/releases/41/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-41-1.4.iso",
|
url: "https://download.fedoraproject.org/pub/fedora/linux/releases/41/Workstation/x86_64/iso/Fedora-Workstation-Live-x86_64-41-1.4.iso",
|
||||||
icon: FedoraIcon,
|
icon: FedoraIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "openSUSE Leap 15.6",
|
||||||
|
url: "https://download.opensuse.org/distribution/leap/15.6/iso/openSUSE-Leap-15.6-NET-x86_64-Media.iso",
|
||||||
|
icon: OpenSUSEIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "openSUSE Tumbleweed",
|
||||||
|
url: "https://download.opensuse.org/tumbleweed/iso/openSUSE-Tumbleweed-NET-x86_64-Current.iso",
|
||||||
|
icon: OpenSUSEIcon,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Arch Linux",
|
name: "Arch Linux",
|
||||||
url: "https://archlinux.doridian.net/iso/2025.02.01/archlinux-2025.02.01-x86_64.iso",
|
url: "https://archlinux.doridian.net/iso/2025.02.01/archlinux-2025.02.01-x86_64.iso",
|
||||||
|
|
|
@ -466,7 +466,7 @@ export default function SettingsSidebar() {
|
||||||
<GridCard>
|
<GridCard>
|
||||||
<div className="flex items-center px-4 py-3 group gap-x-4">
|
<div className="flex items-center px-4 py-3 group gap-x-4">
|
||||||
<img
|
<img
|
||||||
className="w-6 shrink-0"
|
className="w-6 shrink-0 dark:invert"
|
||||||
src={PointingFinger}
|
src={PointingFinger}
|
||||||
alt="Finger touching a screen"
|
alt="Finger touching a screen"
|
||||||
/>
|
/>
|
||||||
|
@ -490,7 +490,7 @@ export default function SettingsSidebar() {
|
||||||
>
|
>
|
||||||
<GridCard>
|
<GridCard>
|
||||||
<div className="flex items-center px-4 py-3 gap-x-4">
|
<div className="flex items-center px-4 py-3 gap-x-4">
|
||||||
<img className="w-6 shrink-0" src={MouseIcon} alt="Mouse icon" />
|
<img className="w-6 shrink-0 dark:invert" src={MouseIcon} alt="Mouse icon" />
|
||||||
<div className="flex items-center justify-between grow">
|
<div className="flex items-center justify-between grow">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-sm font-semibold text-black dark:text-white">
|
<h3 className="text-sm font-semibold text-black dark:text-white">
|
||||||
|
|
2
usb.go
2
usb.go
|
@ -215,7 +215,7 @@ func writeGadgetConfig() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func rebindUsb() error {
|
func rebindUsb() error { //nolint:unused
|
||||||
err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644)
|
err := os.WriteFile("/sys/bus/platform/drivers/dwc3/unbind", []byte(udc), 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"kvm/resource"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
@ -16,12 +15,12 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jetkvm/kvm/resource"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/psanford/httpreadat"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
|
"github.com/psanford/httpreadat"
|
||||||
)
|
)
|
||||||
|
|
||||||
const massStorageName = "mass_storage.usb0"
|
const massStorageName = "mass_storage.usb0"
|
||||||
|
@ -60,11 +59,11 @@ func onDiskMessage(msg webrtc.DataChannelMessage) {
|
||||||
func mountImage(imagePath string) error {
|
func mountImage(imagePath string) error {
|
||||||
err := setMassStorageImage("")
|
err := setMassStorageImage("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Remove Mass Storage Image Error", err)
|
return fmt.Errorf("Remove Mass Storage Image Error: %w", err)
|
||||||
}
|
}
|
||||||
err = setMassStorageImage(imagePath)
|
err = setMassStorageImage(imagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Set Mass Storage Image Error", err)
|
return fmt.Errorf("Set Mass Storage Image Error: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
2
wol.go
2
wol.go
|
@ -43,7 +43,7 @@ func createMagicPacket(mac net.HardwareAddr) []byte {
|
||||||
|
|
||||||
// Write the target MAC address 16 times
|
// Write the target MAC address 16 times
|
||||||
for i := 0; i < 16; i++ {
|
for i := 0; i < 16; i++ {
|
||||||
binary.Write(&buf, binary.BigEndian, mac)
|
_ = binary.Write(&buf, binary.BigEndian, mac)
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
|
|
Loading…
Reference in New Issue