cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.12.4

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

cfapi/ip_route.go

240lines · modecode

1package cfapi
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net"
8 "net/http"
9 "net/url"
10 "path"
11 "time"
12
13 "github.com/google/uuid"
14 "github.com/pkg/errors"
15)
16
17// Route is a mapping from customer's IP space to a tunnel.
18// Each route allows the customer to route eyeballs in their corporate network
19// to certain private IP ranges. Each Route represents an IP range in their
20// network, and says that eyeballs can reach that route using the corresponding
21// tunnel.
22type Route struct {
23 Network CIDR `json:"network"`
24 TunnelID uuid.UUID `json:"tunnel_id"`
25 // Optional field. When unset, it means the Route belongs to the default virtual network.
26 VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
27 Comment string `json:"comment"`
28 CreatedAt time.Time `json:"created_at"`
29 DeletedAt time.Time `json:"deleted_at"`
30}
31
32// CIDR is just a newtype wrapper around net.IPNet. It adds JSON unmarshalling.
33type CIDR net.IPNet
34
35func (c CIDR) String() string {
36 n := net.IPNet(c)
37 return n.String()
38}
39
40func (c CIDR) MarshalJSON() ([]byte, error) {
41 str := c.String()
42 json, err := json.Marshal(str)
43 if err != nil {
44 return nil, errors.Wrap(err, "error serializing CIDR into JSON")
45 }
46 return json, nil
47}
48
49// UnmarshalJSON parses a JSON string into net.IPNet
50func (c *CIDR) UnmarshalJSON(data []byte) error {
51 var s string
52 if err := json.Unmarshal(data, &s); err != nil {
53 return errors.Wrap(err, "error parsing cidr string")
54 }
55 _, network, err := net.ParseCIDR(s)
56 if err != nil {
57 return errors.Wrap(err, "error parsing invalid network from backend")
58 }
59 if network == nil {
60 return fmt.Errorf("backend returned invalid network %s", s)
61 }
62 *c = CIDR(*network)
63 return nil
64}
65
66// NewRoute has all the parameters necessary to add a new route to the table.
67type NewRoute struct {
68 Network net.IPNet
69 TunnelID uuid.UUID
70 Comment string
71 // Optional field. If unset, backend will assume the default vnet for the account.
72 VNetID *uuid.UUID
73}
74
75// MarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
76func (r NewRoute) MarshalJSON() ([]byte, error) {
77 return json.Marshal(&struct {
78 TunnelID uuid.UUID `json:"tunnel_id"`
79 Comment string `json:"comment"`
80 VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
81 }{
82 TunnelID: r.TunnelID,
83 Comment: r.Comment,
84 VNetID: r.VNetID,
85 })
86}
87
88// DetailedRoute is just a Route with some extra fields, e.g. TunnelName.
89type DetailedRoute struct {
90 Network CIDR `json:"network"`
91 TunnelID uuid.UUID `json:"tunnel_id"`
92 // Optional field. When unset, it means the DetailedRoute belongs to the default virtual network.
93 VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
94 Comment string `json:"comment"`
95 CreatedAt time.Time `json:"created_at"`
96 DeletedAt time.Time `json:"deleted_at"`
97 TunnelName string `json:"tunnel_name"`
98}
99
100// IsZero checks if DetailedRoute is the zero value.
101func (r *DetailedRoute) IsZero() bool {
102 return r.TunnelID == uuid.Nil
103}
104
105// TableString outputs a table row summarizing the route, to be used
106// when showing the user their routing table.
107func (r DetailedRoute) TableString() string {
108 deletedColumn := "-"
109 if !r.DeletedAt.IsZero() {
110 deletedColumn = r.DeletedAt.Format(time.RFC3339)
111 }
112 vnetColumn := "default"
113 if r.VNetID != nil {
114 vnetColumn = r.VNetID.String()
115 }
116
117 return fmt.Sprintf(
118 "%s\t%s\t%s\t%s\t%s\t%s\t%s\t",
119 r.Network.String(),
120 vnetColumn,
121 r.Comment,
122 r.TunnelID,
123 r.TunnelName,
124 r.CreatedAt.Format(time.RFC3339),
125 deletedColumn,
126 )
127}
128
129type DeleteRouteParams struct {
130 Network net.IPNet
131 // Optional field. If unset, backend will assume the default vnet for the account.
132 VNetID *uuid.UUID
133}
134
135type GetRouteByIpParams struct {
136 Ip net.IP
137 // Optional field. If unset, backend will assume the default vnet for the account.
138 VNetID *uuid.UUID
139}
140
141// ListRoutes calls the Tunnelstore GET endpoint for all routes under an account.
142func (r *RESTClient) ListRoutes(filter *IpRouteFilter) ([]*DetailedRoute, error) {
143 endpoint := r.baseEndpoints.accountRoutes
144 endpoint.RawQuery = filter.Encode()
145 resp, err := r.sendRequest("GET", endpoint, nil)
146 if err != nil {
147 return nil, errors.Wrap(err, "REST request failed")
148 }
149 defer resp.Body.Close()
150
151 if resp.StatusCode == http.StatusOK {
152 return parseListDetailedRoutes(resp.Body)
153 }
154
155 return nil, r.statusCodeToError("list routes", resp)
156}
157
158// AddRoute calls the Tunnelstore POST endpoint for a given route.
159func (r *RESTClient) AddRoute(newRoute NewRoute) (Route, error) {
160 endpoint := r.baseEndpoints.accountRoutes
161 endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(newRoute.Network.String()))
162 resp, err := r.sendRequest("POST", endpoint, newRoute)
163 if err != nil {
164 return Route{}, errors.Wrap(err, "REST request failed")
165 }
166 defer resp.Body.Close()
167
168 if resp.StatusCode == http.StatusOK {
169 return parseRoute(resp.Body)
170 }
171
172 return Route{}, r.statusCodeToError("add route", resp)
173}
174
175// DeleteRoute calls the Tunnelstore DELETE endpoint for a given route.
176func (r *RESTClient) DeleteRoute(params DeleteRouteParams) error {
177 endpoint := r.baseEndpoints.accountRoutes
178 endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(params.Network.String()))
179 setVnetParam(&endpoint, params.VNetID)
180
181 resp, err := r.sendRequest("DELETE", endpoint, nil)
182 if err != nil {
183 return errors.Wrap(err, "REST request failed")
184 }
185 defer resp.Body.Close()
186
187 if resp.StatusCode == http.StatusOK {
188 _, err := parseRoute(resp.Body)
189 return err
190 }
191
192 return r.statusCodeToError("delete route", resp)
193}
194
195// GetByIP checks which route will proxy a given IP.
196func (r *RESTClient) GetByIP(params GetRouteByIpParams) (DetailedRoute, error) {
197 endpoint := r.baseEndpoints.accountRoutes
198 endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(params.Ip.String()))
199 setVnetParam(&endpoint, params.VNetID)
200
201 resp, err := r.sendRequest("GET", endpoint, nil)
202 if err != nil {
203 return DetailedRoute{}, errors.Wrap(err, "REST request failed")
204 }
205 defer resp.Body.Close()
206
207 if resp.StatusCode == http.StatusOK {
208 return parseDetailedRoute(resp.Body)
209 }
210
211 return DetailedRoute{}, r.statusCodeToError("get route by IP", resp)
212}
213
214func parseListDetailedRoutes(body io.ReadCloser) ([]*DetailedRoute, error) {
215 var routes []*DetailedRoute
216 err := parseResponse(body, &routes)
217 return routes, err
218}
219
220func parseRoute(body io.ReadCloser) (Route, error) {
221 var route Route
222 err := parseResponse(body, &route)
223 return route, err
224}
225
226func parseDetailedRoute(body io.ReadCloser) (DetailedRoute, error) {
227 var route DetailedRoute
228 err := parseResponse(body, &route)
229 return route, err
230}
231
232// setVnetParam overwrites the URL's query parameters with a query param to scope the HostnameRoute action to a certain
233// virtual network (if one is provided).
234func setVnetParam(endpoint *url.URL, vnetID *uuid.UUID) {
235 queryParams := url.Values{}
236 if vnetID != nil {
237 queryParams.Set("virtual_network_id", vnetID.String())
238 }
239 endpoint.RawQuery = queryParams.Encode()
240}
241