cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2026.3.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/ip_route.go

235lines · 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 {
6d1d91d9João Oliveirinha2 years ago78Network string `json:"network"`
571380b3Nuno Diegues4 years ago79TunnelID uuid.UUID `json:"tunnel_id"`
80Comment string `json:"comment"`
81VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
94c639d2Adam Chalmers5 years ago82}{
6d1d91d9João Oliveirinha2 years ago83Network: r.Network.String(),
94c639d2Adam Chalmers5 years ago84TunnelID: r.TunnelID,
85Comment: r.Comment,
571380b3Nuno Diegues4 years ago86VNetID: r.VNetID,
94c639d2Adam Chalmers5 years ago87})
88}
78ffb1b8Adam Chalmers5 years ago89
90// DetailedRoute is just a Route with some extra fields, e.g. TunnelName.
91type DetailedRoute struct {
6d1d91d9João Oliveirinha2 years ago92ID uuid.UUID `json:"id"`
571380b3Nuno Diegues4 years ago93Network CIDR `json:"network"`
94TunnelID uuid.UUID `json:"tunnel_id"`
95// Optional field. When unset, it means the DetailedRoute belongs to the default virtual network.
96VNetID *uuid.UUID `json:"virtual_network_id,omitempty"`
97Comment string `json:"comment"`
98CreatedAt time.Time `json:"created_at"`
99DeletedAt time.Time `json:"deleted_at"`
100TunnelName string `json:"tunnel_name"`
78ffb1b8Adam Chalmers5 years ago101}
102
103// IsZero checks if DetailedRoute is the zero value.
104func (r *DetailedRoute) IsZero() bool {
105return r.TunnelID == uuid.Nil
106}
107
108// TableString outputs a table row summarizing the route, to be used
109// when showing the user their routing table.
110func (r DetailedRoute) TableString() string {
111deletedColumn := "-"
112if !r.DeletedAt.IsZero() {
113deletedColumn = r.DeletedAt.Format(time.RFC3339)
114}
571380b3Nuno Diegues4 years ago115vnetColumn := "default"
116if r.VNetID != nil {
117vnetColumn = r.VNetID.String()
118}
119
78ffb1b8Adam Chalmers5 years ago120return fmt.Sprintf(
6d1d91d9João Oliveirinha2 years ago121"%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t",
122r.ID,
78ffb1b8Adam Chalmers5 years ago123r.Network.String(),
571380b3Nuno Diegues4 years ago124vnetColumn,
78ffb1b8Adam Chalmers5 years ago125r.Comment,
126r.TunnelID,
127r.TunnelName,
128r.CreatedAt.Format(time.RFC3339),
129deletedColumn,
130)
131}
571380b3Nuno Diegues4 years ago132
133type GetRouteByIpParams struct {
134Ip net.IP
135// Optional field. If unset, backend will assume the default vnet for the account.
136VNetID *uuid.UUID
137}
6822e4f8Nuno Diegues4 years ago138
139// ListRoutes calls the Tunnelstore GET endpoint for all routes under an account.
86476e62GoncaloGarcia2 years ago140// Due to pagination on the server side it will call the endpoint multiple times if needed.
6822e4f8Nuno Diegues4 years ago141func (r *RESTClient) ListRoutes(filter *IpRouteFilter) ([]*DetailedRoute, error) {
86476e62GoncaloGarcia2 years ago142fetchFn := func(page int) (*http.Response, error) {
143endpoint := r.baseEndpoints.accountRoutes
144filter.Page(page)
145endpoint.RawQuery = filter.Encode()
146rsp, err := r.sendRequest("GET", endpoint, nil)
147
148if err != nil {
149return nil, errors.Wrap(err, "REST request failed")
150}
151if rsp.StatusCode != http.StatusOK {
152rsp.Body.Close()
153return nil, r.statusCodeToError("list routes", rsp)
154}
155return rsp, nil
6822e4f8Nuno Diegues4 years ago156}
86476e62GoncaloGarcia2 years ago157return fetchExhaustively[DetailedRoute](fetchFn)
6822e4f8Nuno Diegues4 years ago158}
159
160// AddRoute calls the Tunnelstore POST endpoint for a given route.
161func (r *RESTClient) AddRoute(newRoute NewRoute) (Route, error) {
162endpoint := r.baseEndpoints.accountRoutes
6d1d91d9João Oliveirinha2 years ago163endpoint.Path = path.Join(endpoint.Path)
6822e4f8Nuno Diegues4 years ago164resp, err := r.sendRequest("POST", endpoint, newRoute)
165if err != nil {
166return Route{}, errors.Wrap(err, "REST request failed")
167}
168defer resp.Body.Close()
169
170if resp.StatusCode == http.StatusOK {
171return parseRoute(resp.Body)
172}
173
174return Route{}, r.statusCodeToError("add route", resp)
175}
176
177// DeleteRoute calls the Tunnelstore DELETE endpoint for a given route.
6d1d91d9João Oliveirinha2 years ago178func (r *RESTClient) DeleteRoute(id uuid.UUID) error {
6822e4f8Nuno Diegues4 years ago179endpoint := r.baseEndpoints.accountRoutes
6d1d91d9João Oliveirinha2 years ago180endpoint.Path = path.Join(endpoint.Path, url.PathEscape(id.String()))
6822e4f8Nuno Diegues4 years ago181
182resp, err := r.sendRequest("DELETE", endpoint, nil)
183if err != nil {
184return errors.Wrap(err, "REST request failed")
185}
186defer resp.Body.Close()
187
188if resp.StatusCode == http.StatusOK {
189_, err := parseRoute(resp.Body)
190return err
191}
192
193return r.statusCodeToError("delete route", resp)
194}
195
196// GetByIP checks which route will proxy a given IP.
197func (r *RESTClient) GetByIP(params GetRouteByIpParams) (DetailedRoute, error) {
198endpoint := r.baseEndpoints.accountRoutes
199endpoint.Path = path.Join(endpoint.Path, "ip", url.PathEscape(params.Ip.String()))
200setVnetParam(&endpoint, params.VNetID)
201
202resp, err := r.sendRequest("GET", endpoint, nil)
203if err != nil {
204return DetailedRoute{}, errors.Wrap(err, "REST request failed")
205}
206defer resp.Body.Close()
207
208if resp.StatusCode == http.StatusOK {
209return parseDetailedRoute(resp.Body)
210}
211
212return DetailedRoute{}, r.statusCodeToError("get route by IP", resp)
213}
214
215func parseRoute(body io.ReadCloser) (Route, error) {
216var route Route
217err := parseResponse(body, &route)
218return route, err
219}
220
221func parseDetailedRoute(body io.ReadCloser) (DetailedRoute, error) {
222var route DetailedRoute
223err := parseResponse(body, &route)
224return route, err
225}
226
227// setVnetParam overwrites the URL's query parameters with a query param to scope the HostnameRoute action to a certain
228// virtual network (if one is provided).
229func setVnetParam(endpoint *url.URL, vnetID *uuid.UUID) {
230queryParams := url.Values{}
231if vnetID != nil {
232queryParams.Set("virtual_network_id", vnetID.String())
233}
234endpoint.RawQuery = queryParams.Encode()
235}