move to packages
This commit is contained in:
parent
789b9af66e
commit
052a3f276b
|
@ -6,5 +6,4 @@ certs
|
||||||
/cmd/telebitd/telebitd
|
/cmd/telebitd/telebitd
|
||||||
/telebit
|
/telebit
|
||||||
/cmd/telebit/telebit
|
/cmd/telebit/telebit
|
||||||
/m
|
|
||||||
/debug
|
/debug
|
||||||
|
|
|
@ -51,12 +51,13 @@ func Run(ctx context.Context, config *Config) error {
|
||||||
|
|
||||||
authenticated := false
|
authenticated := false
|
||||||
for {
|
for {
|
||||||
|
fmt.Printf("debug serverURL:\n%+v", serverURL)
|
||||||
if conn, _, err := dialer.Dial(serverURL.String(), nil); err == nil {
|
if conn, _, err := dialer.Dial(serverURL.String(), nil); err == nil {
|
||||||
loginfo.Println("connected to remote server")
|
loginfo.Println("connected to remote server")
|
||||||
authenticated = true
|
authenticated = true
|
||||||
handler.HandleConn(ctx, conn)
|
handler.HandleConn(ctx, conn)
|
||||||
} else if !authenticated {
|
} else if !authenticated {
|
||||||
return fmt.Errorf("First connection to server failed - check auth: %v", err)
|
return fmt.Errorf("first connection to server failed - check auth: %s", err.Error())
|
||||||
}
|
}
|
||||||
loginfo.Println("disconnected from remote server")
|
loginfo.Println("disconnected from remote server")
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,11 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/relay"
|
"git.coolaj86.com/coolaj86/go-telebitd/relay"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/server"
|
"git.coolaj86.com/coolaj86/go-telebitd/server"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
@ -36,7 +38,7 @@ var (
|
||||||
argServerAdminBinding string
|
argServerAdminBinding string
|
||||||
argServerExternalBinding string
|
argServerExternalBinding string
|
||||||
argDeadTime int
|
argDeadTime int
|
||||||
connectionTable *server.Table
|
connectionTable *api.Table
|
||||||
secretKey string
|
secretKey string
|
||||||
wssHostName = "localhost.rootprojects.org"
|
wssHostName = "localhost.rootprojects.org"
|
||||||
adminHostName = telebit.InvalidAdminDomain
|
adminHostName = telebit.InvalidAdminDomain
|
||||||
|
@ -114,14 +116,14 @@ func main() {
|
||||||
ctx, cancelContext := context.WithCancel(context.Background())
|
ctx, cancelContext := context.WithCancel(context.Background())
|
||||||
defer cancelContext()
|
defer cancelContext()
|
||||||
|
|
||||||
serverStatus := server.NewStatus(ctx)
|
serverStatus := api.NewStatus(ctx)
|
||||||
serverStatus.AdminDomain = adminHostName
|
serverStatus.AdminDomain = adminHostName
|
||||||
serverStatus.WssDomain = wssHostName
|
serverStatus.WssDomain = wssHostName
|
||||||
serverStatus.Name = nickname
|
serverStatus.Name = nickname
|
||||||
serverStatus.DeadTime = server.NewStatusDeadTime(dwell, idle, cancelcheck)
|
serverStatus.DeadTime = api.NewStatusDeadTime(dwell, idle, cancelcheck)
|
||||||
serverStatus.LoadbalanceDefaultMethod = lbDefaultMethod
|
serverStatus.LoadbalanceDefaultMethod = lbDefaultMethod
|
||||||
|
|
||||||
connectionTable := server.NewTable(dwell, idle, lbDefaultMethod)
|
connectionTable := api.NewTable(dwell, idle, lbDefaultMethod)
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||||
|
@ -139,11 +141,29 @@ func main() {
|
||||||
|
|
||||||
authorizer := func(r *http.Request) (*server.Authz, error) {
|
authorizer := func(r *http.Request) (*server.Authz, error) {
|
||||||
// do we have a valid wss_client?
|
// do we have a valid wss_client?
|
||||||
tokenString := r.URL.Query().Get("access_token")
|
|
||||||
|
var tokenString string
|
||||||
|
if auth := strings.Split(r.Header.Get("Authorization"), " "); len(auth) > 1 {
|
||||||
|
// TODO handle Basic auth tokens as well
|
||||||
|
tokenString = auth[1]
|
||||||
|
}
|
||||||
|
if "" == tokenString {
|
||||||
|
tokenString = r.URL.Query().Get("access_token")
|
||||||
|
}
|
||||||
|
|
||||||
_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
_, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(secretKey), nil
|
return []byte(secretKey), nil
|
||||||
})
|
})
|
||||||
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authz := &server.Authz{
|
||||||
|
Domains: []string{
|
||||||
|
"target.rootprojects.org",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return authz, err
|
||||||
|
|
||||||
/*
|
/*
|
||||||
tokenString := r.URL.Query().Get("access_token")
|
tokenString := r.URL.Query().Get("access_token")
|
||||||
|
|
|
@ -1,40 +1,48 @@
|
||||||
package server
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"log"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
|
|
||||||
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/envelope"
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/server"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
endPointPrefix = "/api/org.rootprojects.tunnel/"
|
endPointPrefix = "/api/org.rootprojects.tunnel/"
|
||||||
)
|
)
|
||||||
|
|
||||||
var connectionTable *Table
|
var connectionTable *api.Table
|
||||||
var serverStatus *Status
|
var serverStatus *api.Status
|
||||||
var serverStatusAPI *Status
|
var serverStatusAPI *api.Status
|
||||||
|
|
||||||
//handleAdminClient -
|
//ListenAndServe -
|
||||||
// - expecting an existing oneConnListener with a qualified wss client connected.
|
// - expecting an existing oneConnListener with a qualified wss client connected.
|
||||||
// - auth will happen again since we were just peeking at the token.
|
// - auth will happen again since we were just peeking at the token.
|
||||||
func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
|
func ListenAndServe(mx *server.MPlexy, adminListener net.Listener) error {
|
||||||
serverStatus = ctx.Value(ctxServerStatus).(*Status)
|
//serverStatus = mx.ctx.Value(ctxServerStatus).(*Status)
|
||||||
|
|
||||||
|
connectionTable = mx.Status.ConnectionTable
|
||||||
|
serverStatusAPI = mx.Status
|
||||||
|
|
||||||
connectionTable = serverStatus.ConnectionTable
|
|
||||||
serverStatusAPI = serverStatus
|
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
router.PathPrefix("/admin/").Handler(http.StripPrefix("/admin/", http.FileServer(http.Dir("html/admin"))))
|
router.PathPrefix("/admin/").Handler(http.StripPrefix("/admin/", http.FileServer(http.Dir("html/admin"))))
|
||||||
|
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
loginfo.Println("HandleFunc /")
|
log.Println("HandleFunc /")
|
||||||
|
|
||||||
|
_, err := mx.AuthorizeAdmin(r)
|
||||||
|
if err == nil {
|
||||||
|
// TODO
|
||||||
|
w.Write([]byte("TODO: handle bad auth"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
@ -64,30 +72,20 @@ func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
Addr: ":80",
|
Addr: ":80",
|
||||||
Handler: router,
|
Handler: router,
|
||||||
}
|
}
|
||||||
|
return s.Serve(adminListener)
|
||||||
err := s.Serve(oneConn)
|
|
||||||
if err != nil {
|
|
||||||
loginfo.Println("Serve error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
loginfo.Println("Cancel signal hit")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStatusEndpoint(w http.ResponseWriter, r *http.Request) {
|
func getStatusEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
pc, _, _, _ := runtime.Caller(0)
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
loginfo.Println(runtime.FuncForPC(pc).Name())
|
log.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
statusContainer := NewStatusAPI(serverStatusAPI)
|
statusContainer := api.NewStatusAPI(serverStatusAPI)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
env := envelope.NewEnvelope("domains/GET")
|
env := NewResponse("domains/GET")
|
||||||
env.Result = statusContainer
|
env.Result = statusContainer
|
||||||
env.GenerateWriter(w)
|
env.GenerateWriter(w)
|
||||||
serverStatus.AdminStats.IncResponses()
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
@ -95,15 +93,15 @@ func getStatusEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func getDomainsEndpoint(w http.ResponseWriter, r *http.Request) {
|
func getDomainsEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
pc, _, _, _ := runtime.Caller(0)
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
loginfo.Println(runtime.FuncForPC(pc).Name())
|
log.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
domainsContainer := NewDomainsAPI(connectionTable.domains)
|
domainsContainer := api.NewDomainsAPI(connectionTable.Domains)
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
env := envelope.NewEnvelope("domains/GET")
|
env := NewResponse("domains/GET")
|
||||||
env.Result = domainsContainer
|
env.Result = domainsContainer
|
||||||
env.GenerateWriter(w)
|
env.GenerateWriter(w)
|
||||||
serverStatus.AdminStats.IncResponses()
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
@ -111,11 +109,11 @@ func getDomainsEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func getDomainEndpoint(w http.ResponseWriter, r *http.Request) {
|
func getDomainEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
pc, _, _, _ := runtime.Caller(0)
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
loginfo.Println(runtime.FuncForPC(pc).Name())
|
log.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
env := envelope.NewEnvelope("domain/GET")
|
env := NewResponse("domain/GET")
|
||||||
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
if id, ok := params["domain-name"]; !ok {
|
if id, ok := params["domain-name"]; !ok {
|
||||||
|
@ -124,16 +122,16 @@ func getDomainEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
env.ErrorDescription = "domain API requires a domain-name"
|
env.ErrorDescription = "domain API requires a domain-name"
|
||||||
} else {
|
} else {
|
||||||
domainName := id
|
domainName := id
|
||||||
if domainLB, ok := connectionTable.domains[domainName]; !ok {
|
if domainLB, ok := connectionTable.Domains[domainName]; !ok {
|
||||||
env.Error = "domain-name was not found"
|
env.Error = "domain-name was not found"
|
||||||
env.ErrorURI = r.RequestURI
|
env.ErrorURI = r.RequestURI
|
||||||
env.ErrorDescription = "domain-name not found"
|
env.ErrorDescription = "domain-name not found"
|
||||||
} else {
|
} else {
|
||||||
var domainAPIContainer []*ServerDomainAPI
|
var domainAPIContainer []*api.ServerDomainAPI
|
||||||
conns := domainLB.Connections()
|
conns := domainLB.Connections()
|
||||||
for pos := range conns {
|
for pos := range conns {
|
||||||
conn := conns[pos]
|
conn := conns[pos]
|
||||||
domainAPI := NewServerDomainAPI(conn, conn.DomainTrack[domainName])
|
domainAPI := api.NewServerDomainAPI(conn, conn.DomainTrack[domainName])
|
||||||
domainAPIContainer = append(domainAPIContainer, domainAPI)
|
domainAPIContainer = append(domainAPIContainer, domainAPI)
|
||||||
}
|
}
|
||||||
env.Result = domainAPIContainer
|
env.Result = domainAPIContainer
|
||||||
|
@ -146,21 +144,21 @@ func getDomainEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func getServersEndpoint(w http.ResponseWriter, r *http.Request) {
|
func getServersEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
pc, _, _, _ := runtime.Caller(0)
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
loginfo.Println(runtime.FuncForPC(pc).Name())
|
log.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
serverContainer := NewServerAPIContainer()
|
serverContainer := api.NewServerAPIContainer()
|
||||||
|
|
||||||
for c := range connectionTable.Connections() {
|
for c := range connectionTable.Connections() {
|
||||||
serverAPI := NewServersAPI(c)
|
serverAPI := api.NewServersAPI(c)
|
||||||
serverContainer.Servers = append(serverContainer.Servers, serverAPI)
|
serverContainer.Servers = append(serverContainer.Servers, serverAPI)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
env := envelope.NewEnvelope("servers/GET")
|
env := NewResponse("servers/GET")
|
||||||
env.Result = serverContainer
|
env.Result = serverContainer
|
||||||
env.GenerateWriter(w)
|
env.GenerateWriter(w)
|
||||||
serverStatus.AdminStats.IncResponses()
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
@ -168,11 +166,11 @@ func getServersEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func getServerEndpoint(w http.ResponseWriter, r *http.Request) {
|
func getServerEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
pc, _, _, _ := runtime.Caller(0)
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
loginfo.Println(runtime.FuncForPC(pc).Name())
|
log.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
serverStatus.AdminStats.IncRequests()
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
env := envelope.NewEnvelope("server/GET")
|
env := NewResponse("server/GET")
|
||||||
|
|
||||||
params := mux.Vars(r)
|
params := mux.Vars(r)
|
||||||
if id, ok := params["server-id"]; !ok {
|
if id, ok := params["server-id"]; !ok {
|
||||||
|
@ -193,8 +191,8 @@ func getServerEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
env.ErrorURI = r.RequestURI
|
env.ErrorURI = r.RequestURI
|
||||||
env.ErrorDescription = "missing server-id, make sure desired service-id is in servers"
|
env.ErrorDescription = "missing server-id, make sure desired service-id is in servers"
|
||||||
} else {
|
} else {
|
||||||
loginfo.Println("test")
|
log.Println("test")
|
||||||
serverAPI := NewServerAPI(conn)
|
serverAPI := api.NewServerAPI(conn)
|
||||||
env.Result = serverAPI
|
env.Result = serverAPI
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package envelope
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -7,8 +7,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Envelope -- Standard response structure
|
//Response -- Standard response structure
|
||||||
type Envelope struct {
|
type Response struct {
|
||||||
TransactionType string `json:"type"`
|
TransactionType string `json:"type"`
|
||||||
Schema string `json:"schema"`
|
Schema string `json:"schema"`
|
||||||
TransactionTimeStamp int64 `json:"txts"`
|
TransactionTimeStamp int64 `json:"txts"`
|
||||||
|
@ -19,12 +19,12 @@ type Envelope struct {
|
||||||
Result interface{} `json:"result"`
|
Result interface{} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewEnvelope -- Constructor
|
//NewResponse -- Constructor
|
||||||
func NewEnvelope(transactionType string) (p *Envelope) {
|
func NewResponse(transactionType string) (p *Response) {
|
||||||
// TODO BUG use atomic
|
// TODO BUG use atomic
|
||||||
transactionID++
|
transactionID++
|
||||||
|
|
||||||
p = &Envelope{}
|
p = &Response{}
|
||||||
p.TransactionType = transactionType
|
p.TransactionType = transactionType
|
||||||
p.TransactionID = transactionID
|
p.TransactionID = transactionID
|
||||||
p.TransactionTimeStamp = time.Now().Unix()
|
p.TransactionTimeStamp = time.Now().Unix()
|
||||||
|
@ -34,13 +34,13 @@ func NewEnvelope(transactionType string) (p *Envelope) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generate -- encode into JSON and return string
|
//Generate -- encode into JSON and return string
|
||||||
func (e *Envelope) Generate() string {
|
func (e *Response) Generate() string {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
json.NewEncoder(buf).Encode(e)
|
json.NewEncoder(buf).Encode(e)
|
||||||
return buf.String()
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
//GenerateWriter --
|
//GenerateWriter --
|
||||||
func (e *Envelope) GenerateWriter(w io.Writer) {
|
func (e *Response) GenerateWriter(w io.Writer) {
|
||||||
json.NewEncoder(w).Encode(e)
|
json.NewEncoder(w).Encode(e)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package envelope
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//ConnectionStatsAPI --
|
//ConnectionStatsAPI --
|
||||||
type ConnectionStatsAPI struct {
|
type ConnectionStatsAPI struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//DomainsAPI -- A collections of all the domains
|
//DomainsAPI -- A collections of all the domains
|
||||||
//List of Domains -> DomainAPI
|
//List of Domains -> DomainAPI
|
||||||
|
@ -36,14 +36,14 @@ type DomainAPI struct {
|
||||||
func NewDomainAPI(domain string, domainLoadBalance *DomainLoadBalance) (p *DomainAPI) {
|
func NewDomainAPI(domain string, domainLoadBalance *DomainLoadBalance) (p *DomainAPI) {
|
||||||
p = new(DomainAPI)
|
p = new(DomainAPI)
|
||||||
p.DomainName = domain
|
p.DomainName = domain
|
||||||
for pos := range domainLoadBalance.connections {
|
for pos := range domainLoadBalance.Connections() {
|
||||||
ds := NewDomainServerAPI(domain, domainLoadBalance.connections[pos])
|
ds := NewDomainServerAPI(domain, domainLoadBalance.Connections()[pos])
|
||||||
p.Servers = append(p.Servers, ds)
|
p.Servers = append(p.Servers, ds)
|
||||||
p.TotalServers++
|
p.TotalServers++
|
||||||
p.Traffic.BytesIn += domainLoadBalance.connections[pos].BytesIn()
|
p.Traffic.BytesIn += domainLoadBalance.Connections()[pos].BytesIn()
|
||||||
p.Traffic.BytesOut += domainLoadBalance.connections[pos].BytesOut()
|
p.Traffic.BytesOut += domainLoadBalance.Connections()[pos].BytesOut()
|
||||||
p.Traffic.Requests += domainLoadBalance.connections[pos].requests
|
p.Traffic.Requests += domainLoadBalance.Connections()[pos].Requests
|
||||||
p.Traffic.Responses += domainLoadBalance.connections[pos].responses
|
p.Traffic.Responses += domainLoadBalance.Connections()[pos].Responses
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
//ServerAPI -- Structure to support the server API
|
//ServerAPI -- Structure to support the server API
|
||||||
type ServerAPI struct {
|
type ServerAPI struct {
|
||||||
|
@ -25,7 +27,7 @@ func NewServerAPI(c *Connection) (s *ServerAPI) {
|
||||||
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
||||||
s.BytesIn = c.BytesIn()
|
s.BytesIn = c.BytesIn()
|
||||||
s.BytesOut = c.BytesOut()
|
s.BytesOut = c.BytesOut()
|
||||||
s.Source = c.source
|
s.Source = c.Source()
|
||||||
|
|
||||||
for domainName := range c.DomainTrack {
|
for domainName := range c.DomainTrack {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//ServerDomainsAPI -- Structure to support the server API
|
//ServerDomainsAPI -- Structure to support the server API
|
||||||
type ServerDomainsAPI struct {
|
type ServerDomainsAPI struct {
|
||||||
|
@ -17,8 +17,8 @@ func NewServerDomainsAPI(c *Connection, d *DomainTrack) (s *ServerDomainsAPI) {
|
||||||
s.ServerID = c.ConnectionID()
|
s.ServerID = c.ConnectionID()
|
||||||
s.BytesIn = d.BytesIn()
|
s.BytesIn = d.BytesIn()
|
||||||
s.BytesOut = d.BytesOut()
|
s.BytesOut = d.BytesOut()
|
||||||
s.Requests = d.requests
|
s.Requests = d.Requests()
|
||||||
s.Responses = d.responses
|
s.Responses = d.Responses()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -53,8 +53,8 @@ func NewServerDomainAPI(c *Connection, d *DomainTrack) (s *ServerDomainAPI) {
|
||||||
s.ServerID = c.ConnectionID()
|
s.ServerID = c.ConnectionID()
|
||||||
s.BytesIn = d.BytesIn()
|
s.BytesIn = d.BytesIn()
|
||||||
s.BytesOut = d.BytesOut()
|
s.BytesOut = d.BytesOut()
|
||||||
s.Requests = d.requests
|
s.Requests = d.Requests()
|
||||||
s.Responses = d.responses
|
s.Responses = d.Responses()
|
||||||
s.Source = c.Source()
|
s.Source = c.Source()
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
//ServersAPI -- Structure to support the server API
|
//ServersAPI -- Structure to support the server API
|
||||||
type ServersAPI struct {
|
type ServersAPI struct {
|
||||||
|
@ -27,8 +31,8 @@ func NewServersAPI(c *Connection) (s *ServersAPI) {
|
||||||
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
||||||
s.BytesIn = c.BytesIn()
|
s.BytesIn = c.BytesIn()
|
||||||
s.BytesOut = c.BytesOut()
|
s.BytesOut = c.BytesOut()
|
||||||
s.Requests = c.requests
|
s.Requests = c.Requests
|
||||||
s.Responses = c.responses
|
s.Responses = c.Responses
|
||||||
s.Source = c.Source()
|
s.Source = c.Source()
|
||||||
s.State = c.State()
|
s.State = c.State()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
@ -26,7 +26,7 @@ func NewStatusAPI(c *Status) (s *StatusAPI) {
|
||||||
s.WssDomain = c.WssDomain
|
s.WssDomain = c.WssDomain
|
||||||
s.AdminDomain = c.AdminDomain
|
s.AdminDomain = c.AdminDomain
|
||||||
s.LoadbalanceDefaultMethod = c.LoadbalanceDefaultMethod
|
s.LoadbalanceDefaultMethod = c.LoadbalanceDefaultMethod
|
||||||
s.DeadTime = NewStatusDeadTimeAPI(c.DeadTime.dwell, c.DeadTime.idle, c.DeadTime.cancelcheck)
|
s.DeadTime = NewStatusDeadTimeAPI(c.DeadTime.dwell, c.DeadTime.idle, c.DeadTime.Cancelcheck)
|
||||||
s.AdminStats = NewTrafficAPI(c.AdminStats.Requests, c.AdminStats.Responses, c.AdminStats.BytesIn, c.AdminStats.BytesOut)
|
s.AdminStats = NewTrafficAPI(c.AdminStats.Requests, c.AdminStats.Responses, c.AdminStats.BytesIn, c.AdminStats.BytesOut)
|
||||||
s.TrafficStats = NewTrafficAPI(c.TrafficStats.Requests, c.TrafficStats.Responses, c.TrafficStats.BytesIn, c.TrafficStats.BytesOut)
|
s.TrafficStats = NewTrafficAPI(c.TrafficStats.Requests, c.TrafficStats.Responses, c.TrafficStats.BytesIn, c.TrafficStats.BytesOut)
|
||||||
s.ExtConnections = NewConnectionStatsAPI(c.ExtConnections.Connections, c.ExtConnections.TotalConnections)
|
s.ExtConnections = NewConnectionStatsAPI(c.ExtConnections.Connections, c.ExtConnections.TotalConnections)
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//StatusDeadTimeAPI -- structure for deadtime configuration
|
//StatusDeadTimeAPI -- structure for deadtime configuration
|
||||||
type StatusDeadTimeAPI struct {
|
type StatusDeadTimeAPI struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//TrafficStats --
|
//TrafficStats --
|
||||||
type TrafficAPI struct {
|
type TrafficAPI struct {
|
|
@ -1,8 +1,9 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -40,19 +41,19 @@ func NewTracking() (p *Tracking) {
|
||||||
|
|
||||||
//Run -
|
//Run -
|
||||||
func (p *Tracking) Run(ctx context.Context) {
|
func (p *Tracking) Run(ctx context.Context) {
|
||||||
loginfo.Println("Tracking Running")
|
log.Println("Tracking Running")
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
loginfo.Println("Cancel signal hit")
|
log.Println("Cancel signal hit")
|
||||||
return
|
return
|
||||||
|
|
||||||
case connection := <-p.register:
|
case connection := <-p.register:
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
key := connection.conn.RemoteAddr().String()
|
key := connection.conn.RemoteAddr().String()
|
||||||
loginfo.Println("register fired", key)
|
log.Println("register fired", key)
|
||||||
p.connections[key] = connection
|
p.connections[key] = connection
|
||||||
p.list()
|
p.list()
|
||||||
p.mutex.Unlock()
|
p.mutex.Unlock()
|
||||||
|
@ -60,7 +61,7 @@ func (p *Tracking) Run(ctx context.Context) {
|
||||||
case connection := <-p.unregister:
|
case connection := <-p.unregister:
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
key := connection.RemoteAddr().String()
|
key := connection.RemoteAddr().String()
|
||||||
loginfo.Println("unregister fired", key)
|
log.Println("unregister fired", key)
|
||||||
if _, ok := p.connections[key]; ok {
|
if _, ok := p.connections[key]; ok {
|
||||||
delete(p.connections, key)
|
delete(p.connections, key)
|
||||||
}
|
}
|
||||||
|
@ -72,7 +73,7 @@ func (p *Tracking) Run(ctx context.Context) {
|
||||||
|
|
||||||
func (p *Tracking) list() {
|
func (p *Tracking) list() {
|
||||||
for c := range p.connections {
|
for c := range p.connections {
|
||||||
loginfo.Println(c)
|
log.Println(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -12,6 +13,8 @@ import (
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/packer"
|
"git.coolaj86.com/coolaj86/go-telebitd/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var connectionID int64 = 0
|
||||||
|
|
||||||
// Connection track websocket and faciliates in and out data
|
// Connection track websocket and faciliates in and out data
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
@ -43,11 +46,11 @@ type Connection struct {
|
||||||
// bytes out
|
// bytes out
|
||||||
bytesOut int64
|
bytesOut int64
|
||||||
|
|
||||||
// requests
|
// Requests
|
||||||
requests int64
|
Requests int64 // TODO atomic
|
||||||
|
|
||||||
// response
|
// Response
|
||||||
responses int64
|
Responses int64 // TODO atomic
|
||||||
|
|
||||||
// Connect Time
|
// Connect Time
|
||||||
connectTime time.Time
|
connectTime time.Time
|
||||||
|
@ -79,8 +82,8 @@ func NewConnection(connectionTable *Table, conn *websocket.Conn, remoteAddress s
|
||||||
p.serverName = serverName
|
p.serverName = serverName
|
||||||
p.bytesIn = 0
|
p.bytesIn = 0
|
||||||
p.bytesOut = 0
|
p.bytesOut = 0
|
||||||
p.requests = 0
|
p.Requests = 0
|
||||||
p.responses = 0
|
p.Responses = 0
|
||||||
p.send = make(chan *SendTrack)
|
p.send = make(chan *SendTrack)
|
||||||
p.connectTime = time.Now()
|
p.connectTime = time.Now()
|
||||||
p.initialDomains = initialDomains
|
p.initialDomains = initialDomains
|
||||||
|
@ -153,11 +156,13 @@ func (c *Connection) addOut(num int64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Connection) addRequests() {
|
func (c *Connection) addRequests() {
|
||||||
c.requests = c.requests + 1
|
// TODO atomic
|
||||||
|
c.Requests++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Connection) addResponse() {
|
func (c *Connection) addResponse() {
|
||||||
c.responses = c.responses + 1
|
// TODO atomic
|
||||||
|
c.Responses++
|
||||||
}
|
}
|
||||||
|
|
||||||
//ConnectionTable -- property
|
//ConnectionTable -- property
|
||||||
|
@ -208,7 +213,7 @@ func (c *Connection) NextWriter(wssMessageType int) (io.WriteCloser, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is returning a nil error actually the proper thing to do here?
|
// Is returning a nil error actually the proper thing to do here?
|
||||||
loginfo.Println("NextWriter aborted, state is not true")
|
log.Println("NextWriter aborted, state is not true")
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,23 +234,23 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
defer func() {
|
defer func() {
|
||||||
c.connectionTable.unregister <- c
|
c.connectionTable.unregister <- c
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
loginfo.Println("reader defer", c)
|
log.Println("reader defer", c)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
loginfo.Println("Reader Start ", c)
|
log.Println("Reader Start ", c)
|
||||||
|
|
||||||
//c.conn.SetReadLimit(65535)
|
//c.conn.SetReadLimit(65535)
|
||||||
for {
|
for {
|
||||||
_, message, err := c.conn.ReadMessage()
|
_, message, err := c.conn.ReadMessage()
|
||||||
|
|
||||||
//loginfo.Println("ReadMessage", msgType, err)
|
//log.Println("ReadMessage", msgType, err)
|
||||||
|
|
||||||
c.Update()
|
c.Update()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
c.SetState(false)
|
c.SetState(false)
|
||||||
loginfo.Printf("error: %v", err)
|
log.Printf("error: %v", err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
@ -255,10 +260,10 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
key := fmt.Sprintf("%s:%d", p.Address(), p.Port())
|
key := fmt.Sprintf("%s:%d", p.Address(), p.Port())
|
||||||
track, err := connectionTrack.Lookup(key)
|
track, err := connectionTrack.Lookup(key)
|
||||||
|
|
||||||
//loginfo.Println(hex.Dump(p.Data.Data()))
|
//log.Println(hex.Dump(p.Data.Data()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//loginfo.Println("Unable to locate Tracking for ", key)
|
//log.Println("Unable to locate Tracking for ", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +278,7 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
|
|
||||||
c.addIn(int64(len(message)))
|
c.addIn(int64(len(message)))
|
||||||
c.addResponse()
|
c.addResponse()
|
||||||
//loginfo.Println("end of read")
|
//log.Println("end of read")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,14 +286,14 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
func (c *Connection) Writer() {
|
func (c *Connection) Writer() {
|
||||||
defer c.conn.Close()
|
defer c.conn.Close()
|
||||||
|
|
||||||
loginfo.Println("Writer Start ", c)
|
log.Println("Writer Start ", c)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
||||||
case message := <-c.send:
|
case message := <-c.send:
|
||||||
w, err := c.NextWriter(websocket.BinaryMessage)
|
w, err := c.NextWriter(websocket.BinaryMessage)
|
||||||
loginfo.Println("next writer ", w)
|
log.Println("next writer ", w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.SetState(false)
|
c.SetState(false)
|
||||||
return
|
return
|
||||||
|
@ -312,12 +317,12 @@ func (c *Connection) Writer() {
|
||||||
//if ok then add to structure, else warn there is something wrong
|
//if ok then add to structure, else warn there is something wrong
|
||||||
domainTrack.AddIn(messageLen)
|
domainTrack.AddIn(messageLen)
|
||||||
domainTrack.AddRequests()
|
domainTrack.AddRequests()
|
||||||
loginfo.Println("adding ", messageLen, " to ", message.domain)
|
log.Println("adding ", messageLen, " to ", message.domain)
|
||||||
} else {
|
} else {
|
||||||
logdebug.Println("attempting to add bytes to ", message.domain, "it does not exist")
|
log.Println("attempting to add bytes to ", message.domain, "it does not exist")
|
||||||
logdebug.Println(c.DomainTrack)
|
log.Println(c.DomainTrack)
|
||||||
}
|
}
|
||||||
loginfo.Println(c)
|
log.Println(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//Registration -- A connection registration structure used to bring up a connection
|
//Registration -- A connection registration structure used to bring up a connection
|
|
@ -1,7 +1,8 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +55,7 @@ func (p *DomainLoadBalance) NextMember() (conn *Connection) {
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
//check for round robin, if not RR then drop out and call calculate
|
//check for round robin, if not RR then drop out and call calculate
|
||||||
loginfo.Println("NextMember:", p)
|
log.Println("NextMember:", p)
|
||||||
if p.method == lbmRoundRobin {
|
if p.method == lbmRoundRobin {
|
||||||
p.lastmember++
|
p.lastmember++
|
||||||
if p.lastmember >= p.count {
|
if p.lastmember >= p.count {
|
||||||
|
@ -75,33 +76,33 @@ func (p *DomainLoadBalance) NextMember() (conn *Connection) {
|
||||||
//this should not affect the next member calculation in RR. However it many in other
|
//this should not affect the next member calculation in RR. However it many in other
|
||||||
//methods
|
//methods
|
||||||
func (p *DomainLoadBalance) AddConnection(conn *Connection) []*Connection {
|
func (p *DomainLoadBalance) AddConnection(conn *Connection) []*Connection {
|
||||||
loginfo.Println("AddConnection", fmt.Sprintf("%p", conn))
|
log.Println("AddConnection", fmt.Sprintf("%p", conn))
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
p.connections = append(p.connections, conn)
|
p.connections = append(p.connections, conn)
|
||||||
p.count++
|
p.count++
|
||||||
loginfo.Println("AddConnection", p)
|
log.Println("AddConnection", p)
|
||||||
return p.connections
|
return p.connections
|
||||||
}
|
}
|
||||||
|
|
||||||
//RemoveConnection -- removes a matching connection from the list. This may
|
//RemoveConnection -- removes a matching connection from the list. This may
|
||||||
//affect the nextmember calculation if found so the recalc flag is set.
|
//affect the nextmember calculation if found so the recalc flag is set.
|
||||||
func (p *DomainLoadBalance) RemoveConnection(conn *Connection) {
|
func (p *DomainLoadBalance) RemoveConnection(conn *Connection) {
|
||||||
loginfo.Println("RemoveConnection", fmt.Sprintf("%p", conn))
|
log.Println("RemoveConnection", fmt.Sprintf("%p", conn))
|
||||||
|
|
||||||
p.mutex.Lock()
|
p.mutex.Lock()
|
||||||
defer p.mutex.Unlock()
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
//scan all the connections
|
//scan all the connections
|
||||||
for pos := range p.connections {
|
for pos := range p.connections {
|
||||||
loginfo.Println("RemoveConnection", pos, len(p.connections), p.count)
|
log.Println("RemoveConnection", pos, len(p.connections), p.count)
|
||||||
if p.connections[pos] == conn {
|
if p.connections[pos] == conn {
|
||||||
//found connection remove it
|
//found connection remove it
|
||||||
loginfo.Printf("found connection %p", conn)
|
log.Printf("found connection %p", conn)
|
||||||
p.connections[pos], p.connections = p.connections[len(p.connections)-1], p.connections[:len(p.connections)-1]
|
p.connections[pos], p.connections = p.connections[len(p.connections)-1], p.connections[:len(p.connections)-1]
|
||||||
p.count--
|
p.count--
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loginfo.Println("RemoveConnection:", p)
|
log.Println("RemoveConnection:", p)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//DomainMapping --
|
//DomainMapping --
|
||||||
type DomainMapping struct {
|
type DomainMapping struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//DomainTrack -- Tracking specifics for domains
|
//DomainTrack -- Tracking specifics for domains
|
||||||
type DomainTrack struct {
|
type DomainTrack struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//SendTrack -- Used as a channel communication to id domain asssociated to domain for outbound WSS
|
//SendTrack -- Used as a channel communication to id domain asssociated to domain for outbound WSS
|
||||||
type SendTrack struct {
|
type SendTrack struct {
|
|
@ -1,8 +1,10 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
//Status --
|
//Status --
|
||||||
|
@ -64,7 +66,7 @@ func (p *Status) ExtConnectionRegister(newTrack *Track) {
|
||||||
//ExtConnectionUnregister --
|
//ExtConnectionUnregister --
|
||||||
//unregisters an ext facing connection
|
//unregisters an ext facing connection
|
||||||
//intercept and update global statistics
|
//intercept and update global statistics
|
||||||
func (p *Status) ExtConnectionUnregister(extConn *WedgeConn) {
|
func (p *Status) ExtConnectionUnregister(extConn *tunnel.WedgeConn) {
|
||||||
p.ConnectionTracking.unregister <- extConn
|
p.ConnectionTracking.unregister <- extConn
|
||||||
p.ExtConnections.DecConnections()
|
p.ExtConnections.DecConnections()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//StatusDeadTime -- structure for deadtime configuration
|
//StatusDeadTime -- structure for deadtime configuration
|
||||||
type StatusDeadTime struct {
|
type StatusDeadTime struct {
|
||||||
dwell int
|
dwell int
|
||||||
idle int
|
idle int
|
||||||
cancelcheck int
|
Cancelcheck int
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewStatusDeadTime -- constructor
|
//NewStatusDeadTime -- constructor
|
||||||
|
@ -12,6 +12,6 @@ func NewStatusDeadTime(dwell, idle, cancelcheck int) (p *StatusDeadTime) {
|
||||||
p = new(StatusDeadTime)
|
p = new(StatusDeadTime)
|
||||||
p.dwell = dwell
|
p.dwell = dwell
|
||||||
p.idle = idle
|
p.idle = idle
|
||||||
p.cancelcheck = cancelcheck
|
p.Cancelcheck = cancelcheck
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//ConnectionStats --
|
//ConnectionStats --
|
||||||
type ConnectionStats struct {
|
type ConnectionStats struct {
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
//TrafficStats --
|
//TrafficStats --
|
||||||
type TrafficStats struct {
|
type TrafficStats struct {
|
|
@ -1,8 +1,9 @@
|
||||||
package server
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ const (
|
||||||
//Table maintains the set of connections
|
//Table maintains the set of connections
|
||||||
type Table struct {
|
type Table struct {
|
||||||
connections map[*Connection][]string
|
connections map[*Connection][]string
|
||||||
domains map[string]*DomainLoadBalance
|
Domains map[string]*DomainLoadBalance
|
||||||
register chan *Registration
|
register chan *Registration
|
||||||
unregister chan *Connection
|
unregister chan *Connection
|
||||||
domainAnnounce chan *DomainMapping
|
domainAnnounce chan *DomainMapping
|
||||||
|
@ -28,7 +29,7 @@ type Table struct {
|
||||||
func NewTable(dwell, idle int, balanceMethod string) (p *Table) {
|
func NewTable(dwell, idle int, balanceMethod string) (p *Table) {
|
||||||
p = new(Table)
|
p = new(Table)
|
||||||
p.connections = make(map[*Connection][]string)
|
p.connections = make(map[*Connection][]string)
|
||||||
p.domains = make(map[string]*DomainLoadBalance)
|
p.Domains = make(map[string]*DomainLoadBalance)
|
||||||
p.register = make(chan *Registration)
|
p.register = make(chan *Registration)
|
||||||
p.unregister = make(chan *Connection)
|
p.unregister = make(chan *Connection)
|
||||||
p.domainAnnounce = make(chan *DomainMapping)
|
p.domainAnnounce = make(chan *DomainMapping)
|
||||||
|
@ -48,11 +49,11 @@ func (c *Table) Connections() map[*Connection][]string {
|
||||||
//if that is the case the system stores these connections and then sends traffic back round-robin
|
//if that is the case the system stores these connections and then sends traffic back round-robin
|
||||||
//back to the WSS connections
|
//back to the WSS connections
|
||||||
func (c *Table) ConnByDomain(domain string) (*Connection, bool) {
|
func (c *Table) ConnByDomain(domain string) (*Connection, bool) {
|
||||||
for dn := range c.domains {
|
for dn := range c.Domains {
|
||||||
loginfo.Println(dn, domain)
|
log.Println(dn, domain)
|
||||||
}
|
}
|
||||||
if domainsLB, ok := c.domains[domain]; ok {
|
if domainsLB, ok := c.Domains[domain]; ok {
|
||||||
loginfo.Println("found")
|
log.Println("found")
|
||||||
conn := domainsLB.NextMember()
|
conn := domainsLB.NextMember()
|
||||||
return conn, ok
|
return conn, ok
|
||||||
}
|
}
|
||||||
|
@ -63,14 +64,14 @@ func (c *Table) ConnByDomain(domain string) (*Connection, bool) {
|
||||||
func (c *Table) reaper(delay int, idle int) {
|
func (c *Table) reaper(delay int, idle int) {
|
||||||
_ = "breakpoint"
|
_ = "breakpoint"
|
||||||
for {
|
for {
|
||||||
loginfo.Println("Reaper waiting for ", delay, " seconds")
|
log.Println("Reaper waiting for ", delay, " seconds")
|
||||||
time.Sleep(time.Duration(delay) * time.Second)
|
time.Sleep(time.Duration(delay) * time.Second)
|
||||||
|
|
||||||
loginfo.Println("Running scanning ", len(c.connections))
|
log.Println("Running scanning ", len(c.connections))
|
||||||
for d := range c.connections {
|
for d := range c.connections {
|
||||||
if !d.State() {
|
if !d.State() {
|
||||||
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
||||||
loginfo.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
log.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
||||||
delete(c.connections, d)
|
delete(c.connections, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ func (c *Table) GetConnection(serverID int64) (*Connection, error) {
|
||||||
|
|
||||||
//Run -- Execute
|
//Run -- Execute
|
||||||
func (c *Table) Run(ctx context.Context) {
|
func (c *Table) Run(ctx context.Context) {
|
||||||
loginfo.Println("ConnectionTable starting")
|
log.Println("ConnectionTable starting")
|
||||||
|
|
||||||
go c.reaper(c.dwell, c.idle)
|
go c.reaper(c.dwell, c.idle)
|
||||||
|
|
||||||
|
@ -99,11 +100,11 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
select {
|
select {
|
||||||
|
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
loginfo.Println("Cancel signal hit")
|
log.Println("Cancel signal hit")
|
||||||
return
|
return
|
||||||
|
|
||||||
case registration := <-c.register:
|
case registration := <-c.register:
|
||||||
loginfo.Println("register fired")
|
log.Println("register fired")
|
||||||
|
|
||||||
connection := NewConnection(c, registration.conn, registration.source, registration.initialDomains,
|
connection := NewConnection(c, registration.conn, registration.source, registration.initialDomains,
|
||||||
registration.connectionTrack, registration.serverName)
|
registration.connectionTrack, registration.serverName)
|
||||||
|
@ -115,17 +116,17 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
// add to the domains regirstation
|
// add to the domains regirstation
|
||||||
|
|
||||||
newDomain := domain
|
newDomain := domain
|
||||||
loginfo.Println("adding domain ", newDomain, " to connection ", connection.conn.RemoteAddr().String())
|
log.Println("adding domain ", newDomain, " to connection ", connection.conn.RemoteAddr().String())
|
||||||
|
|
||||||
//check to see if domain is already present.
|
//check to see if domain is already present.
|
||||||
if _, ok := c.domains[newDomain]; ok {
|
if _, ok := c.Domains[newDomain]; ok {
|
||||||
|
|
||||||
//append to a list of connections for that domain
|
//append to a list of connections for that domain
|
||||||
c.domains[newDomain].AddConnection(connection)
|
c.Domains[newDomain].AddConnection(connection)
|
||||||
} else {
|
} else {
|
||||||
//if not, then add as the 1st to the list of connections
|
//if not, then add as the 1st to the list of connections
|
||||||
c.domains[newDomain] = NewDomainLoadBalance(c.balanceMethod)
|
c.Domains[newDomain] = NewDomainLoadBalance(c.balanceMethod)
|
||||||
c.domains[newDomain].AddConnection(connection)
|
c.Domains[newDomain].AddConnection(connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
// add to the connection domain list
|
// add to the connection domain list
|
||||||
|
@ -136,26 +137,26 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
go connection.Reader(ctx)
|
go connection.Reader(ctx)
|
||||||
|
|
||||||
case connection := <-c.unregister:
|
case connection := <-c.unregister:
|
||||||
loginfo.Println("closing connection ", connection.conn.RemoteAddr().String())
|
log.Println("closing connection ", connection.conn.RemoteAddr().String())
|
||||||
|
|
||||||
//does connection exist in the connection table -- should never be an issue
|
//does connection exist in the connection table -- should never be an issue
|
||||||
if _, ok := c.connections[connection]; ok {
|
if _, ok := c.connections[connection]; ok {
|
||||||
|
|
||||||
//iterate over the connections for the domain
|
//iterate over the connections for the domain
|
||||||
for _, domain := range c.connections[connection] {
|
for _, domain := range c.connections[connection] {
|
||||||
loginfo.Println("remove domain", domain)
|
log.Println("remove domain", domain)
|
||||||
|
|
||||||
//removing domain, make sure it is present (should never be a problem)
|
//removing domain, make sure it is present (should never be a problem)
|
||||||
if _, ok := c.domains[domain]; ok {
|
if _, ok := c.Domains[domain]; ok {
|
||||||
|
|
||||||
domainLB := c.domains[domain]
|
domainLB := c.Domains[domain]
|
||||||
domainLB.RemoveConnection(connection)
|
domainLB.RemoveConnection(connection)
|
||||||
|
|
||||||
//check to see if domain is free of connections, if yes, delete map entry
|
//check to see if domain is free of connections, if yes, delete map entry
|
||||||
if domainLB.count > 0 {
|
if domainLB.count > 0 {
|
||||||
//ignore...perhaps we will do something here dealing wtih the lb method
|
//ignore...perhaps we will do something here dealing wtih the lb method
|
||||||
} else {
|
} else {
|
||||||
delete(c.domains, domain)
|
delete(c.Domains, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +166,7 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case domainMapping := <-c.domainAnnounce:
|
case domainMapping := <-c.domainAnnounce:
|
||||||
loginfo.Println("domainMapping fired ", domainMapping)
|
log.Println("domainMapping fired ", domainMapping)
|
||||||
//check to make sure connection is already regiered, you can no register a domain without an apporved connection
|
//check to make sure connection is already regiered, you can no register a domain without an apporved connection
|
||||||
//if connection, ok := connections[domainMapping.connection]; ok {
|
//if connection, ok := connections[domainMapping.connection]; ok {
|
||||||
|
|
|
@ -3,26 +3,43 @@ package relay
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/admin"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/server"
|
"git.coolaj86.com/coolaj86/go-telebitd/server"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/tunnel"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Relay is probably a layer that doesn't need to exist
|
||||||
type Relay struct {
|
type Relay struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
status *server.Status
|
status *api.Status
|
||||||
mx *server.MPlexy
|
mx *server.MPlexy
|
||||||
table *server.Table
|
table *api.Table
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, tlsConfig *tls.Config, authz server.Authorizer, status *server.Status, table *server.Table) *Relay {
|
// New initializes and returns a relay service
|
||||||
return &Relay{
|
func New(ctx context.Context, tlsConfig *tls.Config, authz server.Authorizer, status *api.Status, table *api.Table) *Relay {
|
||||||
|
// TODO do we need this already setup here? or is it just for logging?
|
||||||
|
status.ConnectionTracking = api.NewTracking()
|
||||||
|
status.ConnectionTable = table
|
||||||
|
authAdmin := authz
|
||||||
|
r := &Relay{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
status: status,
|
status: status,
|
||||||
table: table,
|
table: table,
|
||||||
mx: server.New(ctx, tlsConfig, authz, status),
|
mx: server.New(ctx, tlsConfig, authAdmin, authz, status), // TODO Accept
|
||||||
}
|
}
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListenAndServe sets up all of the tcp, http, https, and tunnel servers
|
||||||
func (r *Relay) ListenAndServe(port int) error {
|
func (r *Relay) ListenAndServe(port int) error {
|
||||||
|
|
||||||
serverStatus := r.status
|
serverStatus := r.status
|
||||||
|
@ -36,11 +53,7 @@ func (r *Relay) ListenAndServe(port int) error {
|
||||||
// - if tls, establish, protocol peek buffer, else decrypted
|
// - if tls, establish, protocol peek buffer, else decrypted
|
||||||
// - match protocol
|
// - match protocol
|
||||||
|
|
||||||
connectionTracking := server.NewTracking()
|
go r.status.ConnectionTracking.Run(r.ctx)
|
||||||
serverStatus.ConnectionTracking = connectionTracking
|
|
||||||
go connectionTracking.Run(r.ctx)
|
|
||||||
|
|
||||||
serverStatus.ConnectionTable = r.table
|
|
||||||
go serverStatus.ConnectionTable.Run(r.ctx)
|
go serverStatus.ConnectionTable.Run(r.ctx)
|
||||||
|
|
||||||
//serverStatus.GenericListeners = genericListeners
|
//serverStatus.GenericListeners = genericListeners
|
||||||
|
@ -48,5 +61,65 @@ func (r *Relay) ListenAndServe(port int) error {
|
||||||
// blocks until it can listen, which it can't until started
|
// blocks until it can listen, which it can't until started
|
||||||
go r.mx.MultiListenAndServe(port)
|
go r.mx.MultiListenAndServe(port)
|
||||||
|
|
||||||
|
// funnel target devices into WebSocket pool
|
||||||
|
tunnelListener := tunnel.NewListener()
|
||||||
|
r.mx.AcceptTargetServer = func(conn net.Conn) {
|
||||||
|
tunnelListener.Feed(conn)
|
||||||
|
}
|
||||||
|
go listenAndServeTargets(r.mx, tunnelListener)
|
||||||
|
|
||||||
|
// funnel admin clients to API
|
||||||
|
adminListener := tunnel.NewListener()
|
||||||
|
r.mx.AcceptAdminClient = func(conn net.Conn) {
|
||||||
|
adminListener.Feed(conn)
|
||||||
|
}
|
||||||
|
go admin.ListenAndServe(r.mx, adminListener)
|
||||||
|
|
||||||
return r.mx.Run()
|
return r.mx.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listenAndServeTargets(mx *server.MPlexy, handler net.Listener) error {
|
||||||
|
serverStatus := mx.Status
|
||||||
|
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Println("HandleFunc /")
|
||||||
|
switch url := r.URL.Path; url {
|
||||||
|
case "/":
|
||||||
|
log.Println("websocket opening ", r.RemoteAddr, " ", r.Host)
|
||||||
|
|
||||||
|
authz, err := mx.AuthorizeTarget(r)
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 65535,
|
||||||
|
WriteBufferSize: 65535,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("WebSocket upgrade failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("before connection table")
|
||||||
|
|
||||||
|
serverName := authz.Domains[0]
|
||||||
|
|
||||||
|
newRegistration := api.NewRegistration(conn, r.RemoteAddr, authz.Domains, serverStatus.ConnectionTracking, serverName)
|
||||||
|
serverStatus.WSSConnectionRegister(newRegistration)
|
||||||
|
|
||||||
|
if ok := <-newRegistration.CommCh(); !ok {
|
||||||
|
log.Println("connection registration failed ", newRegistration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("connection registration accepted ", newRegistration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO setup for http/2
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":80",
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
return s.Serve(handler)
|
||||||
|
}
|
||||||
|
|
|
@ -14,12 +14,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
|
|
||||||
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
telebit "git.coolaj86.com/coolaj86/go-telebitd"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/packer"
|
"git.coolaj86.com/coolaj86/go-telebitd/packer"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||||
"git.coolaj86.com/coolaj86/go-telebitd/sni"
|
"git.coolaj86.com/coolaj86/go-telebitd/sni"
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey string
|
type contextKey string
|
||||||
|
@ -27,9 +26,6 @@ type contextKey string
|
||||||
//CtxConnectionTrack
|
//CtxConnectionTrack
|
||||||
const (
|
const (
|
||||||
ctxServerStatus contextKey = "serverstatus"
|
ctxServerStatus contextKey = "serverstatus"
|
||||||
|
|
||||||
//ctxConnectionTable contextKey = "connectionTable"
|
|
||||||
|
|
||||||
ctxConfig contextKey = "tlsConfig"
|
ctxConfig contextKey = "tlsConfig"
|
||||||
ctxListenerRegistration contextKey = "listenerRegistration"
|
ctxListenerRegistration contextKey = "listenerRegistration"
|
||||||
ctxConnectionTrack contextKey = "connectionTrack"
|
ctxConnectionTrack contextKey = "connectionTrack"
|
||||||
|
@ -37,6 +33,7 @@ const (
|
||||||
ctxAdminHostName contextKey = "adminHostName"
|
ctxAdminHostName contextKey = "adminHostName"
|
||||||
ctxCancelCheck contextKey = "cancelcheck"
|
ctxCancelCheck contextKey = "cancelcheck"
|
||||||
ctxLoadbalanceDefaultMethod contextKey = "lbdefaultmethod"
|
ctxLoadbalanceDefaultMethod contextKey = "lbdefaultmethod"
|
||||||
|
//ctxConnectionTable contextKey = "connectionTable"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO isn't this restriction in the TLS lib?
|
// TODO isn't this restriction in the TLS lib?
|
||||||
|
@ -102,7 +99,7 @@ func (mx *MPlexy) multiListenAndServe(ctx context.Context, listenerRegistration
|
||||||
fmt.Println("New connection from %v on %v", conn.LocalAddr(), conn.RemoteAddr())
|
fmt.Println("New connection from %v on %v", conn.LocalAddr(), conn.RemoteAddr())
|
||||||
|
|
||||||
// TODO maybe put these into something like mx.newConnCh and have an mx.Accept()?
|
// TODO maybe put these into something like mx.newConnCh and have an mx.Accept()?
|
||||||
wedgeConn := NewWedgeConn(conn)
|
wedgeConn := tunnel.NewWedgeConn(conn)
|
||||||
go mx.accept(ctx, wedgeConn)
|
go mx.accept(ctx, wedgeConn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,22 +108,18 @@ func (mx *MPlexy) multiListenAndServe(ctx context.Context, listenerRegistration
|
||||||
//accept -
|
//accept -
|
||||||
// - accept a wedgeConnection along with all the other required attritvues
|
// - accept a wedgeConnection along with all the other required attritvues
|
||||||
// - peek into the buffer, determine TLS or unencrypted
|
// - peek into the buffer, determine TLS or unencrypted
|
||||||
// - if TSL, then terminate with a TLS endpoint, pass to handleStream
|
// - if TSL, then terminate with a TLS endpoint, pass to acceptEcryptedStream
|
||||||
// - if clearText, pass to handleStream
|
// - if clearText, pass to acceptPlainStream
|
||||||
func (mx *MPlexy) accept(ctx context.Context, wConn *WedgeConn) {
|
func (mx *MPlexy) accept(ctx context.Context, wConn *tunnel.WedgeConn) {
|
||||||
// TODO shouldn't this responsibility fall elsewhere?
|
|
||||||
// (otherwise I think we're keeping this function in memory while something else fails to end)
|
|
||||||
// (i.e. something, somewhere is missing a `go doStuff()`
|
|
||||||
defer wConn.Close()
|
|
||||||
peekCnt := 10
|
peekCnt := 10
|
||||||
|
|
||||||
encryptMode := encryptNone
|
encryptMode := encryptNone
|
||||||
|
|
||||||
loginfo.Println("conn", wConn, wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
loginfo.Println("new conn", wConn, wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
||||||
peek, err := wConn.Peek(peekCnt)
|
peek, err := wConn.Peek(peekCnt)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println("error while peeking")
|
loginfo.Println("error while peeking")
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,85 +145,61 @@ func (mx *MPlexy) accept(ctx context.Context, wConn *WedgeConn) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oneConn := &oneConnListener{wConn}
|
|
||||||
tlsConfig := ctx.Value(ctxConfig).(*tls.Config)
|
|
||||||
|
|
||||||
if encryptMode == encryptSSLV2 {
|
if encryptMode == encryptSSLV2 {
|
||||||
loginfo.Println("<= SSLv2 is not accepted")
|
loginfo.Println("<= SSLv2 is not accepted")
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if encryptMode == encryptNone {
|
if encryptMode == encryptNone {
|
||||||
loginfo.Println("Handle Unencrypted")
|
loginfo.Println("Handle Unencrypted")
|
||||||
mx.handleStream(ctx, wConn)
|
mx.acceptPlainStream(ctx, wConn, false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loginfo.Println("Handle Encryption")
|
loginfo.Println("Handle Encryption")
|
||||||
|
mx.acceptEncryptedStream(ctx, wConn)
|
||||||
// check SNI heading
|
|
||||||
// if matched, then looks like a WSS connection
|
|
||||||
// else external don't pull off TLS.
|
|
||||||
|
|
||||||
peek, err = wConn.PeekAll()
|
|
||||||
if err != nil {
|
|
||||||
loginfo.Println("error while peeking")
|
|
||||||
loginfo.Println(hex.Dump(peek[0:]))
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wssHostName := ctx.Value(ctxWssHostName).(string)
|
func (mx *MPlexy) acceptEncryptedStream(ctx context.Context, wConn *tunnel.WedgeConn) {
|
||||||
adminHostName := ctx.Value(ctxAdminHostName).(string)
|
// Peek at SNI (ServerName) from TLS Hello header
|
||||||
|
|
||||||
|
peek, err := wConn.PeekAll()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("Bad socket: read error from", wConn.RemoteAddr(), err)
|
||||||
|
loginfo.Println(hex.Dump(peek[0:]))
|
||||||
|
wConn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
sniHostName, err := sni.GetHostname(peek)
|
sniHostName, err := sni.GetHostname(peek)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
loginfo.Println("Bad socket: no SNI from", wConn.RemoteAddr(), err)
|
||||||
loginfo.Println(err)
|
loginfo.Println(err)
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loginfo.Println("sni:", sniHostName)
|
loginfo.Println("SNI:", sniHostName)
|
||||||
|
|
||||||
// This is where a target device connects to receive traffic
|
if sniHostName == mx.wssHostName || sniHostName == mx.adminHostName {
|
||||||
if sniHostName == wssHostName {
|
// The TLS should be terminated and handled internally
|
||||||
//handle WSS Path
|
tlsConfig := ctx.Value(ctxConfig).(*tls.Config)
|
||||||
tlsListener := tls.NewListener(oneConn, tlsConfig)
|
conn := tls.Client(wConn, tlsConfig)
|
||||||
|
tlsWedgeConn := tunnel.NewWedgeConn(conn)
|
||||||
conn, err := tlsListener.Accept()
|
mx.acceptPlainStream(ctx, tlsWedgeConn, true)
|
||||||
if err != nil {
|
|
||||||
loginfo.Println(err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsWedgeConn := NewWedgeConn(conn)
|
//oneConn := &oneConnListener{wConn}
|
||||||
mx.handleStream(ctx, tlsWedgeConn)
|
|
||||||
return
|
// TLS remains intact and shall be routed downstream, wholesale
|
||||||
|
loginfo.Println("processing non terminating traffic", mx.wssHostName, sniHostName)
|
||||||
|
go mx.routeToTarget(ctx, wConn, sniHostName, "https")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is where an admin of the relay manages it
|
//acceptPlainStream --
|
||||||
if sniHostName == adminHostName {
|
|
||||||
// TODO mx.Admin.CheckRemoteIP(conn) here
|
|
||||||
|
|
||||||
// handle admin path
|
|
||||||
tlsListener := tls.NewListener(oneConn, tlsConfig)
|
|
||||||
|
|
||||||
conn, err := tlsListener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
loginfo.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tlsWedgeConn := NewWedgeConn(conn)
|
|
||||||
mx.handleStream(ctx, tlsWedgeConn)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//traffic not terminating on the rvpn do not decrypt
|
|
||||||
loginfo.Println("processing non terminating traffic", wssHostName, sniHostName)
|
|
||||||
handleExternalHTTPRequest(ctx, wConn, sniHostName, "https")
|
|
||||||
}
|
|
||||||
|
|
||||||
//handleStream --
|
|
||||||
// - we have an unencrypted stream connection with the ability to peek
|
// - we have an unencrypted stream connection with the ability to peek
|
||||||
// - attempt to identify HTTP
|
// - attempt to identify HTTP
|
||||||
// - handle http
|
// - handle http
|
||||||
|
@ -238,15 +207,15 @@ func (mx *MPlexy) accept(ctx context.Context, wConn *WedgeConn) {
|
||||||
// - attempt to identify as ADMIN/API session
|
// - attempt to identify as ADMIN/API session
|
||||||
// - else handle as raw http
|
// - else handle as raw http
|
||||||
// - handle other?
|
// - handle other?
|
||||||
func (mx *MPlexy) handleStream(ctx context.Context, wConn *WedgeConn) {
|
func (mx *MPlexy) acceptPlainStream(ctx context.Context, wConn *tunnel.WedgeConn, encrypted bool) {
|
||||||
loginfo.Println("handle Stream")
|
loginfo.Println("Plain Conn", wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
||||||
loginfo.Println("conn", wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
|
||||||
|
|
||||||
// TODO couldn't this be dangerous? Or is it limited to a single packet?
|
// TODO couldn't reading everything be dangerous? Or is it limited to a single packet?
|
||||||
peek, err := wConn.PeekAll()
|
peek, err := wConn.PeekAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println("error while peeking", err)
|
loginfo.Println("error while peeking", err)
|
||||||
loginfo.Println(hex.Dump(peek[0:]))
|
loginfo.Println(hex.Dump(peek[0:]))
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,11 +224,13 @@ func (mx *MPlexy) handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
|
|
||||||
// HTTP Identifcation // CRLF
|
// HTTP Identifcation // CRLF
|
||||||
if !bytes.Contains(peek[:], []byte{0x0d, 0x0a}) {
|
if !bytes.Contains(peek[:], []byte{0x0d, 0x0a}) {
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//string protocol
|
//string protocol
|
||||||
if !bytes.ContainsAny(peek[:], "HTTP/") {
|
if !bytes.ContainsAny(peek[:], "HTTP/") {
|
||||||
|
wConn.Close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,40 +239,51 @@ func (mx *MPlexy) handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(peek)))
|
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(peek)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println("identified as HTTP, failed request parsing", err)
|
loginfo.Println("identified as HTTP, failed request parsing", err)
|
||||||
return
|
wConn.Close()
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add newtypes
|
|
||||||
// TODO check if this is a websocket
|
|
||||||
_, err = mx.authorize(r)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
loginfo.Println("Valid WSS dected...sending to handler")
|
|
||||||
oneConn := &oneConnListener{wConn}
|
|
||||||
mx.handleWssClient(ctx, oneConn)
|
|
||||||
|
|
||||||
//do we have a invalid domain indicating Admin?
|
|
||||||
//if yes, prep the oneConn and send it to the handler
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(r.Host, telebit.InvalidAdminDomain) {
|
if strings.Contains(r.Host, telebit.InvalidAdminDomain) {
|
||||||
loginfo.Println("admin")
|
loginfo.Println("admin")
|
||||||
oneConn := &oneConnListener{wConn}
|
// TODO mx.Admin.CheckRemoteIP(conn) here
|
||||||
handleAdminClient(ctx, oneConn)
|
// handle admin path
|
||||||
|
mx.AcceptAdminClient(wConn)
|
||||||
return
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
loginfo.Println("unsupported")
|
|
||||||
|
// TODO add newtypes
|
||||||
|
// TODO check if this is a websocket
|
||||||
|
_, err = mx.AuthorizeTarget(r)
|
||||||
|
if err == nil {
|
||||||
|
loginfo.Println("Valid WSS dected...sending to handler")
|
||||||
|
mx.AcceptTargetServer(wConn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO sniHostName is the key to the route, which could also be a port or hostname
|
||||||
|
//traffic not terminating on the rvpn do not decrypt
|
||||||
|
loginfo.Println("processing non terminating traffic", mx.wssHostName, r.Host)
|
||||||
loginfo.Println(hex.Dump(peek))
|
loginfo.Println(hex.Dump(peek))
|
||||||
|
if !encrypted {
|
||||||
|
// TODO request and cache http resources as a feature??
|
||||||
|
go mx.routeToTarget(ctx, wConn, r.Host, "http")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//handleExternalHTTPRequest -
|
// This is not presently possible
|
||||||
|
loginfo.Println("impossible condition: local decryption of routable client", mx.wssHostName, r.Host)
|
||||||
|
go mx.routeToTarget(ctx, wConn, r.Host, "https")
|
||||||
|
}
|
||||||
|
|
||||||
|
//routeToTarget -
|
||||||
// - get a wConn and start processing requests
|
// - get a wConn and start processing requests
|
||||||
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname, service string) {
|
func (mx *MPlexy) routeToTarget(ctx context.Context, extConn *tunnel.WedgeConn, hostname, service string) {
|
||||||
|
// TODO is this the right place to do this?
|
||||||
|
defer extConn.Close()
|
||||||
|
|
||||||
//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||||
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
serverStatus := ctx.Value(ctxServerStatus).(*api.Status)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
serverStatus.ExtConnectionUnregister(extConn)
|
serverStatus.ExtConnectionUnregister(extConn)
|
||||||
|
@ -317,7 +299,7 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
track := NewTrack(extConn, hostname)
|
track := api.NewTrack(extConn, hostname)
|
||||||
serverStatus.ExtConnectionRegister(track)
|
serverStatus.ExtConnectionRegister(track)
|
||||||
|
|
||||||
remoteStr := extConn.RemoteAddr().String()
|
remoteStr := extConn.RemoteAddr().String()
|
||||||
|
@ -352,7 +334,7 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
//loginfo.Println(hex.Dump(buf.Bytes()))
|
//loginfo.Println(hex.Dump(buf.Bytes()))
|
||||||
|
|
||||||
//Bundle up the send request and dispatch
|
//Bundle up the send request and dispatch
|
||||||
sendTrack := NewSendTrack(buf.Bytes(), hostname)
|
sendTrack := api.NewSendTrack(buf.Bytes(), hostname)
|
||||||
serverStatus.SendExtRequest(conn, sendTrack)
|
serverStatus.SendExtRequest(conn, sendTrack)
|
||||||
|
|
||||||
cnt := len(buffer)
|
cnt := len(buffer)
|
||||||
|
@ -363,63 +345,3 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//handleWssClient -
|
|
||||||
// - expecting an existing oneConnListener with a qualified wss client connected.
|
|
||||||
// - auth will happen again since we were just peeking at the token.
|
|
||||||
func (mx *MPlexy) handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
|
||||||
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
|
||||||
|
|
||||||
//connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
|
||||||
|
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
loginfo.Println("HandleFunc /")
|
|
||||||
switch url := r.URL.Path; url {
|
|
||||||
case "/":
|
|
||||||
loginfo.Println("websocket opening ", r.RemoteAddr, " ", r.Host)
|
|
||||||
|
|
||||||
authz, err := mx.authorize(r)
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 65535,
|
|
||||||
WriteBufferSize: 65535,
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
loginfo.Println("WebSocket upgrade failed", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loginfo.Println("before connection table")
|
|
||||||
|
|
||||||
serverName := authz.Domains[0]
|
|
||||||
|
|
||||||
newRegistration := NewRegistration(conn, r.RemoteAddr, authz.Domains, serverStatus.ConnectionTracking, serverName)
|
|
||||||
serverStatus.WSSConnectionRegister(newRegistration)
|
|
||||||
|
|
||||||
if ok := <-newRegistration.CommCh(); !ok {
|
|
||||||
loginfo.Println("connection registration failed ", newRegistration)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loginfo.Println("connection registration accepted ", newRegistration)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
s := &http.Server{
|
|
||||||
Addr: ":80",
|
|
||||||
Handler: router,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.Serve(oneConn)
|
|
||||||
if err != nil {
|
|
||||||
loginfo.Println("Serve error: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
loginfo.Println("Cancel signal hit")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"git.coolaj86.com/coolaj86/go-telebitd/relay/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
//ListenerRegistrationStatus - post registration status
|
//ListenerRegistrationStatus - post registration status
|
||||||
|
@ -65,34 +67,43 @@ func NewListenerRegistration(port int) (p *ListenerRegistration) {
|
||||||
type MPlexy struct {
|
type MPlexy struct {
|
||||||
listeners map[*net.Listener]int
|
listeners map[*net.Listener]int
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
connnectionTable *Table
|
connnectionTable *api.Table
|
||||||
connectionTracking *Tracking
|
connectionTracking *api.Tracking
|
||||||
authorize Authorizer
|
AuthorizeTarget Authorizer
|
||||||
|
AuthorizeAdmin Authorizer
|
||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
register chan *ListenerRegistration
|
register chan *ListenerRegistration
|
||||||
wssHostName string
|
wssHostName string
|
||||||
adminHostName string
|
adminHostName string
|
||||||
cancelCheck int
|
cancelCheck int
|
||||||
lbDefaultMethod string
|
lbDefaultMethod string
|
||||||
serverStatus *Status
|
Status *api.Status
|
||||||
//xservers *MPlexy
|
AcceptTargetServer func(net.Conn)
|
||||||
|
AcceptAdminClient func(net.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates tcp (and https and wss?) listeners
|
// New creates tcp (and https and wss?) listeners
|
||||||
func New(ctx context.Context, tlsConfig *tls.Config, authz Authorizer, serverStatus *Status) (mx *MPlexy) {
|
func New(
|
||||||
|
ctx context.Context,
|
||||||
|
tlsConfig *tls.Config,
|
||||||
|
authAdmin Authorizer,
|
||||||
|
authz Authorizer,
|
||||||
|
serverStatus *api.Status,
|
||||||
|
) (mx *MPlexy) {
|
||||||
mx = &MPlexy{
|
mx = &MPlexy{
|
||||||
listeners: make(map[*net.Listener]int),
|
listeners: make(map[*net.Listener]int),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
connnectionTable: serverStatus.ConnectionTable,
|
connnectionTable: serverStatus.ConnectionTable,
|
||||||
connectionTracking: serverStatus.ConnectionTracking,
|
connectionTracking: serverStatus.ConnectionTracking,
|
||||||
authorize: authz,
|
AuthorizeTarget: authz,
|
||||||
|
AuthorizeAdmin: authz,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
register: make(chan *ListenerRegistration),
|
register: make(chan *ListenerRegistration),
|
||||||
wssHostName: serverStatus.WssDomain,
|
wssHostName: serverStatus.WssDomain,
|
||||||
adminHostName: serverStatus.AdminDomain,
|
adminHostName: serverStatus.AdminDomain,
|
||||||
cancelCheck: serverStatus.DeadTime.cancelcheck,
|
cancelCheck: serverStatus.DeadTime.Cancelcheck,
|
||||||
lbDefaultMethod: serverStatus.LoadbalanceDefaultMethod,
|
lbDefaultMethod: serverStatus.LoadbalanceDefaultMethod,
|
||||||
serverStatus: serverStatus,
|
Status: serverStatus,
|
||||||
}
|
}
|
||||||
return mx
|
return mx
|
||||||
}
|
}
|
||||||
|
@ -117,7 +128,7 @@ func (mx *MPlexy) Run() error {
|
||||||
ctx = context.WithValue(ctx, ctxAdminHostName, mx.adminHostName)
|
ctx = context.WithValue(ctx, ctxAdminHostName, mx.adminHostName)
|
||||||
ctx = context.WithValue(ctx, ctxCancelCheck, mx.cancelCheck)
|
ctx = context.WithValue(ctx, ctxCancelCheck, mx.cancelCheck)
|
||||||
ctx = context.WithValue(ctx, ctxLoadbalanceDefaultMethod, mx.lbDefaultMethod)
|
ctx = context.WithValue(ctx, ctxLoadbalanceDefaultMethod, mx.lbDefaultMethod)
|
||||||
ctx = context.WithValue(ctx, ctxServerStatus, mx.serverStatus)
|
ctx = context.WithValue(ctx, ctxServerStatus, mx.Status)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -138,7 +149,7 @@ func (mx *MPlexy) Run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loginfo.Println("listener starting up ", registration.port)
|
loginfo.Println("listener starting up ", registration.port)
|
||||||
loginfo.Println(ctx.Value(ctxConnectionTrack).(*Tracking))
|
loginfo.Println(ctx.Value(ctxConnectionTrack).(*api.Tracking))
|
||||||
go mx.multiListenAndServe(ctx, registration)
|
go mx.multiListenAndServe(ctx, registration)
|
||||||
|
|
||||||
status := <-registration.commCh
|
status := <-registration.commCh
|
||||||
|
|
|
@ -11,7 +11,7 @@ type oneConnListener struct {
|
||||||
|
|
||||||
func (l *oneConnListener) Accept() (net.Conn, error) {
|
func (l *oneConnListener) Accept() (net.Conn, error) {
|
||||||
if l.conn == nil {
|
if l.conn == nil {
|
||||||
loginfo.Println("Accept EOF")
|
loginfo.Println("oneConnListener Accept EOF")
|
||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,11 +22,11 @@ func (l *oneConnListener) Accept() (net.Conn, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *oneConnListener) Close() error {
|
func (l *oneConnListener) Close() error {
|
||||||
loginfo.Println("close")
|
loginfo.Println("oneConnListener close")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *oneConnListener) Addr() net.Addr {
|
func (l *oneConnListener) Addr() net.Addr {
|
||||||
loginfo.Println("addr")
|
loginfo.Println("oneConnLister addr")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package tunnel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
|
@ -0,0 +1,48 @@
|
||||||
|
package tunnel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Listener defines a listener for use with http servers
|
||||||
|
type Listener struct {
|
||||||
|
Conns chan net.Conn
|
||||||
|
//ParentAddr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewListener creates a channel for connections and returns the listener
|
||||||
|
func NewListener() *Listener {
|
||||||
|
return &Listener{
|
||||||
|
Conns: make(chan net.Conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Feed will block while pushing a net.Conn onto Conns
|
||||||
|
func (l *Listener) Feed(conn net.Conn) {
|
||||||
|
l.Conns <- conn
|
||||||
|
}
|
||||||
|
|
||||||
|
// net.Listener interface
|
||||||
|
|
||||||
|
// Accept will block and wait for a new net.Conn
|
||||||
|
func (l *Listener) Accept() (net.Conn, error) {
|
||||||
|
conn, ok := <-l.Conns
|
||||||
|
if ok {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
return nil, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will close the Conns channel
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
close(l.Conns)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns nil to fulfill the net.Listener interface
|
||||||
|
func (l *Listener) Addr() net.Addr {
|
||||||
|
// Addr may (or may not) return the original TCP or TLS listener's address
|
||||||
|
//return l.ParentAddr
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue