mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-11 15:47:51 +02:00
3.0.1 init commit
- Removed Go HTTP client UA - Added optional bypass of websocket origin check #107 - Added basic forward proxy for debug - Fixed UI error in network utils tab
This commit is contained in:
@@ -272,6 +272,14 @@ func removeHeaders(header http.Header, noCache bool) {
|
||||
header.Del("Cache-Control")
|
||||
header.Set("Cache-Control", "no-store")
|
||||
}
|
||||
|
||||
//Hide Go-HTTP-Client UA if the client didnt sent us one
|
||||
if _, ok := header["User-Agent"]; !ok {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent.
|
||||
header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func addXForwardedForHeader(req *http.Request) {
|
||||
@@ -365,6 +373,12 @@ func (p *ReverseProxy) ProxyHTTP(rw http.ResponseWriter, req *http.Request, rrr
|
||||
}
|
||||
}
|
||||
|
||||
//if res.StatusCode == 501 || res.StatusCode == 500 {
|
||||
// fmt.Println(outreq.Proto, outreq.RemoteAddr, outreq.RequestURI)
|
||||
// fmt.Println(">>>", outreq.Method, res.Header, res.ContentLength, res.StatusCode)
|
||||
// fmt.Println(outreq.Header, req.Host)
|
||||
//}
|
||||
|
||||
//Custom header rewriter functions
|
||||
if res.Header.Get("Location") != "" {
|
||||
locationRewrite := res.Header.Get("Location")
|
||||
|
@@ -114,7 +114,10 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.logRequest(r, true, 101, "subdomain-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.SkipWebSocketOriginCheck,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
@@ -178,7 +181,10 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
h.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, target.SkipCertValidations)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: target.parent.SkipWebSocketOriginCheck,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
@@ -95,9 +95,10 @@ type ProxyEndpoint struct {
|
||||
Domain string //Domain or IP to proxy to
|
||||
|
||||
//TLS/SSL Related
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
RequireTLS bool //Target domain require TLS
|
||||
BypassGlobalTLS bool //Bypass global TLS setting options if TLS Listener enabled (parent.tlsListener != nil)
|
||||
SkipCertValidations bool //Set to true to accept self signed certs
|
||||
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
|
||||
|
||||
//Virtual Directories
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
@@ -115,6 +116,7 @@ type ProxyEndpoint struct {
|
||||
DefaultSiteValue string //Fallback routing target, optional
|
||||
|
||||
Disabled bool //If the rule is disabled
|
||||
|
||||
//Internal Logic Elements
|
||||
parent *Router
|
||||
proxy *dpcore.ReverseProxy `json:"-"`
|
||||
|
25
src/mod/forwardproxy/cproxy/LICENSE.md
Normal file
25
src/mod/forwardproxy/cproxy/LICENSE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Smarty
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
NOTE: Various optional and subordinate components carry their own licensing
|
||||
requirements and restrictions. Use of those components is subject to the terms
|
||||
and conditions outlined the respective license of each component.
|
109
src/mod/forwardproxy/cproxy/config.go
Normal file
109
src/mod/forwardproxy/cproxy/config.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func New(options ...option) http.Handler {
|
||||
var this configuration
|
||||
Options.apply(options...)(&this)
|
||||
return newHandler(this.Filter, this.ClientConnector, this.ServerConnector, this.Monitor)
|
||||
}
|
||||
|
||||
var Options singleton
|
||||
|
||||
type singleton struct{}
|
||||
type option func(*configuration)
|
||||
|
||||
type configuration struct {
|
||||
DialTimeout time.Duration
|
||||
Filter Filter
|
||||
DialAddress string
|
||||
Dialer Dialer
|
||||
LogConnections bool
|
||||
ProxyProtocol bool
|
||||
Initializer initializer
|
||||
ClientConnector clientConnector
|
||||
ServerConnector serverConnector
|
||||
Monitor monitor
|
||||
Logger logger
|
||||
}
|
||||
|
||||
func (singleton) DialTimeout(value time.Duration) option {
|
||||
return func(this *configuration) { this.DialTimeout = value }
|
||||
}
|
||||
func (singleton) Filter(value Filter) option {
|
||||
return func(this *configuration) { this.Filter = value }
|
||||
}
|
||||
func (singleton) ClientConnector(value clientConnector) option {
|
||||
return func(this *configuration) { this.ClientConnector = value }
|
||||
}
|
||||
func (singleton) DialAddress(value string) option {
|
||||
return func(this *configuration) { this.DialAddress = value }
|
||||
}
|
||||
func (singleton) Dialer(value Dialer) option {
|
||||
return func(this *configuration) { this.Dialer = value }
|
||||
}
|
||||
func (singleton) LogConnections(value bool) option {
|
||||
return func(this *configuration) { this.LogConnections = value }
|
||||
}
|
||||
func (singleton) ProxyProtocol(value bool) option {
|
||||
return func(this *configuration) { this.ProxyProtocol = value }
|
||||
}
|
||||
func (singleton) Initializer(value initializer) option {
|
||||
return func(this *configuration) { this.Initializer = value }
|
||||
}
|
||||
func (singleton) ServerConnector(value serverConnector) option {
|
||||
return func(this *configuration) { this.ServerConnector = value }
|
||||
}
|
||||
func (singleton) Monitor(value monitor) option {
|
||||
return func(this *configuration) { this.Monitor = value }
|
||||
}
|
||||
func (singleton) Logger(value logger) option {
|
||||
return func(this *configuration) { this.Logger = value }
|
||||
}
|
||||
|
||||
func (singleton) apply(options ...option) option {
|
||||
return func(this *configuration) {
|
||||
for _, item := range Options.defaults(options...) {
|
||||
item(this)
|
||||
}
|
||||
|
||||
if this.Dialer == nil {
|
||||
this.Dialer = newDialer(this)
|
||||
}
|
||||
|
||||
this.Dialer = newRoutingDialer(this)
|
||||
|
||||
if this.ProxyProtocol {
|
||||
this.Initializer = newProxyProtocolInitializer()
|
||||
}
|
||||
|
||||
if this.Initializer == nil {
|
||||
this.Initializer = nop{}
|
||||
}
|
||||
|
||||
this.Initializer = newLoggingInitializer(this)
|
||||
|
||||
if this.ServerConnector == nil {
|
||||
this.ServerConnector = newServerConnector(this.Dialer, this.Initializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
func (singleton) defaults(options ...option) []option {
|
||||
return append([]option{
|
||||
Options.DialTimeout(time.Second * 10),
|
||||
Options.Filter(newFilter()),
|
||||
Options.ClientConnector(newClientConnector()),
|
||||
Options.Initializer(nop{}),
|
||||
Options.Monitor(nop{}),
|
||||
Options.Logger(nop{}),
|
||||
}, options...)
|
||||
}
|
||||
|
||||
type nop struct{}
|
||||
|
||||
func (nop) Measure(int) {}
|
||||
func (nop) Printf(string, ...interface{}) {}
|
||||
func (nop) Initialize(Socket, Socket) bool { return true }
|
19
src/mod/forwardproxy/cproxy/default_client_connector.go
Normal file
19
src/mod/forwardproxy/cproxy/default_client_connector.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package cproxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
type defaultClientConnector struct{}
|
||||
|
||||
func newClientConnector() *defaultClientConnector {
|
||||
return &defaultClientConnector{}
|
||||
}
|
||||
|
||||
func (this *defaultClientConnector) Connect(response http.ResponseWriter) Socket {
|
||||
if hijacker, ok := response.(http.Hijacker); !ok {
|
||||
return nil
|
||||
} else if socket, _, _ := hijacker.Hijack(); socket == nil {
|
||||
return nil // this 'else if' exists to avoid the pointer nil != interface nil issue
|
||||
} else {
|
||||
return socket
|
||||
}
|
||||
}
|
25
src/mod/forwardproxy/cproxy/default_dialer.go
Normal file
25
src/mod/forwardproxy/cproxy/default_dialer.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
type defaultDialer struct {
|
||||
timeout time.Duration
|
||||
logger logger
|
||||
}
|
||||
|
||||
func newDialer(config *configuration) *defaultDialer {
|
||||
return &defaultDialer{timeout: config.DialTimeout, logger: config.Logger}
|
||||
}
|
||||
|
||||
func (this *defaultDialer) Dial(address string) Socket {
|
||||
if socket, err := net.DialTimeout("tcp", address, this.timeout); err == nil {
|
||||
return socket
|
||||
} else {
|
||||
this.logger.Printf("[INFO] Unable to establish connection to [%s]: %s", address, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
9
src/mod/forwardproxy/cproxy/default_filter.go
Normal file
9
src/mod/forwardproxy/cproxy/default_filter.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package cproxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
type defaultFilter struct{}
|
||||
|
||||
func newFilter() *defaultFilter { return &defaultFilter{} }
|
||||
|
||||
func (this *defaultFilter) IsAuthorized(http.ResponseWriter, *http.Request) bool { return true }
|
56
src/mod/forwardproxy/cproxy/default_handler.go
Normal file
56
src/mod/forwardproxy/cproxy/default_handler.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package cproxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
type defaultHandler struct {
|
||||
filter Filter
|
||||
clientConnector clientConnector
|
||||
serverConnector serverConnector
|
||||
meter monitor
|
||||
}
|
||||
|
||||
func newHandler(filter Filter, clientConnector clientConnector, serverConnector serverConnector, meter monitor) *defaultHandler {
|
||||
return &defaultHandler{
|
||||
filter: filter,
|
||||
clientConnector: clientConnector,
|
||||
serverConnector: serverConnector,
|
||||
meter: meter,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *defaultHandler) ServeHTTP(response http.ResponseWriter, request *http.Request) {
|
||||
this.meter.Measure(MeasurementHTTPRequest)
|
||||
|
||||
if request.Method != "CONNECT" {
|
||||
this.meter.Measure(MeasurementBadMethod)
|
||||
writeResponseStatus(response, http.StatusMethodNotAllowed)
|
||||
|
||||
} else if !this.filter.IsAuthorized(response, request) {
|
||||
this.meter.Measure(MeasurementUnauthorizedRequest)
|
||||
//writeResponseStatus(response, http.StatusUnauthorized)
|
||||
|
||||
} else if client := this.clientConnector.Connect(response); client == nil {
|
||||
this.meter.Measure(MeasurementClientConnectionFailed)
|
||||
writeResponseStatus(response, http.StatusNotImplemented)
|
||||
|
||||
} else if connection := this.serverConnector.Connect(client, request.URL.Host); connection == nil {
|
||||
this.meter.Measure(MeasurementServerConnectionFailed)
|
||||
_, _ = client.Write(statusBadGateway)
|
||||
_ = client.Close()
|
||||
|
||||
} else {
|
||||
this.meter.Measure(MeasurementProxyReady)
|
||||
_, _ = client.Write(statusReady)
|
||||
connection.Proxy()
|
||||
this.meter.Measure(MeasurementProxyComplete)
|
||||
}
|
||||
}
|
||||
|
||||
func writeResponseStatus(response http.ResponseWriter, statusCode int) {
|
||||
http.Error(response, http.StatusText(statusCode), statusCode)
|
||||
}
|
||||
|
||||
var (
|
||||
statusBadGateway = []byte("HTTP/1.1 502 Bad Gateway\r\n\r\n")
|
||||
statusReady = []byte("HTTP/1.1 200 OK\r\n\r\n")
|
||||
)
|
54
src/mod/forwardproxy/cproxy/default_proxy.go
Normal file
54
src/mod/forwardproxy/cproxy/default_proxy.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type defaultProxy struct {
|
||||
client Socket
|
||||
server Socket
|
||||
waiter *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newProxy(client, server Socket) *defaultProxy {
|
||||
waiter := &sync.WaitGroup{}
|
||||
waiter.Add(2) // wait on both client->server and server->client streams
|
||||
|
||||
return &defaultProxy{
|
||||
waiter: waiter,
|
||||
client: client,
|
||||
server: server,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *defaultProxy) Proxy() {
|
||||
go this.streamAndClose(this.client, this.server)
|
||||
go this.streamAndClose(this.server, this.client)
|
||||
this.closeSockets()
|
||||
}
|
||||
|
||||
func (this *defaultProxy) streamAndClose(reader, writer Socket) {
|
||||
_, _ = io.Copy(writer, reader)
|
||||
|
||||
tryCloseRead(reader)
|
||||
tryCloseWrite(writer)
|
||||
|
||||
this.waiter.Done()
|
||||
}
|
||||
func tryCloseRead(socket Socket) {
|
||||
if tcp, ok := socket.(tcpSocket); ok {
|
||||
_ = tcp.CloseRead()
|
||||
}
|
||||
}
|
||||
func tryCloseWrite(socket Socket) {
|
||||
if tcp, ok := socket.(tcpSocket); ok {
|
||||
_ = tcp.CloseWrite()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *defaultProxy) closeSockets() {
|
||||
this.waiter.Wait()
|
||||
_ = this.client.Close()
|
||||
_ = this.server.Close()
|
||||
}
|
24
src/mod/forwardproxy/cproxy/default_server_connector.go
Normal file
24
src/mod/forwardproxy/cproxy/default_server_connector.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cproxy
|
||||
|
||||
type defaultServerConnector struct {
|
||||
dialer Dialer
|
||||
initializer initializer
|
||||
}
|
||||
|
||||
func newServerConnector(dialer Dialer, initializer initializer) *defaultServerConnector {
|
||||
return &defaultServerConnector{dialer: dialer, initializer: initializer}
|
||||
}
|
||||
|
||||
func (this *defaultServerConnector) Connect(client Socket, serverAddress string) proxy {
|
||||
server := this.dialer.Dial(serverAddress)
|
||||
if server == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !this.initializer.Initialize(client, server) {
|
||||
_ = server.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
return newProxy(client, server)
|
||||
}
|
32
src/mod/forwardproxy/cproxy/hostname_filter.go
Normal file
32
src/mod/forwardproxy/cproxy/hostname_filter.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package cproxy
|
||||
|
||||
import "net/http"
|
||||
|
||||
type hostnameFilter struct {
|
||||
authorized []string
|
||||
}
|
||||
|
||||
func NewHostnameFilter(authorized []string) Filter {
|
||||
return &hostnameFilter{authorized: authorized}
|
||||
}
|
||||
|
||||
func (this hostnameFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
|
||||
if len(this.authorized) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
host := request.URL.Host
|
||||
hostLength := len(host)
|
||||
for _, authorized := range this.authorized {
|
||||
if authorized[:2] == "*." {
|
||||
have, want := hostLength, len(authorized)-1
|
||||
if have > want && authorized[1:] == host[hostLength-want:] {
|
||||
return true
|
||||
}
|
||||
} else if authorized == host {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
26
src/mod/forwardproxy/cproxy/hostname_suffix_filter.go
Normal file
26
src/mod/forwardproxy/cproxy/hostname_suffix_filter.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type hostnameSuffixFilter struct {
|
||||
authorized []string
|
||||
}
|
||||
|
||||
func NewHostnameSuffixFilter(authorized []string) Filter {
|
||||
return &hostnameSuffixFilter{authorized: authorized}
|
||||
}
|
||||
|
||||
func (this hostnameSuffixFilter) IsAuthorized(_ http.ResponseWriter, request *http.Request) bool {
|
||||
host := request.URL.Host
|
||||
|
||||
for _, authorized := range this.authorized {
|
||||
if strings.HasSuffix(host, authorized) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
67
src/mod/forwardproxy/cproxy/interfaces.go
Normal file
67
src/mod/forwardproxy/cproxy/interfaces.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
Filter interface {
|
||||
IsAuthorized(http.ResponseWriter, *http.Request) bool
|
||||
}
|
||||
|
||||
clientConnector interface {
|
||||
Connect(http.ResponseWriter) Socket
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
Dialer interface {
|
||||
Dial(string) Socket
|
||||
}
|
||||
|
||||
serverConnector interface {
|
||||
Connect(Socket, string) proxy
|
||||
}
|
||||
|
||||
initializer interface {
|
||||
Initialize(Socket, Socket) bool
|
||||
}
|
||||
|
||||
proxy interface {
|
||||
Proxy()
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
Socket interface {
|
||||
io.ReadWriteCloser
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
tcpSocket interface {
|
||||
Socket
|
||||
CloseRead() error
|
||||
CloseWrite() error
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
monitor interface {
|
||||
Measure(int)
|
||||
}
|
||||
logger interface {
|
||||
Printf(string, ...interface{})
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
MeasurementHTTPRequest int = iota
|
||||
MeasurementBadMethod
|
||||
MeasurementUnauthorizedRequest
|
||||
MeasurementClientConnectionFailed
|
||||
MeasurementServerConnectionFailed
|
||||
MeasurementProxyReady
|
||||
MeasurementProxyComplete
|
||||
)
|
24
src/mod/forwardproxy/cproxy/logging_initializer.go
Normal file
24
src/mod/forwardproxy/cproxy/logging_initializer.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package cproxy
|
||||
|
||||
type loggingInitializer struct {
|
||||
logger logger
|
||||
inner initializer
|
||||
}
|
||||
|
||||
func newLoggingInitializer(config *configuration) initializer {
|
||||
if !config.LogConnections {
|
||||
return config.Initializer
|
||||
}
|
||||
|
||||
return &loggingInitializer{inner: config.Initializer, logger: config.Logger}
|
||||
}
|
||||
|
||||
func (this *loggingInitializer) Initialize(client, server Socket) bool {
|
||||
result := this.inner.Initialize(client, server)
|
||||
|
||||
if !result {
|
||||
this.logger.Printf("[INFO] Connection failed [%s] -> [%s]", client.RemoteAddr(), server.RemoteAddr())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
36
src/mod/forwardproxy/cproxy/proxy_protocol_initializer.go
Normal file
36
src/mod/forwardproxy/cproxy/proxy_protocol_initializer.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package cproxy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type proxyProtocolInitializer struct{}
|
||||
|
||||
func newProxyProtocolInitializer() *proxyProtocolInitializer {
|
||||
return &proxyProtocolInitializer{}
|
||||
}
|
||||
|
||||
func (this *proxyProtocolInitializer) Initialize(client, server Socket) bool {
|
||||
header := formatHeader(client.RemoteAddr(), server.RemoteAddr())
|
||||
_, err := io.WriteString(server, header)
|
||||
return err == nil
|
||||
}
|
||||
func formatHeader(client, server net.Addr) string {
|
||||
clientAddress, clientPort := parseAddress(client.String())
|
||||
serverAddress, serverPort := parseAddress(server.String())
|
||||
if strings.Contains(clientAddress, ":") {
|
||||
return fmt.Sprintf(proxyProtocolIPv6Preamble, clientAddress, serverAddress, clientPort, serverPort)
|
||||
}
|
||||
|
||||
return fmt.Sprintf(proxyProtocolIPv4Preamble, clientAddress, serverAddress, clientPort, serverPort)
|
||||
}
|
||||
func parseAddress(address string) (string, string) {
|
||||
address, port, _ := net.SplitHostPort(address)
|
||||
return address, port
|
||||
}
|
||||
|
||||
const proxyProtocolIPv4Preamble = "PROXY TCP4 %s %s %s %s\r\n"
|
||||
const proxyProtocolIPv6Preamble = "PROXY TCP6 %s %s %s %s\r\n"
|
18
src/mod/forwardproxy/cproxy/routing_dialer.go
Normal file
18
src/mod/forwardproxy/cproxy/routing_dialer.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package cproxy
|
||||
|
||||
type routingDialer struct {
|
||||
inner Dialer
|
||||
targetAddress string
|
||||
}
|
||||
|
||||
func newRoutingDialer(config *configuration) Dialer {
|
||||
if len(config.DialAddress) == 0 {
|
||||
return config.Dialer
|
||||
}
|
||||
|
||||
return &routingDialer{inner: config.Dialer, targetAddress: config.DialAddress}
|
||||
}
|
||||
|
||||
func (this *routingDialer) Dial(string) Socket {
|
||||
return this.inner.Dial(this.targetAddress)
|
||||
}
|
137
src/mod/forwardproxy/forwardproxy.go
Normal file
137
src/mod/forwardproxy/forwardproxy.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package forwardproxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/forwardproxy/cproxy"
|
||||
"imuslab.com/zoraxy/mod/info/logger"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
type ZrFilter struct {
|
||||
//To be implemented
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
server *http.Server
|
||||
handler *http.Handler
|
||||
running bool
|
||||
db *database.Database
|
||||
logger *logger.Logger
|
||||
Port int
|
||||
}
|
||||
|
||||
func NewForwardProxy(sysdb *database.Database, port int, logger *logger.Logger) *Handler {
|
||||
thisFilter := ZrFilter{}
|
||||
handler := cproxy.New(cproxy.Options.Filter(thisFilter))
|
||||
|
||||
return &Handler{
|
||||
db: sysdb,
|
||||
server: nil,
|
||||
handler: &handler,
|
||||
running: false,
|
||||
logger: logger,
|
||||
Port: port,
|
||||
}
|
||||
}
|
||||
|
||||
// Start the forward proxy
|
||||
func (h *Handler) Start() error {
|
||||
if h.running {
|
||||
return errors.New("forward proxy already running")
|
||||
}
|
||||
server := &http.Server{Addr: ":" + strconv.Itoa(h.Port), Handler: *h.handler}
|
||||
h.server = server
|
||||
|
||||
go func() {
|
||||
if err := server.ListenAndServe(); err != nil {
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
h.running = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop the forward proxy
|
||||
func (h *Handler) Stop() error {
|
||||
if h.running && h.server != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := h.server.Shutdown(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
h.running = false
|
||||
h.server = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update the port number of the forward proxy
|
||||
func (h *Handler) UpdatePort(newPort int) error {
|
||||
h.Stop()
|
||||
h.Port = newPort
|
||||
return h.Start()
|
||||
}
|
||||
|
||||
func (it ZrFilter) IsAuthorized(w http.ResponseWriter, r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle port change of the forward proxy
|
||||
func (h *Handler) HandlePort(w http.ResponseWriter, r *http.Request) {
|
||||
port, err := utils.PostInt(r, "port")
|
||||
if err != nil {
|
||||
js, _ := json.Marshal(h.Port)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
//Update the port
|
||||
err = h.UpdatePort(port)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy port updated to :"+strconv.Itoa(h.Port), nil)
|
||||
h.db.Write("fwdproxy", "port", port)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle power toggle of the forward proxys
|
||||
func (h *Handler) HandleToogle(w http.ResponseWriter, r *http.Request) {
|
||||
enabled, err := utils.PostBool(r, "enable")
|
||||
if err != nil {
|
||||
//Get the current state of the forward proxy
|
||||
js, _ := json.Marshal(h.running)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else {
|
||||
if enabled {
|
||||
err = h.Start()
|
||||
if err != nil {
|
||||
h.logger.PrintAndLog("Forward Proxy", "Unable to start forward proxy server", err)
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Started, listening on :"+strconv.Itoa(h.Port), nil)
|
||||
} else {
|
||||
err = h.Stop()
|
||||
if err != nil {
|
||||
h.logger.PrintAndLog("Forward Proxy", "Unable to stop forward proxy server", err)
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
h.logger.PrintAndLog("Forward Proxy", "HTTP Forward Proxy Stopped", nil)
|
||||
}
|
||||
h.db.Write("fwdproxy", "enabled", enabled)
|
||||
utils.SendOK(w)
|
||||
}
|
||||
}
|
@@ -85,7 +85,10 @@ func (m *Manager) HandleHttpByInstanceId(instanceId string, w http.ResponseWrite
|
||||
r.Header.Set("Zr-Origin-Upgrade", "websocket")
|
||||
requestURL = strings.TrimPrefix(requestURL, "/")
|
||||
u, _ := url.Parse("ws://127.0.0.1:" + strconv.Itoa(targetInstance.AssignedPort) + "/" + requestURL)
|
||||
wspHandler := websocketproxy.NewProxy(u, false)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: false,
|
||||
SkipOriginCheck: false,
|
||||
})
|
||||
wspHandler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
@@ -47,19 +47,26 @@ type WebsocketProxy struct {
|
||||
// If nil, DefaultDialer is used.
|
||||
Dialer *websocket.Dialer
|
||||
|
||||
Verbal bool
|
||||
SkipTlsValidation bool
|
||||
Verbal bool
|
||||
|
||||
Options Options
|
||||
}
|
||||
|
||||
// Additional options for websocket proxy runtime
|
||||
type Options struct {
|
||||
SkipTLSValidation bool //Skip backend TLS validation
|
||||
SkipOriginCheck bool //Skip origin check
|
||||
}
|
||||
|
||||
// ProxyHandler returns a new http.Handler interface that reverse proxies the
|
||||
// request to the given target.
|
||||
func ProxyHandler(target *url.URL, skipTlsValidation bool) http.Handler {
|
||||
return NewProxy(target, skipTlsValidation)
|
||||
func ProxyHandler(target *url.URL, options Options) http.Handler {
|
||||
return NewProxy(target, options)
|
||||
}
|
||||
|
||||
// NewProxy returns a new Websocket reverse proxy that rewrites the
|
||||
// URL's to the scheme, host and base path provider in target.
|
||||
func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
|
||||
func NewProxy(target *url.URL, options Options) *WebsocketProxy {
|
||||
backend := func(r *http.Request) *url.URL {
|
||||
// Shallow copy
|
||||
u := *target
|
||||
@@ -68,7 +75,7 @@ func NewProxy(target *url.URL, skipTlsValidation bool) *WebsocketProxy {
|
||||
u.RawQuery = r.URL.RawQuery
|
||||
return &u
|
||||
}
|
||||
return &WebsocketProxy{Backend: backend, Verbal: false, SkipTlsValidation: skipTlsValidation}
|
||||
return &WebsocketProxy{Backend: backend, Verbal: false, Options: options}
|
||||
}
|
||||
|
||||
// ServeHTTP implements the http.Handler that proxies WebSocket connections.
|
||||
@@ -88,7 +95,7 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
||||
dialer := w.Dialer
|
||||
if w.Dialer == nil {
|
||||
if w.SkipTlsValidation {
|
||||
if w.Options.SkipTLSValidation {
|
||||
//Disable TLS secure check if target allow skip verification
|
||||
bypassDialer := websocket.DefaultDialer
|
||||
bypassDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
@@ -171,6 +178,13 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
upgrader = DefaultUpgrader
|
||||
}
|
||||
|
||||
//Fixing issue #107 by bypassing request origin check
|
||||
if w.Options.SkipOriginCheck {
|
||||
upgrader.CheckOrigin = func(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Only pass those headers to the upgrader.
|
||||
upgradeHeader := http.Header{}
|
||||
if hdr := resp.Header.Get("Sec-Websocket-Protocol"); hdr != "" {
|
||||
|
@@ -28,7 +28,10 @@ func TestProxy(t *testing.T) {
|
||||
}
|
||||
|
||||
u, _ := url.Parse(backendURL)
|
||||
proxy := NewProxy(u, false)
|
||||
proxy := NewProxy(u, Options{
|
||||
SkipTLSValidation: false,
|
||||
SkipOriginCheck: false,
|
||||
})
|
||||
proxy.Upgrader = upgrader
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
Reference in New Issue
Block a user