// Package netsocket exposes the narrow group-restricted tera-netd protocol. package netsocket import ( "bufio" "bytes" "context" "encoding/json" "errors" "fmt" "io" "net" "time" "tera-clone/services/tera-api/internal/networkconfig" ) const DefaultSocketPath = "/run/tera-netd/control.sock" type request struct { Action string `json:"action"` Config networkconfig.Config `json:"config,omitempty"` TransactionID string `json:"transaction_id,omitempty"` } type response struct { ApplyResult *networkconfig.ApplyResult `json:"apply_result,omitempty"` Status *networkconfig.Status `json:"status,omitempty"` Code string `json:"code,omitempty"` Error string `json:"error,omitempty"` } type Server struct{ Controller networkconfig.Controller } func (s Server) Serve(listener net.Listener) error { if s.Controller == nil { return errors.New("network controller is required") } for { connection, err := listener.Accept() if err != nil { if errors.Is(err, net.ErrClosed) { return nil } if temporary, ok := err.(interface{ Temporary() bool }); ok && temporary.Temporary() { continue } return err } go s.handleConnection(connection) } } func (s Server) handleConnection(connection net.Conn) { defer connection.Close() _ = connection.SetDeadline(time.Now().Add(35 * time.Second)) payload, err := bufio.NewReader(io.LimitReader(connection, 65537)).ReadBytes('\n') if err != nil && !errors.Is(err, io.EOF) { s.writeResponse(connection, response{Code: "validation_failed", Error: "invalid network request"}) return } if len(payload) == 0 || len(payload) > 65536 || payload[len(payload)-1] != '\n' { s.writeResponse(connection, response{Code: "validation_failed", Error: "invalid network request"}) return } decoder := json.NewDecoder(bytes.NewReader(payload)) decoder.DisallowUnknownFields() var command request if err := decoder.Decode(&command); err != nil { s.writeResponse(connection, response{Code: "validation_failed", Error: "invalid network request"}) return } var extra any if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { s.writeResponse(connection, response{Code: "validation_failed", Error: "network request must contain one JSON value"}) return } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() switch command.Action { case "factory_reset": resetter, ok := s.Controller.(networkconfig.FactoryResetter) if !ok { s.writeError(connection, errors.New("network factory reset unavailable")) return } if err := resetter.FactoryReset(ctx, command.Config); err != nil { s.writeError(connection, err) return } s.writeResponse(connection, response{}) case "apply": result, err := s.Controller.Apply(ctx, command.Config) if err != nil { s.writeError(connection, err) return } s.writeResponse(connection, response{ApplyResult: &result}) case "confirm": status, err := s.Controller.Confirm(ctx, command.TransactionID) if err != nil { s.writeError(connection, err) return } s.writeResponse(connection, response{Status: &status}) case "rollback": status, err := s.Controller.Rollback(ctx, command.TransactionID) if err != nil { s.writeError(connection, err) return } s.writeResponse(connection, response{Status: &status}) case "status": status, err := s.Controller.Status(ctx) if err != nil { s.writeError(connection, err) return } s.writeResponse(connection, response{Status: &status}) default: s.writeResponse(connection, response{Code: "validation_failed", Error: "network action is not supported"}) } } func (s Server) writeError(connection net.Conn, err error) { code := "network_failed" if networkconfig.IsUnavailable(err) { code = "network_unavailable" } s.writeResponse(connection, response{Code: code, Error: err.Error()}) } func (Server) writeResponse(connection net.Conn, value response) { _ = json.NewEncoder(connection).Encode(value) } type Client struct { path string timeout time.Duration } func NewClient(path string) *Client { if path == "" { path = DefaultSocketPath } return &Client{path: path, timeout: 35 * time.Second} } func (c *Client) Apply(ctx context.Context, config networkconfig.Config) (networkconfig.ApplyResult, error) { result, err := c.execute(ctx, request{Action: "apply", Config: config}) if err != nil { return networkconfig.ApplyResult{}, err } if result.ApplyResult == nil { return networkconfig.ApplyResult{}, errors.New("tera-netd returned no apply result") } return *result.ApplyResult, nil } func (c *Client) Confirm(ctx context.Context, transactionID string) (networkconfig.Status, error) { return c.statusAction(ctx, "confirm", transactionID) } func (c *Client) Rollback(ctx context.Context, transactionID string) (networkconfig.Status, error) { return c.statusAction(ctx, "rollback", transactionID) } func (c *Client) Status(ctx context.Context) (networkconfig.Status, error) { return c.statusAction(ctx, "status", "") } func (c *Client) statusAction(ctx context.Context, action, transactionID string) (networkconfig.Status, error) { result, err := c.execute(ctx, request{Action: action, TransactionID: transactionID}) if err != nil { return networkconfig.Status{}, err } if result.Status == nil { return networkconfig.Status{}, errors.New("tera-netd returned no status") } return *result.Status, nil } func (c *Client) execute(ctx context.Context, command request) (response, error) { dialer := net.Dialer{Timeout: c.timeout} connection, err := dialer.DialContext(ctx, "unix", c.path) if err != nil { return response{}, &networkconfig.UnavailableError{Reason: "tera-netd is not reachable"} } defer connection.Close() deadline := time.Now().Add(c.timeout) if contextDeadline, ok := ctx.Deadline(); ok && contextDeadline.Before(deadline) { deadline = contextDeadline } _ = connection.SetDeadline(deadline) if err := json.NewEncoder(connection).Encode(command); err != nil { return response{}, errors.New("network request failed") } var result response if err := json.NewDecoder(io.LimitReader(connection, 65536)).Decode(&result); err != nil { return response{}, &networkconfig.UnavailableError{Reason: "tera-netd returned no response"} } if result.Error != "" { if result.Code == "network_unavailable" { return response{}, &networkconfig.UnavailableError{Reason: result.Error} } return response{}, fmt.Errorf("tera-netd: %s", result.Error) } return result, nil } func (c *Client) FactoryReset(ctx context.Context, config networkconfig.Config) error { _, err := c.execute(ctx, request{Action: "factory_reset", Config: config}) return err }