cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2022.4.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/ip_route.go

240lines · modeblame

6822e4f8Nuno Diegues4 years ago1package cfapi
94c639d2Adam Chalmers5 years ago2
3import (
4"encoding/json"
5"fmt"
6822e4f8Nuno Diegues4 years ago6"io"
94c639d2Adam Chalmers5 years ago7"net"
6822e4f8Nuno Diegues4 years ago8"net/http"
9"net/url"
10"path"
94c639d2Adam Chalmers5 years ago11"time"
12
13"github.com/google/uuid"
78ffb1b8Adam Chalmers5 years ago14"github.com/pkg/errors"
94c639d2Adam Chalmers5 years ago15)
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 {
571380b3Nuno Diegues4 years ago23Network CIDR `json:"network"`
24TunnelID uuid.UUID `json:"tunnel_id"`
25// Optional field. When unset, it means the Route belongs to the default virtual network.
26VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
27Comment string `json:"comment"`
28CreatedAt time.Time `json:"created_at"`
29DeletedAt time.Time `json:"deleted_at"`
94c639d2Adam Chalmers5 years ago30}
31
78ffb1b8Adam Chalmers5 years ago32// CIDR is just a newtype wrapper around net.IPNet. It adds JSON unmarshalling.
33type CIDR net.IPNet
94c639d2Adam Chalmers5 years ago34
6681d179Nuno Diegues5 years ago35func (c CIDR) String() string {
36n := net.IPNet(c)
78ffb1b8Adam Chalmers5 years ago37return n.String()
38}
94c639d2Adam Chalmers5 years ago39
6681d179Nuno Diegues5 years ago40func (c CIDR) MarshalJSON() ([]byte, error) {
41str := c.String()
42json, err := json.Marshal(str)
43if err != nil {
44return nil, errors.Wrap(err, "error serializing CIDR into JSON")
45}
46return json, nil
47}
48
78ffb1b8Adam Chalmers5 years ago49// UnmarshalJSON parses a JSON string into net.IPNet
50func (c *CIDR) UnmarshalJSON(data []byte) error {
51var s string
52if err := json.Unmarshal(data, &s); err != nil {
53return errors.Wrap(err, "error parsing cidr string")
94c639d2Adam Chalmers5 years ago54}
78ffb1b8Adam Chalmers5 years ago55_, network, err := net.ParseCIDR(s)
56if err != nil {
57return errors.Wrap(err, "error parsing invalid network from backend")
94c639d2Adam Chalmers5 years ago58}
78ffb1b8Adam Chalmers5 years ago59if network == nil {
60return fmt.Errorf("backend returned invalid network %s", s)
94c639d2Adam Chalmers5 years ago61}
78ffb1b8Adam Chalmers5 years ago62*c = CIDR(*network)
94c639d2Adam Chalmers5 years ago63return nil
64}
65
66// NewRoute has all the parameters necessary to add a new route to the table.
67type NewRoute struct {
68Network net.IPNet
69TunnelID uuid.UUID
70Comment string
571380b3Nuno Diegues4 years ago71// Optional field. If unset, backend will assume the default vnet for the account.
72VNetID *uuid.UUID
94c639d2Adam Chalmers5 years ago73}
74
75// MarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
76func (r NewRoute) MarshalJSON() ([]byte, error) {
77return json.Marshal(&struct {
571380b3Nuno Diegues4 years ago78TunnelID uuid.UUID `json:"tunnel_id"`
79Comment string `json:"comment"`
80VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
94c639d2Adam Chalmers5 years ago81}{
82TunnelID: r.TunnelID,
83Comment: r.Comment,
571380b3Nuno Diegues4 years ago84VNetID: r.VNetID,
94c639d2Adam Chalmers5 years ago85})
86}
78ffb1b8Adam Chalmers5 years ago87
88// DetailedRoute is just a Route with some extra fields, e.g. TunnelName.
89type DetailedRoute struct {
571380b3Nuno Diegues4 years ago90Network CIDR `json:"network"`
91TunnelID uuid.UUID `json:"tunnel_id"`
92// Optional field. When unset, it means the DetailedRoute belongs to the default virtual network.
93VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
94Comment string `json:"comment"`
95CreatedAt time.Time `json:"created_at"`
96DeletedAt time.Time `json:"deleted_at"`
97TunnelName string `json:"tunnel_name"`
78ffb1b8Adam Chalmers5 years ago98}
99
100// IsZero checks if DetailedRoute is the zero value.
101func (r *DetailedRoute) IsZero() bool {
102return 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 {
108deletedColumn := "-"
109if !r.DeletedAt.IsZero() {
110deletedColumn = r.DeletedAt.Format(time.RFC3339)
111}
571380b3Nuno Diegues4 years ago112vnetColumn := "default"
113if r.VNetID != nil {
114vnetColumn = r.VNetID.String()
115}
116
78ffb1b8Adam Chalmers5 years ago117return fmt.Sprintf(
571380b3Nuno Diegues4 years ago118"%s\t%s\t%s\t%s\t%s\t%s\t%s\t",
78ffb1b8Adam Chalmers5 years ago119r.Network.String(),
571380b3Nuno Diegues4 years ago120vnetColumn,
78ffb1b8Adam Chalmers5 years ago121r.Comment,
122r.TunnelID,
123r.TunnelName,
124r.CreatedAt.Format(time.RFC3339),
125deletedColumn,
126)
127}
571380b3Nuno Diegues4 years ago128
129type DeleteRouteParams struct {
130Network net.IPNet
131// Optional field. If unset, backend will assume the default vnet for the account.
132VNetID *uuid.UUID
133}
134
135type GetRouteByIpParams struct {
136Ip net.IP
137// Optional field. If unset, backend will assume the default vnet for the account.
138VNetID *uuid.UUID
139}
6822e4f8Nuno Diegues4 years ago140
141// ListRoutes calls the Tunnelstore GET endpoint for all routes under an account.
142func (r *RESTClient) ListRoutes(filter *IpRouteFilter) ([]*DetailedRoute, error) {
143endpoint := r.baseEndpoints.accountRoutes
144endpoint.RawQuery = filter.Encode()
145resp, err := r.sendRequest("GET", endpoint, nil)
146if err != nil {
147return nil, errors.Wrap(err, "REST request failed")
148}
149defer resp.Body.Close()
150
151if resp.StatusCode == http.StatusOK {
152return parseListDetailedRoutes(resp.Body)
153}
154
155return 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) {
160endpoint := r.baseEndpoints.accountRoutes
161endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(newRoute.Network.String()))
162resp, err := r.sendRequest("POST", endpoint, newRoute)
163if err != nil {
164return Route{}, errors.Wrap(err, "REST request failed")
165}
166defer resp.Body.Close()
167
168if resp.StatusCode == http.StatusOK {
169return parseRoute(resp.Body)
170}
171
172return 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 {
177endpoint := r.baseEndpoints.accountRoutes
178endpoint.Path = path.Join(endpoint.Path, "network", url.PathEscape(params.Network.String()))
179setVnetParam(&endpoint, params.VNetID)
180
181resp, err := r.sendRequest("DELETE", endpoint, nil)
182if err != nil {
183return errors.Wrap(err, "REST request failed")
184}
185defer resp.Body.Close()
186
187if resp.StatusCode == http.StatusOK {
188_, err := parseRoute(resp.Body)
189return err
190}
191
192return 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) {
197endpoint := r.baseEndpoints.accountRoutes
198endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(params.Ip.String()))
199setVnetParam(&endpoint, params.VNetID)
200
201resp, err := r.sendRequest("GET", endpoint, nil)
202if err != nil {
203return DetailedRoute{}, errors.Wrap(err, "REST request failed")
204}
205defer resp.Body.Close()
206
207if resp.StatusCode == http.StatusOK {
208return parseDetailedRoute(resp.Body)
209}
210
211return DetailedRoute{}, r.statusCodeToError("get route by IP", resp)
212}
213
214func parseListDetailedRoutes(body io.ReadCloser) ([]*DetailedRoute, error) {
215var routes []*DetailedRoute
216err := parseResponse(body, &routes)
217return routes, err
218}
219
220func parseRoute(body io.ReadCloser) (Route, error) {
221var route Route
222err := parseResponse(body, &route)
223return route, err
224}
225
226func parseDetailedRoute(body io.ReadCloser) (DetailedRoute, error) {
227var route DetailedRoute
228err := parseResponse(body, &route)
229return 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) {
235queryParams := url.Values{}
236if vnetID != nil {
237queryParams.Set("virtual_network_id", vnetID.String())
238}
239endpoint.RawQuery = queryParams.Encode()
240}