331 lines
6.2 KiB
Go
331 lines
6.2 KiB
Go
package tun2
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Xe/uuid"
|
|
)
|
|
|
|
// testing constants
|
|
const (
|
|
user = "shachi"
|
|
token = "orcaz r kewl"
|
|
noPermToken = "aw heck"
|
|
otherUserToken = "even more heck"
|
|
domain = "cetacean.club"
|
|
)
|
|
|
|
func TestNewServerNullConfig(t *testing.T) {
|
|
_, err := NewServer(nil)
|
|
if err == nil {
|
|
t.Fatalf("expected NewServer(nil) to fail, got non-failure")
|
|
}
|
|
}
|
|
|
|
func TestGen502Page(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequest("GET", "http://cetacean.club", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
substring := uuid.New()
|
|
|
|
req = req.WithContext(ctx)
|
|
req.Header.Add("X-Request-Id", substring)
|
|
req.Host = "cetacean.club"
|
|
|
|
resp := gen502Page(req)
|
|
if resp == nil {
|
|
t.Fatalf("expected response to be non-nil")
|
|
}
|
|
|
|
if resp.Body != nil {
|
|
defer resp.Body.Close()
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !strings.Contains(string(data), substring) {
|
|
fmt.Println(string(data))
|
|
t.Fatalf("502 page did not contain needed substring %q", substring)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBackendAuthV1(t *testing.T) {
|
|
st := MockStorage()
|
|
|
|
s, err := NewServer(&ServerConfig{
|
|
Storage: st,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer s.Close()
|
|
|
|
st.AddRoute(domain, user)
|
|
st.AddToken(token, user, []string{"connect"})
|
|
st.AddToken(noPermToken, user, nil)
|
|
st.AddToken(otherUserToken, "cadey", []string{"connect"})
|
|
|
|
cases := []struct {
|
|
name string
|
|
auth Auth
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "basic everything should work",
|
|
auth: Auth{
|
|
Token: token,
|
|
Domain: domain,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "invalid domain",
|
|
auth: Auth{
|
|
Token: token,
|
|
Domain: "aw.heck",
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid token",
|
|
auth: Auth{
|
|
Token: "asdfwtweg",
|
|
Domain: domain,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "invalid token scopes",
|
|
auth: Auth{
|
|
Token: noPermToken,
|
|
Domain: domain,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "user token doesn't match domain owner",
|
|
auth: Auth{
|
|
Token: otherUserToken,
|
|
Domain: domain,
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
data, err := json.Marshal(cs.auth)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, _, err = s.backendAuthv1(ctx, bytes.NewBuffer(data))
|
|
|
|
if cs.wantErr && err == nil {
|
|
t.Fatalf("auth did not err as expected")
|
|
}
|
|
|
|
if !cs.wantErr && err != nil {
|
|
t.Fatalf("unexpected auth err: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBackendRouting(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
st := MockStorage()
|
|
|
|
st.AddRoute(domain, user)
|
|
st.AddToken(token, user, []string{"connect"})
|
|
|
|
s, err := NewServer(&ServerConfig{
|
|
Storage: st,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer s.Close()
|
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer l.Close()
|
|
|
|
go s.Listen(l, false)
|
|
|
|
cases := []struct {
|
|
name string
|
|
wantStatusCode int
|
|
handler http.HandlerFunc
|
|
}{
|
|
{
|
|
name: "200 everything's okay",
|
|
wantStatusCode: http.StatusOK,
|
|
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "HTTP 200, everything is okay :)", http.StatusOK)
|
|
}),
|
|
},
|
|
{
|
|
name: "500 internal error",
|
|
wantStatusCode: http.StatusInternalServerError,
|
|
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "HTTP 500, the world is on fire", http.StatusInternalServerError)
|
|
}),
|
|
},
|
|
}
|
|
|
|
for _, cs := range cases {
|
|
t.Run(cs.name, func(t *testing.T) {
|
|
ts := httptest.NewServer(cs.handler)
|
|
defer ts.Close()
|
|
|
|
cc := &ClientConfig{
|
|
ConnType: "tcp",
|
|
ServerAddr: l.Addr().String(),
|
|
Token: token,
|
|
BackendURL: ts.URL,
|
|
Domain: domain,
|
|
|
|
forceTCPClear: true,
|
|
}
|
|
|
|
c, err := NewClient(cc)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
go c.Connect(ctx) // TODO: fix the client library so this ends up actually getting cleaned up
|
|
|
|
time.Sleep(125 * time.Millisecond)
|
|
|
|
req, err := http.NewRequest("GET", "http://cetacean.club/", nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
resp, err := s.RoundTrip(req)
|
|
if err != nil {
|
|
t.Fatalf("error in doing round trip: %v", err)
|
|
}
|
|
|
|
if cs.wantStatusCode != resp.StatusCode {
|
|
resp.Write(os.Stdout)
|
|
t.Fatalf("got status %d instead of %d", resp.StatusCode, cs.wantStatusCode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func setupTestServer() (*Server, *mockStorage, net.Listener, error) {
|
|
st := MockStorage()
|
|
|
|
st.AddRoute(domain, user)
|
|
st.AddToken(token, user, []string{"connect"})
|
|
|
|
s, err := NewServer(&ServerConfig{
|
|
Storage: st,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
defer s.Close()
|
|
|
|
l, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
go s.Listen(l, false)
|
|
|
|
return s, st, l, nil
|
|
}
|
|
|
|
func BenchmarkHTTP200(b *testing.B) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
s, _, l, err := setupTestServer()
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
defer s.Close()
|
|
defer l.Close()
|
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
|
defer ts.Close()
|
|
|
|
cc := &ClientConfig{
|
|
ConnType: "tcp",
|
|
ServerAddr: l.Addr().String(),
|
|
Token: token,
|
|
BackendURL: ts.URL,
|
|
Domain: domain,
|
|
|
|
forceTCPClear: true,
|
|
}
|
|
|
|
c, err := NewClient(cc)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
go c.Connect(ctx) // TODO: fix the client library so this ends up actually getting cleaned up
|
|
|
|
for {
|
|
r := s.GetBackendsForDomain(domain)
|
|
if len(r) == 0 {
|
|
time.Sleep(125 * time.Millisecond)
|
|
continue
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
req, err := http.NewRequest("GET", "http://cetacean.club/", nil)
|
|
if err != nil {
|
|
b.Fatal(err)
|
|
}
|
|
|
|
_, err = s.RoundTrip(req)
|
|
if err != nil {
|
|
b.Fatalf("got error on initial request exchange: %v", err)
|
|
}
|
|
|
|
for n := 0; n < b.N; n++ {
|
|
resp, err := s.RoundTrip(req)
|
|
if err != nil {
|
|
b.Fatalf("got error on %d: %v", n, err)
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
b.Fail()
|
|
b.Logf("got %d instead of 200", resp.StatusCode)
|
|
}
|
|
}
|
|
}
|