Initial commit: Import from /home/simon/test/ac
This commit is contained in:
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
# Git
|
||||
.git/
|
||||
.gitattributes
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.sublime-*
|
||||
|
||||
# Language specific
|
||||
node_modules/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.gradle/
|
||||
target/
|
||||
dist/
|
||||
build/
|
||||
.next/
|
||||
.nuxt/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Temporary
|
||||
*.tmp
|
||||
*.temp
|
||||
*.cache
|
||||
52
Makefile
Executable file
52
Makefile
Executable file
@@ -0,0 +1,52 @@
|
||||
GO_BIN_PATH = bin
|
||||
GO_SRC_PATH = src
|
||||
ROOT_PATH = $(shell pwd)
|
||||
|
||||
GO_NF = ac
|
||||
|
||||
GO_FILES = $(shell find $(GO_SRC_PATH)/$(%) -name "*.go" ! -name "*_test.go")
|
||||
|
||||
VERSION = 1.2408.0
|
||||
COMMIT_HASH = $(shell git log -1 --format=%h)
|
||||
COMMIT_TIME = $(shell git log --pretty="@%at" -1 | xargs date -u +"%Y-%m-%d %H:%M:%SZ" -d)
|
||||
LDFLAGS = -X 'ac/internal/version.VERSION=$(VERSION)' \
|
||||
-X 'ac/internal/version.COMMIT_HASH=$(COMMIT_HASH)' \
|
||||
-X 'ac/internal/version.COMMIT_TIME=$(COMMIT_TIME)'
|
||||
|
||||
.PHONY: $(GO_NF) clean
|
||||
|
||||
.DEFAULT_GOAL: $(GO_NF)
|
||||
|
||||
all: $(GO_NF)
|
||||
|
||||
debug: GCFLAGS += -N -l
|
||||
debug: all
|
||||
|
||||
$(GO_NF): % : $(GO_BIN_PATH)/%
|
||||
|
||||
$(GO_BIN_PATH)/%: $(GO_FILES)
|
||||
# $(@F): The file-within-directory part of the file name of the target.
|
||||
@echo "Start building $(@F)...."
|
||||
cd $(GO_SRC_PATH)/cmd && \
|
||||
go build -gcflags "$(GCFLAGS)" -ldflags "$(LDFLAGS)" -o $(ROOT_PATH)/$@ main.go
|
||||
|
||||
vpath %.go $(addprefix $(GO_SRC_PATH)/, $(GO_NF))
|
||||
|
||||
deb:
|
||||
test -d debian && rm -rf debian/* || mkdir debian
|
||||
mkdir -p debian/DEBIAN
|
||||
mkdir -p debian/usr/local/bin
|
||||
mkdir -p debian/usr/local/etc/ac/default
|
||||
mkdir -p debian/lib/systemd/system
|
||||
cp $(GO_BIN_PATH)/$(GO_NF) debian/usr/local/bin
|
||||
cp config/ac.yaml debian/usr/local/etc/ac/default
|
||||
cp scripts/ac.service debian/lib/systemd/system
|
||||
cp scripts/postinst debian/DEBIAN
|
||||
cp scripts/prerm debian/DEBIAN
|
||||
cp scripts/control debian/DEBIAN
|
||||
fakeroot dpkg-deb --build debian
|
||||
mv debian.deb debian/ac-r$(VERSION)-ub22.deb
|
||||
|
||||
clean:
|
||||
rm -rf $(addprefix $(GO_BIN_PATH)/, $(GO_NF))
|
||||
|
||||
2
config/ac.yaml
Executable file
2
config/ac.yaml
Executable file
@@ -0,0 +1,2 @@
|
||||
debugLevel: trace
|
||||
capwapAddr: 192.168.10.158
|
||||
11
scripts/ac.service
Executable file
11
scripts/ac.service
Executable file
@@ -0,0 +1,11 @@
|
||||
[Service]
|
||||
Type=idle
|
||||
Environment=GOTRACEBACK=crash
|
||||
ExecStart=/usr/local/bin/ac
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
StartLimitInterval=0
|
||||
StandardOutput=null
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
10
scripts/control
Normal file
10
scripts/control
Normal file
@@ -0,0 +1,10 @@
|
||||
Package: ac
|
||||
Version: 2.2409.0
|
||||
Section: net
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Essential: no
|
||||
Depends:
|
||||
Conflicts: ac
|
||||
Maintainer: AC maintainer
|
||||
Description: WLAN Software
|
||||
15
scripts/postinst
Executable file
15
scripts/postinst
Executable file
@@ -0,0 +1,15 @@
|
||||
#! /bin/bash
|
||||
|
||||
service_name=ac
|
||||
|
||||
test ! -f /usr/local/etc/ac/ac.yaml && cp -f /usr/local/etc/ac/default/ac.yaml /usr/local/etc/ac
|
||||
|
||||
if test -x /sbin/ldconfig
|
||||
then
|
||||
/sbin/ldconfig
|
||||
else
|
||||
echo Cannot run /sbin/ldconfig
|
||||
fi
|
||||
|
||||
systemctl enable $service_name
|
||||
|
||||
7
scripts/prerm
Executable file
7
scripts/prerm
Executable file
@@ -0,0 +1,7 @@
|
||||
#! /bin/bash
|
||||
|
||||
# Commands to be run before uninstall of the package
|
||||
|
||||
service_name=ac
|
||||
|
||||
systemctl disable $service_name
|
||||
49
src/cmd/main.go
Normal file
49
src/cmd/main.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"ac/internal/logger"
|
||||
"ac/internal/version"
|
||||
"ac/pkg/factory"
|
||||
"ac/pkg/service"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
// Print stack for panic to log. Fatalf() will let program exit.
|
||||
logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
logger.MainLog.Infoln("AC version: ", version.GetVersionAndHash())
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
<-sigCh // Wait for interrupt signal to gracefully shutdown
|
||||
cancel() // Notify each goroutine and wait them stopped
|
||||
}()
|
||||
|
||||
cfg, err := factory.ReadConfig()
|
||||
if err != nil {
|
||||
logger.MainLog.Errorf("AC Run error: %v\n", err)
|
||||
return
|
||||
}
|
||||
factory.AcConfig = cfg
|
||||
|
||||
ac, err := service.NewApp(ctx, cfg)
|
||||
if err != nil {
|
||||
logger.MainLog.Errorf("AC Run error: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
ac.Start()
|
||||
}
|
||||
28
src/go.mod
Normal file
28
src/go.mod
Normal file
@@ -0,0 +1,28 @@
|
||||
module ac
|
||||
|
||||
go 1.23.3
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/mochi-mqtt/server/v2 v2.6.6
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/tim-ywliu/nested-logrus-formatter v1.3.2
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/rs/xid v1.4.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
53
src/go.sum
Normal file
53
src/go.sum
Normal file
@@ -0,0 +1,53 @@
|
||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/mochi-mqtt/server/v2 v2.6.6 h1:FmL5ebeIIA+AKo/nX0DF8Yc2MMWFLQCwh3FZBEmg6dQ=
|
||||
github.com/mochi-mqtt/server/v2 v2.6.6/go.mod h1:TqztjKGO0/ArOjJt9x9idk0kqPT3CVN8Pb+l+PS5Gdo=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
|
||||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 h1:jugNJ2/CNCI79SxOJCOhwUHeN3O7/7/bj+ZRGOFlCSw=
|
||||
github.com/tim-ywliu/nested-logrus-formatter v1.3.2/go.mod h1:oGPmcxZB65j9Wo7mCnQKSrKEJtVDqyjD666SGmyStXI=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
17
src/internal/capwap/handler.go
Normal file
17
src/internal/capwap/handler.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package capwap
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
capwap_service "ac/internal/capwap/service"
|
||||
"ac/internal/logger"
|
||||
)
|
||||
|
||||
func HandleMessage(peerAddr net.Addr, msg []byte) {
|
||||
if len(msg) > 0 {
|
||||
discoverResponse := [...]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x0f, 0xaa, 0x92, 0xab, 0x5a, 0x4b, 0x31, 0x30, 0x35, 0x30, 0x46, 0x30, 0x32, 0x34, 0x38, 0x31, 0x33, 0x30, 0x30, 0x30, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x8c, 0xbf, 0x64, 0xb1, 0x46, 0xd1, 0x64, 0xcf, 0x88, 0x6d, 0x28, 0x47, 0x12, 0xa1, 0x75, 0xdb, 0xbe, 0x1d, 0x35, 0x0b, 0xfa, 0xa6, 0xa7, 0x2c, 0x36, 0x89, 0x27, 0x7f, 0x98, 0xff, 0xa6}
|
||||
if _, err := capwap_service.SendMsg(peerAddr, discoverResponse[:]); err != nil {
|
||||
logger.CapwapLog.Errorf("Capwap send Discovery Response error : %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/internal/capwap/service/service.go
Normal file
60
src/internal/capwap/service/service.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"net"
|
||||
|
||||
"ac/internal/logger"
|
||||
)
|
||||
|
||||
type Handler func(addr net.Addr, msg []byte)
|
||||
|
||||
var pktConn net.PacketConn
|
||||
|
||||
func Run(address string, msgHandler Handler) {
|
||||
var err error
|
||||
laddr, err := net.ResolveUDPAddr("udp", "["+address+"]:5246")
|
||||
if err != nil {
|
||||
logger.CapwapLog.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// setup underlying connection first.
|
||||
// not using net.Dial, as it binds src/dst IP:Port, which makes it harder to
|
||||
// handle multiple connections with a Conn.
|
||||
pktConn, err = net.ListenPacket(laddr.Network(), laddr.String())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
buf := make([]byte, 4096)
|
||||
|
||||
n, addr, err := pktConn.ReadFrom(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
logger.CapwapLog.Tracef("Read %d bytes", n)
|
||||
logger.CapwapLog.Tracef("Packet content:\n%+v", hex.Dump(buf[:n]))
|
||||
|
||||
msgHandler(addr, buf[:n])
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// SendMsg - used to send out message to UDP connection
|
||||
func SendMsg(raddr net.Addr, msg []byte) (int, error) {
|
||||
if pktConn == nil || raddr == nil {
|
||||
logger.CapwapLog.Warn("SendMsg failed.")
|
||||
return 0, nil
|
||||
}
|
||||
return pktConn.WriteTo(msg, raddr)
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
if pktConn != nil {
|
||||
pktConn.Close()
|
||||
}
|
||||
}
|
||||
74
src/internal/context/context.go
Normal file
74
src/internal/context/context.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
mqtt_server "github.com/mochi-mqtt/server/v2"
|
||||
|
||||
"ac/internal/mqtt/message"
|
||||
"ac/pkg/factory"
|
||||
)
|
||||
|
||||
var acContext ACContext
|
||||
|
||||
func init() {
|
||||
}
|
||||
|
||||
type ACContext struct {
|
||||
ApPool sync.Map // map[mqtt_server.Client]*Ap
|
||||
CapwapAddr string
|
||||
}
|
||||
|
||||
type Ap struct {
|
||||
Client *mqtt_server.Client
|
||||
LastEcho *message.Echo
|
||||
}
|
||||
|
||||
func InitAcContext(context *ACContext) {
|
||||
config := factory.AcConfig
|
||||
context.CapwapAddr = config.CapwapAddr
|
||||
}
|
||||
|
||||
func (context *ACContext) NewAp(client *mqtt_server.Client) *Ap {
|
||||
ap := Ap{Client: client}
|
||||
context.ApPool.Store(client, &ap)
|
||||
return &ap
|
||||
}
|
||||
|
||||
// use mqtt_server.Client to find AP context, return *Ap and ok bit
|
||||
func (context *ACContext) ApFindByClient(client *mqtt_server.Client) (*Ap, bool) {
|
||||
if value, ok := context.ApPool.Load(client); ok {
|
||||
return value.(*Ap), ok
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// use clientId to find AP context, return *Ap and ok bit
|
||||
func (context *ACContext) ApFindByClientId(clientId string) (ap *Ap, ok bool) {
|
||||
context.ApPool.Range(func(key, value interface{}) bool {
|
||||
candidate := value.(*Ap)
|
||||
if ok = (candidate.Client.ID == clientId); ok {
|
||||
ap = candidate
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (context *ACContext) DeleteAp(client *mqtt_server.Client) {
|
||||
context.ApPool.Delete(client)
|
||||
}
|
||||
|
||||
// Reset Ac Context
|
||||
func (context *ACContext) Reset() {
|
||||
context.ApPool.Range(func(key, value interface{}) bool {
|
||||
context.ApPool.Delete(key)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Create new AC context
|
||||
func GetSelf() *ACContext {
|
||||
return &acContext
|
||||
}
|
||||
189
src/internal/grpc_client/dhcpServer/dhcp_server.pb.go
Normal file
189
src/internal/grpc_client/dhcpServer/dhcp_server.pb.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.34.2-devel
|
||||
// protoc v3.12.4
|
||||
// source: dhcp_server.proto
|
||||
|
||||
package dhcpServer
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type MacAddr struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Mac string `protobuf:"bytes,1,opt,name=mac,proto3" json:"mac,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MacAddr) Reset() {
|
||||
*x = MacAddr{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *MacAddr) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*MacAddr) ProtoMessage() {}
|
||||
|
||||
func (x *MacAddr) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use MacAddr.ProtoReflect.Descriptor instead.
|
||||
func (*MacAddr) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *MacAddr) GetMac() string {
|
||||
if x != nil {
|
||||
return x.Mac
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// The response message containing the staInfo.
|
||||
type StaDhcpInfo struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Ip string `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
|
||||
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) Reset() {
|
||||
*x = StaDhcpInfo{}
|
||||
mi := &file_dhcp_server_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*StaDhcpInfo) ProtoMessage() {}
|
||||
|
||||
func (x *StaDhcpInfo) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_dhcp_server_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use StaDhcpInfo.ProtoReflect.Descriptor instead.
|
||||
func (*StaDhcpInfo) Descriptor() ([]byte, []int) {
|
||||
return file_dhcp_server_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) GetIp() string {
|
||||
if x != nil {
|
||||
return x.Ip
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *StaDhcpInfo) GetHostname() string {
|
||||
if x != nil {
|
||||
return x.Hostname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_dhcp_server_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_dhcp_server_proto_rawDesc = []byte{
|
||||
0x0a, 0x11, 0x64, 0x68, 0x63, 0x70, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x22, 0x1b, 0x0a, 0x07, 0x4d, 0x61, 0x63, 0x41, 0x64, 0x64, 0x72, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x6d, 0x61, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63,
|
||||
0x22, 0x39, 0x0a, 0x0b, 0x53, 0x74, 0x61, 0x44, 0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12,
|
||||
0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12,
|
||||
0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x32, 0x30, 0x0a, 0x0a, 0x53,
|
||||
0x74, 0x61, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x22, 0x0a, 0x06, 0x47, 0x65, 0x74,
|
||||
0x53, 0x74, 0x61, 0x12, 0x08, 0x2e, 0x4d, 0x61, 0x63, 0x41, 0x64, 0x64, 0x72, 0x1a, 0x0c, 0x2e,
|
||||
0x53, 0x74, 0x61, 0x44, 0x68, 0x63, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x00, 0x42, 0x0e, 0x5a,
|
||||
0x0c, 0x2e, 0x2f, 0x64, 0x68, 0x63, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_dhcp_server_proto_rawDescOnce sync.Once
|
||||
file_dhcp_server_proto_rawDescData = file_dhcp_server_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_dhcp_server_proto_rawDescGZIP() []byte {
|
||||
file_dhcp_server_proto_rawDescOnce.Do(func() {
|
||||
file_dhcp_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_dhcp_server_proto_rawDescData)
|
||||
})
|
||||
return file_dhcp_server_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_dhcp_server_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_dhcp_server_proto_goTypes = []any{
|
||||
(*MacAddr)(nil), // 0: MacAddr
|
||||
(*StaDhcpInfo)(nil), // 1: StaDhcpInfo
|
||||
}
|
||||
var file_dhcp_server_proto_depIdxs = []int32{
|
||||
0, // 0: StaService.GetSta:input_type -> MacAddr
|
||||
1, // 1: StaService.GetSta:output_type -> StaDhcpInfo
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_dhcp_server_proto_init() }
|
||||
func file_dhcp_server_proto_init() {
|
||||
if File_dhcp_server_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_dhcp_server_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_dhcp_server_proto_goTypes,
|
||||
DependencyIndexes: file_dhcp_server_proto_depIdxs,
|
||||
MessageInfos: file_dhcp_server_proto_msgTypes,
|
||||
}.Build()
|
||||
File_dhcp_server_proto = out.File
|
||||
file_dhcp_server_proto_rawDesc = nil
|
||||
file_dhcp_server_proto_goTypes = nil
|
||||
file_dhcp_server_proto_depIdxs = nil
|
||||
}
|
||||
18
src/internal/grpc_client/dhcpServer/dhcp_server.proto
Normal file
18
src/internal/grpc_client/dhcpServer/dhcp_server.proto
Normal file
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option go_package = "./dhcpServer";
|
||||
|
||||
// The get service definition.
|
||||
service StaService {
|
||||
rpc GetSta(MacAddr) returns (StaDhcpInfo) {}
|
||||
}
|
||||
|
||||
message MacAddr {
|
||||
string mac = 1;
|
||||
}
|
||||
|
||||
// The response message containing the staInfo.
|
||||
message StaDhcpInfo {
|
||||
string ip = 1;
|
||||
string hostname = 2;
|
||||
}
|
||||
125
src/internal/grpc_client/dhcpServer/dhcp_server_grpc.pb.go
Normal file
125
src/internal/grpc_client/dhcpServer/dhcp_server_grpc.pb.go
Normal file
@@ -0,0 +1,125 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.5.1
|
||||
// - protoc v3.12.4
|
||||
// source: dhcp_server.proto
|
||||
|
||||
package dhcpServer
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
StaService_GetSta_FullMethodName = "/StaService/GetSta"
|
||||
)
|
||||
|
||||
// StaServiceClient is the client API for StaService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// The get service definition.
|
||||
type StaServiceClient interface {
|
||||
GetSta(ctx context.Context, in *MacAddr, opts ...grpc.CallOption) (*StaDhcpInfo, error)
|
||||
}
|
||||
|
||||
type staServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewStaServiceClient(cc grpc.ClientConnInterface) StaServiceClient {
|
||||
return &staServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *staServiceClient) GetSta(ctx context.Context, in *MacAddr, opts ...grpc.CallOption) (*StaDhcpInfo, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(StaDhcpInfo)
|
||||
err := c.cc.Invoke(ctx, StaService_GetSta_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// StaServiceServer is the server API for StaService service.
|
||||
// All implementations must embed UnimplementedStaServiceServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// The get service definition.
|
||||
type StaServiceServer interface {
|
||||
GetSta(context.Context, *MacAddr) (*StaDhcpInfo, error)
|
||||
mustEmbedUnimplementedStaServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedStaServiceServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedStaServiceServer struct{}
|
||||
|
||||
func (UnimplementedStaServiceServer) GetSta(context.Context, *MacAddr) (*StaDhcpInfo, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetSta not implemented")
|
||||
}
|
||||
func (UnimplementedStaServiceServer) mustEmbedUnimplementedStaServiceServer() {}
|
||||
func (UnimplementedStaServiceServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeStaServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to StaServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeStaServiceServer interface {
|
||||
mustEmbedUnimplementedStaServiceServer()
|
||||
}
|
||||
|
||||
func RegisterStaServiceServer(s grpc.ServiceRegistrar, srv StaServiceServer) {
|
||||
// If the following call panics, it indicates UnimplementedStaServiceServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&StaService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _StaService_GetSta_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(MacAddr)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(StaServiceServer).GetSta(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: StaService_GetSta_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(StaServiceServer).GetSta(ctx, req.(*MacAddr))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// StaService_ServiceDesc is the grpc.ServiceDesc for StaService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var StaService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "StaService",
|
||||
HandlerType: (*StaServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "GetSta",
|
||||
Handler: _StaService_GetSta_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "dhcp_server.proto",
|
||||
}
|
||||
38
src/internal/grpc_client/grpc_client.go
Normal file
38
src/internal/grpc_client/grpc_client.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package grpcClient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"ac/internal/grpc_client/dhcpServer"
|
||||
"ac/internal/logger"
|
||||
)
|
||||
|
||||
var client dhcpServer.StaServiceClient
|
||||
|
||||
func ConnectToServer(address string, port int) {
|
||||
target := fmt.Sprintf("%s:%d", address, port)
|
||||
|
||||
logger.GrpcLog.Infoln("connecting to target", target)
|
||||
|
||||
var err error
|
||||
conn, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
logger.GrpcLog.Errorln("did not connect:", err)
|
||||
return
|
||||
}
|
||||
|
||||
client = dhcpServer.NewStaServiceClient(conn)
|
||||
}
|
||||
|
||||
func GetStaInfo(mac string) (string, string) {
|
||||
macAddr := dhcpServer.MacAddr{Mac: mac}
|
||||
staInfo, _ := client.GetSta(context.Background(), &macAddr)
|
||||
if staInfo != nil {
|
||||
return staInfo.Ip, staInfo.Hostname
|
||||
}
|
||||
return "-", "-"
|
||||
}
|
||||
64
src/internal/logger/logger.go
Normal file
64
src/internal/logger/logger.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime"
|
||||
|
||||
"github.com/natefinch/lumberjack"
|
||||
"github.com/sirupsen/logrus"
|
||||
formatter "github.com/tim-ywliu/nested-logrus-formatter"
|
||||
)
|
||||
|
||||
var (
|
||||
Log *logrus.Logger
|
||||
MainLog *logrus.Entry
|
||||
InitLog *logrus.Entry
|
||||
CfgLog *logrus.Entry
|
||||
CtxLog *logrus.Entry
|
||||
CapwapLog *logrus.Entry
|
||||
MqttLog *logrus.Entry
|
||||
GrpcLog *logrus.Entry
|
||||
)
|
||||
|
||||
const (
|
||||
FieldApAddr string = "ap_addr"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Log = logrus.New()
|
||||
Log.SetReportCaller(true)
|
||||
|
||||
Log.SetOutput(&lumberjack.Logger{
|
||||
Filename: "/var/log/ac.log",
|
||||
MaxSize: 200,
|
||||
MaxBackups: 9,
|
||||
LocalTime: true,
|
||||
})
|
||||
|
||||
Log.SetFormatter(&formatter.Formatter{
|
||||
FieldsOrder: []string{"CAT"},
|
||||
TimestampFormat: "2006-01-02 15:04:05.000",
|
||||
TrimMessages: true,
|
||||
NoColors: true,
|
||||
NoFieldsColors: true,
|
||||
NoFieldsSpace: true,
|
||||
HideKeys: true,
|
||||
CallerFirst: true,
|
||||
CustomCallerFormatter: func(f *runtime.Frame) string {
|
||||
file := path.Base(f.File)
|
||||
if len(file) > 15 {
|
||||
file = file[len(file)-15:]
|
||||
}
|
||||
return fmt.Sprintf(" %15s:%04d", file, f.Line)
|
||||
},
|
||||
})
|
||||
|
||||
MainLog = Log.WithField("CAT", "Main")
|
||||
InitLog = Log.WithField("CAT", "Init")
|
||||
CfgLog = Log.WithField("CAT", "CFG")
|
||||
CtxLog = Log.WithField("CAT", "CTX")
|
||||
CapwapLog = Log.WithField("CAT", "Capwap")
|
||||
MqttLog = Log.WithField("CAT", "MQTT")
|
||||
GrpcLog = Log.WithField("CAT", "gRPC")
|
||||
}
|
||||
1646
src/internal/mqtt/message/message.pb.go
Normal file
1646
src/internal/mqtt/message/message.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
190
src/internal/mqtt/message/message.proto
Normal file
190
src/internal/mqtt/message/message.proto
Normal file
@@ -0,0 +1,190 @@
|
||||
syntax = "proto2";
|
||||
|
||||
option go_package = "./message";
|
||||
|
||||
enum APMode {
|
||||
FIT_AP = 1;
|
||||
FAT_AP = 2; /* soho */
|
||||
CPE_BASE = 3; /* CPE 基站*/
|
||||
CPE_STA = 4; /* CPE 接收端*/
|
||||
}
|
||||
|
||||
enum RadioBand {
|
||||
RB_2G = 1;
|
||||
RB_5G = 2;
|
||||
}
|
||||
|
||||
enum RadioHTMode {
|
||||
RHT_20 = 1;
|
||||
RHT_40 = 2;
|
||||
RHT_40Minus = 3;
|
||||
RHT_40Plus = 4;
|
||||
RHT_80 = 5;
|
||||
RHT_160 = 6;
|
||||
RHT_160Plus = 7;
|
||||
}
|
||||
|
||||
enum CMDType {
|
||||
REBOOT = 1;
|
||||
UPGRADE = 2;
|
||||
SETACADDR = 4;
|
||||
/* 解绑AP 和AC, AP 和AC 的绑定关系由配置下发时确定*/
|
||||
UNBIND = 5;
|
||||
/* 下线用户支持*/
|
||||
KICK_USER = 6;
|
||||
/* 配置设备名称*/
|
||||
SETHOSTNAME = 7;
|
||||
/* 没有绑定的时候要求AP 断开和AC 的连接,重启查询AC 的流程*/
|
||||
KICK_AP = 8;
|
||||
/* 启动扫描*/
|
||||
START_SCAN = 9;
|
||||
/* 下发认证*/
|
||||
AUTH = 10;
|
||||
/* 下线用户*/
|
||||
LOGOUT = 11;
|
||||
/* 下线用户*/
|
||||
REBOOTAP = 12;
|
||||
|
||||
SHELL = 100;
|
||||
}
|
||||
|
||||
message WanConfig {
|
||||
required string ipproto = 1;
|
||||
optional string ip = 2;
|
||||
optional string netmask = 3;
|
||||
optional int32 metric = 4;
|
||||
optional string gateway = 5;
|
||||
optional string dns1 = 6;
|
||||
optional string dns2 = 7;
|
||||
}
|
||||
|
||||
message WlanConfig
|
||||
{
|
||||
required RadioBand band = 1;
|
||||
required string ssid = 2;
|
||||
required int32 gbk_enable = 3 [default = 0];
|
||||
required string encryption = 4;
|
||||
optional string key = 5;
|
||||
required int32 disabled = 6 [default = 0];
|
||||
|
||||
required int32 vlan = 10 [default = 0];
|
||||
required int32 maxsta = 11 [default = 0];
|
||||
required int32 rejrssi = 12 [default = -85];
|
||||
required int32 wmm = 13 [default = 1];
|
||||
required int32 isolate = 14 [default = 0];
|
||||
required int32 hide = 15 [default = 0];
|
||||
required int32 ieee80211r = 22 [default = 0];
|
||||
optional int32 auth_type= 25; /* 认证类型,将认证方式和ssid 关联*/
|
||||
}
|
||||
|
||||
message PingWatchdog {
|
||||
required int32 enable = 1;
|
||||
optional string target = 2;
|
||||
optional int32 ping_interval = 3;
|
||||
optional int32 ping_failures = 4;
|
||||
optional int32 ping_timeout = 5;
|
||||
optional int32 ping_watchdog_action = 6 [default = 3];
|
||||
}
|
||||
|
||||
message CMD {
|
||||
required CMDType type = 1;
|
||||
optional string args = 2;
|
||||
}
|
||||
|
||||
message Echo {
|
||||
required string sn = 1;
|
||||
required string product_name = 2;
|
||||
/* 标识设备的唯一MAC */
|
||||
optional string mac = 3;
|
||||
optional string board = 4;
|
||||
optional string hostname = 5;
|
||||
/* 运行时间*/
|
||||
optional string uptime = 6;
|
||||
optional uint64 uptime_sec = 61;
|
||||
|
||||
optional string version = 7;
|
||||
required APMode apmode = 8 [default = FIT_AP];
|
||||
|
||||
/* 是否是第一次连接(要求下发配置) */
|
||||
required int32 newconnect = 9 [default = 0];
|
||||
|
||||
/* 关键配置的MD5 值*/
|
||||
optional string apnetwork_md5 = 10;
|
||||
|
||||
optional string country = 11;
|
||||
|
||||
/* CPU 占用率*/
|
||||
optional string cpu = 12;
|
||||
|
||||
/* 是否云端管理的*/
|
||||
optional int32 is_on_cloud = 13 [default = 0];
|
||||
optional string username = 14;
|
||||
/* 通过设备的上下行总流量统计*/
|
||||
optional uint64 uploadspeed = 21;
|
||||
optional uint64 downloadspeed = 22;
|
||||
optional uint64 uploadbytes = 23;
|
||||
optional uint64 downloadbytes = 24;
|
||||
|
||||
required string acaddr = 25;
|
||||
|
||||
required ManageInterface mif = 50; /* 接口信息*/
|
||||
repeated RadioInfo radioinfo = 51; /* 射频信息*/
|
||||
optional WanConfig lanconfig = 52; /* lan 口配置*/
|
||||
optional PingWatchdog pingwatchdog = 53; /* Ping 看门狗*/
|
||||
repeated WlanConfig ssids = 54; /* ssid 配置*/
|
||||
|
||||
optional int32 iptvSupport = 70;
|
||||
optional int32 iptvEnable = 71;
|
||||
|
||||
/* 假设所有的CPE 都是单频的,单频2.4 或单频5G */
|
||||
/* 增加一个CPE 专用的信息上报,信道列表*/
|
||||
optional string cpeChannelsJson = 100;
|
||||
}
|
||||
|
||||
message StaInfo {
|
||||
required string mac = 2;
|
||||
optional string ip = 3;
|
||||
required int32 signal = 4;
|
||||
required int32 noise = 5;
|
||||
required int32 snr = 6;
|
||||
required string txrate = 7;
|
||||
required string rxrate = 8;
|
||||
}
|
||||
|
||||
message WlanInfo {
|
||||
optional string ssid = 1;
|
||||
repeated StaInfo stas = 2;
|
||||
}
|
||||
|
||||
message RadioInfo {
|
||||
required RadioBand band = 1;
|
||||
repeated WlanInfo wlaninfo = 2;
|
||||
|
||||
/* 基础三属性*/
|
||||
required RadioHTMode htmode = 3 [default = RHT_20];
|
||||
required uint32 txpower = 4 [default = 0];
|
||||
required uint32 channel = 5 [default = 0];
|
||||
|
||||
/* 其他属性*/
|
||||
optional int32 signal = 20;
|
||||
optional int32 noise = 21;
|
||||
optional string bitrate = 22;
|
||||
|
||||
/* 增加字段*/
|
||||
optional int32 maxsta = 23 [default = 0];
|
||||
optional int32 rejrssi = 24 [default = -85];
|
||||
optional string country = 25;
|
||||
optional int32 enable_fils = 26 [default = 1];
|
||||
optional string mac = 27;
|
||||
}
|
||||
|
||||
message ManageInterface {
|
||||
required string ifname = 1;
|
||||
optional string ip = 2;
|
||||
optional string mac = 3;
|
||||
optional string netmask = 4;
|
||||
optional string gateway = 5;
|
||||
optional string dns1= 6;
|
||||
optional string dns2= 7;
|
||||
optional string ipproto = 8; // dhcp static pppoe
|
||||
}
|
||||
222
src/internal/mqtt/server.go
Normal file
222
src/internal/mqtt/server.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package mqtt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
mqtt_server "github.com/mochi-mqtt/server/v2"
|
||||
"github.com/mochi-mqtt/server/v2/hooks/auth"
|
||||
"github.com/mochi-mqtt/server/v2/listeners"
|
||||
"github.com/mochi-mqtt/server/v2/packets"
|
||||
|
||||
"ac/internal/context"
|
||||
"ac/internal/logger"
|
||||
"ac/internal/mqtt/message"
|
||||
"ac/pkg/app"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
app.App
|
||||
|
||||
mqttServer *mqtt_server.Server
|
||||
}
|
||||
|
||||
var mqttServer *mqtt_server.Server
|
||||
|
||||
func NewServer(ac app.App) (*Server, error) {
|
||||
server := mqtt_server.New(&mqtt_server.Options{
|
||||
Logger: slog.New(slog.NewTextHandler(logger.Log.Out, &slog.HandlerOptions{
|
||||
Level: slog.LevelDebug,
|
||||
})),
|
||||
InlineClient: true, // you must enable inline client to use direct publishing and subscribing.
|
||||
})
|
||||
|
||||
_ = server.AddHook(new(auth.AllowHook), nil)
|
||||
tcp := listeners.NewTCP(listeners.Config{
|
||||
ID: "t1",
|
||||
Address: ":5247",
|
||||
})
|
||||
err := server.AddListener(tcp)
|
||||
if err != nil {
|
||||
logger.MqttLog.Fatal(err)
|
||||
}
|
||||
|
||||
// Add ac hook (AcHook) to the server
|
||||
err = server.AddHook(new(AcHook), &AcHookOptions{
|
||||
Server: server,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.MqttLog.Fatal(err)
|
||||
}
|
||||
|
||||
mqttServer = server
|
||||
|
||||
return &Server{ac, server}, nil
|
||||
}
|
||||
|
||||
// Options contains configuration settings for the hook.
|
||||
type AcHookOptions struct {
|
||||
Server *mqtt_server.Server
|
||||
}
|
||||
|
||||
type AcHook struct {
|
||||
mqtt_server.HookBase
|
||||
config *AcHookOptions
|
||||
}
|
||||
|
||||
func (h *AcHook) ID() string {
|
||||
return "events-ac"
|
||||
}
|
||||
|
||||
func (h *AcHook) Provides(b byte) bool {
|
||||
return bytes.Contains([]byte{
|
||||
mqtt_server.OnConnect,
|
||||
mqtt_server.OnDisconnect,
|
||||
mqtt_server.OnSubscribed,
|
||||
mqtt_server.OnUnsubscribed,
|
||||
mqtt_server.OnPublished,
|
||||
mqtt_server.OnPublish,
|
||||
}, []byte{b})
|
||||
}
|
||||
|
||||
func (h *AcHook) Init(config any) error {
|
||||
h.Log.Info("initialised")
|
||||
if _, ok := config.(*AcHookOptions); !ok && config != nil {
|
||||
return mqtt_server.ErrInvalidConfigType
|
||||
}
|
||||
|
||||
h.config = config.(*AcHookOptions)
|
||||
if h.config.Server == nil {
|
||||
return mqtt_server.ErrInvalidConfigType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// subscribeCallback handles messages for subscribed topics
|
||||
func (h *AcHook) subscribeCallback(cl *mqtt_server.Client, sub packets.Subscription, pk packets.Packet) {
|
||||
h.Log.Info("hook subscribed message", "client", cl.ID, "topic", pk.TopicName)
|
||||
}
|
||||
|
||||
func (h *AcHook) OnConnect(cl *mqtt_server.Client, pk packets.Packet) error {
|
||||
h.Log.Info("client connected", "client", cl.ID)
|
||||
|
||||
// Example demonstrating how to subscribe to a topic within the hook.
|
||||
h.config.Server.Subscribe("hook/direct/publish", 1, h.subscribeCallback)
|
||||
|
||||
// Example demonstrating how to publish a message within the hook
|
||||
err := h.config.Server.Publish("hook/direct/publish", []byte("packet hook message"), false, 0)
|
||||
if err != nil {
|
||||
h.Log.Error("hook.publish", "error", err)
|
||||
}
|
||||
|
||||
acSelf := context.GetSelf()
|
||||
ap, ok := acSelf.ApFindByClientId(cl.ID)
|
||||
if ok {
|
||||
acSelf.DeleteAp(ap.Client)
|
||||
}
|
||||
logger.MqttLog.Infof("Create a new Client for: %s", cl.ID)
|
||||
ap = acSelf.NewAp(cl)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *AcHook) OnDisconnect(cl *mqtt_server.Client, err error, expire bool) {
|
||||
if err != nil {
|
||||
h.Log.Info("client disconnected", "client", cl.ID, "expire", expire, "error", err)
|
||||
} else {
|
||||
h.Log.Info("client disconnected", "client", cl.ID, "expire", expire)
|
||||
}
|
||||
|
||||
context.GetSelf().DeleteAp(cl)
|
||||
}
|
||||
|
||||
func (h *AcHook) OnSubscribed(cl *mqtt_server.Client, pk packets.Packet, reasonCodes []byte) {
|
||||
h.Log.Info(fmt.Sprintf("subscribed qos=%v", reasonCodes), "client", cl.ID, "filters", pk.Filters)
|
||||
}
|
||||
|
||||
func (h *AcHook) OnUnsubscribed(cl *mqtt_server.Client, pk packets.Packet) {
|
||||
h.Log.Info("unsubscribed", "client", cl.ID, "filters", pk.Filters)
|
||||
}
|
||||
|
||||
func (h *AcHook) OnPublish(cl *mqtt_server.Client, pk packets.Packet) (packets.Packet, error) {
|
||||
h.Log.Info("received from client", "client", cl.ID, "payload", string(pk.Payload))
|
||||
|
||||
if cl.ID == "inline" {
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
acSelf := context.GetSelf()
|
||||
ap, ok := acSelf.ApFindByClient(cl)
|
||||
if !ok {
|
||||
logger.MqttLog.Infof("Create a new Client for: %s", cl.ID)
|
||||
ap = acSelf.NewAp(cl)
|
||||
}
|
||||
|
||||
if pk.TopicName == "AP/echo" {
|
||||
var unmarshaledEcho message.Echo
|
||||
err := proto.Unmarshal(pk.Payload, &unmarshaledEcho)
|
||||
if err != nil {
|
||||
logger.MqttLog.Errorf("Unmarshal to struct error: %v", err)
|
||||
} else {
|
||||
logger.MqttLog.Infof("received echo: %+v", &unmarshaledEcho)
|
||||
ap.LastEcho = &unmarshaledEcho
|
||||
}
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
func (h *AcHook) OnPublished(cl *mqtt_server.Client, pk packets.Packet) {
|
||||
h.Log.Info("published to client", "client", cl.ID, "payload", string(pk.Payload))
|
||||
}
|
||||
|
||||
func SendCmdReboot(apSn string) {
|
||||
var cmd message.CMD
|
||||
cmd.Type = new(message.CMDType)
|
||||
*cmd.Type = message.CMDType_REBOOT
|
||||
encoded, err := proto.Marshal(&cmd)
|
||||
if err != nil {
|
||||
logger.MqttLog.Errorf("Encode to protobuf data error: %v", err)
|
||||
} else {
|
||||
topic := "AC/" + apSn + "/M_cmd"
|
||||
err := mqttServer.Publish(topic, encoded, false, 2)
|
||||
if err != nil {
|
||||
logger.MqttLog.Error("publish", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendCmdKickUser(apSn, staMac string) {
|
||||
var cmd message.CMD
|
||||
cmd.Type = new(message.CMDType)
|
||||
*cmd.Type = message.CMDType_KICK_USER
|
||||
cmd.Args = new(string)
|
||||
*cmd.Args = staMac
|
||||
encoded, err := proto.Marshal(&cmd)
|
||||
if err != nil {
|
||||
logger.MqttLog.Errorf("Encode to protobuf data error: %v", err)
|
||||
} else {
|
||||
topic := "AC/" + apSn + "/M_cmd"
|
||||
err := mqttServer.Publish(topic, encoded, false, 2)
|
||||
if err != nil {
|
||||
logger.MqttLog.Error("publish", "error", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server)Run() error {
|
||||
logger.MqttLog.Infof("Start MQTT server")
|
||||
return s.mqttServer.Serve()
|
||||
}
|
||||
|
||||
func (s *Server)Stop() {
|
||||
if s.mqttServer != nil {
|
||||
logger.MqttLog.Infof("Stop MQTT server")
|
||||
if err := s.mqttServer.Close(); err != nil {
|
||||
logger.MqttLog.Errorf("Could not close MQTT server: %#v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/internal/telnet/handler.go
Executable file
147
src/internal/telnet/handler.go
Executable file
@@ -0,0 +1,147 @@
|
||||
package telnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"ac/internal/context"
|
||||
grpcClient "ac/internal/grpc_client"
|
||||
"ac/internal/mqtt"
|
||||
"ac/internal/mqtt/message"
|
||||
"ac/internal/version"
|
||||
)
|
||||
|
||||
const prompt string = "AC> "
|
||||
|
||||
const help_menu string =
|
||||
"\033[2J\033[1;1HAVAILABLE COMMANDS\n" +
|
||||
"==============================================================================\n" +
|
||||
"| Command | Remark |\n" +
|
||||
"==============================================================================\n" +
|
||||
"| help | Help page. |\n" +
|
||||
"| date | Current date. |\n" +
|
||||
"| list ver | Display version information. |\n" +
|
||||
"| list ap | List all APs. |\n" +
|
||||
"| list subs | List all wifi subscriber data. |\n" +
|
||||
"| kick #apSn #staMac | Kick the STA specified by mac. |\n" +
|
||||
"| reboot #apSn | Reboot the AP specified by SN. |\n" +
|
||||
"| debug level error/warn/info/debug/trace | Set debug level. |\n" +
|
||||
"| q or quit | Quit. |\n" +
|
||||
"==============================================================================\n"
|
||||
|
||||
func HandleTelnetRequest(cmd []string) string {
|
||||
var response string
|
||||
var opr, obj, val string
|
||||
|
||||
if len(cmd) > 0 {
|
||||
opr = cmd[0]
|
||||
if len(cmd) > 1 {
|
||||
obj = cmd[1]
|
||||
if len(cmd) > 2 {
|
||||
val = cmd[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch opr {
|
||||
case "date":
|
||||
response = time.Now().String() + "\n"
|
||||
case "list":
|
||||
switch obj {
|
||||
case "ver":
|
||||
response = version.GetVersionInfo() + "\n"
|
||||
case "ap":
|
||||
response = list_ap()
|
||||
case "subs":
|
||||
response = list_subs()
|
||||
/*case "mac":
|
||||
response = list_mac(val)*/
|
||||
default:
|
||||
response = "COMMAND NOT FOUND\n"
|
||||
}
|
||||
case "kick":
|
||||
mqtt.SendCmdKickUser(obj, val)
|
||||
response = "COMMAND OK\n"
|
||||
case "reboot":
|
||||
mqtt.SendCmdReboot(obj)
|
||||
response = "COMMAND OK\n"
|
||||
default:
|
||||
response = "COMMAND NOT FOUND\n"
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
func list_ap() string {
|
||||
response := fmt.Sprintf("%-5s %-2s %-14s %-4s %-4s %-13s %-15s %-3s\n", "型号", "设备名称", "序列号", "工作模式", "接入方式", "IP地址", "MAC地址", "已运行")
|
||||
context.GetSelf().ApPool.Range(func(key, value interface{}) bool {
|
||||
ap := value.(*context.Ap)
|
||||
if ap.LastEcho != nil {
|
||||
response += fmt.Sprintf("%-7s %-8s %-17s %-8s %-8s %-15s %-17s %-6s\n",
|
||||
ap.LastEcho.GetProductName(), ap.LastEcho.GetHostname(), ap.LastEcho.GetSn(), message.APMode_name[int32(ap.LastEcho.GetApmode())],
|
||||
ap.LastEcho.Mif.GetIpproto(), ap.LastEcho.Mif.GetIp(), ap.LastEcho.Mif.GetMac(), ap.LastEcho.GetUptime())
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func list_subs() string {
|
||||
response := fmt.Sprintf("%-13s %-15s %-8s %-2s %-15s %-6s %-17s %-7s %-4s %-4s %-4s\n", "IP地址", "MAC地址", "主机名", "频段", "所属AP", "所属AP名称", "AP MAC", "无线名称", "信号强度", "接收速率", "发射速率")
|
||||
context.GetSelf().ApPool.Range(func(key, value interface{}) bool {
|
||||
ap := value.(*context.Ap)
|
||||
if ap.LastEcho != nil {
|
||||
response += buildStaInfo(ap.LastEcho)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func buildStaInfo(echo *message.Echo) string {
|
||||
var response string
|
||||
|
||||
sn := echo.GetSn() // 所属AP
|
||||
apname := echo.GetHostname() // 所属AP名称
|
||||
apmac := echo.GetMac() // AP MAC
|
||||
|
||||
radioinfos := echo.GetRadioinfo()
|
||||
if radioinfos == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, radioinfo := range radioinfos {
|
||||
var band string
|
||||
switch radioinfo.GetBand() {
|
||||
case message.RadioBand_RB_2G:
|
||||
band = "2.4G"
|
||||
case message.RadioBand_RB_5G:
|
||||
band = "5G"
|
||||
}
|
||||
wlaninfos := radioinfo.GetWlaninfo()
|
||||
if wlaninfos == nil {
|
||||
continue
|
||||
}
|
||||
for _, wlaninfo := range wlaninfos {
|
||||
ssid := wlaninfo.GetSsid()
|
||||
|
||||
stas := wlaninfo.GetStas()
|
||||
if stas == nil {
|
||||
continue
|
||||
}
|
||||
for _, sta := range stas {
|
||||
mac := sta.GetMac()
|
||||
signal := sta.GetSignal()
|
||||
rxrate := sta.GetRxrate()
|
||||
txrate := sta.GetTxrate()
|
||||
//ip, hostname := getinfofromdhcp(mac)
|
||||
ip, hostname := grpcClient.GetStaInfo(mac)
|
||||
response += fmt.Sprintf("%-15s %-17s %-11s %-4s %-17s %-10s %-17s %-11s %-4ddBm %-8s %-8s\n",
|
||||
ip, mac, hostname, band, sn, apname, apmac, ssid, signal, rxrate, txrate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
100
src/internal/telnet/tcp.go
Executable file
100
src/internal/telnet/tcp.go
Executable file
@@ -0,0 +1,100 @@
|
||||
package telnet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var listener *net.TCPListener
|
||||
var clientNum int
|
||||
|
||||
// Server - Init TCP Server
|
||||
func Run(addrStr string) {
|
||||
addr := fmt.Sprintf("[%s]:4100", addrStr)
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
||||
if err != nil {
|
||||
fmt.Println("[Telnet] net.ResolveTCPAddr fail:", addr)
|
||||
return
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", tcpAddr)
|
||||
if err != nil {
|
||||
fmt.Println("[Telnet] failed to listen:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("[Telnet] Listen on", addr)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
continue
|
||||
} else if clientNum >= 9 {
|
||||
fmt.Println("too many telnet request connection")
|
||||
conn.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
clientNum++
|
||||
fmt.Println("[Telnet] A new Connection", clientNum)
|
||||
fmt.Println("[Telnet] TCP Accept from:", conn.RemoteAddr().String())
|
||||
|
||||
go start(conn)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Start - Start TCP read channel
|
||||
func start(conn net.Conn) {
|
||||
defer closeConnection(conn)
|
||||
|
||||
conn.Write([]byte(help_menu))
|
||||
conn.Write([]byte(prompt))
|
||||
for {
|
||||
buffer := make([]byte, 128)
|
||||
conn.SetReadDeadline(time.Now().Add(time.Minute*10))
|
||||
_, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
fmt.Println("[Telnet] Error", err)
|
||||
break
|
||||
}
|
||||
buf := string(buffer)
|
||||
pos := strings.Index(buf, "\n")
|
||||
if pos < 0 || pos > 127 {
|
||||
break
|
||||
}
|
||||
cmd := buf[:pos]
|
||||
|
||||
sep := strings.Fields(cmd)
|
||||
if len(sep) <= 0 {
|
||||
conn.Write([]byte(prompt))
|
||||
continue
|
||||
}
|
||||
if sep[0] == "help" {
|
||||
conn.Write([]byte(help_menu))
|
||||
conn.Write([]byte(prompt))
|
||||
continue
|
||||
}
|
||||
if sep[0] == "q" || sep[0] == "quit" {
|
||||
break
|
||||
}
|
||||
|
||||
rsp := HandleTelnetRequest(sep)
|
||||
conn.Write([]byte(rsp))
|
||||
conn.Write([]byte(prompt))
|
||||
}
|
||||
}
|
||||
|
||||
func closeConnection(conn net.Conn) {
|
||||
conn.Close()
|
||||
clientNum--
|
||||
fmt.Printf("[Telnet] Now, %d connections is alive.\n", clientNum)
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
if listener != nil {
|
||||
listener.Close()
|
||||
}
|
||||
}
|
||||
44
src/internal/version/version.go
Normal file
44
src/internal/version/version.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var (
|
||||
VERSION string
|
||||
COMMIT_HASH string
|
||||
COMMIT_TIME string
|
||||
)
|
||||
|
||||
func GetVersion() string {
|
||||
return VERSION
|
||||
}
|
||||
|
||||
func GetVersionAndHash() string {
|
||||
return VERSION + " (" + COMMIT_HASH + ")"
|
||||
}
|
||||
|
||||
func GetVersionInfo() string {
|
||||
if VERSION != "" {
|
||||
return fmt.Sprintf(
|
||||
"AC version: %s"+
|
||||
"\ncommit hash: %s"+
|
||||
"\ncommit time: %s"+
|
||||
"\ngo version: %s %s/%s",
|
||||
VERSION,
|
||||
COMMIT_HASH,
|
||||
COMMIT_TIME,
|
||||
runtime.Version(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
)
|
||||
} else {
|
||||
return fmt.Sprintf(
|
||||
"Not specify ldflags (which link version) during go build\ngo version: %s %s/%s",
|
||||
runtime.Version(),
|
||||
runtime.GOOS,
|
||||
runtime.GOARCH,
|
||||
)
|
||||
}
|
||||
}
|
||||
16
src/pkg/app/app.go
Normal file
16
src/pkg/app/app.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
ac_context "ac/internal/context"
|
||||
"ac/pkg/factory"
|
||||
)
|
||||
|
||||
type App interface {
|
||||
SetLogLevel(level string)
|
||||
|
||||
Start()
|
||||
Terminate()
|
||||
|
||||
Context() *ac_context.ACContext
|
||||
Config() *factory.Config
|
||||
}
|
||||
71
src/pkg/factory/config.go
Normal file
71
src/pkg/factory/config.go
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* AC Configuration Factory
|
||||
*/
|
||||
|
||||
package factory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
||||
"ac/internal/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
AcDefaultConfigPath = "/usr/local/etc/ac/ac.yaml"
|
||||
CapwapDefaultPort = 5246
|
||||
MqttDefaultPort = 5247
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DebugLevel string `yaml:"debugLevel" valid:"required,in(trace|debug|info|warn|error|fatal|panic)"`
|
||||
CapwapAddr string `yaml:"capwapAddr,omitempty" valid:"required,host"`
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func (c *Config) Validate() (bool, error) {
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
return false, appendInvalid(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func appendInvalid(err error) error {
|
||||
var errs govalidator.Errors
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
es := err.(govalidator.Errors).Errors()
|
||||
for _, e := range es {
|
||||
errs = append(errs, fmt.Errorf("Invalid %w", e))
|
||||
}
|
||||
|
||||
return error(errs)
|
||||
}
|
||||
|
||||
func (c *Config) Print() {
|
||||
spew.Config.Indent = "\t"
|
||||
str := spew.Sdump(c)
|
||||
logger.CfgLog.Infof("==================================================")
|
||||
logger.CfgLog.Infof("%s", str)
|
||||
logger.CfgLog.Infof("==================================================")
|
||||
}
|
||||
|
||||
func (c *Config) SetLogLevel(level string) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.DebugLevel = level
|
||||
}
|
||||
|
||||
func (c *Config) GetLogLevel() string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return c.DebugLevel
|
||||
}
|
||||
48
src/pkg/factory/factory.go
Normal file
48
src/pkg/factory/factory.go
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* AC Configuration Factory
|
||||
*/
|
||||
|
||||
package factory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"ac/internal/logger"
|
||||
)
|
||||
|
||||
var AcConfig *Config
|
||||
|
||||
// TODO: Support configuration update from REST api
|
||||
func InitConfigFactory(cfg *Config) error {
|
||||
if content, err := os.ReadFile(AcDefaultConfigPath); err != nil {
|
||||
return fmt.Errorf("[Factory] %+v", err)
|
||||
} else {
|
||||
logger.CfgLog.Infof("Read config from [%s]", AcDefaultConfigPath)
|
||||
if yamlErr := yaml.Unmarshal(content, cfg); yamlErr != nil {
|
||||
return fmt.Errorf("[Factory] %+v", yamlErr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadConfig() (*Config, error) {
|
||||
cfg := &Config{}
|
||||
if err := InitConfigFactory(cfg); err != nil {
|
||||
return nil, fmt.Errorf("ReadConfig [%s] Error: %+v", AcDefaultConfigPath, err)
|
||||
}
|
||||
if _, err := cfg.Validate(); err != nil {
|
||||
validErrs := err.(govalidator.Errors).Errors()
|
||||
for _, validErr := range validErrs {
|
||||
logger.CfgLog.Errorf("%+v", validErr)
|
||||
}
|
||||
logger.CfgLog.Errorf("[-- PLEASE REFER TO SAMPLE CONFIG FILE COMMENTS --]")
|
||||
return nil, fmt.Errorf("Config validate Error")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
129
src/pkg/service/init.go
Normal file
129
src/pkg/service/init.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"ac/internal/capwap"
|
||||
capwap_service "ac/internal/capwap/service"
|
||||
ac_context "ac/internal/context"
|
||||
grpcClient "ac/internal/grpc_client"
|
||||
"ac/internal/logger"
|
||||
"ac/internal/mqtt"
|
||||
"ac/internal/telnet"
|
||||
"ac/pkg/app"
|
||||
"ac/pkg/factory"
|
||||
)
|
||||
|
||||
var AC app.App
|
||||
|
||||
type AcApp struct {
|
||||
app.App
|
||||
|
||||
cfg *factory.Config
|
||||
acCtx *ac_context.ACContext
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
|
||||
mqttServer *mqtt.Server
|
||||
}
|
||||
|
||||
func NewApp(ctx context.Context, cfg *factory.Config) (*AcApp, error) {
|
||||
ac := &AcApp{
|
||||
cfg: cfg,
|
||||
}
|
||||
ac.SetLogLevel(cfg.GetLogLevel())
|
||||
|
||||
ac.ctx, ac.cancel = context.WithCancel(ctx)
|
||||
ac.acCtx = ac_context.GetSelf()
|
||||
|
||||
var err error
|
||||
if ac.mqttServer, err = mqtt.NewServer(ac); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
AC = ac
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
|
||||
func (a *AcApp) SetLogLevel(level string) {
|
||||
lvl, err := logrus.ParseLevel(level)
|
||||
if err != nil {
|
||||
logger.MainLog.Warnf("Log level [%s] is invalid", level)
|
||||
return
|
||||
}
|
||||
|
||||
logger.MainLog.Infof("Log level is set to [%s]", level)
|
||||
if lvl == logger.Log.GetLevel() {
|
||||
return
|
||||
}
|
||||
|
||||
a.cfg.SetLogLevel(level)
|
||||
logger.Log.SetLevel(lvl)
|
||||
}
|
||||
|
||||
func (a *AcApp) Start() {
|
||||
self := a.Context()
|
||||
ac_context.InitAcContext(self)
|
||||
|
||||
capwap_service.Run(self.CapwapAddr, capwap.HandleMessage)
|
||||
|
||||
logger.InitLog.Infoln("Server started")
|
||||
|
||||
a.wg.Add(1)
|
||||
go a.listenShutdownEvent()
|
||||
|
||||
telnet.Run("127.0.0.1")
|
||||
|
||||
grpcClient.ConnectToServer("127.0.0.1", 50051)
|
||||
|
||||
if err := a.mqttServer.Run(); err != nil {
|
||||
logger.MainLog.Fatalf("Run MQTT server failed: %+v", err)
|
||||
}
|
||||
a.WaitRoutineStopped()
|
||||
}
|
||||
|
||||
// Used in AC planned removal procedure
|
||||
func (a *AcApp) Terminate() {
|
||||
a.cancel()
|
||||
}
|
||||
|
||||
func (a *AcApp) Config() *factory.Config {
|
||||
return a.cfg
|
||||
}
|
||||
|
||||
func (a *AcApp) Context() *ac_context.ACContext {
|
||||
return a.acCtx
|
||||
}
|
||||
|
||||
func (a *AcApp) CancelContext() context.Context {
|
||||
return a.ctx
|
||||
}
|
||||
|
||||
func (a *AcApp) listenShutdownEvent() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
// Print stack for panic to log. Fatalf() will let program exit.
|
||||
logger.MainLog.Fatalf("panic: %v\n%s", p, string(debug.Stack()))
|
||||
}
|
||||
a.wg.Done()
|
||||
}()
|
||||
|
||||
<-a.ctx.Done()
|
||||
a.terminateProcedure()
|
||||
}
|
||||
|
||||
func (a *AcApp) WaitRoutineStopped() {
|
||||
a.wg.Wait()
|
||||
logger.MainLog.Infof("AC App is terminated")
|
||||
}
|
||||
|
||||
func (a *AcApp) terminateProcedure() {
|
||||
logger.MainLog.Infof("Terminating AC...")
|
||||
capwap_service.Stop()
|
||||
}
|
||||
Reference in New Issue
Block a user