Initial commit

This commit is contained in:
Chris Marshall
2020-08-02 13:23:17 -04:00
committed by chris
commit ffca54fdb8
60 changed files with 34080 additions and 0 deletions

View File

@ -0,0 +1,116 @@
package client
import (
"bytes"
"context"
"fmt"
"log"
"net"
"net/http"
"time"
"github.com/gorilla/websocket"
)
var DefaultUpgrader = &websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
type WebsocketUDPProxy struct {
Upgrader *websocket.Upgrader
addr net.Addr
}
func NewProxy(addr string) (*WebsocketUDPProxy, error) {
raddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
return &WebsocketUDPProxy{addr: raddr}, nil
}
func (w *WebsocketUDPProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
upgrader := w.Upgrader
if w.Upgrader == nil {
upgrader = DefaultUpgrader
}
upgradeHeader := http.Header{}
if hdr := req.Header.Get("Sec-Websocket-Protocol"); hdr != "" {
upgradeHeader.Set("Sec-Websocket-Protocol", hdr)
}
ws, err := upgrader.Upgrade(rw, req, upgradeHeader)
if err != nil {
log.Printf("wsproxy: couldn't upgrade %v", err)
return
}
defer ws.Close()
backend, err := net.ListenPacket("udp", "0.0.0.0:0")
if err != nil {
return
}
defer backend.Close()
errc := make(chan error, 1)
go func() {
for {
_, msg, err := ws.ReadMessage()
if err != nil {
m := websocket.FormatCloseMessage(websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
m = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
errc <- err
ws.WriteMessage(websocket.CloseMessage, m)
return
}
if bytes.HasPrefix(msg, []byte("\xff\xff\xff\xffport")) {
continue
}
if err := backend.SetWriteDeadline(time.Now().Add(5 * time.Second)); err != nil {
errc <- err
return
}
_, err = backend.WriteTo(msg, w.addr)
if err != nil {
errc <- err
return
}
}
}()
go func() {
buffer := make([]byte, 1024*1024)
for {
n, _, err := backend.ReadFrom(buffer)
if err != nil {
errc <- err
return
}
if err := ws.WriteMessage(websocket.BinaryMessage, buffer[:n]); err != nil {
errc <- err
return
}
}
}()
select {
case err = <-errc:
if e, ok := err.(*websocket.CloseError); !ok || e.Code == websocket.CloseAbnormalClosure {
log.Printf("wsproxy: %v", err)
}
case <-ctx.Done():
return
}
}

View File

@ -0,0 +1,130 @@
package client
import (
"bytes"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
type Config struct {
ContentServerURL string
ServerAddr string
Files http.FileSystem
}
func NewRouter(cfg *Config) (*echo.Echo, error) {
e := echo.New()
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
}))
f, err := cfg.Files.Open("index.html")
if err != nil {
return nil, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
return nil, err
}
templates, err := template.New("index").Parse(string(data))
if err != nil {
return nil, err
}
e.Renderer = &TemplateRenderer{templates}
// default route
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "index", map[string]string{
"ServerAddr": cfg.ServerAddr,
})
})
raddr, err := net.ResolveUDPAddr("udp", cfg.ServerAddr)
if err != nil {
return nil, err
}
e.GET("/info", func(c echo.Context) error {
conn, err := net.ListenPacket("udp", "0.0.0.0:0")
if err != nil {
return err
}
defer conn.Close()
buffer := make([]byte, 1024*1024)
if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {
return err
}
n, err := conn.WriteTo([]byte("\xff\xff\xff\xffgetinfo xxx"), raddr)
if err != nil {
return err
}
n, _, err = conn.ReadFrom(buffer)
if err != nil {
return err
}
resp := buffer[:n]
resp = bytes.TrimPrefix(resp, []byte("\xff\xff\xff\xffinfoResponse\n\\"))
resp = bytes.TrimSuffix(resp, []byte("\\xxx"))
parts := bytes.Split(resp, []byte("\\"))
m := make(map[string]string)
for i := 0; i < len(parts)-1; i += 2 {
m[string(parts[i])] = string(parts[i+1])
}
return c.JSON(http.StatusOK, m)
})
// static files
e.GET("/*", echo.WrapHandler(http.FileServer(cfg.Files)))
// Quake3 assets requests must be proxied to the content server. The host
// header is manipulated to ensure that services like CloudFlare will not
// reject requests based upon incorrect host header.
csurl, err := url.Parse(cfg.ContentServerURL)
if err != nil {
return nil, err
}
g := e.Group("/assets")
g.Use(middleware.ProxyWithConfig(middleware.ProxyConfig{
Balancer: middleware.NewRoundRobinBalancer([]*middleware.ProxyTarget{
{URL: csurl},
}),
Transport: &HostHeaderTransport{RoundTripper: http.DefaultTransport, Host: csurl.Host},
}))
return e, nil
}
type HostHeaderTransport struct {
http.RoundTripper
Host string
}
func (t *HostHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Host = t.Host
return t.RoundTripper.RoundTrip(req)
}
type TemplateRenderer struct {
*template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.ExecuteTemplate(w, name, data)
}

View File

@ -0,0 +1,58 @@
package client
import (
"net"
"net/http"
"time"
"github.com/cockroachdb/cmux"
)
type Server struct {
Addr string
Handler http.Handler
ServerAddr string
}
func (s *Server) Serve(l net.Listener) error {
m := cmux.New(l)
websocketL := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))
httpL := m.Match(cmux.Any())
go func() {
s := &http.Server{
Addr: s.Addr,
Handler: s.Handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
}
if err := s.Serve(httpL); err != cmux.ErrListenerClosed {
panic(err)
}
}()
wsproxy, err := NewProxy(s.ServerAddr)
if err != nil {
return err
}
go func() {
s := &http.Server{
Handler: wsproxy,
}
if err := s.Serve(websocketL); err != cmux.ErrListenerClosed {
panic(err)
}
}()
return m.Serve()
}
func (s *Server) ListenAndServe() error {
l, err := net.Listen("tcp", s.Addr)
if err != nil {
return err
}
return s.Serve(l)
}