189 lines
5.5 KiB
Go
189 lines
5.5 KiB
Go
// Package bus provides NATS messaging for the poller service.
|
|
//
|
|
// tunnel_responder.go wires the tunnel.Manager to NATS subjects tunnel.open,
|
|
// tunnel.close, tunnel.status, and tunnel.status.list.
|
|
package bus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/nats-io/nats.go"
|
|
|
|
"github.com/staack/the-other-dude/poller/internal/store"
|
|
"github.com/staack/the-other-dude/poller/internal/tunnel"
|
|
"github.com/staack/the-other-dude/poller/internal/vault"
|
|
)
|
|
|
|
// TunnelOpenRequest is the JSON payload for a tunnel.open NATS request.
|
|
type TunnelOpenRequest struct {
|
|
DeviceID string `json:"device_id"`
|
|
TenantID string `json:"tenant_id"`
|
|
UserID string `json:"user_id"`
|
|
TargetPort int `json:"target_port"`
|
|
}
|
|
|
|
// TunnelCloseRequest is the JSON payload for a tunnel.close NATS request.
|
|
type TunnelCloseRequest struct {
|
|
TunnelID string `json:"tunnel_id"`
|
|
}
|
|
|
|
// TunnelStatusRequest is the JSON payload for tunnel.status and
|
|
// tunnel.status.list NATS requests.
|
|
type TunnelStatusRequest struct {
|
|
TunnelID string `json:"tunnel_id,omitempty"`
|
|
DeviceID string `json:"device_id,omitempty"`
|
|
}
|
|
|
|
// TunnelResponder handles NATS request-reply for WinBox tunnel management.
|
|
type TunnelResponder struct {
|
|
nc *nats.Conn
|
|
manager *tunnel.Manager
|
|
deviceStore *store.DeviceStore
|
|
credCache *vault.CredentialCache
|
|
subs []*nats.Subscription
|
|
}
|
|
|
|
// NewTunnelResponder creates a TunnelResponder using the given NATS connection,
|
|
// tunnel manager, device store, and credential cache.
|
|
func NewTunnelResponder(nc *nats.Conn, mgr *tunnel.Manager, ds *store.DeviceStore, cc *vault.CredentialCache) *TunnelResponder {
|
|
return &TunnelResponder{nc: nc, manager: mgr, deviceStore: ds, credCache: cc}
|
|
}
|
|
|
|
// Subscribe registers NATS handlers for tunnel.open, tunnel.close,
|
|
// tunnel.status, and tunnel.status.list.
|
|
func (tr *TunnelResponder) Subscribe() error {
|
|
subjects := []struct {
|
|
subject string
|
|
handler nats.MsgHandler
|
|
}{
|
|
{"tunnel.open", tr.handleOpen},
|
|
{"tunnel.close", tr.handleClose},
|
|
{"tunnel.status", tr.handleStatus},
|
|
{"tunnel.status.list", tr.handleStatusList},
|
|
}
|
|
|
|
for _, s := range subjects {
|
|
sub, err := tr.nc.Subscribe(s.subject, s.handler)
|
|
if err != nil {
|
|
return fmt.Errorf("subscribing to %s: %w", s.subject, err)
|
|
}
|
|
tr.subs = append(tr.subs, sub)
|
|
}
|
|
|
|
slog.Info("tunnel NATS responder subscribed")
|
|
return nil
|
|
}
|
|
|
|
// Stop unsubscribes all tunnel NATS subscriptions.
|
|
func (tr *TunnelResponder) Stop() {
|
|
for _, sub := range tr.subs {
|
|
if err := sub.Unsubscribe(); err != nil {
|
|
slog.Warn("error unsubscribing tunnel responder", "error", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// handleOpen processes a tunnel.open request: looks up the device, derives
|
|
// the remote address, and delegates to the tunnel Manager.
|
|
func (tr *TunnelResponder) handleOpen(msg *nats.Msg) {
|
|
var req TunnelOpenRequest
|
|
if err := json.Unmarshal(msg.Data, &req); err != nil {
|
|
tr.respondError(msg, "invalid request")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
dev, err := tr.deviceStore.GetDevice(ctx, req.DeviceID)
|
|
if err != nil {
|
|
slog.Error("tunnel: device lookup failed", "device_id", req.DeviceID, "err", err)
|
|
tr.respondError(msg, "device not found")
|
|
return
|
|
}
|
|
|
|
targetPort := req.TargetPort
|
|
if targetPort == 0 {
|
|
targetPort = 8291
|
|
}
|
|
remoteAddr := fmt.Sprintf("%s:%d", dev.IPAddress, targetPort)
|
|
|
|
resp, err := tr.manager.OpenTunnel(req.DeviceID, req.TenantID, req.UserID, remoteAddr)
|
|
if err != nil {
|
|
slog.Error("tunnel: open failed", "device_id", req.DeviceID, "err", err)
|
|
tr.respondError(msg, err.Error())
|
|
return
|
|
}
|
|
|
|
data, _ := json.Marshal(resp)
|
|
if err := msg.Respond(data); err != nil {
|
|
slog.Error("tunnel: failed to respond to open request", "error", err)
|
|
}
|
|
}
|
|
|
|
// handleClose processes a tunnel.close request.
|
|
func (tr *TunnelResponder) handleClose(msg *nats.Msg) {
|
|
var req TunnelCloseRequest
|
|
if err := json.Unmarshal(msg.Data, &req); err != nil {
|
|
tr.respondError(msg, "invalid request")
|
|
return
|
|
}
|
|
|
|
if err := tr.manager.CloseTunnel(req.TunnelID); err != nil {
|
|
tr.respondError(msg, err.Error())
|
|
return
|
|
}
|
|
|
|
if err := msg.Respond([]byte(`{"ok":true}`)); err != nil {
|
|
slog.Error("tunnel: failed to respond to close request", "error", err)
|
|
}
|
|
}
|
|
|
|
// handleStatus processes a tunnel.status request for a single tunnel.
|
|
func (tr *TunnelResponder) handleStatus(msg *nats.Msg) {
|
|
var req TunnelStatusRequest
|
|
if err := json.Unmarshal(msg.Data, &req); err != nil {
|
|
tr.respondError(msg, "invalid request")
|
|
return
|
|
}
|
|
|
|
status, err := tr.manager.GetTunnel(req.TunnelID)
|
|
if err != nil {
|
|
tr.respondError(msg, err.Error())
|
|
return
|
|
}
|
|
|
|
data, _ := json.Marshal(status)
|
|
if err := msg.Respond(data); err != nil {
|
|
slog.Error("tunnel: failed to respond to status request", "error", err)
|
|
}
|
|
}
|
|
|
|
// handleStatusList processes a tunnel.status.list request, returning all
|
|
// tunnels for the given device_id.
|
|
func (tr *TunnelResponder) handleStatusList(msg *nats.Msg) {
|
|
var req TunnelStatusRequest
|
|
if err := json.Unmarshal(msg.Data, &req); err != nil {
|
|
tr.respondError(msg, "invalid request")
|
|
return
|
|
}
|
|
|
|
list := tr.manager.ListTunnels(req.DeviceID)
|
|
data, _ := json.Marshal(list)
|
|
if err := msg.Respond(data); err != nil {
|
|
slog.Error("tunnel: failed to respond to status list request", "error", err)
|
|
}
|
|
}
|
|
|
|
// respondError sends a JSON error response to a NATS request.
|
|
func (tr *TunnelResponder) respondError(msg *nats.Msg, errMsg string) {
|
|
resp, _ := json.Marshal(map[string]string{"error": errMsg})
|
|
if err := msg.Respond(resp); err != nil {
|
|
slog.Error("tunnel: failed to respond with error", "error", err)
|
|
}
|
|
}
|