From 5f176e731f07b846fd0ff9a98351063ceba45b90 Mon Sep 17 00:00:00 2001 From: Tim Beatham Date: Mon, 13 Nov 2023 10:44:14 +0000 Subject: [PATCH] Developed a rest API --- cmd/api/main.go | 17 +++ cmd/wgmeshd/configuration.yaml | 2 +- go.mod | 19 +++ pkg/api/apiserver.go | 203 +++++++++++++++++++++++++++++++++ pkg/api/types.go | 28 +++++ pkg/ctrlserver/ctrltypes.go | 1 + pkg/ipc/ipc.go | 2 +- pkg/log/log.go | 6 + pkg/robin/requester.go | 1 + 9 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 cmd/api/main.go create mode 100644 pkg/api/apiserver.go create mode 100644 pkg/api/types.go diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..b4e757d --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "log" + + "github.com/tim-beatham/wgmesh/pkg/api" +) + +func main() { + apiServer, err := api.NewSmegServer() + + if err != nil { + log.Fatal(err.Error()) + } + + apiServer.Run(":8080") +} diff --git a/cmd/wgmeshd/configuration.yaml b/cmd/wgmeshd/configuration.yaml index bde7ff8..01bb357 100644 --- a/cmd/wgmeshd/configuration.yaml +++ b/cmd/wgmeshd/configuration.yaml @@ -11,4 +11,4 @@ interClusterChance: 0.15 branchRate: 3 infectionCount: 3 keepAliveTime: 10 -pruneTime: 20 \ No newline at end of file +pruneTime: 20 diff --git a/go.mod b/go.mod index 6660fcf..d1ef671 100644 --- a/go.mod +++ b/go.mod @@ -15,14 +15,33 @@ require ( ) require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/josharian/native v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.3.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect + golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.13.0 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/sync v0.3.0 // indirect diff --git a/pkg/api/apiserver.go b/pkg/api/apiserver.go new file mode 100644 index 0000000..bf074b4 --- /dev/null +++ b/pkg/api/apiserver.go @@ -0,0 +1,203 @@ +package api + +import ( + "fmt" + "net/http" + + ipcRpc "net/rpc" + + "github.com/gin-gonic/gin" + "github.com/tim-beatham/wgmesh/pkg/ctrlserver" + "github.com/tim-beatham/wgmesh/pkg/ipc" + logging "github.com/tim-beatham/wgmesh/pkg/log" +) + +const SockAddr = "/tmp/wgmesh_ipc.sock" + +type ApiServer interface { + GetMeshes(c *gin.Context) + Run(addr string) error +} + +type SmegServer struct { + router *gin.Engine + client *ipcRpc.Client +} + +func meshNodeToAPIMeshNode(meshNode ctrlserver.MeshNode) *SmegNode { + if meshNode.Routes == nil { + meshNode.Routes = make([]string, 0) + } + + return &SmegNode{ + WgHost: meshNode.WgHost, + WgEndpoint: meshNode.WgEndpoint, + Endpoint: meshNode.HostEndpoint, + Timestamp: int(meshNode.Timestamp), + Description: meshNode.Description, + Routes: meshNode.Routes, + PublicKey: meshNode.PublicKey, + } +} + +func meshToAPIMesh(meshId string, nodes []ctrlserver.MeshNode) SmegMesh { + var smegMesh SmegMesh + smegMesh.MeshId = meshId + smegMesh.Nodes = make(map[string]SmegNode) + + for _, node := range nodes { + smegMesh.Nodes[node.WgHost] = *meshNodeToAPIMeshNode(node) + } + + return smegMesh +} + +// CreateMesh: creates a mesh network +func (s *SmegServer) CreateMesh(c *gin.Context) { + var createMesh CreateMeshRequest + + if err := c.ShouldBindJSON(&createMesh); err != nil { + c.JSON(http.StatusBadRequest, &gin.H{ + "error": err.Error(), + }) + return + } + + ipcRequest := ipc.NewMeshArgs{ + IfName: createMesh.IfName, + WgPort: createMesh.WgPort, + } + + var reply string + + err := s.client.Call("IpcHandler.CreateMesh", &ipcRequest, &reply) + + if err != nil { + c.JSON(http.StatusBadRequest, &gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, &gin.H{ + "meshid": reply, + }) +} + +// JoinMesh: joins a mesh network +func (s *SmegServer) JoinMesh(c *gin.Context) { + var joinMesh JoinMeshRequest + + if err := c.ShouldBindJSON(&joinMesh); err != nil { + c.JSON(http.StatusBadRequest, &gin.H{ + "error": err.Error(), + }) + return + } + + ipcRequest := ipc.JoinMeshArgs{ + MeshId: joinMesh.MeshId, + IpAdress: joinMesh.Bootstrap, + IfName: joinMesh.IfName, + Port: joinMesh.WgPort, + } + + var reply string + + err := s.client.Call("IpcHandler.JoinMesh", &ipcRequest, &reply) + + if err != nil { + c.JSON(http.StatusBadRequest, &gin.H{ + "error": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, &gin.H{ + "status": "success", + }) +} + +// GetMesh: given a meshId returns the corresponding mesh +// network. +func (s *SmegServer) GetMesh(c *gin.Context) { + meshidParam := c.Param("meshid") + + var meshid string = meshidParam + + getMeshReply := new(ipc.GetMeshReply) + + err := s.client.Call("IpcHandler.GetMesh", &meshid, &getMeshReply) + + if err != nil { + c.JSON(http.StatusNotFound, + &gin.H{ + "error": fmt.Sprintf("could not find mesh %s", meshidParam), + }) + return + } + + mesh := meshToAPIMesh(meshidParam, getMeshReply.Nodes) + + c.JSON(http.StatusOK, mesh) +} + +func (s *SmegServer) GetMeshes(c *gin.Context) { + listMeshesReply := new(ipc.ListMeshReply) + + err := s.client.Call("IpcHandler.ListMeshes", "", &listMeshesReply) + + if err != nil { + logging.Log.WriteErrorf(err.Error()) + c.JSON(http.StatusBadRequest, nil) + return + } + + meshes := make([]SmegMesh, 0) + + for _, mesh := range listMeshesReply.Meshes { + getMeshReply := new(ipc.GetMeshReply) + + err := s.client.Call("IpcHandler.GetMesh", &mesh, &getMeshReply) + + if err != nil { + logging.Log.WriteErrorf(err.Error()) + c.JSON(http.StatusBadRequest, nil) + return + } + + meshes = append(meshes, meshToAPIMesh(mesh, getMeshReply.Nodes)) + } + + c.JSON(http.StatusOK, meshes) +} + +func (s *SmegServer) Run(addr string) error { + logging.Log.WriteInfof("Running API server") + return s.router.Run(addr) +} + +func NewSmegServer() (ApiServer, error) { + client, err := ipcRpc.DialHTTP("unix", SockAddr) + + if err != nil { + return nil, err + } + + router := gin.Default() + + router.Use(gin.LoggerWithConfig(gin.LoggerConfig{ + Output: logging.Log.Writer(), + })) + + smegServer := &SmegServer{ + router: router, + client: client, + } + + router.GET("/meshes", smegServer.GetMeshes) + router.GET("/mesh/:meshid", smegServer.GetMesh) + router.POST("/mesh/create", smegServer.CreateMesh) + router.POST("/mesh/join", smegServer.JoinMesh) + return smegServer, nil +} diff --git a/pkg/api/types.go b/pkg/api/types.go new file mode 100644 index 0000000..b99aa4a --- /dev/null +++ b/pkg/api/types.go @@ -0,0 +1,28 @@ +package api + +type SmegNode struct { + WgHost string `json:"wgHost"` + WgEndpoint string `json:"wgEndpoint"` + Endpoint string `json:"endpoint"` + Timestamp int `json:"timestamp"` + Description string `json:"description"` + PublicKey string `json:"publicKey"` + Routes []string `json:"routes"` +} + +type SmegMesh struct { + MeshId string `json:"meshid"` + Nodes map[string]SmegNode `json:"nodes"` +} + +type CreateMeshRequest struct { + IfName string `json:"ifName" binding:"required"` + WgPort int `json:"port" binding:"required,gte=1024,lt=65535"` +} + +type JoinMeshRequest struct { + IfName string `json:"ifName" binding:"required"` + WgPort int `json:"port" binding:"required,gte=1024,lt=65535"` + Bootstrap string `json:"bootstrap" binding:"required"` + MeshId string `json:"meshid" binding:"required"` +} diff --git a/pkg/ctrlserver/ctrltypes.go b/pkg/ctrlserver/ctrltypes.go index 72a7d65..6f290db 100644 --- a/pkg/ctrlserver/ctrltypes.go +++ b/pkg/ctrlserver/ctrltypes.go @@ -17,6 +17,7 @@ type MeshNode struct { WgHost string Timestamp int64 Routes []string + Description string } // Represents a WireGuard Mesh diff --git a/pkg/ipc/ipc.go b/pkg/ipc/ipc.go index 208be4a..e816b5b 100644 --- a/pkg/ipc/ipc.go +++ b/pkg/ipc/ipc.go @@ -63,7 +63,7 @@ const SockAddr = "/tmp/wgmesh_ipc.sock" func RunIpcHandler(server MeshIpc) error { if err := os.RemoveAll(SockAddr); err != nil { - return errors.New("Could not find to address") + return errors.New("could not find to address") } rpc.Register(server) diff --git a/pkg/log/log.go b/pkg/log/log.go index 2a07ddb..71ff0f1 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -2,6 +2,7 @@ package logging import ( + "io" "os" "github.com/sirupsen/logrus" @@ -15,6 +16,7 @@ type Logger interface { WriteInfof(msg string, args ...interface{}) WriteErrorf(msg string, args ...interface{}) WriteWarnf(msg string, args ...interface{}) + Writer() io.Writer } type LogrusLogger struct { @@ -33,6 +35,10 @@ func (l *LogrusLogger) WriteWarnf(msg string, args ...interface{}) { l.logger.Warnf(msg, args...) } +func (l *LogrusLogger) Writer() io.Writer { + return l.logger.Writer() +} + func NewLogrusLogger() *LogrusLogger { logger := logrus.New() logger.SetFormatter(&logrus.TextFormatter{FullTimestamp: true}) diff --git a/pkg/robin/requester.go b/pkg/robin/requester.go index 7dba7a4..08a38b4 100644 --- a/pkg/robin/requester.go +++ b/pkg/robin/requester.go @@ -144,6 +144,7 @@ func (n *IpcHandler) GetMesh(meshId string, reply *ipc.GetMeshReply) error { WgHost: node.GetWgHost().String(), Timestamp: node.GetTimeStamp(), Routes: node.GetRoutes(), + Description: node.GetDescription(), } nodes[i] = node