ip_route.go 6.7 KB

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