Initial commit: Import from /home/simon/test/ac

This commit is contained in:
zhangsz
2025-11-05 13:16:01 +08:00
commit b1dc1e18e7
28 changed files with 3471 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View 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
View 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)
}
}
}