TUN-3688: Subcommand for users to check which route an IP proxies through

This commit is contained in:
Adam Chalmers
2021-01-05 17:55:18 -06:00
parent 92fd039440
commit 78ffb1b846
6 changed files with 160 additions and 53 deletions

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
// Route is a mapping from customer's IP space to a tunnel.
@@ -15,56 +16,35 @@ import (
// network, and says that eyeballs can reach that route using the corresponding
// tunnel.
type Route struct {
Network net.IPNet
TunnelID uuid.UUID
Network CIDR
TunnelID uuid.UUID `json:"tunnel_id"`
Comment string
CreatedAt time.Time
DeletedAt time.Time
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
}
// TableString outputs a table row summarizing the route, to be used
// when showing the user their routing table.
func (r Route) TableString() string {
deletedColumn := "-"
if !r.DeletedAt.IsZero() {
deletedColumn = r.DeletedAt.Format(time.RFC3339)
}
return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t",
r.Network.String(),
r.Comment,
r.TunnelID,
r.CreatedAt.Format(time.RFC3339),
deletedColumn,
)
// CIDR is just a newtype wrapper around net.IPNet. It adds JSON unmarshalling.
type CIDR net.IPNet
func (c *CIDR) String() string {
n := net.IPNet(*c)
return n.String()
}
// UnmarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
func (r *Route) UnmarshalJSON(data []byte) error {
// This is the raw JSON format that cloudflared receives from tunnelstore.
// Note it does not understand types like IPNet.
var resp struct {
Network string `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"`
Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
// UnmarshalJSON parses a JSON string into net.IPNet
func (c *CIDR) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return errors.Wrap(err, "error parsing cidr string")
}
if err := json.Unmarshal(data, &resp); err != nil {
return err
_, network, err := net.ParseCIDR(s)
if err != nil {
return errors.Wrap(err, "error parsing invalid network from backend")
}
// Parse the raw JSON into a properly-typed response.
_, network, err := net.ParseCIDR(resp.Network)
if err != nil || network == nil {
return fmt.Errorf("backend returned invalid network %s", resp.Network)
if network == nil {
return fmt.Errorf("backend returned invalid network %s", s)
}
r.Network = *network
r.TunnelID = resp.TunnelID
r.Comment = resp.Comment
r.CreatedAt = resp.CreatedAt
r.DeletedAt = resp.DeletedAt
*c = CIDR(*network)
return nil
}
@@ -78,7 +58,6 @@ type NewRoute struct {
// MarshalJSON handles fields with non-JSON types (e.g. net.IPNet).
func (r NewRoute) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Network string `json:"network"`
TunnelID uuid.UUID `json:"tunnel_id"`
Comment string `json:"comment"`
}{
@@ -86,3 +65,36 @@ func (r NewRoute) MarshalJSON() ([]byte, error) {
Comment: r.Comment,
})
}
// DetailedRoute is just a Route with some extra fields, e.g. TunnelName.
type DetailedRoute struct {
Network CIDR
TunnelID uuid.UUID `json:"tunnel_id"`
Comment string
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
TunnelName string `json:"tunnel_name"`
}
// IsZero checks if DetailedRoute is the zero value.
func (r *DetailedRoute) IsZero() bool {
return r.TunnelID == uuid.Nil
}
// TableString outputs a table row summarizing the route, to be used
// when showing the user their routing table.
func (r DetailedRoute) TableString() string {
deletedColumn := "-"
if !r.DeletedAt.IsZero() {
deletedColumn = r.DeletedAt.Format(time.RFC3339)
}
return fmt.Sprintf(
"%s\t%s\t%s\t%s\t%s\t%s\t",
r.Network.String(),
r.Comment,
r.TunnelID,
r.TunnelName,
r.CreatedAt.Format(time.RFC3339),
deletedColumn,
)
}

View File

@@ -21,7 +21,7 @@ func TestUnmarshalRoute(t *testing.T) {
"deleted_at":null
}`
var r Route
err := r.UnmarshalJSON([]byte(data))
err := json.Unmarshal([]byte(data), &r)
// Check everything worked
require.NoError(t, err)
@@ -29,10 +29,34 @@ func TestUnmarshalRoute(t *testing.T) {
require.Equal(t, "test", r.Comment)
_, cidr, err := net.ParseCIDR("10.1.2.40/29")
require.NoError(t, err)
require.Equal(t, *cidr, r.Network)
require.Equal(t, CIDR(*cidr), r.Network)
require.Equal(t, "test", r.Comment)
}
func TestUnmarshalDetailedRoute(t *testing.T) {
// Response from the teamnet route backend
data := `{
"network":"10.1.2.40/29",
"tunnel_id":"fba6ffea-807f-4e7a-a740-4184ee1b82c8",
"tunnel_name":"Mr. Tun",
"comment":"test",
"created_at":"2020-12-22T02:00:15.587008Z",
"deleted_at":null
}`
var r DetailedRoute
err := json.Unmarshal([]byte(data), &r)
// Check everything worked
require.NoError(t, err)
require.Equal(t, uuid.MustParse("fba6ffea-807f-4e7a-a740-4184ee1b82c8"), r.TunnelID)
require.Equal(t, "test", r.Comment)
_, cidr, err := net.ParseCIDR("10.1.2.40/29")
require.NoError(t, err)
require.Equal(t, CIDR(*cidr), r.Network)
require.Equal(t, "test", r.Comment)
require.Equal(t, "Mr. Tun", r.TunnelName)
}
func TestMarshalNewRoute(t *testing.T) {
_, network, err := net.ParseCIDR("1.2.3.4/32")
require.NoError(t, err)
@@ -58,8 +82,8 @@ func TestRouteTableString(t *testing.T) {
_, network, err := net.ParseCIDR("1.2.3.4/32")
require.NoError(t, err)
require.NotNil(t, network)
r := Route{
Network: *network,
r := DetailedRoute{
Network: CIDR(*network),
}
row := r.TableString()
fmt.Println(row)