Initial commit
This commit is contained in:
226
main.go
Normal file
226
main.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mdlayher/ethernet"
|
||||
"github.com/mdlayher/lldp"
|
||||
"github.com/mdlayher/raw"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func main() {
|
||||
link, err := getLink()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Network interface: %s", link.Attrs().Name)
|
||||
|
||||
api, err := findApi(link)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("UniFi API URL: %s", api)
|
||||
|
||||
upstream, err := getUpstream(link.Attrs().Name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Connected to %s [%s] on %s", upstream.device, upstream.addr, upstream.port)
|
||||
}
|
||||
|
||||
type upstream struct {
|
||||
device string
|
||||
port string
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func getUpstream(intName string) (*upstream, error) {
|
||||
intr, err := net.InterfaceByName(intName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sock, err := raw.ListenPacket(intr, 0x88cc /* LLDP ethertype */, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sock.Close()
|
||||
|
||||
buf := make([]byte, intr.MTU)
|
||||
bufLen, addr, err := sock.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ethFrame := ðernet.Frame{}
|
||||
err = ethFrame.UnmarshalBinary(buf[:bufLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lldpFrame := &lldp.Frame{}
|
||||
err = lldpFrame.UnmarshalBinary(ethFrame.Payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &upstream {
|
||||
addr: addr,
|
||||
}
|
||||
|
||||
for _, tlv := range lldpFrame.Optional {
|
||||
switch tlv.Type {
|
||||
case lldp.TLVTypePortDescription:
|
||||
ret.port = strings.ToLower(string(tlv.Value))
|
||||
case lldp.TLVTypeSystemName:
|
||||
ret.device = string(tlv.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func findApi(link netlink.Link) (string, error) {
|
||||
addrs, err := netlink.AddrList(link, 2 /* AF_INET */)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apis := []string{}
|
||||
|
||||
for _, addr := range addrs {
|
||||
ip := binary.BigEndian.Uint32(addr.IPNet.IP.To4())
|
||||
mask := binary.BigEndian.Uint32(addr.IPNet.Mask)
|
||||
start := ip & mask
|
||||
end := ip | (^mask & 0xffffffff)
|
||||
|
||||
addr_apis, err := findApis(start, end)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apis = append(apis, addr_apis...)
|
||||
}
|
||||
|
||||
if len(apis) == 0 {
|
||||
return "", fmt.Errorf("no UniFi APIs found")
|
||||
}
|
||||
|
||||
if len(apis) > 1 {
|
||||
return "", fmt.Errorf("multiple APIs found TODO")
|
||||
}
|
||||
|
||||
return apis[0], nil
|
||||
}
|
||||
|
||||
func findApis(start_ip, end_ip uint32) ([]string, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(int(end_ip - start_ip + 1))
|
||||
|
||||
ret := []string{}
|
||||
|
||||
for iter := start_ip; iter <= end_ip; iter++ {
|
||||
go func(ip uint32) {
|
||||
defer wg.Done()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 500 * time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
url := fmt.Sprintf(
|
||||
"https://%d.%d.%d.%d/",
|
||||
ip >> 24 & 0xff,
|
||||
ip >> 16 & 0xff,
|
||||
ip >> 8 & 0xff,
|
||||
ip >> 0 & 0xff,
|
||||
)
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
// TODO: should return an outer error
|
||||
return
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
client := http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// Probably not https
|
||||
return
|
||||
}
|
||||
|
||||
set_cookie := resp.Header["Set-Cookie"]
|
||||
if len(set_cookie) != 1 || !strings.HasPrefix(set_cookie[0], "TOKEN=") {
|
||||
// Probably not Unifi
|
||||
return
|
||||
}
|
||||
|
||||
ret = append(ret, url)
|
||||
}(iter)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func getLink() (netlink.Link, error) {
|
||||
links, err := getLinks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(links) == 0 {
|
||||
return nil, fmt.Errorf("no links found (TODO)")
|
||||
}
|
||||
|
||||
if len(links) > 1 {
|
||||
return nil, fmt.Errorf("more than one link (TODO)")
|
||||
}
|
||||
|
||||
return links[0], nil
|
||||
}
|
||||
|
||||
func getLinks() ([]netlink.Link, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := []netlink.Link{}
|
||||
|
||||
for _, link := range links {
|
||||
if link.Attrs().Flags & net.FlagUp == 0 {
|
||||
// Link is down
|
||||
continue
|
||||
}
|
||||
|
||||
if link.Attrs().Flags & net.FlagLoopback != 0 {
|
||||
// Link is loopback
|
||||
continue
|
||||
}
|
||||
|
||||
if link.Attrs().EncapType != "ether" {
|
||||
// Not ethernet
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, link)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
Reference in New Issue
Block a user