feat: Add, Check, Get, and Delete peers
This commit is contained in:
parent
4a1039e5b1
commit
eb97d49d1f
@ -6,6 +6,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
@ -58,8 +59,9 @@ func run(ctx *cli.Context) {
|
||||
slog.Debug("Starting run()")
|
||||
|
||||
apiMux := http.NewServeMux()
|
||||
apiMux.HandleFunc("GET /peer", hvpnnode3.HandleGetPeer(wgLink))
|
||||
apiMux.HandleFunc("POST /peer", hvpnnode3.HandlePostPeer(IPPool, wgLink))
|
||||
apiMux.HandleFunc("GET /peer/{pubkey}", hvpnnode3.HandleGetPeer(wgLink))
|
||||
apiMux.HandleFunc("POST /peer", hvpnnode3.HandlePostPeer(wgLink))
|
||||
apiMux.HandleFunc("DELETE /peer/{pubkey}", hvpnnode3.HandleDeletePeer(wgLink))
|
||||
apiMux.HandleFunc("GET /peers", hvpnnode3.HandleGetPeers(wgLink))
|
||||
|
||||
var handler http.Handler = apiMux
|
||||
@ -262,6 +264,7 @@ func setup() error {
|
||||
slog.Debug("main.testVip: Test IP Freed")
|
||||
|
||||
IPPool = ipPool
|
||||
wgLink.IPPool = ipPool
|
||||
|
||||
//defer wgLink.Close()
|
||||
c := make(chan os.Signal, 1)
|
||||
@ -289,7 +292,10 @@ func testWgPeerAdd(wgLink *hvpnnode3.WGLink) error {
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
|
||||
ip, err := IPPool.Allocate()
|
||||
urlsafe := url.QueryEscape(publicKey.String())
|
||||
slog.Debug(urlsafe)
|
||||
|
||||
ip, err := wgLink.Allocate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -323,7 +329,7 @@ func testWgPeerAdd(wgLink *hvpnnode3.WGLink) error {
|
||||
return err
|
||||
}
|
||||
slog.Debug("Removed test peer")
|
||||
IPPool.Free(ip)
|
||||
wgLink.Free(ip)
|
||||
slog.Debug("Freed test peer ip")
|
||||
|
||||
|
||||
|
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/felixge/httpsnoop v1.0.4
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/urfave/cli/v2 v2.27.1
|
||||
github.com/vishvananda/netlink v1.1.0
|
||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||
|
2
go.sum
2
go.sum
@ -4,6 +4,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
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/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw=
|
||||
|
165
handlers.go
165
handlers.go
@ -1,29 +1,51 @@
|
||||
package hvpnnode3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/felixge/httpsnoop"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"gitea.hbanafa.com/HeshamTB/hvpn-node3/proto"
|
||||
)
|
||||
|
||||
type CtxKey string
|
||||
const CtxReqID CtxKey = "request_id"
|
||||
|
||||
func HandleGetPeer(wgLink *WGLink) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
slog.Info("GET Peer is not implemented")
|
||||
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
||||
pubkey := r.PathValue("pubkey")
|
||||
debug(pubkey, reqID)
|
||||
peer, err := wgLink.GetPeer(pubkey)
|
||||
if err != nil {
|
||||
if errors.Is(err, proto.PeerDoesNotExist){
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Peer does not exist"},
|
||||
)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(proto.WgPeerToPeer(*peer))
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func HandleGetPeers(wgLink *WGLink) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
||||
debug("GET Peers for", reqID)
|
||||
dev, err := wgLink.Device(wgLink.Name)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@ -32,13 +54,7 @@ func HandleGetPeers(wgLink *WGLink) http.HandlerFunc {
|
||||
}
|
||||
var peers []proto.Peer
|
||||
for _, peer := range dev.Peers {
|
||||
p := proto.Peer{
|
||||
Address: peer.AllowedIPs[0].IP,
|
||||
PublicKey: peer.PublicKey.String(),
|
||||
PersistentKeepalive: peer.PersistentKeepaliveInterval,
|
||||
TX: peer.TransmitBytes,
|
||||
RX: peer.ReceiveBytes,
|
||||
}
|
||||
p := proto.WgPeerToPeer(peer)
|
||||
peers = append(peers, p)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(peers)
|
||||
@ -48,8 +64,11 @@ func HandleGetPeers(wgLink *WGLink) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
}
|
||||
func HandlePostPeer(IPPool IPPool, wgLink *WGLink) http.HandlerFunc {
|
||||
|
||||
func HandlePostPeer(wgLink *WGLink) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r* http.Request) {
|
||||
reqID := r.Context().Value(CtxReqID).(uuid.UUID)
|
||||
debug("POST Peer for", reqID)
|
||||
peerRequest := proto.CreatePeerRequest{}
|
||||
err := json.NewDecoder(r.Body).Decode(&peerRequest)
|
||||
if err != nil {
|
||||
@ -59,79 +78,105 @@ func HandlePostPeer(IPPool IPPool, wgLink *WGLink) http.HandlerFunc {
|
||||
)
|
||||
return
|
||||
}
|
||||
// TODO: Check if pubkey is already registered
|
||||
dev, err := wgLink.Device(wgLink.Name)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
if valid := ValidKey(peerRequest.PublicKey); !valid {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Invalid public key"},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
for _, peer := range dev.Peers {
|
||||
if peer.PublicKey.String() == peerRequest.PublicKey {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
exists, err := wgLink.Exists(peerRequest.PublicKey)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Error while checking peer existance"},
|
||||
)
|
||||
return
|
||||
}
|
||||
if exists {
|
||||
debugf("Peer %s already exists", reqID, peerRequest.PublicKey)
|
||||
w.WriteHeader(http.StatusFound)
|
||||
peer, err := wgLink.GetPeer(peerRequest.PublicKey)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newIP, err := IPPool.Allocate()
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.WgPeerToPeer(*peer),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Add peer to wg device
|
||||
// TODO: Declare subnet mask for the service
|
||||
pkey, _ := wgtypes.GeneratePrivateKey()
|
||||
newPeer := wgtypes.PeerConfig{
|
||||
PublicKey: pkey.PublicKey(),
|
||||
ReplaceAllowedIPs: true,
|
||||
AllowedIPs: []net.IPNet{
|
||||
{
|
||||
IP: newIP,
|
||||
Mask: net.IPv4Mask(255, 255, 255, 255),
|
||||
},
|
||||
},
|
||||
}
|
||||
err = wgLink.ConfigureDevice(
|
||||
wgLink.Name,
|
||||
wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{
|
||||
newPeer,
|
||||
},
|
||||
ReplacePeers: false,
|
||||
},
|
||||
)
|
||||
peer, err := wgLink.AddPeer(peerRequest.PublicKey)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Error while adding peer"},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
debugf("Allocated IP: %s", reqID, peer.AllowedIPs[0].IP.String())
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(
|
||||
&proto.Peer{
|
||||
Address: newIP,
|
||||
MTU: 1384,
|
||||
PublicKey: peerRequest.PublicKey,
|
||||
Endpoint: "vpn.test.com:8487",
|
||||
AllowedIPs: net.ParseIP("0.0.0.0/0"),
|
||||
PersistentKeepalive: 25,
|
||||
},
|
||||
proto.WgPeerToPeer(*peer),
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func HandleDeletePeer(wg *WGLink) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
reqId := r.Context().Value(CtxReqID).(uuid.UUID)
|
||||
pubkey := r.PathValue("pubkey")
|
||||
debugf("DELETE Peer %s", reqId, pubkey)
|
||||
exists, err := wg.Exists(pubkey)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
slog.Error(err.Error())
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Peer does not exist"},
|
||||
)
|
||||
return
|
||||
}
|
||||
err = wg.DeletePeer(pubkey)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(
|
||||
proto.ErrJSONResponse{Message: "Error while deleting peer"},
|
||||
)
|
||||
slog.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HttpLogHandler2(h http.Handler) http.Handler {
|
||||
// https://blog.kowalczyk.info/article/e00e89c3841e4f8c8c769a78b8a90b47/logging-http-requests-in-go.html
|
||||
fn := func(w http.ResponseWriter, r* http.Request) {
|
||||
reqID, _ := uuid.NewRandom()
|
||||
rCtx := context.WithValue(r.Context(), CtxReqID, reqID)
|
||||
r = r.WithContext(rCtx)
|
||||
slog.Info(fmt.Sprintf("Starting request with ID: %s", reqID.String()))
|
||||
m := httpsnoop.CaptureMetrics(h, w, r)
|
||||
msg := fmt.Sprintf(
|
||||
"%s %s %s %d %s", r.RemoteAddr, r.Method,
|
||||
r.URL.String(),m.Code, m.Duration)
|
||||
"[HTTP] %s %s %s %d %s %s", r.RemoteAddr, r.Method,
|
||||
r.URL.String(),m.Code, m.Duration, reqID.String())
|
||||
slog.Info(msg)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func debugf(format string, reqID uuid.UUID, args ...any) {
|
||||
format = format + " " + reqID.String()
|
||||
slog.Debug(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func debug(msg string, reqID uuid.UUID) {
|
||||
msg = msg + " " + reqID.String()
|
||||
debugf("%s", reqID, msg)
|
||||
}
|
||||
|
138
link.go
138
link.go
@ -2,15 +2,26 @@ package hvpnnode3
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.hbanafa.com/HeshamTB/hvpn-node3/proto"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.zx2c4.com/wireguard/wgctrl"
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
var SINGLE_IP_MASK net.IPMask = net.IPv4Mask(255, 255, 255, 255)
|
||||
var PEER_KEEP_ALIVE_INTERVAL time.Duration = time.Second * 25
|
||||
|
||||
type WGLink struct {
|
||||
*netlink.LinkAttrs
|
||||
*wgctrl.Client
|
||||
IPPool
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
// Retruns an existing or create a WGLink
|
||||
@ -48,6 +59,8 @@ func InitWGLink(ifName string, privateKey *wgtypes.Key, port int) (*WGLink, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wg.lock = &sync.Mutex{}
|
||||
|
||||
return &wg, netlink.LinkSetUp(&wg)
|
||||
}
|
||||
|
||||
@ -72,6 +85,131 @@ func (wg *WGLink) initClient() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Adds a peer to the wireguard netlink.
|
||||
func (wg *WGLink) AddPeer(publicKey string) (*wgtypes.Peer, error) {
|
||||
slog.Debug(fmt.Sprintf("Trying to add peer %s", publicKey))
|
||||
pubKey, err := wgtypes.ParseKey(publicKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
exists, err := wg.exists(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exists {
|
||||
return nil, proto.PeerExistsErr{PublicKey: publicKey}
|
||||
}
|
||||
|
||||
peerIp, err := wg.Allocate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerConfig := wgtypes.PeerConfig{
|
||||
PublicKey: pubKey,
|
||||
AllowedIPs: []net.IPNet{
|
||||
{
|
||||
IP: peerIp,
|
||||
Mask: SINGLE_IP_MASK,
|
||||
},
|
||||
},
|
||||
PersistentKeepaliveInterval: &PEER_KEEP_ALIVE_INTERVAL,
|
||||
ReplaceAllowedIPs: true,
|
||||
}
|
||||
|
||||
wgConf := wgtypes.Config{
|
||||
ReplacePeers: false,
|
||||
Peers: []wgtypes.PeerConfig{peerConfig},
|
||||
}
|
||||
|
||||
err = wg.ApplyConfig(wgConf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wg.getPeer(pubKey)
|
||||
}
|
||||
|
||||
func (wg *WGLink) DeletePeer(publickey string) error {
|
||||
pkey, err := wgtypes.ParseKey(publickey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return wg.deletePeer(pkey)
|
||||
}
|
||||
|
||||
func (wg *WGLink) deletePeer(publickey wgtypes.Key) error {
|
||||
rmCfg := createARemovePeerCfg(publickey)
|
||||
return wg.ApplyConfig(rmCfg)
|
||||
}
|
||||
|
||||
func (wg *WGLink) Exists(publicKey string) (bool, error) {
|
||||
pubkey, err := wgtypes.ParseKey(publicKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return wg.exists(pubkey)
|
||||
}
|
||||
|
||||
func (wg *WGLink) exists(pubkey wgtypes.Key) (bool, error) {
|
||||
|
||||
dev, err := wg.Client.Device(wg.Name)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, peer := range dev.Peers {
|
||||
if peer.PublicKey == pubkey {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
|
||||
}
|
||||
|
||||
// Calls wgctrl.ConfigureDevice wrapped with a Mutex to ensure sync
|
||||
func (wg *WGLink) ApplyConfig(cfg wgtypes.Config) error {
|
||||
wg.lock.Lock()
|
||||
defer wg.lock.Unlock()
|
||||
return wg.ConfigureDevice(wg.Name, cfg)
|
||||
}
|
||||
|
||||
func (wg *WGLink) GetPeer(publickey string) (*wgtypes.Peer, error) {
|
||||
k, err := wgtypes.ParseKey(publickey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wg.getPeer(k)
|
||||
}
|
||||
|
||||
func (wg *WGLink) getPeer(pubkey wgtypes.Key) (*wgtypes.Peer, error) {
|
||||
dev, err := wg.Device(wg.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, peer := range dev.Peers {
|
||||
if peer.PublicKey == pubkey {
|
||||
return &peer, nil
|
||||
}
|
||||
}
|
||||
return nil, proto.PeerDoesNotExist
|
||||
}
|
||||
|
||||
func createARemovePeerCfg(publickey wgtypes.Key) wgtypes.Config {
|
||||
rmPeerCfg := wgtypes.PeerConfig{
|
||||
Remove: true,
|
||||
PublicKey: publickey,
|
||||
}
|
||||
return wgtypes.Config{
|
||||
Peers: []wgtypes.PeerConfig{rmPeerCfg},
|
||||
ReplacePeers: false,
|
||||
}
|
||||
}
|
||||
|
||||
func ValidKey(key string) bool {
|
||||
_, err := wgtypes.ParseKey(key)
|
||||
if err != nil { return false }
|
||||
return true
|
||||
}
|
||||
|
20
proto/conv.go
Normal file
20
proto/conv.go
Normal file
@ -0,0 +1,20 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
|
||||
)
|
||||
|
||||
func WgPeerToPeer(peer wgtypes.Peer) Peer {
|
||||
|
||||
return Peer{
|
||||
PublicKey: peer.PublicKey.String(),
|
||||
PublicKeyUrlSafe: url.QueryEscape(peer.PublicKey.String()),
|
||||
MTU: 1380,
|
||||
TX: peer.TransmitBytes,
|
||||
RX: peer.ReceiveBytes,
|
||||
AllowedIPs: peer.AllowedIPs[0].IP,
|
||||
PersistentKeepalive: peer.PersistentKeepaliveInterval,
|
||||
}
|
||||
}
|
@ -1,5 +1,18 @@
|
||||
package proto
|
||||
|
||||
import "errors"
|
||||
|
||||
type ErrJSONResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type PeerExistsErr struct {
|
||||
PublicKey string
|
||||
}
|
||||
|
||||
func (e PeerExistsErr) Error() string {
|
||||
return e.PublicKey
|
||||
}
|
||||
|
||||
var PeerDoesNotExist = errors.New("Peer does not exist")
|
||||
|
||||
|
@ -13,9 +13,9 @@ type CreatePeerRequest struct {
|
||||
/* JSON returned for user to use. This means that the peer can connect with
|
||||
these parameters as a wireguard peer */
|
||||
type Peer struct {
|
||||
Address net.IP `json:"address"`
|
||||
MTU uint16 `json:"mtu"`
|
||||
PublicKey string `json:"public_key"`
|
||||
PublicKeyUrlSafe string `json:"public_key_url_safe"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
AllowedIPs net.IP `json:"allowed_ips"`
|
||||
PersistentKeepalive time.Duration `json:"presistent_keepalive"`
|
||||
@ -23,4 +23,3 @@ type Peer struct {
|
||||
RX int64
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user