1package cli
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "net"
8
9 "github.com/spf13/cobra"
10
11 "github.com/bnema/gordon/internal/adapters/dto"
12)
13
14var trafficResolveControlPlane = resolveControlPlane
15
16func newTrafficCmd() *cobra.Command {
17 var jsonOut bool
18 cmd := &cobra.Command{
19 Use: "traffic",
20 Short: "Show traffic entrypoint, router, and counter status",
21 Args: cobra.NoArgs,
22 RunE: func(cmd *cobra.Command, args []string) error {
23 handle, err := trafficResolveControlPlane(cliConfigPath)
24 if err != nil {
25 return err
26 }
27 defer handle.close()
28 return runTrafficStatus(cmd.Context(), handle.plane, cmd.OutOrStdout(), jsonOut)
29 },
30 }
31 cmd.Flags().BoolVar(&jsonOut, "json", false, "Output JSON")
32 return cmd
33}
34
35func runTrafficStatus(ctx context.Context, cp ControlPlane, out io.Writer, jsonOut bool) error {
36 status, err := cp.GetTrafficStatus(ctx)
37 if err != nil {
38 return fmt.Errorf("fetch traffic status: %w", err)
39 }
40 if err := renderTrafficStatus(out, status, jsonOut); err != nil {
41 return fmt.Errorf("render traffic status: %w", err)
42 }
43 return nil
44}
45
46func renderTrafficStatus(out io.Writer, status *dto.TrafficStatusResponse, jsonOut bool) error {
47 if jsonOut {
48 return writeJSON(out, status)
49 }
50 if err := renderTrafficStatusHeader(out, status); err != nil {
51 return err
52 }
53 if err := renderTrafficEntryPoints(out, status.EntryPoints); err != nil {
54 return err
55 }
56 if err := renderTrafficRouters(out, status.Routers); err != nil {
57 return err
58 }
59 if err := renderTrafficServices(out, status.Services); err != nil {
60 return err
61 }
62 return renderTrafficTotals(out, status.Counters)
63}
64
65func renderTrafficStatusHeader(out io.Writer, status *dto.TrafficStatusResponse) error {
66 if err := cliWriteLine(out, cliRenderTitle("Traffic Status")); err != nil {
67 return err
68 }
69 if err := cliWriteLine(out, cliRenderMeta("Reload", status.LastReloadStatus)); err != nil {
70 return err
71 }
72 if status.LastReloadError == "" {
73 return nil
74 }
75 return cliWriteLine(out, cliRenderWarning("Last reload error: "+status.LastReloadError))
76}
77
78func renderTrafficEntryPoints(out io.Writer, entries []dto.TrafficEntryPointStatus) error {
79 if err := cliWriteLine(out, ""); err != nil {
80 return err
81 }
82 if err := cliWriteLine(out, "Entrypoints:"); err != nil {
83 return err
84 }
85 if len(entries) == 0 {
86 return cliWriteLine(out, cliRenderMuted(" none"))
87 }
88 for _, entry := range entries {
89 if err := renderTrafficEntryPoint(out, entry); err != nil {
90 return err
91 }
92 }
93 return nil
94}
95
96func renderTrafficEntryPoint(out io.Writer, entry dto.TrafficEntryPointStatus) error {
97 if err := cliWritef(out, " %s %s %s active=%t tcp=%d udp=%d accepted=%d refused=%d errors=%d\n",
98 entry.Name, entry.Protocol, entry.Address, entry.Active, entry.ActiveTCPConnections, entry.ActiveUDPSessions,
99 entry.TotalAccepted, entry.TotalRefused, entry.TotalErrors); err != nil {
100 return err
101 }
102 if hasSmartTCPCounters(entry.SmartTCP) {
103 return renderSmartTCPCounters(out, " smart_tcp", entry.SmartTCP)
104 }
105 return nil
106}
107
108func renderTrafficTotals(out io.Writer, counters dto.TrafficCounters) error {
109 if err := cliWriteLine(out, ""); err != nil {
110 return err
111 }
112 if err := cliWritef(out, "Totals: tcp=%d udp=%d accepted=%d refused=%d errors=%d bytes_in=%d bytes_out=%d\n",
113 counters.ActiveTCPConnections, counters.ActiveUDPSessions, counters.TotalAccepted,
114 counters.TotalRefused, counters.TotalErrors, counters.BytesIn, counters.BytesOut); err != nil {
115 return err
116 }
117 if hasSmartTCPCounters(counters.SmartTCP) {
118 return renderSmartTCPCounters(out, "Smart TCP totals", counters.SmartTCP)
119 }
120 return nil
121}
122
123func hasSmartTCPCounters(c dto.SmartTCPCounters) bool {
124 return c.HTTPAccepted != 0 || c.H2CAccepted != 0 || c.HTTPSFallbackAccepted != 0 || c.TLSPassthroughAccepted != 0 ||
125 c.RawFallbackAccepted != 0 || c.EntrypointCIDRRefused != 0 || c.RawFallbackCIDRRefused != 0 || c.PROXYRefused != 0 ||
126 c.UnknownNoFallbackRefused != 0 || c.MalformedRejected != 0 || c.SniffTimeout != 0 || c.ClientHelloTooLarge != 0
127}
128
129func renderSmartTCPCounters(out io.Writer, label string, c dto.SmartTCPCounters) error {
130 return cliWritef(out, "%s: http_accepted=%d h2c_accepted=%d https_fallback_accepted=%d tls_passthrough_accepted=%d raw_fallback_accepted=%d entrypoint_cidr_refused=%d raw_fallback_cidr_refused=%d proxy_refused=%d unknown_no_fallback_refused=%d malformed_rejected=%d sniff_timeout=%d client_hello_too_large=%d\n",
131 label, c.HTTPAccepted, c.H2CAccepted, c.HTTPSFallbackAccepted, c.TLSPassthroughAccepted, c.RawFallbackAccepted,
132 c.EntrypointCIDRRefused, c.RawFallbackCIDRRefused, c.PROXYRefused, c.UnknownNoFallbackRefused, c.MalformedRejected,
133 c.SniffTimeout, c.ClientHelloTooLarge)
134}
135
136func renderTrafficRouters(out io.Writer, routers []dto.TrafficRouterStatus) error {
137 if err := cliWriteLine(out, ""); err != nil {
138 return err
139 }
140 if err := cliWriteLine(out, "Routers:"); err != nil {
141 return err
142 }
143 if len(routers) == 0 {
144 return cliWriteLine(out, cliRenderMuted(" none"))
145 }
146 for _, router := range routers {
147 rule := router.Rule.Host
148 if rule == "" {
149 rule = router.Rule.SNI
150 }
151 if err := cliWritef(out, " %s %s entrypoint=%s rule=%s service=%s active=%t\n",
152 router.Name, router.Protocol, router.EntryPoint, rule, router.Service, router.Active); err != nil {
153 return err
154 }
155 }
156 return nil
157}
158
159func renderTrafficServices(out io.Writer, services []dto.TrafficServiceStatus) error {
160 if err := cliWriteLine(out, ""); err != nil {
161 return err
162 }
163 if err := cliWriteLine(out, "Services:"); err != nil {
164 return err
165 }
166 if len(services) == 0 {
167 return cliWriteLine(out, cliRenderMuted(" none"))
168 }
169 for _, service := range services {
170 if err := cliWritef(out, " %s active=%t\n", service.Name, service.Active); err != nil {
171 return err
172 }
173 for _, backend := range service.Backends {
174 addr := net.JoinHostPort(backend.Host, fmt.Sprint(backend.Port))
175 if err := cliWritef(out, " %s %s://%s active=%t\n",
176 backend.Name, backend.Protocol, addr, backend.Active); err != nil {
177 return err
178 }
179 }
180 }
181 return nil
182}