commit 203d986e0836a324a00272e2fc3834041c2fdb98 Author: jasinco Date: Thu Nov 20 11:47:00 2025 +0800 init diff --git a/api/auth/handlr.go b/api/auth/handlr.go new file mode 100644 index 0000000..4466106 --- /dev/null +++ b/api/auth/handlr.go @@ -0,0 +1,52 @@ +package auth + +import ( + "errors" + + "git.jasinco.work/wgcl/internal/federate" + "git.jasinco.work/wgcl/internal/logger" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var Driver federate.AUTH = nil + +type login struct { + Username string `form:"username" json:"username" binding:"required"` + Password string `form:"password" json:"password" binding:"required"` +} + +var ErrBadRequest = errors.New("Failed to parse request") +var ErrNoDriver = errors.New("No Auth Driver") + +func UserLogin(c *gin.Context) (*User, error) { + var cred login + if c.ShouldBind(&cred) != nil { + return nil, ErrBadRequest + } + if Driver == nil { + return nil, ErrNoDriver + } + + userdn, err := Driver.UserAuthenticate(cred.Username, cred.Password) + if err == nil { + var power uint16 = 0 + + if stat, err := Driver.UserAuthorizeWG(userdn); err == nil { + if stat { + power |= AuthorizeWG + } + } else { + logger.Logger.Error(err.Error()) + } + logger.Logger.Info("user authorize", zap.String("user", userdn), zap.Uint16("power", power)) + + u := &User{ + UserName: userdn, + Authorized: power, + } + u.setupClaim() + return u, nil + } + return nil, err +} diff --git a/api/auth/jwt.go b/api/auth/jwt.go new file mode 100644 index 0000000..bb4bfc2 --- /dev/null +++ b/api/auth/jwt.go @@ -0,0 +1,88 @@ +package auth + +import ( + "errors" + "strings" + "time" + + "git.jasinco.work/wgcl/internal/logger" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "go.uber.org/zap" +) + +var AuthorizeWG uint16 = 0x1 +var AuthorizeRevProxy uint16 = 0x2 + +var JWTKey = []byte("fecv") + +type User struct { + UserName string `json:"dn" binding:"required"` + Authorized uint16 `json:"pwr" binding:"required"` + jwt.RegisteredClaims +} + +func (u *User) setupClaim() { + u.ExpiresAt = jwt.NewNumericDate(time.Now().Add(24 * time.Hour)) + u.Issuer = "tethsues" + u.Subject = "smb" + u.ID = "1" + u.Audience = nil +} +func AuthMiddleware() gin.HandlerFunc { + return func(ctx *gin.Context) { + + logger.Logger.Info("auth middleware first") + var user *User = nil + auth_head, auth_head_exist := ctx.Request.Header["Authorization"] + // cookie,cookie_err := ctx.Request.Cookie("token") + if auth_head_exist && strings.HasPrefix(auth_head[0], "Bearer ") { + _token, err := jwt.ParseWithClaims(strings.TrimPrefix(auth_head[0], "Bearer "), &User{}, func(t *jwt.Token) (any, error) { + return JWTKey, nil + }) + if err != nil { + ctx.Status(400) + ctx.Abort() + return + } + if _user, ok := _token.Claims.(*User); ok { + user = _user + } + } + if user != nil { + logger.Logger.Info("auth middleware", zap.String("dn", user.UserName)) + ctx.Set("token", user) + } else { + ctx.Set("token", nil) + ctx.Status(400) + ctx.Abort() + return + } + ctx.Next() + + } +} +func IssueJWT(c *gin.Context) { + cred, err := UserLogin(c) + if errors.Is(err, ErrBadRequest) { + c.Status(400) + return + } else if err != nil { + logger.Logger.Warn(err.Error()) + c.Status(500) + return + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, *cred) + ss, err := token.SignedString(JWTKey) + if err != nil { + logger.Logger.Warn(err.Error()) + c.Status(500) + return + } + + c.JSON(200, gin.H{ + "token": ss, + }) + + // c.SetCookie("credential", ) +} diff --git a/api/vpn/controller.go b/api/vpn/controller.go new file mode 100644 index 0000000..21364c4 --- /dev/null +++ b/api/vpn/controller.go @@ -0,0 +1 @@ +package vpn diff --git a/api/vpn/handler.go b/api/vpn/handler.go new file mode 100644 index 0000000..a3e2868 --- /dev/null +++ b/api/vpn/handler.go @@ -0,0 +1,142 @@ +package vpn + +import ( + "context" + "io" + "net/netip" + "time" + + "git.jasinco.work/wgcl/internal/logger" + "git.jasinco.work/wgcl/internal/wg" + protovpn "git.jasinco.work/wgcl/proto/out" + "github.com/gin-gonic/gin" + "google.golang.org/protobuf/encoding/protojson" +) + +var wgctrl *wg.WG +var sync_cancel context.CancelFunc + +func SyncWGStat(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + wgctrl.SyncStatus() + logger.Logger.Info("sync wg status") + time.Sleep(5 * time.Second) + } + } +} + +func StartWG() { + var err error + baseip4 := netip.AddrFrom4([4]byte{172, 16, 0, 1}) + pool := wg.IPPool_Init(baseip4, 26) + + wgctrl, err = wg.WG_init("wg0", wg.NewKey(), 51823, pool) + if err != nil { + logger.Logger.Warn(err.Error()) + logger.Logger.Fatal("Failed init wgctrl") + } + ctx, cancel := context.WithCancel(context.Background()) + sync_cancel = cancel + go SyncWGStat(ctx) +} + +func StopWG() { + logger.Logger.Info("Stopping WG") + sync_cancel() + wgctrl.Deinit() +} + +func GETPeers(c *gin.Context) { + resp := wgctrl.GetPeersJson() + resp_json, err := protojson.Marshal(resp) + if err != nil { + c.Status(500) + return + } + c.Data(200, "application/json", resp_json) +} +func NewPeer(c *gin.Context) { + req := &protovpn.PeerReq{} + req_json, err := io.ReadAll(c.Request.Body) + if err != nil { + c.Status(400) + return + } + if protojson.Unmarshal(req_json, req) != nil { + c.Status(400) + return + } + wgctrl.WriteTxStart() + defer wgctrl.WriteTxEnd() + + var ip4 *netip.Addr + if req.HasIp4() { + _ip4, err := netip.ParseAddr(req.GetIp4()) + ip4 = &_ip4 + if err != nil { + c.Status(400) + return + } + } else { + _ip4, err := wgctrl.IP4Pool.Next() + if err != nil { + logger.Logger.Warn("ip pool exhausted") + c.Status(500) + return + } + ip4 = &_ip4 + } + peer := wg.WGPeer_init(req.GetName(), wg.NewKey(), *ip4) + wgctrl.AddPeer(peer) + wgctrl.IP4Pool.Lease(*ip4) + wgctrl.Apply() + + c.Status(200) +} + +func SearchPeer(c *gin.Context) { + if pubkey := c.Query("pubkey"); len(pubkey) > 0 { + p, err := wgctrl.SearchPeerPubkey(pubkey) + if p == nil { + if err != nil { + logger.Logger.Warn(err.Error()) + } + c.Status(400) + return + } + b, err := protojson.Marshal(p.ToPeerStatus()) + if err != nil { + logger.Logger.Warn(err.Error()) + } + c.Data(200, "application/json", b) + } else { + c.Status(400) + } +} + +func DeletePeer(c *gin.Context) { + if pubkey := c.Query("pubkey"); len(pubkey) > 0 { + p, err := wgctrl.SearchPeerPubkey(pubkey) + if p == nil { + if err != nil { + logger.Logger.Warn(err.Error()) + } + c.Status(400) + return + } + wgctrl.WriteTxStart() + defer wgctrl.WriteTxEnd() + p.Disable() + if err = wgctrl.Apply(); err != nil { + logger.Logger.Warn("Can't apply disable peer") + } + wgctrl.RemovePeer(p) + c.Status(200) + } else { + c.Status(400) + } +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..8153439 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "time" + + "git.jasinco.work/wgcl/api/auth" + "git.jasinco.work/wgcl/api/vpn" + "git.jasinco.work/wgcl/internal/federate" + "git.jasinco.work/wgcl/internal/logger" + "github.com/fvbock/endless" + + //"github.com/gin-contrib/pprof" + ginzap "github.com/gin-contrib/zap" + "github.com/gin-gonic/gin" +) + +func main() { + + r := gin.Default() + r.Use(ginzap.Ginzap(logger.Logger, time.RFC3339, true)) + // r.Use(ginzap.RecoveryWithZap(logger.Logger, true)) + defer logger.Flush() + + l := federate.FedLDAP{ + Base_dn: "dc=example,dc=com", + User_filter: "(&(objectClass=person)(uid=%s))", + Group_wg_filter: "(&(dn=%s)(memberof=cn=wgopr,ou=groups,dc=example,dc=com))", + } + l.Init("ldap://localhost:3890") + + if err := l.AuthBind("uid=admin,ou=people,dc=example,dc=com", "recrecrec"); err != nil { + logger.Logger.Fatal(err.Error()) + } + + auth.Driver = &l + + vpn.StartWG() + defer vpn.StopWG() + // pprof.Register(r) + r.POST("/api/auth", auth.IssueJWT) + + { + g := r.Group("/api", auth.AuthMiddleware()) + v := g.Group("/vpn") + v.GET("/peers", vpn.GETPeers) + v.POST("/peer", vpn.NewPeer) + v.GET("/peer", vpn.SearchPeer) + v.DELETE("/peer", vpn.DeletePeer) + } + + endless.ListenAndServe("0.0.0.0:8080", r) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..eb59e4f --- /dev/null +++ b/go.mod @@ -0,0 +1,55 @@ +module git.jasinco.work/wgcl + +go 1.25.4 + +require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/appleboy/gin-jwt/v3 v3.2.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.14.1 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gin-contrib/pprof v1.5.3 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/gin-contrib/zap v1.1.5 // indirect + github.com/gin-gonic/gin v1.11.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // 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.28.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/go-set/v3 v3.0.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // 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.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.55.0 // indirect + github.com/redis/rueidis v1.0.66 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + github.com/vishvananda/netlink v1.3.1 // indirect + github.com/vishvananda/netns v0.0.5 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.uber.org/mock v0.6.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.29.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..79e5c47 --- /dev/null +++ b/go.sum @@ -0,0 +1,147 @@ +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/appleboy/gin-jwt/v3 v3.2.0 h1:Ifa8Zsm2cZ93u/HIAhcTmftTK8rIaaoAoH77lcajWD0= +github.com/appleboy/gin-jwt/v3 v3.2.0/go.mod h1:ANNEPdDkdOp6jXAbicMFX7N4mIIx70m9A3asDmXdbYo= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= +github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6 h1:6VSn3hB5U5GeA6kQw4TwWIWbOhtvR2hmbBJnTOtqTWc= +github.com/fvbock/endless v0.0.0-20170109170031-447134032cb6/go.mod h1:YxOVT5+yHzKvwhsiSIWmbAYM3Dr9AEEbER2dVayfBkg= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/pprof v1.5.3 h1:Bj5SxJ3kQDVez/s/+f9+meedJIqLS+xlkIVDe/lcvgM= +github.com/gin-contrib/pprof v1.5.3/go.mod h1:0+LQSZ4SLO0B6+2n6JBzaEygpTBxe/nI+YEYpfQQ6xY= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-contrib/zap v1.1.5 h1:qKwhWb4DQgPriCl1AHLLob6hav/KUIctKXIjTmWIN3I= +github.com/gin-contrib/zap v1.1.5/go.mod h1:lAchUtGz9M2K6xDr1rwtczyDrThmSx6c9F384T45iOE= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-set/v3 v3.0.1 h1:ZwO15ZYmIrFYL9zSm2wBuwcRiHxVdp46m/XA/MUlM6I= +github.com/hashicorp/go-set/v3 v3.0.1/go.mod h1:0oPQqhtitglZeT2ZiWnRIfUG6gJAHnn7LzrS7SbgNY4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= +github.com/redis/rueidis v1.0.66 h1:7rvyrl0vL/cAEkE97+L5v3MJ3Vg8IKz+KIxUTfT+yJk= +github.com/redis/rueidis v1.0.66/go.mod h1:Lkhr2QTgcoYBhxARU7kJRO8SyVlgUuEkcJO1Y8MCluA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0= +github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4= +github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY= +github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/federate/if.go b/internal/federate/if.go new file mode 100644 index 0000000..7ccb90a --- /dev/null +++ b/internal/federate/if.go @@ -0,0 +1,13 @@ +package federate + +import "errors" + +type AUTH interface { + IsUser(name string) (string, error) + UserAuthorizeWG(name string) (bool, error) + // return IsUser check + UserAuthenticate(name string, password string) (string, error) +} + +var ErrAUTHNoUser = errors.New("No user for auth") +var ErrAUTHAuthenticateInvalidPassword = errors.New("Incorrect Password") diff --git a/internal/federate/ldap.go b/internal/federate/ldap.go new file mode 100644 index 0000000..3d9f997 --- /dev/null +++ b/internal/federate/ldap.go @@ -0,0 +1,80 @@ +package federate + +import ( + "errors" + "fmt" + + "git.jasinco.work/wgcl/internal/logger" + "github.com/go-ldap/ldap/v3" +) + +type FedLDAP struct { + Base_dn string + User_filter string + endpoint *ldap.Conn + Group_wg_filter string + auth_endpoint *ldap.Conn +} + +func (l *FedLDAP) Init(endpoint string) { + var err error + + l.endpoint, err = ldap.DialURL(endpoint) + if err != nil { + logger.Logger.Fatal(err.Error()) + } + l.auth_endpoint, err = ldap.DialURL(endpoint) + if err != nil { + logger.Logger.Fatal(err.Error()) + } + +} + +func (l *FedLDAP) AuthBind(user string, pass string) error { + return l.endpoint.Bind(user, pass) +} + +func (l *FedLDAP) IsUser(name string) (string, error) { + req := ldap.NewSearchRequest(l.Base_dn, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, + 1, 0, false, + fmt.Sprintf(l.User_filter, ldap.EscapeFilter(name)), []string{"dn"}, nil) + resp, err := l.endpoint.Search(req) + if err != nil { + return "", err + } + if len(resp.Entries) != 1 { + return "", ErrAUTHNoUser + } + return resp.Entries[0].DN, nil +} +func (l *FedLDAP) UserAuthorizeWG(user_dn string) (bool, error) { + req := ldap.NewSearchRequest(l.Base_dn, ldap.ScopeBaseObject, ldap.NeverDerefAliases, + 1, 0, false, + fmt.Sprintf(l.Group_wg_filter, user_dn), []string{"dn"}, nil) + + resp, err := l.endpoint.Search(req) + if err != nil { + return false, err + } + return len(resp.Entries) > 0, nil +} +func (l *FedLDAP) UserAuthenticate(name string, password string) (string, error) { + // find user + userdn, err := l.IsUser(name) + if err != nil { + return "", err + } + + req := ldap.NewSimpleBindRequest(userdn, ldap.EscapeFilter(password), nil) + _, err = l.auth_endpoint.SimpleBind(req) + var ldaperr *ldap.Error + if errors.As(err, &ldaperr) && ldaperr.ResultCode == ldap.LDAPResultInvalidCredentials { + return "", ErrAUTHAuthenticateInvalidPassword + } + + if err != nil { + return "", err + } + + return userdn, nil +} diff --git a/internal/logger/lib.go b/internal/logger/lib.go new file mode 100644 index 0000000..bb66918 --- /dev/null +++ b/internal/logger/lib.go @@ -0,0 +1,23 @@ +package logger + +import "go.uber.org/zap" + +var Logger *zap.Logger + +func init() { + Logger, _ = zap.NewProduction() + // sugar := logger.Sugar() + // sugar.Infow("failed to fetch URL", + // // Structured context as loosely typed key-value pairs. + // "url", url, + // "attempt", 3, + // "backoff", time.Second, + // ) + // sugar.Infof("Failed to fetch URL: %s", url) + + // Create a Gin router with default middleware (logger and recovery) +} + +func Flush() { + Logger.Sync() // flushes buffer, if any +} diff --git a/internal/wg/ip_pool.go b/internal/wg/ip_pool.go new file mode 100644 index 0000000..b9398f5 --- /dev/null +++ b/internal/wg/ip_pool.go @@ -0,0 +1,67 @@ +package wg + +import ( + "encoding/binary" + "errors" + "net/netip" + + "github.com/hashicorp/go-set/v3" +) + +type IPPool struct { + scope netip.Prefix + leasure *set.Set[netip.Addr] + cache netip.Addr +} + +var ErrIPPoolExhausted = errors.New("IP Pool Exhausted") + +// @param base_ip4: is used as the gateway address +func IPPool_Init(base_ip4 netip.Addr, cidr uint8) *IPPool { + scope := netip.PrefixFrom(base_ip4, int(cidr)) + pool := &IPPool{leasure: set.New[netip.Addr](1 << (32 - cidr)), scope: scope} + pool.cache = pool.scope.Addr().Next() + + //calculate broadcast address + var wildcard uint32 = (1 << (32 - cidr)) - 1 + + wildcard_mask := [4]byte{} + binary.BigEndian.PutUint32(wildcard_mask[:4], wildcard) + broadcast := base_ip4.As4() + broadcast[0] |= wildcard_mask[0] + broadcast[1] |= wildcard_mask[1] + broadcast[2] |= wildcard_mask[2] + broadcast[3] |= wildcard_mask[3] + + broadcast_addr := netip.AddrFrom4(broadcast) + pool.Lease(broadcast_addr) + //gateway + pool.Lease(base_ip4) + + return pool +} +func (p *IPPool) Next() (netip.Addr, error) { + cyclic := false + for ; ; p.cache = p.cache.Next() { + if p.leasure.Contains(p.cache) && p.cache.IsValid() { + continue + } else { + if p.cache.IsValid() && p.scope.Contains(p.cache) && !p.leasure.Contains(p.cache) { + return p.cache, nil + } else if !cyclic { + p.cache = p.scope.Masked().Addr() + cyclic = true + + } else { + return p.cache, ErrIPPoolExhausted + } + } + } +} + +func (p *IPPool) Lease(addr netip.Addr) { + p.leasure.Insert(addr) +} +func (p *IPPool) HasIP(addr netip.Addr) { + p.leasure.Contains(addr) +} diff --git a/internal/wg/wg.go b/internal/wg/wg.go new file mode 100644 index 0000000..8cc20bd --- /dev/null +++ b/internal/wg/wg.go @@ -0,0 +1,273 @@ +package wg + +/* + #cgo CFLAGS: -I./ + #include + #include + #include + enum wg_device_flags dev_cfg = WGDEVICE_HAS_PRIVATE_KEY|WGDEVICE_HAS_LISTEN_PORT; +*/ +import "C" +import ( + "bytes" + "encoding/binary" + "errors" + "math/bits" + "net/netip" + "sync" + "unsafe" + + "git.jasinco.work/wgcl/internal/logger" + protovpn "git.jasinco.work/wgcl/proto/out" + "github.com/vishvananda/netlink" + "google.golang.org/protobuf/proto" +) + +// TODO: inspect all api, complete error handling + +type WGPeer struct { + name string + privkey string //base64 string + peer C.wg_peer + next *WGPeer +} + +type WG struct { + dev *C.wg_device + lock sync.RWMutex + keep_alive bool + peers *WGPeer + peers_len int + IP4Pool *IPPool +} + +var WGfailedadddev = errors.New("failed add wgdev") +var WGfailedsetdev = errors.New("failed set wgdev") +var WGfailedparseb64key = errors.New("failed parse base64 key") + +func GetB64Key(key *C.wg_key) string { + var psk_b64 C.wg_key_b64_string + + psk_ptr := (*C.uint8_t)(unsafe.Pointer(&(*key)[0])) + psk_b64_ptr := (*C.char)(unsafe.Pointer(&psk_b64[0])) + C.wg_key_to_base64(psk_b64_ptr, psk_ptr) + return C.GoString(psk_b64_ptr) +} + +func NewKey() string { + var privkey C.wg_key + + privkey_as_ptr := (*C.uint8_t)(unsafe.Pointer(&privkey[0])) + C.wg_generate_private_key(privkey_as_ptr) + return GetB64Key(&privkey) +} + +func NewPresharedKey() string { + var psk C.wg_key + psk_ptr := (*C.uint8_t)(unsafe.Pointer(&psk[0])) + C.wg_generate_preshared_key(psk_ptr) + return GetB64Key(&psk) +} + +// device name and receive base64 encoded keys +func WG_init(name string, privkey string, listen uint16, ip4_pool *IPPool) (*WG, error) { + var dev WG + dev.dev = (*C.wg_device)(C.malloc(C.sizeof_wg_device)) + + // hence it only call once at startup, I'd rather not to free it explicitly + C.wg_key_from_base64((*C.uint8_t)(unsafe.Pointer(&dev.dev.private_key[0])), C.CString(privkey)) + + // set device name + ifname := C.CString(name) + C.strncpy(&dev.dev.name[0], ifname, 16) + + dev.dev.flags = C.dev_cfg + dev.dev.first_peer = (*C.wg_peer)(C.NULL) + dev.dev.last_peer = (*C.wg_peer)(C.NULL) + dev.dev.listen_port = (C.uint16_t)(listen) + if C.wg_add_device((*C.char)(unsafe.Pointer(&dev.dev.name[0]))) < 0 { + return nil, WGfailedadddev + } + if C.wg_set_device(dev.dev) < 0 { + return nil, WGfailedsetdev + } + if C.wg_get_device(&dev.dev, (*C.char)(unsafe.Pointer(&dev.dev.name[0]))) < 0 { + return nil, WGfailedsetdev + } + dev.peers = nil + dev.IP4Pool = ip4_pool + + // setup interface + var err error + wg_if, err := netlink.LinkByIndex(int(dev.dev.ifindex)) + if err != nil { + return nil, err + } + nl_addr, err := netlink.ParseAddr(ip4_pool.scope.String()) + if err != nil { + return nil, err + } + err = netlink.AddrAdd(wg_if, nl_addr) + if err != nil { + return nil, err + } + err = netlink.LinkSetUp(wg_if) + if err != nil { + return nil, err + } + + return &dev, nil +} + +func (wg *WG) SyncStatus() { + wg.lock.Lock() + defer wg.lock.Unlock() + if C.wg_get_device(&wg.dev, (*C.char)(unsafe.Pointer(&wg.dev.name[0]))) < 0 { + logger.Logger.Info("Failed to sync wg status") + } +} + +func (wg *WG) Deinit() { + C.wg_del_device((*C.char)(unsafe.Pointer(&wg.dev.name[0]))) +} + +func (wg *WG) WriteTxStart() { + wg.lock.Lock() +} +func (wg *WG) WriteTxEnd() { + wg.lock.Unlock() +} + +func (wg *WG) AddPeer(peer *WGPeer) { + peer.next = nil + wg.peers_len += 1 + if wg.peers == nil { + wg.peers = peer + return + } + ptr := wg.peers + for ptr.next != nil { + ptr = ptr.next + } + ptr.next = peer +} + +func (wg *WG) RemovePeer(peer *WGPeer) { + if wg.peers == peer { + wg.peers = peer.next + } + for ptr := wg.peers; ptr != nil; ptr = ptr.next { + if ptr.next == peer { + ptr.next = peer.next + break + } + } + wg.peers_len -= 1 +} + +func (wg *WG) Apply() error { + + if wg.peers != nil { + first_ptr := (*C.wg_peer)(unsafe.Pointer(&wg.peers.peer)) + wg.dev.first_peer = first_ptr + } else { + wg.dev.first_peer = nil + wg.dev.last_peer = nil + } + + for ptr := wg.peers; ptr != nil; ptr = (*WGPeer)(ptr.next) { + if ptr.next == nil { + ptr.peer.next_peer = nil + last_ptr := (*C.wg_peer)(unsafe.Pointer(&ptr.peer)) + wg.dev.last_peer = last_ptr + break + } + ptr.peer.next_peer = &(*WGPeer)(ptr.next).peer + } + wgdev := (*C.wg_device)(unsafe.Pointer(wg.dev)) + if C.wg_set_device(wgdev) < 0 { + return WGfailedsetdev + } + return nil +} +func (wg *WG) SearchPeerIP4(addr netip.Addr) *WGPeer { + wg.lock.RLock() + defer wg.lock.RUnlock() + ip4_slice := addr.As4() + for ptr := wg.peers; ptr != nil; ptr = ptr.next { + if bytes.Equal(ip4_slice[:], ptr.peer.first_allowedip.anon0[:4]) { + return ptr + } + } + return nil +} +func (wg *WG) SearchPeerPubkey(pubkey string) (*WGPeer, error) { + wg.lock.RLock() + defer wg.lock.RUnlock() + pubkey_cstr := C.CString(pubkey) + defer C.free(unsafe.Pointer(pubkey_cstr)) + var pubkey_bin C.wg_key + if C.wg_key_from_base64((*C.uint8_t)(&pubkey_bin[0]), pubkey_cstr) < 0 { + return nil, WGfailedparseb64key + } + for ptr := wg.peers; ptr != nil; ptr = ptr.next { + if C.wg_key_equal(&pubkey_bin, &ptr.peer.public_key) == 0 { + return ptr, nil + } + } + return nil, nil +} +func (w *WG) GetPeersJson() *protovpn.PeerResp { + w.lock.RLock() + defer w.lock.RUnlock() + peers := make([]*protovpn.PeerStatus, w.peers_len) + peers_ptr := 0 + for ptr := w.peers; ptr != nil; ptr = ptr.next { + peers[peers_ptr] = ptr.ToPeerStatus() + peers_ptr += 1 + } + resp := protovpn.PeerResp_builder{Peers: peers} + return resp.Build() +} + +func WGPeer_init(name string, privkey string, remote netip.Addr) *WGPeer { + var peer WGPeer + peer.name = name + peer.privkey = privkey + + var privkey_bin C.wg_key + priv_cstr := C.CString(privkey) + defer C.free(unsafe.Pointer(priv_cstr)) + C.wg_key_from_base64((*C.uint8_t)(unsafe.Pointer(&privkey_bin[0])), priv_cstr) + C.wg_generate_public_key((*C.uint8_t)(unsafe.Pointer(&peer.peer.public_key[0])), (*C.uint8_t)(unsafe.Pointer(&privkey_bin[0]))) + peer.peer.flags = C.WGPEER_HAS_PUBLIC_KEY | C.WGPEER_REPLACE_ALLOWEDIPS + + remote_ip_arr := remote.As4() + remote_ip := binary.LittleEndian.Uint32(remote_ip_arr[:]) + remote_endpoint := C.wg_allowed_ip_new_ip4((C.in_addr_t)(remote_ip), 32) + + peer.peer.first_allowedip = remote_endpoint + peer.peer.last_allowedip = remote_endpoint + + return &peer +} + +func WGPeer_free(peer *WGPeer) { + C.free(unsafe.Pointer(peer.peer.first_allowedip)) +} +func (p *WGPeer) Disable() { + p.peer.flags |= C.WGPEER_REMOVE_ME +} +func (p *WGPeer) Enable() { + p.peer.flags &= bits.Reverse32(C.WGPEER_REMOVE_ME) +} +func (p *WGPeer) ToPeerStatus() *protovpn.PeerStatus { + ip4, _ := netip.AddrFromSlice(p.peer.first_allowedip.anon0[:4]) + return protovpn.PeerStatus_builder{ + Enabled: proto.Bool((p.peer.flags & C.WGPEER_REMOVE_ME) == 0), + Pubkey: proto.String(GetB64Key(&p.peer.public_key)), + Nick: proto.String(p.name), + Ipv4: proto.String(ip4.String()), + Beacon: proto.Int64(int64(p.peer.last_handshake_time.tv_sec)), + }.Build() +} diff --git a/internal/wg/wireguard.c b/internal/wg/wireguard.c new file mode 100644 index 0000000..ac942e7 --- /dev/null +++ b/internal/wg/wireguard.c @@ -0,0 +1,1720 @@ +// SPDX-License-Identifier: LGPL-2.1+ +/* + * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights + * Reserved. Copyright (C) 2008-2012 Pablo Neira Ayuso . + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wireguard.h" + +/* wireguard.h netlink uapi: */ + +#define WG_GENL_NAME "wireguard" +#define WG_GENL_VERSION 1 + +enum wg_cmd { WG_CMD_GET_DEVICE, WG_CMD_SET_DEVICE, __WG_CMD_MAX }; + +enum wgdevice_flag { WGDEVICE_F_REPLACE_PEERS = 1U << 0 }; +enum wgdevice_attribute { + WGDEVICE_A_UNSPEC, + WGDEVICE_A_IFINDEX, + WGDEVICE_A_IFNAME, + WGDEVICE_A_PRIVATE_KEY, + WGDEVICE_A_PUBLIC_KEY, + WGDEVICE_A_FLAGS, + WGDEVICE_A_LISTEN_PORT, + WGDEVICE_A_FWMARK, + WGDEVICE_A_PEERS, + __WGDEVICE_A_LAST +}; + +enum wgpeer_flag { + WGPEER_F_REMOVE_ME = 1U << 0, + WGPEER_F_REPLACE_ALLOWEDIPS = 1U << 1 +}; +enum wgpeer_attribute { + WGPEER_A_UNSPEC, + WGPEER_A_PUBLIC_KEY, + WGPEER_A_PRESHARED_KEY, + WGPEER_A_FLAGS, + WGPEER_A_ENDPOINT, + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + WGPEER_A_LAST_HANDSHAKE_TIME, + WGPEER_A_RX_BYTES, + WGPEER_A_TX_BYTES, + WGPEER_A_ALLOWEDIPS, + WGPEER_A_PROTOCOL_VERSION, + __WGPEER_A_LAST +}; + +enum wgallowedip_attribute { + WGALLOWEDIP_A_UNSPEC, + WGALLOWEDIP_A_FAMILY, + WGALLOWEDIP_A_IPADDR, + WGALLOWEDIP_A_CIDR_MASK, + __WGALLOWEDIP_A_LAST +}; + +/* libmnl mini library: */ + +#define MNL_SOCKET_AUTOPID 0 +#define MNL_ALIGNTO 4 +#define MNL_ALIGN(len) (((len) + MNL_ALIGNTO - 1) & ~(MNL_ALIGNTO - 1)) +#define MNL_NLMSG_HDRLEN MNL_ALIGN(sizeof(struct nlmsghdr)) +#define MNL_ATTR_HDRLEN MNL_ALIGN(sizeof(struct nlattr)) + +enum mnl_attr_data_type { + MNL_TYPE_UNSPEC, + MNL_TYPE_U8, + MNL_TYPE_U16, + MNL_TYPE_U32, + MNL_TYPE_U64, + MNL_TYPE_STRING, + MNL_TYPE_FLAG, + MNL_TYPE_MSECS, + MNL_TYPE_NESTED, + MNL_TYPE_NESTED_COMPAT, + MNL_TYPE_NUL_STRING, + MNL_TYPE_BINARY, + MNL_TYPE_MAX, +}; + +#define mnl_attr_for_each(attr, nlh, offset) \ + for ((attr) = mnl_nlmsg_get_payload_offset((nlh), (offset)); mnl_attr_ok( \ + (attr), (char *)mnl_nlmsg_get_payload_tail(nlh) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_nested(attr, nest) \ + for ((attr) = mnl_attr_get_payload(nest); mnl_attr_ok( \ + (attr), (char *)mnl_attr_get_payload(nest) + \ + mnl_attr_get_payload_len(nest) - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define mnl_attr_for_each_payload(payload, payload_size) \ + for ((attr) = (payload); \ + mnl_attr_ok((attr), (char *)(payload) + payload_size - (char *)(attr)); \ + (attr) = mnl_attr_next(attr)) + +#define MNL_CB_ERROR -1 +#define MNL_CB_STOP 0 +#define MNL_CB_OK 1 + +typedef int (*mnl_attr_cb_t)(const struct nlattr *attr, void *data); +typedef int (*mnl_cb_t)(const struct nlmsghdr *nlh, void *data); + +#ifndef MNL_ARRAY_SIZE +#define MNL_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +static size_t mnl_ideal_socket_buffer_size(void) { + static size_t size = 0; + + if (size) + return size; + size = (size_t)sysconf(_SC_PAGESIZE); + if (size > 8192) + size = 8192; + return size; +} + +static size_t mnl_nlmsg_size(size_t len) { return len + MNL_NLMSG_HDRLEN; } + +static struct nlmsghdr *mnl_nlmsg_put_header(void *buf) { + int len = MNL_ALIGN(sizeof(struct nlmsghdr)); + struct nlmsghdr *nlh = buf; + + memset(buf, 0, len); + nlh->nlmsg_len = len; + return nlh; +} + +static void *mnl_nlmsg_put_extra_header(struct nlmsghdr *nlh, size_t size) { + char *ptr = (char *)nlh + nlh->nlmsg_len; + size_t len = MNL_ALIGN(size); + nlh->nlmsg_len += len; + memset(ptr, 0, len); + return ptr; +} + +static void *mnl_nlmsg_get_payload(const struct nlmsghdr *nlh) { + return (void *)nlh + MNL_NLMSG_HDRLEN; +} + +static void *mnl_nlmsg_get_payload_offset(const struct nlmsghdr *nlh, + size_t offset) { + return (void *)nlh + MNL_NLMSG_HDRLEN + MNL_ALIGN(offset); +} + +static bool mnl_nlmsg_ok(const struct nlmsghdr *nlh, int len) { + return len >= (int)sizeof(struct nlmsghdr) && + nlh->nlmsg_len >= sizeof(struct nlmsghdr) && + (int)nlh->nlmsg_len <= len; +} + +static struct nlmsghdr *mnl_nlmsg_next(const struct nlmsghdr *nlh, int *len) { + *len -= MNL_ALIGN(nlh->nlmsg_len); + return (struct nlmsghdr *)((void *)nlh + MNL_ALIGN(nlh->nlmsg_len)); +} + +static void *mnl_nlmsg_get_payload_tail(const struct nlmsghdr *nlh) { + return (void *)nlh + MNL_ALIGN(nlh->nlmsg_len); +} + +static bool mnl_nlmsg_seq_ok(const struct nlmsghdr *nlh, unsigned int seq) { + return nlh->nlmsg_seq && seq ? nlh->nlmsg_seq == seq : true; +} + +static bool mnl_nlmsg_portid_ok(const struct nlmsghdr *nlh, + unsigned int portid) { + return nlh->nlmsg_pid && portid ? nlh->nlmsg_pid == portid : true; +} + +static uint16_t mnl_attr_get_type(const struct nlattr *attr) { + return attr->nla_type & NLA_TYPE_MASK; +} + +static uint16_t mnl_attr_get_payload_len(const struct nlattr *attr) { + return attr->nla_len - MNL_ATTR_HDRLEN; +} + +static void *mnl_attr_get_payload(const struct nlattr *attr) { + return (void *)attr + MNL_ATTR_HDRLEN; +} + +static bool mnl_attr_ok(const struct nlattr *attr, int len) { + return len >= (int)sizeof(struct nlattr) && + attr->nla_len >= sizeof(struct nlattr) && (int)attr->nla_len <= len; +} + +static struct nlattr *mnl_attr_next(const struct nlattr *attr) { + return (struct nlattr *)((void *)attr + MNL_ALIGN(attr->nla_len)); +} + +static int mnl_attr_type_valid(const struct nlattr *attr, uint16_t max) { + if (mnl_attr_get_type(attr) > max) { + errno = EOPNOTSUPP; + return -1; + } + return 1; +} + +static int __mnl_attr_validate(const struct nlattr *attr, + enum mnl_attr_data_type type, size_t exp_len) { + uint16_t attr_len = mnl_attr_get_payload_len(attr); + const char *attr_data = mnl_attr_get_payload(attr); + + if (attr_len < exp_len) { + errno = ERANGE; + return -1; + } + switch (type) { + case MNL_TYPE_FLAG: + if (attr_len > 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NUL_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + if (attr_data[attr_len - 1] != '\0') { + errno = EINVAL; + return -1; + } + break; + case MNL_TYPE_STRING: + if (attr_len == 0) { + errno = ERANGE; + return -1; + } + break; + case MNL_TYPE_NESTED: + + if (attr_len == 0) + break; + + if (attr_len < MNL_ATTR_HDRLEN) { + errno = ERANGE; + return -1; + } + break; + default: + + break; + } + if (exp_len && attr_len > exp_len) { + errno = ERANGE; + return -1; + } + return 0; +} + +static const size_t mnl_attr_data_type_len[MNL_TYPE_MAX] = { + [MNL_TYPE_U8] = sizeof(uint8_t), [MNL_TYPE_U16] = sizeof(uint16_t), + [MNL_TYPE_U32] = sizeof(uint32_t), [MNL_TYPE_U64] = sizeof(uint64_t), + [MNL_TYPE_MSECS] = sizeof(uint64_t), +}; + +static int mnl_attr_validate(const struct nlattr *attr, + enum mnl_attr_data_type type) { + int exp_len; + + if (type >= MNL_TYPE_MAX) { + errno = EINVAL; + return -1; + } + exp_len = mnl_attr_data_type_len[type]; + return __mnl_attr_validate(attr, type, exp_len); +} + +static int mnl_attr_parse(const struct nlmsghdr *nlh, unsigned int offset, + mnl_attr_cb_t cb, void *data) { + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each(attr, nlh, offset) if ((ret = cb(attr, data)) <= + MNL_CB_STOP) return ret; + return ret; +} + +static int mnl_attr_parse_nested(const struct nlattr *nested, mnl_attr_cb_t cb, + void *data) { + int ret = MNL_CB_OK; + const struct nlattr *attr; + + mnl_attr_for_each_nested(attr, nested) if ((ret = cb(attr, data)) <= + MNL_CB_STOP) return ret; + return ret; +} + +static uint8_t mnl_attr_get_u8(const struct nlattr *attr) { + return *((uint8_t *)mnl_attr_get_payload(attr)); +} + +static uint16_t mnl_attr_get_u16(const struct nlattr *attr) { + return *((uint16_t *)mnl_attr_get_payload(attr)); +} + +static uint32_t mnl_attr_get_u32(const struct nlattr *attr) { + return *((uint32_t *)mnl_attr_get_payload(attr)); +} + +static uint64_t mnl_attr_get_u64(const struct nlattr *attr) { + uint64_t tmp; + memcpy(&tmp, mnl_attr_get_payload(attr), sizeof(tmp)); + return tmp; +} + +static const char *mnl_attr_get_str(const struct nlattr *attr) { + return mnl_attr_get_payload(attr); +} + +static void mnl_attr_put(struct nlmsghdr *nlh, uint16_t type, size_t len, + const void *data) { + struct nlattr *attr = mnl_nlmsg_get_payload_tail(nlh); + uint16_t payload_len = MNL_ALIGN(sizeof(struct nlattr)) + len; + int pad; + + attr->nla_type = type; + attr->nla_len = payload_len; + memcpy(mnl_attr_get_payload(attr), data, len); + nlh->nlmsg_len += MNL_ALIGN(payload_len); + pad = MNL_ALIGN(len) - len; + if (pad > 0) + memset(mnl_attr_get_payload(attr) + len, 0, pad); +} + +static void mnl_attr_put_u16(struct nlmsghdr *nlh, uint16_t type, + uint16_t data) { + mnl_attr_put(nlh, type, sizeof(uint16_t), &data); +} + +static void mnl_attr_put_u32(struct nlmsghdr *nlh, uint16_t type, + uint32_t data) { + mnl_attr_put(nlh, type, sizeof(uint32_t), &data); +} + +static void mnl_attr_put_strz(struct nlmsghdr *nlh, uint16_t type, + const char *data) { + mnl_attr_put(nlh, type, strlen(data) + 1, data); +} + +static struct nlattr *mnl_attr_nest_start(struct nlmsghdr *nlh, uint16_t type) { + struct nlattr *start = mnl_nlmsg_get_payload_tail(nlh); + + start->nla_type = NLA_F_NESTED | type; + nlh->nlmsg_len += MNL_ALIGN(sizeof(struct nlattr)); + return start; +} + +static bool mnl_attr_put_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, size_t len, const void *data) { + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN + MNL_ALIGN(len) > buflen) + return false; + mnl_attr_put(nlh, type, len, data); + return true; +} + +static bool mnl_attr_put_u8_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint8_t data) { + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint8_t), &data); +} + +static bool mnl_attr_put_u16_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint16_t data) { + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint16_t), &data); +} + +static bool mnl_attr_put_u32_check(struct nlmsghdr *nlh, size_t buflen, + uint16_t type, uint32_t data) { + return mnl_attr_put_check(nlh, buflen, type, sizeof(uint32_t), &data); +} + +static struct nlattr *mnl_attr_nest_start_check(struct nlmsghdr *nlh, + size_t buflen, uint16_t type) { + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > buflen) + return NULL; + return mnl_attr_nest_start(nlh, type); +} + +static void mnl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *start) { + start->nla_len = mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +static void mnl_attr_nest_cancel(struct nlmsghdr *nlh, struct nlattr *start) { + nlh->nlmsg_len -= mnl_nlmsg_get_payload_tail(nlh) - (void *)start; +} + +static int mnl_cb_noop(__attribute__((unused)) const struct nlmsghdr *nlh, + __attribute__((unused)) void *data) { + return MNL_CB_OK; +} + +static int mnl_cb_error(const struct nlmsghdr *nlh, + __attribute__((unused)) void *data) { + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { + errno = EBADMSG; + return MNL_CB_ERROR; + } + + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnl_cb_stop(__attribute__((unused)) const struct nlmsghdr *nlh, + __attribute__((unused)) void *data) { + return MNL_CB_STOP; +} + +static const mnl_cb_t default_cb_array[NLMSG_MIN_TYPE] = { + [NLMSG_NOOP] = mnl_cb_noop, + [NLMSG_ERROR] = mnl_cb_error, + [NLMSG_DONE] = mnl_cb_stop, + [NLMSG_OVERRUN] = mnl_cb_noop, +}; + +static int __mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) { + int ret = MNL_CB_OK, len = numbytes; + const struct nlmsghdr *nlh = buf; + + while (mnl_nlmsg_ok(nlh, len)) { + + if (!mnl_nlmsg_portid_ok(nlh, portid)) { + errno = ESRCH; + return -1; + } + + if (!mnl_nlmsg_seq_ok(nlh, seq)) { + errno = EPROTO; + return -1; + } + + if (nlh->nlmsg_flags & NLM_F_DUMP_INTR) { + errno = EINTR; + return -1; + } + + if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { + if (cb_data) { + ret = cb_data(nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (nlh->nlmsg_type < cb_ctl_array_len) { + if (cb_ctl_array && cb_ctl_array[nlh->nlmsg_type]) { + ret = cb_ctl_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + } else if (default_cb_array[nlh->nlmsg_type]) { + ret = default_cb_array[nlh->nlmsg_type](nlh, data); + if (ret <= MNL_CB_STOP) + goto out; + } + nlh = mnl_nlmsg_next(nlh, &len); + } +out: + return ret; +} + +static int mnl_cb_run2(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data, + const mnl_cb_t *cb_ctl_array, + unsigned int cb_ctl_array_len) { + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, cb_ctl_array, + cb_ctl_array_len); +} + +static int mnl_cb_run(const void *buf, size_t numbytes, unsigned int seq, + unsigned int portid, mnl_cb_t cb_data, void *data) { + return __mnl_cb_run(buf, numbytes, seq, portid, cb_data, data, NULL, 0); +} + +struct mnl_socket { + int fd; + struct sockaddr_nl addr; +}; + +static unsigned int mnl_socket_get_portid(const struct mnl_socket *nl) { + return nl->addr.nl_pid; +} + +static struct mnl_socket *__mnl_socket_open(int bus, int flags) { + struct mnl_socket *nl; + + nl = calloc(1, sizeof(struct mnl_socket)); + if (nl == NULL) + return NULL; + + nl->fd = socket(AF_NETLINK, SOCK_RAW | flags, bus); + if (nl->fd == -1) { + free(nl); + return NULL; + } + + return nl; +} + +static struct mnl_socket *mnl_socket_open(int bus) { + return __mnl_socket_open(bus, 0); +} + +static int mnl_socket_bind(struct mnl_socket *nl, unsigned int groups, + pid_t pid) { + int ret; + socklen_t addr_len; + + nl->addr.nl_family = AF_NETLINK; + nl->addr.nl_groups = groups; + nl->addr.nl_pid = pid; + + ret = bind(nl->fd, (struct sockaddr *)&nl->addr, sizeof(nl->addr)); + if (ret < 0) + return ret; + + addr_len = sizeof(nl->addr); + ret = getsockname(nl->fd, (struct sockaddr *)&nl->addr, &addr_len); + if (ret < 0) + return ret; + + if (addr_len != sizeof(nl->addr)) { + errno = EINVAL; + return -1; + } + if (nl->addr.nl_family != AF_NETLINK) { + errno = EINVAL; + return -1; + } + return 0; +} + +static ssize_t mnl_socket_sendto(const struct mnl_socket *nl, const void *buf, + size_t len) { + static const struct sockaddr_nl snl = {.nl_family = AF_NETLINK}; + return sendto(nl->fd, buf, len, 0, (struct sockaddr *)&snl, sizeof(snl)); +} + +static ssize_t mnl_socket_recvfrom(const struct mnl_socket *nl, void *buf, + size_t bufsiz) { + ssize_t ret; + struct sockaddr_nl addr; + struct iovec iov = { + .iov_base = buf, + .iov_len = bufsiz, + }; + struct msghdr msg = { + .msg_name = &addr, + .msg_namelen = sizeof(struct sockaddr_nl), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + ret = recvmsg(nl->fd, &msg, 0); + if (ret == -1) + return ret; + + if (msg.msg_flags & MSG_TRUNC) { + errno = ENOSPC; + return -1; + } + if (msg.msg_namelen != sizeof(struct sockaddr_nl)) { + errno = EINVAL; + return -1; + } + return ret; +} + +static int mnl_socket_close(struct mnl_socket *nl) { + int ret = close(nl->fd); + free(nl); + return ret; +} + +/* mnlg mini library: */ + +struct mnlg_socket { + struct mnl_socket *nl; + char *buf; + uint16_t id; + uint8_t version; + unsigned int seq; + unsigned int portid; +}; + +static struct nlmsghdr *__mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, + uint16_t flags, uint16_t id, + uint8_t version) { + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + + nlh = mnl_nlmsg_put_header(nlg->buf); + nlh->nlmsg_type = id; + nlh->nlmsg_flags = flags; + nlg->seq = time(NULL); + nlh->nlmsg_seq = nlg->seq; + + genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd = cmd; + genl->version = version; + + return nlh; +} + +static struct nlmsghdr *mnlg_msg_prepare(struct mnlg_socket *nlg, uint8_t cmd, + uint16_t flags) { + return __mnlg_msg_prepare(nlg, cmd, flags, nlg->id, nlg->version); +} + +static int mnlg_socket_send(struct mnlg_socket *nlg, + const struct nlmsghdr *nlh) { + return mnl_socket_sendto(nlg->nl, nlh, nlh->nlmsg_len); +} + +static int mnlg_cb_noop(const struct nlmsghdr *nlh, void *data) { + (void)nlh; + (void)data; + return MNL_CB_OK; +} + +static int mnlg_cb_error(const struct nlmsghdr *nlh, void *data) { + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + (void)data; + + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) { + errno = EBADMSG; + return MNL_CB_ERROR; + } + /* Netlink subsystems returns the errno value with different signess */ + if (err->error < 0) + errno = -err->error; + else + errno = err->error; + + return err->error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; +} + +static int mnlg_cb_stop(const struct nlmsghdr *nlh, void *data) { + (void)data; + if (nlh->nlmsg_flags & NLM_F_MULTI && + nlh->nlmsg_len == mnl_nlmsg_size(sizeof(int))) { + int error = *(int *)mnl_nlmsg_get_payload(nlh); + /* Netlink subsystems returns the errno value with different signess */ + if (error < 0) + errno = -error; + else + errno = error; + + return error == 0 ? MNL_CB_STOP : MNL_CB_ERROR; + } + return MNL_CB_STOP; +} + +static const mnl_cb_t mnlg_cb_array[] = { + [NLMSG_NOOP] = mnlg_cb_noop, + [NLMSG_ERROR] = mnlg_cb_error, + [NLMSG_DONE] = mnlg_cb_stop, + [NLMSG_OVERRUN] = mnlg_cb_noop, +}; + +static int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, + void *data) { + int err; + + do { + err = + mnl_socket_recvfrom(nlg->nl, nlg->buf, mnl_ideal_socket_buffer_size()); + if (err <= 0) + break; + err = mnl_cb_run2(nlg->buf, err, nlg->seq, nlg->portid, data_cb, data, + mnlg_cb_array, MNL_ARRAY_SIZE(mnlg_cb_array)); + } while (err > 0); + + return err; +} + +static int get_family_id_attr_cb(const struct nlattr *attr, void *data) { + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) + return MNL_CB_ERROR; + + if (type == CTRL_ATTR_FAMILY_ID && mnl_attr_validate(attr, MNL_TYPE_U16) < 0) + return MNL_CB_ERROR; + tb[type] = attr; + return MNL_CB_OK; +} + +static int get_family_id_cb(const struct nlmsghdr *nlh, void *data) { + uint16_t *p_id = data; + struct nlattr *tb[CTRL_ATTR_MAX + 1] = {0}; + + mnl_attr_parse(nlh, sizeof(struct genlmsghdr), get_family_id_attr_cb, tb); + if (!tb[CTRL_ATTR_FAMILY_ID]) + return MNL_CB_ERROR; + *p_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + return MNL_CB_OK; +} + +static struct mnlg_socket *mnlg_socket_open(const char *family_name, + uint8_t version) { + struct mnlg_socket *nlg; + struct nlmsghdr *nlh; + int err; + + nlg = malloc(sizeof(*nlg)); + if (!nlg) + return NULL; + nlg->id = 0; + + err = -ENOMEM; + nlg->buf = malloc(mnl_ideal_socket_buffer_size()); + if (!nlg->buf) + goto err_buf_alloc; + + nlg->nl = mnl_socket_open(NETLINK_GENERIC); + if (!nlg->nl) { + err = -errno; + goto err_mnl_socket_open; + } + + if (mnl_socket_bind(nlg->nl, 0, MNL_SOCKET_AUTOPID) < 0) { + err = -errno; + goto err_mnl_socket_bind; + } + + nlg->portid = mnl_socket_get_portid(nlg->nl); + + nlh = __mnlg_msg_prepare(nlg, CTRL_CMD_GETFAMILY, NLM_F_REQUEST | NLM_F_ACK, + GENL_ID_CTRL, 1); + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, family_name); + + if (mnlg_socket_send(nlg, nlh) < 0) { + err = -errno; + goto err_mnlg_socket_send; + } + + errno = 0; + if (mnlg_socket_recv_run(nlg, get_family_id_cb, &nlg->id) < 0) { + errno = errno == ENOENT ? EPROTONOSUPPORT : errno; + err = errno ? -errno : -ENOSYS; + goto err_mnlg_socket_recv_run; + } + + nlg->version = version; + errno = 0; + return nlg; + +err_mnlg_socket_recv_run: +err_mnlg_socket_send: +err_mnl_socket_bind: + mnl_socket_close(nlg->nl); +err_mnl_socket_open: + free(nlg->buf); +err_buf_alloc: + free(nlg); + errno = -err; + return NULL; +} + +static void mnlg_socket_close(struct mnlg_socket *nlg) { + mnl_socket_close(nlg->nl); + free(nlg->buf); + free(nlg); +} + +/* wireguard-specific parts: */ + +struct string_list { + char *buffer; + size_t len; + size_t cap; +}; + +static int string_list_add(struct string_list *list, const char *str) { + size_t len = strlen(str) + 1; + + if (len == 1) + return 0; + + if (len >= list->cap - list->len) { + char *new_buffer; + size_t new_cap = list->cap * 2; + + if (new_cap < list->len + len + 1) + new_cap = list->len + len + 1; + new_buffer = realloc(list->buffer, new_cap); + if (!new_buffer) + return -errno; + list->buffer = new_buffer; + list->cap = new_cap; + } + memcpy(list->buffer + list->len, str, len); + list->len += len; + list->buffer[list->len] = '\0'; + return 0; +} + +struct interface { + const char *name; + bool is_wireguard; +}; + +static int parse_linkinfo(const struct nlattr *attr, void *data) { + struct interface *interface = data; + + if (mnl_attr_get_type(attr) == IFLA_INFO_KIND && + !strcmp(WG_GENL_NAME, mnl_attr_get_str(attr))) + interface->is_wireguard = true; + return MNL_CB_OK; +} + +static int parse_infomsg(const struct nlattr *attr, void *data) { + struct interface *interface = data; + + if (mnl_attr_get_type(attr) == IFLA_LINKINFO) + return mnl_attr_parse_nested(attr, parse_linkinfo, data); + else if (mnl_attr_get_type(attr) == IFLA_IFNAME) + interface->name = mnl_attr_get_str(attr); + return MNL_CB_OK; +} + +static int read_devices_cb(const struct nlmsghdr *nlh, void *data) { + struct string_list *list = data; + struct interface interface = {0}; + int ret; + + ret = + mnl_attr_parse(nlh, sizeof(struct ifinfomsg), parse_infomsg, &interface); + if (ret != MNL_CB_OK) + return ret; + if (interface.name && interface.is_wireguard) + ret = string_list_add(list, interface.name); + if (ret < 0) + return ret; + if (nlh->nlmsg_type != NLMSG_DONE) + return MNL_CB_OK + 1; + return MNL_CB_OK; +} + +static int fetch_device_names(struct string_list *list) { + struct mnl_socket *nl = NULL; + char *rtnl_buffer = NULL; + size_t message_len; + unsigned int portid, seq; + ssize_t len; + int ret = 0; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + + ret = -ENOMEM; + rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1); + if (!rtnl_buffer) + goto cleanup; + + nl = mnl_socket_open(NETLINK_ROUTE); + if (!nl) { + ret = -errno; + goto cleanup; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + ret = -errno; + goto cleanup; + } + + seq = time(NULL); + portid = mnl_socket_get_portid(nl); + nlh = mnl_nlmsg_put_header(rtnl_buffer); + nlh->nlmsg_type = RTM_GETLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP; + nlh->nlmsg_seq = seq; + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + message_len = nlh->nlmsg_len; + + if (mnl_socket_sendto(nl, rtnl_buffer, message_len) < 0) { + ret = -errno; + goto cleanup; + } + +another: + if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, + mnl_ideal_socket_buffer_size())) < 0) { + ret = -errno; + goto cleanup; + } + if ((len = mnl_cb_run(rtnl_buffer, len, seq, portid, read_devices_cb, list)) < + 0) { + /* Netlink returns NLM_F_DUMP_INTR if the set of all tunnels changed + * during the dump. That's unfortunate, but is pretty common on busy + * systems that are adding and removing tunnels all the time. Rather + * than retrying, potentially indefinitely, we just work with the + * partial results. */ + if (errno != EINTR) { + ret = -errno; + goto cleanup; + } + } + if (len == MNL_CB_OK + 1) + goto another; + ret = 0; + +cleanup: + free(rtnl_buffer); + if (nl) + mnl_socket_close(nl); + return ret; +} + +static int add_del_iface(const char *ifname, bool add) { + struct mnl_socket *nl = NULL; + char *rtnl_buffer; + ssize_t len; + int ret; + struct nlmsghdr *nlh; + struct ifinfomsg *ifm; + struct nlattr *nest; + + rtnl_buffer = calloc(mnl_ideal_socket_buffer_size(), 1); + if (!rtnl_buffer) { + ret = -ENOMEM; + goto cleanup; + } + + nl = mnl_socket_open(NETLINK_ROUTE); + if (!nl) { + ret = -errno; + goto cleanup; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + ret = -errno; + goto cleanup; + } + + nlh = mnl_nlmsg_put_header(rtnl_buffer); + nlh->nlmsg_type = add ? RTM_NEWLINK : RTM_DELLINK; + nlh->nlmsg_flags = + NLM_F_REQUEST | NLM_F_ACK | (add ? NLM_F_CREATE | NLM_F_EXCL : 0); + nlh->nlmsg_seq = time(NULL); + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + mnl_attr_put_strz(nlh, IFLA_IFNAME, ifname); + nest = mnl_attr_nest_start(nlh, IFLA_LINKINFO); + mnl_attr_put_strz(nlh, IFLA_INFO_KIND, WG_GENL_NAME); + mnl_attr_nest_end(nlh, nest); + + if (mnl_socket_sendto(nl, rtnl_buffer, nlh->nlmsg_len) < 0) { + ret = -errno; + goto cleanup; + } + if ((len = mnl_socket_recvfrom(nl, rtnl_buffer, + mnl_ideal_socket_buffer_size())) < 0) { + ret = -errno; + goto cleanup; + } + if (mnl_cb_run(rtnl_buffer, len, nlh->nlmsg_seq, mnl_socket_get_portid(nl), + NULL, NULL) < 0) { + ret = -errno; + goto cleanup; + } + ret = 0; + +cleanup: + free(rtnl_buffer); + if (nl) + mnl_socket_close(nl); + return ret; +} + +int wg_set_device(wg_device *dev) { + int ret = 0; + wg_peer *peer = NULL; + wg_allowedip *allowedip = NULL; + struct nlattr *peers_nest, *peer_nest, *allowedips_nest, *allowedip_nest; + struct nlmsghdr *nlh; + struct mnlg_socket *nlg; + + nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); + if (!nlg) + return -errno; + +again: + nlh = mnlg_msg_prepare(nlg, WG_CMD_SET_DEVICE, NLM_F_REQUEST | NLM_F_ACK); + mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, dev->name); + + if (!peer) { + uint32_t flags = 0; + + if (dev->flags & WGDEVICE_HAS_PRIVATE_KEY) + mnl_attr_put(nlh, WGDEVICE_A_PRIVATE_KEY, sizeof(dev->private_key), + dev->private_key); + if (dev->flags & WGDEVICE_HAS_LISTEN_PORT) + mnl_attr_put_u16(nlh, WGDEVICE_A_LISTEN_PORT, dev->listen_port); + if (dev->flags & WGDEVICE_HAS_FWMARK) + mnl_attr_put_u32(nlh, WGDEVICE_A_FWMARK, dev->fwmark); + if (dev->flags & WGDEVICE_REPLACE_PEERS) + flags |= WGDEVICE_F_REPLACE_PEERS; + if (flags) + mnl_attr_put_u32(nlh, WGDEVICE_A_FLAGS, flags); + } + if (!dev->first_peer) + goto send; + peers_nest = peer_nest = allowedips_nest = allowedip_nest = NULL; + peers_nest = mnl_attr_nest_start(nlh, WGDEVICE_A_PEERS); + for (peer = peer ? peer : dev->first_peer; peer; peer = peer->next_peer) { + uint32_t flags = 0; + + peer_nest = + mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0); + if (!peer_nest) + goto toobig_peers; + if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), + WGPEER_A_PUBLIC_KEY, sizeof(peer->public_key), + peer->public_key)) + goto toobig_peers; + if (peer->flags & WGPEER_REMOVE_ME) + flags |= WGPEER_F_REMOVE_ME; + if (!allowedip) { + if (peer->flags & WGPEER_REPLACE_ALLOWEDIPS) + flags |= WGPEER_F_REPLACE_ALLOWEDIPS; + if (peer->flags & WGPEER_HAS_PRESHARED_KEY) { + if (!mnl_attr_put_check( + nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_PRESHARED_KEY, + sizeof(peer->preshared_key), peer->preshared_key)) + goto toobig_peers; + } + if (peer->endpoint.addr.sa_family == AF_INET) { + if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), + WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr4), + &peer->endpoint.addr4)) + goto toobig_peers; + } else if (peer->endpoint.addr.sa_family == AF_INET6) { + if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), + WGPEER_A_ENDPOINT, sizeof(peer->endpoint.addr6), + &peer->endpoint.addr6)) + goto toobig_peers; + } + if (peer->flags & WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL) { + if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), + WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, + peer->persistent_keepalive_interval)) + goto toobig_peers; + } + } + if (flags) { + if (!mnl_attr_put_u32_check(nlh, mnl_ideal_socket_buffer_size(), + WGPEER_A_FLAGS, flags)) + goto toobig_peers; + } + if (peer->first_allowedip) { + if (!allowedip) + allowedip = peer->first_allowedip; + allowedips_nest = mnl_attr_nest_start_check( + nlh, mnl_ideal_socket_buffer_size(), WGPEER_A_ALLOWEDIPS); + if (!allowedips_nest) + goto toobig_allowedips; + for (; allowedip; allowedip = allowedip->next_allowedip) { + allowedip_nest = + mnl_attr_nest_start_check(nlh, mnl_ideal_socket_buffer_size(), 0); + if (!allowedip_nest) + goto toobig_allowedips; + if (!mnl_attr_put_u16_check(nlh, mnl_ideal_socket_buffer_size(), + WGALLOWEDIP_A_FAMILY, allowedip->family)) + goto toobig_allowedips; + if (allowedip->family == AF_INET) { + if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), + WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip4), + &allowedip->ip4)) + goto toobig_allowedips; + } else if (allowedip->family == AF_INET6) { + if (!mnl_attr_put_check(nlh, mnl_ideal_socket_buffer_size(), + WGALLOWEDIP_A_IPADDR, sizeof(allowedip->ip6), + &allowedip->ip6)) + goto toobig_allowedips; + } + if (!mnl_attr_put_u8_check(nlh, mnl_ideal_socket_buffer_size(), + WGALLOWEDIP_A_CIDR_MASK, allowedip->cidr)) + goto toobig_allowedips; + mnl_attr_nest_end(nlh, allowedip_nest); + allowedip_nest = NULL; + } + mnl_attr_nest_end(nlh, allowedips_nest); + allowedips_nest = NULL; + } + + mnl_attr_nest_end(nlh, peer_nest); + peer_nest = NULL; + } + mnl_attr_nest_end(nlh, peers_nest); + peers_nest = NULL; + goto send; +toobig_allowedips: + if (allowedip_nest) + mnl_attr_nest_cancel(nlh, allowedip_nest); + if (allowedips_nest) + mnl_attr_nest_end(nlh, allowedips_nest); + mnl_attr_nest_end(nlh, peer_nest); + mnl_attr_nest_end(nlh, peers_nest); + goto send; +toobig_peers: + if (peer_nest) + mnl_attr_nest_cancel(nlh, peer_nest); + mnl_attr_nest_end(nlh, peers_nest); + goto send; +send: + if (mnlg_socket_send(nlg, nlh) < 0) { + ret = -errno; + goto out; + } + errno = 0; + if (mnlg_socket_recv_run(nlg, NULL, NULL) < 0) { + ret = errno ? -errno : -EINVAL; + goto out; + } + if (peer) + goto again; + +out: + mnlg_socket_close(nlg); + errno = -ret; + return ret; +} + +static int parse_allowedip(const struct nlattr *attr, void *data) { + wg_allowedip *allowedip = data; + + switch (mnl_attr_get_type(attr)) { + case WGALLOWEDIP_A_UNSPEC: + break; + case WGALLOWEDIP_A_FAMILY: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + allowedip->family = mnl_attr_get_u16(attr); + break; + case WGALLOWEDIP_A_IPADDR: + if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip4)) + memcpy(&allowedip->ip4, mnl_attr_get_payload(attr), + sizeof(allowedip->ip4)); + else if (mnl_attr_get_payload_len(attr) == sizeof(allowedip->ip6)) + memcpy(&allowedip->ip6, mnl_attr_get_payload(attr), + sizeof(allowedip->ip6)); + break; + case WGALLOWEDIP_A_CIDR_MASK: + if (!mnl_attr_validate(attr, MNL_TYPE_U8)) + allowedip->cidr = mnl_attr_get_u8(attr); + break; + } + + return MNL_CB_OK; +} + +static int parse_allowedips(const struct nlattr *attr, void *data) { + wg_peer *peer = data; + wg_allowedip *new_allowedip = calloc(1, sizeof(wg_allowedip)); + int ret; + + if (!new_allowedip) + return MNL_CB_ERROR; + if (!peer->first_allowedip) + peer->first_allowedip = peer->last_allowedip = new_allowedip; + else { + peer->last_allowedip->next_allowedip = new_allowedip; + peer->last_allowedip = new_allowedip; + } + ret = mnl_attr_parse_nested(attr, parse_allowedip, new_allowedip); + if (!ret) + return ret; + if (!((new_allowedip->family == AF_INET && new_allowedip->cidr <= 32) || + (new_allowedip->family == AF_INET6 && new_allowedip->cidr <= 128))) { + errno = EAFNOSUPPORT; + return MNL_CB_ERROR; + } + return MNL_CB_OK; +} + +bool wg_key_is_zero(const wg_key key) { + volatile uint8_t acc = 0; + unsigned int i; + + for (i = 0; i < sizeof(wg_key); ++i) { + acc |= key[i]; + __asm__("" : "=r"(acc) : "0"(acc)); + } + return 1 & ((acc - 1) >> 8); +} + +static int parse_peer(const struct nlattr *attr, void *data) { + wg_peer *peer = data; + + switch (mnl_attr_get_type(attr)) { + case WGPEER_A_UNSPEC: + break; + case WGPEER_A_PUBLIC_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->public_key)) { + memcpy(peer->public_key, mnl_attr_get_payload(attr), + sizeof(peer->public_key)); + peer->flags |= WGPEER_HAS_PUBLIC_KEY; + } + break; + case WGPEER_A_PRESHARED_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->preshared_key)) { + memcpy(peer->preshared_key, mnl_attr_get_payload(attr), + sizeof(peer->preshared_key)); + if (!wg_key_is_zero(peer->preshared_key)) + peer->flags |= WGPEER_HAS_PRESHARED_KEY; + } + break; + case WGPEER_A_ENDPOINT: { + struct sockaddr *addr; + + if (mnl_attr_get_payload_len(attr) < sizeof(*addr)) + break; + addr = mnl_attr_get_payload(attr); + if (addr->sa_family == AF_INET && + mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr4)) + memcpy(&peer->endpoint.addr4, addr, sizeof(peer->endpoint.addr4)); + else if (addr->sa_family == AF_INET6 && + mnl_attr_get_payload_len(attr) == sizeof(peer->endpoint.addr6)) + memcpy(&peer->endpoint.addr6, addr, sizeof(peer->endpoint.addr6)); + break; + } + case WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + peer->persistent_keepalive_interval = mnl_attr_get_u16(attr); + break; + case WGPEER_A_LAST_HANDSHAKE_TIME: + if (mnl_attr_get_payload_len(attr) == sizeof(peer->last_handshake_time)) + memcpy(&peer->last_handshake_time, mnl_attr_get_payload(attr), + sizeof(peer->last_handshake_time)); + break; + case WGPEER_A_RX_BYTES: + if (!mnl_attr_validate(attr, MNL_TYPE_U64)) + peer->rx_bytes = mnl_attr_get_u64(attr); + break; + case WGPEER_A_TX_BYTES: + if (!mnl_attr_validate(attr, MNL_TYPE_U64)) + peer->tx_bytes = mnl_attr_get_u64(attr); + break; + case WGPEER_A_ALLOWEDIPS: + return mnl_attr_parse_nested(attr, parse_allowedips, peer); + } + + return MNL_CB_OK; +} + +static int parse_peers(const struct nlattr *attr, void *data) { + wg_device *device = data; + wg_peer *new_peer = calloc(1, sizeof(wg_peer)); + int ret; + + if (!new_peer) + return MNL_CB_ERROR; + if (!device->first_peer) + device->first_peer = device->last_peer = new_peer; + else { + device->last_peer->next_peer = new_peer; + device->last_peer = new_peer; + } + ret = mnl_attr_parse_nested(attr, parse_peer, new_peer); + if (!ret) + return ret; + if (!(new_peer->flags & WGPEER_HAS_PUBLIC_KEY)) { + errno = ENXIO; + return MNL_CB_ERROR; + } + return MNL_CB_OK; +} + +static int parse_device(const struct nlattr *attr, void *data) { + wg_device *device = data; + + switch (mnl_attr_get_type(attr)) { + case WGDEVICE_A_UNSPEC: + break; + case WGDEVICE_A_IFINDEX: + if (!mnl_attr_validate(attr, MNL_TYPE_U32)) + device->ifindex = mnl_attr_get_u32(attr); + break; + case WGDEVICE_A_IFNAME: + if (!mnl_attr_validate(attr, MNL_TYPE_STRING)) { + strncpy(device->name, mnl_attr_get_str(attr), sizeof(device->name) - 1); + device->name[sizeof(device->name) - 1] = '\0'; + } + break; + case WGDEVICE_A_PRIVATE_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(device->private_key)) { + memcpy(device->private_key, mnl_attr_get_payload(attr), + sizeof(device->private_key)); + device->flags |= WGDEVICE_HAS_PRIVATE_KEY; + } + break; + case WGDEVICE_A_PUBLIC_KEY: + if (mnl_attr_get_payload_len(attr) == sizeof(device->public_key)) { + memcpy(device->public_key, mnl_attr_get_payload(attr), + sizeof(device->public_key)); + device->flags |= WGDEVICE_HAS_PUBLIC_KEY; + } + break; + case WGDEVICE_A_LISTEN_PORT: + if (!mnl_attr_validate(attr, MNL_TYPE_U16)) + device->listen_port = mnl_attr_get_u16(attr); + break; + case WGDEVICE_A_FWMARK: + if (!mnl_attr_validate(attr, MNL_TYPE_U32)) + device->fwmark = mnl_attr_get_u32(attr); + break; + case WGDEVICE_A_PEERS: + return mnl_attr_parse_nested(attr, parse_peers, device); + } + + return MNL_CB_OK; +} + +static int read_device_cb(const struct nlmsghdr *nlh, void *data) { + return mnl_attr_parse(nlh, sizeof(struct genlmsghdr), parse_device, data); +} + +static void coalesce_peers(wg_device *device) { + wg_peer *old_next_peer, *peer = device->first_peer; + + while (peer && peer->next_peer) { + if (memcmp(peer->public_key, peer->next_peer->public_key, sizeof(wg_key))) { + peer = peer->next_peer; + continue; + } + if (!peer->first_allowedip) { + peer->first_allowedip = peer->next_peer->first_allowedip; + peer->last_allowedip = peer->next_peer->last_allowedip; + } else { + peer->last_allowedip->next_allowedip = peer->next_peer->first_allowedip; + peer->last_allowedip = peer->next_peer->last_allowedip; + } + old_next_peer = peer->next_peer; + peer->next_peer = old_next_peer->next_peer; + free(old_next_peer); + } +} + +int wg_get_device(wg_device **device, const char *device_name) { + int ret = 0; + struct nlmsghdr *nlh; + struct mnlg_socket *nlg; + +try_again: + *device = calloc(1, sizeof(wg_device)); + if (!*device) + return -errno; + + nlg = mnlg_socket_open(WG_GENL_NAME, WG_GENL_VERSION); + if (!nlg) { + wg_free_device(*device); + *device = NULL; + return -errno; + } + + nlh = mnlg_msg_prepare(nlg, WG_CMD_GET_DEVICE, + NLM_F_REQUEST | NLM_F_ACK | NLM_F_DUMP); + mnl_attr_put_strz(nlh, WGDEVICE_A_IFNAME, device_name); + if (mnlg_socket_send(nlg, nlh) < 0) { + ret = -errno; + goto out; + } + errno = 0; + if (mnlg_socket_recv_run(nlg, read_device_cb, *device) < 0) { + ret = errno ? -errno : -EINVAL; + goto out; + } + coalesce_peers(*device); + +out: + if (nlg) + mnlg_socket_close(nlg); + if (ret) { + wg_free_device(*device); + if (ret == -EINTR) + goto try_again; + *device = NULL; + } + errno = -ret; + return ret; +} + +/* first\0second\0third\0forth\0last\0\0 */ +char *wg_list_device_names(void) { + struct string_list list = {0}; + int ret = fetch_device_names(&list); + + errno = -ret; + if (errno) { + free(list.buffer); + return NULL; + } + return list.buffer ?: strdup("\0"); +} + +int wg_add_device(const char *device_name) { + return add_del_iface(device_name, true); +} + +int wg_del_device(const char *device_name) { + return add_del_iface(device_name, false); +} + +void wg_free_device(wg_device *dev) { + wg_peer *peer, *np; + wg_allowedip *allowedip, *na; + + if (!dev) + return; + for (peer = dev->first_peer, np = peer ? peer->next_peer : NULL; peer; + peer = np, np = peer ? peer->next_peer : NULL) { + for (allowedip = peer->first_allowedip, + na = allowedip ? allowedip->next_allowedip : NULL; + allowedip; + allowedip = na, na = allowedip ? allowedip->next_allowedip : NULL) + free(allowedip); + free(peer); + } + free(dev); +} + +static void encode_base64(char dest[static 4], const uint8_t src[static 3]) { + const uint8_t input[] = {(src[0] >> 2) & 63, + ((src[0] << 4) | (src[1] >> 4)) & 63, + ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63}; + unsigned int i; + + for (i = 0; i < 4; ++i) + dest[i] = input[i] + 'A' + (((25 - input[i]) >> 8) & 6) - + (((51 - input[i]) >> 8) & 75) - (((61 - input[i]) >> 8) & 15) + + (((62 - input[i]) >> 8) & 3); +} + +void wg_key_to_base64(wg_key_b64_string base64, const wg_key key) { + unsigned int i; + + for (i = 0; i < 32 / 3; ++i) + encode_base64(&base64[i * 4], &key[i * 3]); + encode_base64(&base64[i * 4], + (const uint8_t[]){key[i * 3 + 0], key[i * 3 + 1], 0}); + base64[sizeof(wg_key_b64_string) - 2] = '='; + base64[sizeof(wg_key_b64_string) - 1] = '\0'; +} + +static int decode_base64(const char src[static 4]) { + int val = 0; + unsigned int i; + + for (i = 0; i < 4; ++i) + val |= + (-1 + + ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & + (src[i] - 64)) + + ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & + (src[i] - 70)) + + ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5)) + + ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63) + + ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)) + << (18 - 6 * i); + return val; +} + +int wg_key_from_base64(wg_key key, const wg_key_b64_string base64) { + unsigned int i; + int val; + volatile uint8_t ret = 0; + + if (strlen(base64) != sizeof(wg_key_b64_string) - 1 || + base64[sizeof(wg_key_b64_string) - 2] != '=') { + errno = EINVAL; + goto out; + } + + for (i = 0; i < 32 / 3; ++i) { + val = decode_base64(&base64[i * 4]); + ret |= (uint32_t)val >> 31; + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + key[i * 3 + 2] = val & 0xff; + } + val = decode_base64((const char[]){base64[i * 4 + 0], base64[i * 4 + 1], + base64[i * 4 + 2], 'A'}); + ret |= ((uint32_t)val >> 31) | (val & 0xff); + key[i * 3 + 0] = (val >> 16) & 0xff; + key[i * 3 + 1] = (val >> 8) & 0xff; + errno = EINVAL & ~((ret - 1) >> 8); +out: + return -errno; +} + +typedef int64_t fe[16]; + +static __attribute__((noinline)) void memzero_explicit(void *s, size_t count) { + memset(s, 0, count); + __asm__ __volatile__("" : : "r"(s) : "memory"); +} + +static void carry(fe o) { + int i; + + for (i = 0; i < 16; ++i) { + o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16); + o[i] &= 0xffff; + } +} + +static void cswap(fe p, fe q, int b) { + int i; + int64_t t, c = ~(b - 1); + + for (i = 0; i < 16; ++i) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + + memzero_explicit(&t, sizeof(t)); + memzero_explicit(&c, sizeof(c)); + memzero_explicit(&b, sizeof(b)); +} + +static void pack(uint8_t *o, const fe n) { + int i, j, b; + fe m, t; + + memcpy(t, n, sizeof(t)); + carry(t); + carry(t); + carry(t); + for (j = 0; j < 2; ++j) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; ++i) { + m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1); + m[i - 1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1); + b = (m[15] >> 16) & 1; + m[14] &= 0xffff; + cswap(t, m, 1 - b); + } + for (i = 0; i < 16; ++i) { + o[2 * i] = t[i] & 0xff; + o[2 * i + 1] = t[i] >> 8; + } + + memzero_explicit(m, sizeof(m)); + memzero_explicit(t, sizeof(t)); + memzero_explicit(&b, sizeof(b)); +} + +static void add(fe o, const fe a, const fe b) { + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] + b[i]; +} + +static void subtract(fe o, const fe a, const fe b) { + int i; + + for (i = 0; i < 16; ++i) + o[i] = a[i] - b[i]; +} + +static void multmod(fe o, const fe a, const fe b) { + int i, j; + int64_t t[31] = {0}; + + for (i = 0; i < 16; ++i) { + for (j = 0; j < 16; ++j) + t[i + j] += a[i] * b[j]; + } + for (i = 0; i < 15; ++i) + t[i] += 38 * t[i + 16]; + memcpy(o, t, sizeof(fe)); + carry(o); + carry(o); + + memzero_explicit(t, sizeof(t)); +} + +static void invert(fe o, const fe i) { + fe c; + int a; + + memcpy(c, i, sizeof(c)); + for (a = 253; a >= 0; --a) { + multmod(c, c, c); + if (a != 2 && a != 4) + multmod(c, c, i); + } + memcpy(o, c, sizeof(fe)); + + memzero_explicit(c, sizeof(c)); +} + +static void clamp_key(uint8_t *z) { + z[31] = (z[31] & 127) | 64; + z[0] &= 248; +} + +void wg_generate_public_key(wg_key public_key, const wg_key private_key) { + int i, r; + uint8_t z[32]; + fe a = {1}, b = {9}, c = {0}, d = {1}, e, f; + + memcpy(z, private_key, sizeof(z)); + clamp_key(z); + + for (i = 254; i >= 0; --i) { + r = (z[i >> 3] >> (i & 7)) & 1; + cswap(a, b, r); + cswap(c, d, r); + add(e, a, c); + subtract(a, a, c); + add(c, b, d); + subtract(b, b, d); + multmod(d, e, e); + multmod(f, a, a); + multmod(a, c, a); + multmod(c, b, e); + add(e, a, c); + subtract(a, a, c); + multmod(b, a, a); + subtract(c, d, f); + multmod(a, c, (const fe){0xdb41, 1}); + add(a, a, d); + multmod(c, c, a); + multmod(a, d, f); + multmod(d, b, (const fe){9}); + multmod(b, e, e); + cswap(a, b, r); + cswap(c, d, r); + } + invert(c, c); + multmod(a, a, c); + pack(public_key, a); + + memzero_explicit(&r, sizeof(r)); + memzero_explicit(z, sizeof(z)); + memzero_explicit(a, sizeof(a)); + memzero_explicit(b, sizeof(b)); + memzero_explicit(c, sizeof(c)); + memzero_explicit(d, sizeof(d)); + memzero_explicit(e, sizeof(e)); + memzero_explicit(f, sizeof(f)); +} + +void wg_generate_private_key(wg_key private_key) { + wg_generate_preshared_key(private_key); + clamp_key(private_key); +} + +void wg_generate_preshared_key(wg_key preshared_key) { + ssize_t ret; + size_t i; + int fd; +#if defined(__OpenBSD__) || \ + (defined(__APPLE__) && \ + MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_12) || \ + (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25))) + if (!getentropy(preshared_key, sizeof(wg_key))) + return; +#endif +#if defined(__NR_getrandom) && defined(__linux__) + if (syscall(__NR_getrandom, preshared_key, sizeof(wg_key), 0) == + sizeof(wg_key)) + return; +#endif + fd = open("/dev/urandom", O_RDONLY); + assert(fd >= 0); + for (i = 0; i < sizeof(wg_key); i += ret) { + ret = read(fd, preshared_key + i, sizeof(wg_key) - i); + assert(ret > 0); + } + close(fd); +} +wg_allowedip *wg_allowed_ip_new_ip4(in_addr_t ip4, uint8_t cidr) { + wg_allowedip *allow = malloc(sizeof(wg_allowedip)); + allow->next_allowedip = NULL; + allow->ip4.s_addr = ip4; + allow->cidr = cidr; + allow->family = AF_INET; + return allow; +} + +int wg_key_equal(wg_key *k1, wg_key *k2) { + return memcmp(*k1, *k2, sizeof(wg_key)); +} diff --git a/internal/wg/wireguard.h b/internal/wg/wireguard.h new file mode 100644 index 0000000..3bb1705 --- /dev/null +++ b/internal/wg/wireguard.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +/* + * Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights + * Reserved. + */ + +#ifndef WIREGUARD_H +#define WIREGUARD_H + +#include +#include +#include +#include +#include +#include +#include + +typedef uint8_t wg_key[32]; +typedef char wg_key_b64_string[((sizeof(wg_key) + 2) / 3) * 4 + 1]; + +/* Cross platform __kernel_timespec */ +struct timespec64 { + int64_t tv_sec; + int64_t tv_nsec; +}; + +typedef struct wg_allowedip { + uint16_t family; + union { + struct in_addr ip4; + struct in6_addr ip6; + }; + uint8_t cidr; + struct wg_allowedip *next_allowedip; +} wg_allowedip; + +wg_allowedip *wg_allowed_ip_new_ip4(in_addr_t ip4, uint8_t cidr); + +enum wg_peer_flags { + WGPEER_REMOVE_ME = 1U << 0, + WGPEER_REPLACE_ALLOWEDIPS = 1U << 1, + WGPEER_HAS_PUBLIC_KEY = 1U << 2, + WGPEER_HAS_PRESHARED_KEY = 1U << 3, + WGPEER_HAS_PERSISTENT_KEEPALIVE_INTERVAL = 1U << 4 +}; + +typedef union wg_endpoint { + struct sockaddr addr; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; +} wg_endpoint; + +typedef struct wg_peer { + enum wg_peer_flags flags; + + wg_key public_key; + wg_key preshared_key; + + wg_endpoint endpoint; + + struct timespec64 last_handshake_time; + uint64_t rx_bytes, tx_bytes; + uint16_t persistent_keepalive_interval; + + struct wg_allowedip *first_allowedip, *last_allowedip; + struct wg_peer *next_peer; +} wg_peer; + +enum wg_device_flags { + WGDEVICE_REPLACE_PEERS = 1U << 0, + WGDEVICE_HAS_PRIVATE_KEY = 1U << 1, + WGDEVICE_HAS_PUBLIC_KEY = 1U << 2, + WGDEVICE_HAS_LISTEN_PORT = 1U << 3, + WGDEVICE_HAS_FWMARK = 1U << 4 +}; + +typedef struct wg_device { + char name[IFNAMSIZ]; + uint32_t ifindex; + + enum wg_device_flags flags; + + wg_key public_key; + wg_key private_key; + + uint32_t fwmark; + uint16_t listen_port; + + struct wg_peer *first_peer, *last_peer; +} wg_device; + +#define wg_for_each_device_name(__names, __name, __len) \ + for ((__name) = (__names), (__len) = 0; ((__len) = strlen(__name)); \ + (__name) += (__len) + 1) +#define wg_for_each_peer(__dev, __peer) \ + for ((__peer) = (__dev)->first_peer; (__peer); (__peer) = (__peer)->next_peer) +#define wg_for_each_allowedip(__peer, __allowedip) \ + for ((__allowedip) = (__peer)->first_allowedip; (__allowedip); \ + (__allowedip) = (__allowedip)->next_allowedip) + +int wg_set_device(wg_device *dev); +int wg_get_device(wg_device **dev, const char *device_name); +int wg_add_device(const char *device_name); +int wg_del_device(const char *device_name); +void wg_free_device(wg_device *dev); +char *wg_list_device_names(void); /* first\0second\0third\0forth\0last\0\0 */ +void wg_key_to_base64(wg_key_b64_string base64, const wg_key key); +int wg_key_from_base64(wg_key key, const wg_key_b64_string base64); +bool wg_key_is_zero(const wg_key key); +void wg_generate_public_key(wg_key public_key, const wg_key private_key); +void wg_generate_private_key(wg_key private_key); +void wg_generate_preshared_key(wg_key preshared_key); +int wg_key_equal(wg_key *k1, wg_key *k2); + +#endif diff --git a/justfile b/justfile new file mode 100644 index 0000000..c139568 --- /dev/null +++ b/justfile @@ -0,0 +1,13 @@ +genproto: + protoc --proto_path=proto --go_out=proto/out --go_opt=paths=source_relative vpn.proto +build: + go build -o srv ./cmd/main.go +run: build clif + sudo ./srv +clif: + sudo ip link delete wg0 || echo "no wg0" +fzuser: + #!/usr/bin/env bash + cred=$(curlie POST localhost:8080/api/auth username=gggg password=gggggggg | jq -r ".token") + echo "bearer ${cred}" + curlie POST localhost:8080/api/vpn/peer "Authorization:Bearer $cred" name="test" diff --git a/proto/out/vpn.pb.go b/proto/out/vpn.pb.go new file mode 100644 index 0000000..ece3468 --- /dev/null +++ b/proto/out/vpn.pb.go @@ -0,0 +1,452 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// protoc v6.33.0 +// source: vpn.proto + +package out + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + unsafe "unsafe" +) + +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 PeerStatus struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Pubkey *string `protobuf:"bytes,1,opt,name=pubkey"` + xxx_hidden_Ipv4 *string `protobuf:"bytes,2,opt,name=ipv4"` + xxx_hidden_Enabled bool `protobuf:"varint,3,opt,name=enabled"` + xxx_hidden_Nick *string `protobuf:"bytes,4,opt,name=nick"` + xxx_hidden_Beacon int64 `protobuf:"varint,5,opt,name=beacon"` + XXX_raceDetectHookData protoimpl.RaceDetectHookData + XXX_presence [1]uint32 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PeerStatus) Reset() { + *x = PeerStatus{} + mi := &file_vpn_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PeerStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerStatus) ProtoMessage() {} + +func (x *PeerStatus) ProtoReflect() protoreflect.Message { + mi := &file_vpn_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) +} + +func (x *PeerStatus) GetPubkey() string { + if x != nil { + if x.xxx_hidden_Pubkey != nil { + return *x.xxx_hidden_Pubkey + } + return "" + } + return "" +} + +func (x *PeerStatus) GetIpv4() string { + if x != nil { + if x.xxx_hidden_Ipv4 != nil { + return *x.xxx_hidden_Ipv4 + } + return "" + } + return "" +} + +func (x *PeerStatus) GetEnabled() bool { + if x != nil { + return x.xxx_hidden_Enabled + } + return false +} + +func (x *PeerStatus) GetNick() string { + if x != nil { + if x.xxx_hidden_Nick != nil { + return *x.xxx_hidden_Nick + } + return "" + } + return "" +} + +func (x *PeerStatus) GetBeacon() int64 { + if x != nil { + return x.xxx_hidden_Beacon + } + return 0 +} + +func (x *PeerStatus) SetPubkey(v string) { + x.xxx_hidden_Pubkey = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 5) +} + +func (x *PeerStatus) SetIpv4(v string) { + x.xxx_hidden_Ipv4 = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 5) +} + +func (x *PeerStatus) SetEnabled(v bool) { + x.xxx_hidden_Enabled = v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 5) +} + +func (x *PeerStatus) SetNick(v string) { + x.xxx_hidden_Nick = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 3, 5) +} + +func (x *PeerStatus) SetBeacon(v int64) { + x.xxx_hidden_Beacon = v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 4, 5) +} + +func (x *PeerStatus) HasPubkey() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 0) +} + +func (x *PeerStatus) HasIpv4() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 1) +} + +func (x *PeerStatus) HasEnabled() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 2) +} + +func (x *PeerStatus) HasNick() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 3) +} + +func (x *PeerStatus) HasBeacon() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 4) +} + +func (x *PeerStatus) ClearPubkey() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 0) + x.xxx_hidden_Pubkey = nil +} + +func (x *PeerStatus) ClearIpv4() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 1) + x.xxx_hidden_Ipv4 = nil +} + +func (x *PeerStatus) ClearEnabled() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 2) + x.xxx_hidden_Enabled = false +} + +func (x *PeerStatus) ClearNick() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 3) + x.xxx_hidden_Nick = nil +} + +func (x *PeerStatus) ClearBeacon() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 4) + x.xxx_hidden_Beacon = 0 +} + +type PeerStatus_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Pubkey *string + Ipv4 *string + Enabled *bool + Nick *string + Beacon *int64 +} + +func (b0 PeerStatus_builder) Build() *PeerStatus { + m0 := &PeerStatus{} + b, x := &b0, m0 + _, _ = b, x + if b.Pubkey != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 5) + x.xxx_hidden_Pubkey = b.Pubkey + } + if b.Ipv4 != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 5) + x.xxx_hidden_Ipv4 = b.Ipv4 + } + if b.Enabled != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 5) + x.xxx_hidden_Enabled = *b.Enabled + } + if b.Nick != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 3, 5) + x.xxx_hidden_Nick = b.Nick + } + if b.Beacon != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 4, 5) + x.xxx_hidden_Beacon = *b.Beacon + } + return m0 +} + +type PeerResp struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Peers *[]*PeerStatus `protobuf:"bytes,1,rep,name=peers"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PeerResp) Reset() { + *x = PeerResp{} + mi := &file_vpn_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PeerResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerResp) ProtoMessage() {} + +func (x *PeerResp) ProtoReflect() protoreflect.Message { + mi := &file_vpn_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) +} + +func (x *PeerResp) GetPeers() []*PeerStatus { + if x != nil { + if x.xxx_hidden_Peers != nil { + return *x.xxx_hidden_Peers + } + } + return nil +} + +func (x *PeerResp) SetPeers(v []*PeerStatus) { + x.xxx_hidden_Peers = &v +} + +type PeerResp_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Peers []*PeerStatus +} + +func (b0 PeerResp_builder) Build() *PeerResp { + m0 := &PeerResp{} + b, x := &b0, m0 + _, _ = b, x + x.xxx_hidden_Peers = &b.Peers + return m0 +} + +type PeerReq struct { + state protoimpl.MessageState `protogen:"opaque.v1"` + xxx_hidden_Name *string `protobuf:"bytes,1,req,name=name"` + xxx_hidden_Ip4 *string `protobuf:"bytes,2,opt,name=ip4"` + XXX_raceDetectHookData protoimpl.RaceDetectHookData + XXX_presence [1]uint32 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PeerReq) Reset() { + *x = PeerReq{} + mi := &file_vpn_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PeerReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PeerReq) ProtoMessage() {} + +func (x *PeerReq) ProtoReflect() protoreflect.Message { + mi := &file_vpn_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +func (x *PeerReq) GetName() string { + if x != nil { + if x.xxx_hidden_Name != nil { + return *x.xxx_hidden_Name + } + return "" + } + return "" +} + +func (x *PeerReq) GetIp4() string { + if x != nil { + if x.xxx_hidden_Ip4 != nil { + return *x.xxx_hidden_Ip4 + } + return "" + } + return "" +} + +func (x *PeerReq) SetName(v string) { + x.xxx_hidden_Name = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 2) +} + +func (x *PeerReq) SetIp4(v string) { + x.xxx_hidden_Ip4 = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 2) +} + +func (x *PeerReq) HasName() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 0) +} + +func (x *PeerReq) HasIp4() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 1) +} + +func (x *PeerReq) ClearName() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 0) + x.xxx_hidden_Name = nil +} + +func (x *PeerReq) ClearIp4() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 1) + x.xxx_hidden_Ip4 = nil +} + +type PeerReq_builder struct { + _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. + + Name *string + Ip4 *string +} + +func (b0 PeerReq_builder) Build() *PeerReq { + m0 := &PeerReq{} + b, x := &b0, m0 + _, _ = b, x + if b.Name != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 2) + x.xxx_hidden_Name = b.Name + } + if b.Ip4 != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 2) + x.xxx_hidden_Ip4 = b.Ip4 + } + return m0 +} + +var File_vpn_proto protoreflect.FileDescriptor + +const file_vpn_proto_rawDesc = "" + + "\n" + + "\tvpn.proto\x12\bprotovpn\"~\n" + + "\n" + + "PeerStatus\x12\x16\n" + + "\x06pubkey\x18\x01 \x01(\tR\x06pubkey\x12\x12\n" + + "\x04ipv4\x18\x02 \x01(\tR\x04ipv4\x12\x18\n" + + "\aenabled\x18\x03 \x01(\bR\aenabled\x12\x12\n" + + "\x04nick\x18\x04 \x01(\tR\x04nick\x12\x16\n" + + "\x06beacon\x18\x05 \x01(\x03R\x06beacon\"6\n" + + "\bPeerResp\x12*\n" + + "\x05peers\x18\x01 \x03(\v2\x14.protovpn.PeerStatusR\x05peers\"=\n" + + "\aPeerReq\x12\x19\n" + + "\x04name\x18\x01 \x01(\tB\x05\xaa\x01\x02\b\x03R\x04name\x12\x17\n" + + "\x03ip4\x18\x02 \x01(\tB\x05\xaa\x01\x02\b\x01R\x03ip4B!Z\x1fgit.jasinco.work/wgcl/proto/outb\beditionsp\xe9\a" + +var file_vpn_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_vpn_proto_goTypes = []any{ + (*PeerStatus)(nil), // 0: protovpn.PeerStatus + (*PeerResp)(nil), // 1: protovpn.PeerResp + (*PeerReq)(nil), // 2: protovpn.PeerReq +} +var file_vpn_proto_depIdxs = []int32{ + 0, // 0: protovpn.PeerResp.peers:type_name -> protovpn.PeerStatus + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_vpn_proto_init() } +func file_vpn_proto_init() { + if File_vpn_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_vpn_proto_rawDesc), len(file_vpn_proto_rawDesc)), + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_vpn_proto_goTypes, + DependencyIndexes: file_vpn_proto_depIdxs, + MessageInfos: file_vpn_proto_msgTypes, + }.Build() + File_vpn_proto = out.File + file_vpn_proto_goTypes = nil + file_vpn_proto_depIdxs = nil +} diff --git a/proto/vpn.proto b/proto/vpn.proto new file mode 100644 index 0000000..d1a5980 --- /dev/null +++ b/proto/vpn.proto @@ -0,0 +1,19 @@ +edition = "2024"; +package protovpn; +option go_package = "git.jasinco.work/wgcl/proto/out"; + +message PeerStatus { + string pubkey = 1; + string ipv4 = 2; + bool enabled = 3; + string nick = 4; + int64 beacon = 5; +} + +message PeerResp { + repeated PeerStatus peers = 1; +} +message PeerReq { + string name = 1 [features.field_presence = LEGACY_REQUIRED]; + string ip4 = 2 [features.field_presence = EXPLICIT]; +}