mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Support Proxy protocol (#12527)
This PR adds functionality to allow Gitea to sit behind an HAProxy and HAProxy protocolled connections directly. Fix #7508 Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
		
							
								
								
									
										28
									
								
								cmd/web.go
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								cmd/web.go
									
									
									
									
									
								
							@@ -76,7 +76,7 @@ func runHTTPRedirector() {
 | 
			
		||||
		http.Redirect(w, r, target, http.StatusTemporaryRedirect)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	err := runHTTP("tcp", source, "HTTP Redirector", handler)
 | 
			
		||||
	err := runHTTP("tcp", source, "HTTP Redirector", handler, setting.RedirectorUseProxyProtocol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Failed to start port redirection: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -231,40 +231,38 @@ func listen(m http.Handler, handleRedirector bool) error {
 | 
			
		||||
		if handleRedirector {
 | 
			
		||||
			NoHTTPRedirector()
 | 
			
		||||
		}
 | 
			
		||||
		err = runHTTP("tcp", listenAddr, "Web", m)
 | 
			
		||||
		err = runHTTP("tcp", listenAddr, "Web", m, setting.UseProxyProtocol)
 | 
			
		||||
	case setting.HTTPS:
 | 
			
		||||
		if setting.EnableAcme {
 | 
			
		||||
			err = runACME(listenAddr, m)
 | 
			
		||||
			break
 | 
			
		||||
		} else {
 | 
			
		||||
			if handleRedirector {
 | 
			
		||||
				if setting.RedirectOtherPort {
 | 
			
		||||
					go runHTTPRedirector()
 | 
			
		||||
				} else {
 | 
			
		||||
					NoHTTPRedirector()
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m)
 | 
			
		||||
		}
 | 
			
		||||
		if handleRedirector {
 | 
			
		||||
			if setting.RedirectOtherPort {
 | 
			
		||||
				go runHTTPRedirector()
 | 
			
		||||
			} else {
 | 
			
		||||
				NoHTTPRedirector()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
 | 
			
		||||
	case setting.FCGI:
 | 
			
		||||
		if handleRedirector {
 | 
			
		||||
			NoHTTPRedirector()
 | 
			
		||||
		}
 | 
			
		||||
		err = runFCGI("tcp", listenAddr, "FCGI Web", m)
 | 
			
		||||
		err = runFCGI("tcp", listenAddr, "FCGI Web", m, setting.UseProxyProtocol)
 | 
			
		||||
	case setting.HTTPUnix:
 | 
			
		||||
		if handleRedirector {
 | 
			
		||||
			NoHTTPRedirector()
 | 
			
		||||
		}
 | 
			
		||||
		err = runHTTP("unix", listenAddr, "Web", m)
 | 
			
		||||
		err = runHTTP("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
 | 
			
		||||
	case setting.FCGIUnix:
 | 
			
		||||
		if handleRedirector {
 | 
			
		||||
			NoHTTPRedirector()
 | 
			
		||||
		}
 | 
			
		||||
		err = runFCGI("unix", listenAddr, "Web", m)
 | 
			
		||||
		err = runFCGI("unix", listenAddr, "Web", m, setting.UseProxyProtocol)
 | 
			
		||||
	default:
 | 
			
		||||
		log.Fatal("Invalid protocol: %s", setting.Protocol)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Critical("Failed to start server: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -113,14 +113,14 @@ func runACME(listenAddr string, m http.Handler) error {
 | 
			
		||||
 | 
			
		||||
			log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
 | 
			
		||||
			// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
 | 
			
		||||
			err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
 | 
			
		||||
			err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m)
 | 
			
		||||
	return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,8 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runHTTP(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
	return graceful.HTTPListenAndServe(network, listenAddr, name, m)
 | 
			
		||||
func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
 | 
			
		||||
	return graceful.HTTPListenAndServe(network, listenAddr, name, m, useProxyProtocol)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
 | 
			
		||||
@@ -36,7 +36,7 @@ func NoInstallListener() {
 | 
			
		||||
	graceful.GetManager().InformCleanup()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runFCGI(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
func runFCGI(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error {
 | 
			
		||||
	// This needs to handle stdin as fcgi point
 | 
			
		||||
	fcgiServer := graceful.NewServer(network, listenAddr, name)
 | 
			
		||||
 | 
			
		||||
@@ -47,7 +47,7 @@ func runFCGI(network, listenAddr, name string, m http.Handler) error {
 | 
			
		||||
			}
 | 
			
		||||
			m.ServeHTTP(resp, req)
 | 
			
		||||
		}))
 | 
			
		||||
	})
 | 
			
		||||
	}, useProxyProtocol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatal("Failed to start FCGI main server: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -129,14 +129,14 @@ var (
 | 
			
		||||
	defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// runHTTPs listens on the provided network address and then calls
 | 
			
		||||
// runHTTPS listens on the provided network address and then calls
 | 
			
		||||
// Serve to handle requests on incoming TLS connections.
 | 
			
		||||
//
 | 
			
		||||
// Filenames containing a certificate and matching private key for the server must
 | 
			
		||||
// be provided. If the certificate is signed by a certificate authority, the
 | 
			
		||||
// certFile should be the concatenation of the server's certificate followed by the
 | 
			
		||||
// CA's certificate.
 | 
			
		||||
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler) error {
 | 
			
		||||
func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 | 
			
		||||
	tlsConfig := &tls.Config{}
 | 
			
		||||
	if tlsConfig.NextProtos == nil {
 | 
			
		||||
		tlsConfig.NextProtos = []string{"h2", "http/1.1"}
 | 
			
		||||
@@ -184,9 +184,9 @@ func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handle
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
 | 
			
		||||
	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error {
 | 
			
		||||
	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m)
 | 
			
		||||
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 | 
			
		||||
	return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,18 @@ RUN_MODE = ; prod
 | 
			
		||||
;; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. Defaults to 'http'
 | 
			
		||||
;PROTOCOL = http
 | 
			
		||||
;;
 | 
			
		||||
;; Expect PROXY protocol headers on connections
 | 
			
		||||
;USE_PROXY_PROTOCOL = false
 | 
			
		||||
;;
 | 
			
		||||
;; Use PROXY protocol in TLS Bridging mode
 | 
			
		||||
;PROXY_PROTOCOL_TLS_BRIDGING = false
 | 
			
		||||
;;
 | 
			
		||||
; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
 | 
			
		||||
;PROXY_PROTOCOL_HEADER_TIMEOUT=5s
 | 
			
		||||
;;
 | 
			
		||||
; Accept PROXY protocol headers with UNKNOWN type
 | 
			
		||||
;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false
 | 
			
		||||
;;
 | 
			
		||||
;; Set the domain for the server
 | 
			
		||||
;DOMAIN = localhost
 | 
			
		||||
;;
 | 
			
		||||
@@ -51,6 +63,8 @@ RUN_MODE = ; prod
 | 
			
		||||
;REDIRECT_OTHER_PORT = false
 | 
			
		||||
;PORT_TO_REDIRECT = 80
 | 
			
		||||
;;
 | 
			
		||||
;; expect PROXY protocol header on connections to https redirector.
 | 
			
		||||
;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)
 | 
			
		||||
;; Minimum and maximum supported TLS versions
 | 
			
		||||
;SSL_MIN_VERSION=TLSv1.2
 | 
			
		||||
;SSL_MAX_VERSION=
 | 
			
		||||
@@ -76,13 +90,19 @@ RUN_MODE = ; prod
 | 
			
		||||
;; Do not set this variable if PROTOCOL is set to 'unix'.
 | 
			
		||||
;LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
 | 
			
		||||
;;
 | 
			
		||||
;; When making local connections pass the PROXY protocol header.
 | 
			
		||||
;LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL)
 | 
			
		||||
;;
 | 
			
		||||
;; Disable SSH feature when not available
 | 
			
		||||
;DISABLE_SSH = false
 | 
			
		||||
;;
 | 
			
		||||
;; Whether to use the builtin SSH server or not.
 | 
			
		||||
;START_SSH_SERVER = false
 | 
			
		||||
;;
 | 
			
		||||
;; Username to use for the builtin SSH server.
 | 
			
		||||
;; Expect PROXY protocol header on connections to the built-in SSH server
 | 
			
		||||
;SSH_SERVER_USE_PROXY_PROTOCOL = false
 | 
			
		||||
;;
 | 
			
		||||
;; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
 | 
			
		||||
;BUILTIN_SSH_SERVER_USER = %(RUN_USER)s
 | 
			
		||||
;;
 | 
			
		||||
;; Domain name to be exposed in clone URL
 | 
			
		||||
 
 | 
			
		||||
@@ -238,6 +238,10 @@ The following configuration set `Content-Type: application/vnd.android.package-a
 | 
			
		||||
## Server (`server`)
 | 
			
		||||
 | 
			
		||||
- `PROTOCOL`: **http**: \[http, https, fcgi, http+unix, fcgi+unix\]
 | 
			
		||||
- `USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol headers on connections
 | 
			
		||||
- `PROXY_PROTOCOL_TLS_BRIDGING`: **false**: When protocol is https, expect PROXY protocol headers after TLS negotiation.
 | 
			
		||||
- `PROXY_PROTOCOL_HEADER_TIMEOUT`: **5s**: Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
 | 
			
		||||
- `PROXY_PROTOCOL_ACCEPT_UNKNOWN`: **false**: Accept PROXY protocol headers with Unknown type.
 | 
			
		||||
- `DOMAIN`: **localhost**: Domain name of this server.
 | 
			
		||||
- `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**:
 | 
			
		||||
   Overwrite the automatically generated public URL.
 | 
			
		||||
@@ -262,12 +266,15 @@ The following configuration set `Content-Type: application/vnd.android.package-a
 | 
			
		||||
   most cases you do not need to change the default value. Alter it only if
 | 
			
		||||
   your SSH server node is not the same as HTTP node. Do not set this variable
 | 
			
		||||
   if `PROTOCOL` is set to `http+unix`.
 | 
			
		||||
- `LOCAL_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: When making local connections pass the PROXY protocol header.
 | 
			
		||||
   This should be set to false if the local connection will go through the proxy.
 | 
			
		||||
- `PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the connection. (Set to -1 to
 | 
			
		||||
   disable all timeouts.)
 | 
			
		||||
- `PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to connections.
 | 
			
		||||
 | 
			
		||||
- `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
 | 
			
		||||
- `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server.
 | 
			
		||||
- `SSH_SERVER_USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol header on connections to the built-in SSH Server.
 | 
			
		||||
- `BUILTIN_SSH_SERVER_USER`: **%(RUN_USER)s**: Username to use for the built-in SSH Server.
 | 
			
		||||
- `SSH_USER`: **%(BUILTIN_SSH_SERVER_USER)**: SSH username displayed in clone URLs. This is only for people who configure the SSH server themselves; in most cases, you want to leave this blank and modify the `BUILTIN_SSH_SERVER_USER`.
 | 
			
		||||
- `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL.
 | 
			
		||||
@@ -313,6 +320,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a
 | 
			
		||||
- `LFS_LOCKS_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page.
 | 
			
		||||
 | 
			
		||||
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on.
 | 
			
		||||
- `REDIRECTOR_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: expect PROXY protocol header on connections to https redirector.
 | 
			
		||||
- `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true.
 | 
			
		||||
- `SSL_MIN_VERSION`: **TLSv1.2**: Set the minimum version of ssl support.
 | 
			
		||||
- `SSL_MAX_VERSION`: **\<empty\>**: Set the maximum version of ssl support.
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/proxyprotocol"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -79,16 +80,27 @@ func NewServer(network, address, name string) *Server {
 | 
			
		||||
 | 
			
		||||
// ListenAndServe listens on the provided network address and then calls Serve
 | 
			
		||||
// to handle requests on incoming connections.
 | 
			
		||||
func (srv *Server) ListenAndServe(serve ServeFunction) error {
 | 
			
		||||
func (srv *Server) ListenAndServe(serve ServeFunction, useProxyProtocol bool) error {
 | 
			
		||||
	go srv.awaitShutdown()
 | 
			
		||||
 | 
			
		||||
	l, err := GetListener(srv.network, srv.address)
 | 
			
		||||
	listener, err := GetListener(srv.network, srv.address)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to GetListener: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	srv.listener = newWrappedListener(l, srv)
 | 
			
		||||
	// we need to wrap the listener to take account of our lifecycle
 | 
			
		||||
	listener = newWrappedListener(listener, srv)
 | 
			
		||||
 | 
			
		||||
	// Now we need to take account of ProxyProtocol settings...
 | 
			
		||||
	if useProxyProtocol {
 | 
			
		||||
		listener = &proxyprotocol.Listener{
 | 
			
		||||
			Listener:           listener,
 | 
			
		||||
			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
 | 
			
		||||
			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	srv.listener = listener
 | 
			
		||||
 | 
			
		||||
	srv.BeforeBegin(srv.network, srv.address)
 | 
			
		||||
 | 
			
		||||
@@ -97,22 +109,44 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
 | 
			
		||||
 | 
			
		||||
// ListenAndServeTLSConfig listens on the provided network address and then calls
 | 
			
		||||
// Serve to handle requests on incoming TLS connections.
 | 
			
		||||
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
 | 
			
		||||
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 | 
			
		||||
	go srv.awaitShutdown()
 | 
			
		||||
 | 
			
		||||
	if tlsConfig.MinVersion == 0 {
 | 
			
		||||
		tlsConfig.MinVersion = tls.VersionTLS12
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	l, err := GetListener(srv.network, srv.address)
 | 
			
		||||
	listener, err := GetListener(srv.network, srv.address)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Error("Unable to get Listener: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wl := newWrappedListener(l, srv)
 | 
			
		||||
	srv.listener = tls.NewListener(wl, tlsConfig)
 | 
			
		||||
	// we need to wrap the listener to take account of our lifecycle
 | 
			
		||||
	listener = newWrappedListener(listener, srv)
 | 
			
		||||
 | 
			
		||||
	// Now we need to take account of ProxyProtocol settings... If we're not bridging then we expect that the proxy will forward the connection to us
 | 
			
		||||
	if useProxyProtocol && !proxyProtocolTLSBridging {
 | 
			
		||||
		listener = &proxyprotocol.Listener{
 | 
			
		||||
			Listener:           listener,
 | 
			
		||||
			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
 | 
			
		||||
			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now handle the tls protocol
 | 
			
		||||
	listener = tls.NewListener(listener, tlsConfig)
 | 
			
		||||
 | 
			
		||||
	// Now if we're bridging then we need the proxy to tell us who we're bridging for...
 | 
			
		||||
	if useProxyProtocol && proxyProtocolTLSBridging {
 | 
			
		||||
		listener = &proxyprotocol.Listener{
 | 
			
		||||
			Listener:           listener,
 | 
			
		||||
			ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout,
 | 
			
		||||
			AcceptUnknown:      setting.ProxyProtocolAcceptUnknown,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	srv.listener = listener
 | 
			
		||||
	srv.BeforeBegin(srv.network, srv.address)
 | 
			
		||||
 | 
			
		||||
	return srv.Serve(serve)
 | 
			
		||||
 
 | 
			
		||||
@@ -28,14 +28,14 @@ func newHTTPServer(network, address, name string, handler http.Handler) (*Server
 | 
			
		||||
 | 
			
		||||
// HTTPListenAndServe listens on the provided network address and then calls Serve
 | 
			
		||||
// to handle requests on incoming connections.
 | 
			
		||||
func HTTPListenAndServe(network, address, name string, handler http.Handler) error {
 | 
			
		||||
func HTTPListenAndServe(network, address, name string, handler http.Handler, useProxyProtocol bool) error {
 | 
			
		||||
	server, lHandler := newHTTPServer(network, address, name, handler)
 | 
			
		||||
	return server.ListenAndServe(lHandler)
 | 
			
		||||
	return server.ListenAndServe(lHandler, useProxyProtocol)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve
 | 
			
		||||
// to handle requests on incoming connections.
 | 
			
		||||
func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler) error {
 | 
			
		||||
func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error {
 | 
			
		||||
	server, lHandler := newHTTPServer(network, address, name, handler)
 | 
			
		||||
	return server.ListenAndServeTLSConfig(tlsConfig, lHandler)
 | 
			
		||||
	return server.ListenAndServeTLSConfig(tlsConfig, lHandler, useProxyProtocol, proxyProtocolTLSBridging)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import (
 | 
			
		||||
	"code.gitea.io/gitea/modules/httplib"
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/proxyprotocol"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -50,7 +51,32 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques
 | 
			
		||||
		req.SetTransport(&http.Transport{
 | 
			
		||||
			DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
 | 
			
		||||
				var d net.Dialer
 | 
			
		||||
				return d.DialContext(ctx, "unix", setting.HTTPAddr)
 | 
			
		||||
				conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return conn, err
 | 
			
		||||
				}
 | 
			
		||||
				if setting.LocalUseProxyProtocol {
 | 
			
		||||
					if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
			
		||||
						_ = conn.Close()
 | 
			
		||||
						return nil, err
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				return conn, err
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	} else if setting.LocalUseProxyProtocol {
 | 
			
		||||
		req.SetTransport(&http.Transport{
 | 
			
		||||
			DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
 | 
			
		||||
				var d net.Dialer
 | 
			
		||||
				conn, err := d.DialContext(ctx, network, address)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return conn, err
 | 
			
		||||
				}
 | 
			
		||||
				if err = proxyprotocol.WriteLocalHeader(conn); err != nil {
 | 
			
		||||
					_ = conn.Close()
 | 
			
		||||
					return nil, err
 | 
			
		||||
				}
 | 
			
		||||
				return conn, err
 | 
			
		||||
			},
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										506
									
								
								modules/proxyprotocol/conn.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										506
									
								
								modules/proxyprotocol/conn.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,506 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package proxyprotocol
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// v1Prefix is the string we look for at the start of a connection
 | 
			
		||||
	// to check if this connection is using the proxy protocol
 | 
			
		||||
	v1Prefix    = []byte("PROXY ")
 | 
			
		||||
	v1PrefixLen = len(v1Prefix)
 | 
			
		||||
	v2Prefix    = []byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A")
 | 
			
		||||
	v2PrefixLen = len(v2Prefix)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Conn is used to wrap and underlying connection which is speaking the
 | 
			
		||||
// Proxy Protocol. RemoteAddr() will return the address of the client
 | 
			
		||||
// instead of the proxy address.
 | 
			
		||||
type Conn struct {
 | 
			
		||||
	bufReader          *bufio.Reader
 | 
			
		||||
	conn               net.Conn
 | 
			
		||||
	localAddr          net.Addr
 | 
			
		||||
	remoteAddr         net.Addr
 | 
			
		||||
	once               sync.Once
 | 
			
		||||
	proxyHeaderTimeout time.Duration
 | 
			
		||||
	acceptUnknown      bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewConn is used to wrap a net.Conn speaking the proxy protocol into
 | 
			
		||||
// a proxyprotocol.Conn
 | 
			
		||||
func NewConn(conn net.Conn, timeout time.Duration) *Conn {
 | 
			
		||||
	pConn := &Conn{
 | 
			
		||||
		bufReader:          bufio.NewReader(conn),
 | 
			
		||||
		conn:               conn,
 | 
			
		||||
		proxyHeaderTimeout: timeout,
 | 
			
		||||
	}
 | 
			
		||||
	return pConn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Read reads data from the connection.
 | 
			
		||||
// It will initially read the proxy protocol header.
 | 
			
		||||
// If there is an error parsing the header, it is returned and the socket is closed.
 | 
			
		||||
func (p *Conn) Read(b []byte) (int, error) {
 | 
			
		||||
	if err := p.readProxyHeaderOnce(); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return p.bufReader.Read(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReadFrom reads data from a provided reader and copies it to the connection.
 | 
			
		||||
func (p *Conn) ReadFrom(r io.Reader) (int64, error) {
 | 
			
		||||
	if err := p.readProxyHeaderOnce(); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	if rf, ok := p.conn.(io.ReaderFrom); ok {
 | 
			
		||||
		return rf.ReadFrom(r)
 | 
			
		||||
	}
 | 
			
		||||
	return io.Copy(p.conn, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WriteTo reads data from the connection and writes it to the writer.
 | 
			
		||||
// It will initially read the proxy protocol header.
 | 
			
		||||
// If there is an error parsing the header, it is returned and the socket is closed.
 | 
			
		||||
func (p *Conn) WriteTo(w io.Writer) (int64, error) {
 | 
			
		||||
	if err := p.readProxyHeaderOnce(); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return p.bufReader.WriteTo(w)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Write writes data to the connection.
 | 
			
		||||
// Write can be made to time out and return an error after a fixed
 | 
			
		||||
// time limit; see SetDeadline and SetWriteDeadline.
 | 
			
		||||
func (p *Conn) Write(b []byte) (int, error) {
 | 
			
		||||
	if err := p.readProxyHeaderOnce(); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	return p.conn.Write(b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the connection.
 | 
			
		||||
// Any blocked Read or Write operations will be unblocked and return errors.
 | 
			
		||||
func (p *Conn) Close() error {
 | 
			
		||||
	return p.conn.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LocalAddr returns the local network address.
 | 
			
		||||
func (p *Conn) LocalAddr() net.Addr {
 | 
			
		||||
	_ = p.readProxyHeaderOnce()
 | 
			
		||||
	if p.localAddr != nil {
 | 
			
		||||
		return p.localAddr
 | 
			
		||||
	}
 | 
			
		||||
	return p.conn.LocalAddr()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoteAddr returns the address of the client if the proxy
 | 
			
		||||
// protocol is being used, otherwise just returns the address of
 | 
			
		||||
// the socket peer. If there is an error parsing the header, the
 | 
			
		||||
// address of the client is not returned, and the socket is closed.
 | 
			
		||||
// One implication of this is that the call could block if the
 | 
			
		||||
// client is slow. Using a Deadline is recommended if this is called
 | 
			
		||||
// before Read()
 | 
			
		||||
func (p *Conn) RemoteAddr() net.Addr {
 | 
			
		||||
	_ = p.readProxyHeaderOnce()
 | 
			
		||||
	if p.remoteAddr != nil {
 | 
			
		||||
		return p.remoteAddr
 | 
			
		||||
	}
 | 
			
		||||
	return p.conn.RemoteAddr()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetDeadline sets the read and write deadlines associated
 | 
			
		||||
// with the connection. It is equivalent to calling both
 | 
			
		||||
// SetReadDeadline and SetWriteDeadline.
 | 
			
		||||
//
 | 
			
		||||
// A deadline is an absolute time after which I/O operations
 | 
			
		||||
// fail instead of blocking. The deadline applies to all future
 | 
			
		||||
// and pending I/O, not just the immediately following call to
 | 
			
		||||
// Read or Write. After a deadline has been exceeded, the
 | 
			
		||||
// connection can be refreshed by setting a deadline in the future.
 | 
			
		||||
//
 | 
			
		||||
// If the deadline is exceeded a call to Read or Write or to other
 | 
			
		||||
// I/O methods will return an error that wraps os.ErrDeadlineExceeded.
 | 
			
		||||
// This can be tested using errors.Is(err, os.ErrDeadlineExceeded).
 | 
			
		||||
// The error's Timeout method will return true, but note that there
 | 
			
		||||
// are other possible errors for which the Timeout method will
 | 
			
		||||
// return true even if the deadline has not been exceeded.
 | 
			
		||||
//
 | 
			
		||||
// An idle timeout can be implemented by repeatedly extending
 | 
			
		||||
// the deadline after successful Read or Write calls.
 | 
			
		||||
//
 | 
			
		||||
// A zero value for t means I/O operations will not time out.
 | 
			
		||||
func (p *Conn) SetDeadline(t time.Time) error {
 | 
			
		||||
	return p.conn.SetDeadline(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetReadDeadline sets the deadline for future Read calls
 | 
			
		||||
// and any currently-blocked Read call.
 | 
			
		||||
// A zero value for t means Read will not time out.
 | 
			
		||||
func (p *Conn) SetReadDeadline(t time.Time) error {
 | 
			
		||||
	return p.conn.SetReadDeadline(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetWriteDeadline sets the deadline for future Write calls
 | 
			
		||||
// and any currently-blocked Write call.
 | 
			
		||||
// Even if write times out, it may return n > 0, indicating that
 | 
			
		||||
// some of the data was successfully written.
 | 
			
		||||
// A zero value for t means Write will not time out.
 | 
			
		||||
func (p *Conn) SetWriteDeadline(t time.Time) error {
 | 
			
		||||
	return p.conn.SetWriteDeadline(t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// readProxyHeaderOnce will ensure that the proxy header has been read
 | 
			
		||||
func (p *Conn) readProxyHeaderOnce() (err error) {
 | 
			
		||||
	p.once.Do(func() {
 | 
			
		||||
		if err = p.readProxyHeader(); err != nil && err != io.EOF {
 | 
			
		||||
			log.Error("Failed to read proxy prefix: %v", err)
 | 
			
		||||
			p.Close()
 | 
			
		||||
			p.bufReader = bufio.NewReader(p.conn)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Conn) readProxyHeader() error {
 | 
			
		||||
	if p.proxyHeaderTimeout != 0 {
 | 
			
		||||
		readDeadLine := time.Now().Add(p.proxyHeaderTimeout)
 | 
			
		||||
		_ = p.conn.SetReadDeadline(readDeadLine)
 | 
			
		||||
		defer func() {
 | 
			
		||||
			_ = p.conn.SetReadDeadline(time.Time{})
 | 
			
		||||
		}()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inp, err := p.bufReader.Peek(v1PrefixLen)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if bytes.Equal(inp, v1Prefix) {
 | 
			
		||||
		return p.readV1ProxyHeader()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	inp, err = p.bufReader.Peek(v2PrefixLen)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if bytes.Equal(inp, v2Prefix) {
 | 
			
		||||
		return p.readV2ProxyHeader()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &ErrBadHeader{inp}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Conn) readV2ProxyHeader() error {
 | 
			
		||||
	// The binary header format starts with a constant 12 bytes block containing the
 | 
			
		||||
	// protocol signature :
 | 
			
		||||
	//
 | 
			
		||||
	//    \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A
 | 
			
		||||
	//
 | 
			
		||||
	// Note that this block contains a null byte at the 5th position, so it must not
 | 
			
		||||
	// be handled as a null-terminated string.
 | 
			
		||||
 | 
			
		||||
	if _, err := p.bufReader.Discard(v2PrefixLen); err != nil {
 | 
			
		||||
		// This shouldn't happen as we have already asserted that there should be enough in the buffer
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The next byte (the 13th one) is the protocol version and command.
 | 
			
		||||
	version, err := p.bufReader.ReadByte()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The 14th byte contains the transport protocol and address family.otocol.
 | 
			
		||||
	familyByte, err := p.bufReader.ReadByte()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The 15th and 16th bytes is the address length in bytes in network endian order.
 | 
			
		||||
	var addressLen uint16
 | 
			
		||||
	if err := binary.Read(p.bufReader, binary.BigEndian, &addressLen); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now handle the version byte: (14th byte).
 | 
			
		||||
	// The highest four bits contains the version. As of this specification, it must
 | 
			
		||||
	// always be sent as \x2 and the receiver must only accept this value.
 | 
			
		||||
	if version>>4 != 0x2 {
 | 
			
		||||
		return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The lowest four bits represents the command :
 | 
			
		||||
	switch version & 0xf {
 | 
			
		||||
	case 0x0:
 | 
			
		||||
		// - \x0 : LOCAL : the connection was established on purpose by the proxy
 | 
			
		||||
		//   without being relayed. The connection endpoints are the sender and the
 | 
			
		||||
		//   receiver. Such connections exist when the proxy sends health-checks to the
 | 
			
		||||
		//   server. The receiver must accept this connection as valid and must use the
 | 
			
		||||
		//   real connection endpoints and discard the protocol block including the
 | 
			
		||||
		//   family which is ignored.
 | 
			
		||||
 | 
			
		||||
		// We therefore ignore the 14th, 15th and 16th bytes
 | 
			
		||||
		p.remoteAddr = p.conn.LocalAddr()
 | 
			
		||||
		p.localAddr = p.conn.RemoteAddr()
 | 
			
		||||
		return nil
 | 
			
		||||
	case 0x1:
 | 
			
		||||
	// - \x1 : PROXY : the connection was established on behalf of another node,
 | 
			
		||||
	//   and reflects the original connection endpoints. The receiver must then use
 | 
			
		||||
	//   the information provided in the protocol block to get original the address.
 | 
			
		||||
	default:
 | 
			
		||||
		// - other values are unassigned and must not be emitted by senders. Receivers
 | 
			
		||||
		//   must drop connections presenting unexpected values here.
 | 
			
		||||
		return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Now handle the familyByte byte: (15th byte).
 | 
			
		||||
	// The highest 4 bits contain the address family, the lowest 4 bits contain the protocol
 | 
			
		||||
 | 
			
		||||
	// 	The address family maps to the original socket family without necessarily
 | 
			
		||||
	// matching the values internally used by the system. It may be one of :
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x0 : AF_UNSPEC : the connection is forwarded for an unknown, unspecified
 | 
			
		||||
	//     or unsupported protocol. The sender should use this family when sending
 | 
			
		||||
	//     LOCAL commands or when dealing with unsupported protocol families. The
 | 
			
		||||
	//     receiver is free to accept the connection anyway and use the real endpoint
 | 
			
		||||
	//     addresses or to reject it. The receiver should ignore address information.
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x1 : AF_INET : the forwarded connection uses the AF_INET address family
 | 
			
		||||
	//     (IPv4). The addresses are exactly 4 bytes each in network byte order,
 | 
			
		||||
	//     followed by transport protocol information (typically ports).
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x2 : AF_INET6 : the forwarded connection uses the AF_INET6 address family
 | 
			
		||||
	//     (IPv6). The addresses are exactly 16 bytes each in network byte order,
 | 
			
		||||
	//     followed by transport protocol information (typically ports).
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x3 : AF_UNIX : the forwarded connection uses the AF_UNIX address family
 | 
			
		||||
	//     (UNIX). The addresses are exactly 108 bytes each.
 | 
			
		||||
	//
 | 
			
		||||
	//   - other values are unspecified and must not be emitted in version 2 of this
 | 
			
		||||
	//     protocol and must be rejected as invalid by receivers.
 | 
			
		||||
 | 
			
		||||
	// 	The transport protocol is specified in the lowest 4 bits of the 14th byte :
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x0 : UNSPEC : the connection is forwarded for an unknown, unspecified
 | 
			
		||||
	//     or unsupported protocol. The sender should use this family when sending
 | 
			
		||||
	//     LOCAL commands or when dealing with unsupported protocol families. The
 | 
			
		||||
	//     receiver is free to accept the connection anyway and use the real endpoint
 | 
			
		||||
	//     addresses or to reject it. The receiver should ignore address information.
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x1 : STREAM : the forwarded connection uses a SOCK_STREAM protocol (eg:
 | 
			
		||||
	//     TCP or UNIX_STREAM). When used with AF_INET/AF_INET6 (TCP), the addresses
 | 
			
		||||
	//     are followed by the source and destination ports represented on 2 bytes
 | 
			
		||||
	//     each in network byte order.
 | 
			
		||||
	//
 | 
			
		||||
	//   - 0x2 : DGRAM : the forwarded connection uses a SOCK_DGRAM protocol (eg:
 | 
			
		||||
	//     UDP or UNIX_DGRAM). When used with AF_INET/AF_INET6 (UDP), the addresses
 | 
			
		||||
	//     are followed by the source and destination ports represented on 2 bytes
 | 
			
		||||
	//     each in network byte order.
 | 
			
		||||
	//
 | 
			
		||||
	//   - other values are unspecified and must not be emitted in version 2 of this
 | 
			
		||||
	//     protocol and must be rejected as invalid by receivers.
 | 
			
		||||
 | 
			
		||||
	if familyByte>>4 == 0x0 || familyByte&0xf == 0x0 {
 | 
			
		||||
		//   - hi 0x0 : AF_UNSPEC : the connection is forwarded for an unknown address type
 | 
			
		||||
		// or
 | 
			
		||||
		//   - lo 0x0 : UNSPEC : the connection is forwarded for an unspecified protocol
 | 
			
		||||
		if !p.acceptUnknown {
 | 
			
		||||
			p.conn.Close()
 | 
			
		||||
			return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
		}
 | 
			
		||||
		p.remoteAddr = p.conn.LocalAddr()
 | 
			
		||||
		p.localAddr = p.conn.RemoteAddr()
 | 
			
		||||
		_, err = p.bufReader.Discard(int(addressLen))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// other address or protocol
 | 
			
		||||
	if (familyByte>>4) > 0x3 || (familyByte&0xf) > 0x2 {
 | 
			
		||||
		return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Handle AF_UNIX addresses
 | 
			
		||||
	if familyByte>>4 == 0x3 {
 | 
			
		||||
		//   - \x31 : UNIX stream : the forwarded connection uses SOCK_STREAM over the
 | 
			
		||||
		//     AF_UNIX protocol family. Address length is 2*108 = 216 bytes.
 | 
			
		||||
		//   - \x32 : UNIX datagram : the forwarded connection uses SOCK_DGRAM over the
 | 
			
		||||
		//     AF_UNIX protocol family. Address length is 2*108 = 216 bytes.
 | 
			
		||||
		if addressLen != 216 {
 | 
			
		||||
			return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
		}
 | 
			
		||||
		remoteName := make([]byte, 108)
 | 
			
		||||
		localName := make([]byte, 108)
 | 
			
		||||
		if _, err := p.bufReader.Read(remoteName); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := p.bufReader.Read(localName); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		protocol := "unix"
 | 
			
		||||
		if familyByte&0xf == 2 {
 | 
			
		||||
			protocol = "unixgram"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		p.remoteAddr = &net.UnixAddr{
 | 
			
		||||
			Name: string(remoteName),
 | 
			
		||||
			Net:  protocol,
 | 
			
		||||
		}
 | 
			
		||||
		p.localAddr = &net.UnixAddr{
 | 
			
		||||
			Name: string(localName),
 | 
			
		||||
			Net:  protocol,
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var remoteIP []byte
 | 
			
		||||
	var localIP []byte
 | 
			
		||||
	var remotePort uint16
 | 
			
		||||
	var localPort uint16
 | 
			
		||||
 | 
			
		||||
	if familyByte>>4 == 0x1 {
 | 
			
		||||
		// AF_INET
 | 
			
		||||
		// 	 - \x11 : TCP over IPv4 : the forwarded connection uses TCP over the AF_INET
 | 
			
		||||
		//     protocol family. Address length is 2*4 + 2*2 = 12 bytes.
 | 
			
		||||
		//   - \x12 : UDP over IPv4 : the forwarded connection uses UDP over the AF_INET
 | 
			
		||||
		//     protocol family. Address length is 2*4 + 2*2 = 12 bytes.
 | 
			
		||||
		if addressLen != 12 {
 | 
			
		||||
			return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		remoteIP = make([]byte, 4)
 | 
			
		||||
		localIP = make([]byte, 4)
 | 
			
		||||
	} else {
 | 
			
		||||
		// AF_INET6
 | 
			
		||||
		// - \x21 : TCP over IPv6 : the forwarded connection uses TCP over the AF_INET6
 | 
			
		||||
		// 	 protocol family. Address length is 2*16 + 2*2 = 36 bytes.
 | 
			
		||||
		// - \x22 : UDP over IPv6 : the forwarded connection uses UDP over the AF_INET6
 | 
			
		||||
		// 	 protocol family. Address length is 2*16 + 2*2 = 36 bytes.
 | 
			
		||||
		if addressLen != 36 {
 | 
			
		||||
			return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		remoteIP = make([]byte, 16)
 | 
			
		||||
		localIP = make([]byte, 16)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := p.bufReader.Read(remoteIP); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := p.bufReader.Read(localIP); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := binary.Read(p.bufReader, binary.BigEndian, &remotePort); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := binary.Read(p.bufReader, binary.BigEndian, &localPort); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if familyByte&0xf == 1 {
 | 
			
		||||
		p.remoteAddr = &net.TCPAddr{
 | 
			
		||||
			IP:   remoteIP,
 | 
			
		||||
			Port: int(remotePort),
 | 
			
		||||
		}
 | 
			
		||||
		p.localAddr = &net.TCPAddr{
 | 
			
		||||
			IP:   localIP,
 | 
			
		||||
			Port: int(localPort),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		p.remoteAddr = &net.UDPAddr{
 | 
			
		||||
			IP:   remoteIP,
 | 
			
		||||
			Port: int(remotePort),
 | 
			
		||||
		}
 | 
			
		||||
		p.localAddr = &net.UDPAddr{
 | 
			
		||||
			IP:   localIP,
 | 
			
		||||
			Port: int(localPort),
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Conn) readV1ProxyHeader() error {
 | 
			
		||||
	// Read until a newline
 | 
			
		||||
	header, err := p.bufReader.ReadString('\n')
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if header[len(header)-2] != '\r' {
 | 
			
		||||
		return &ErrBadHeader{[]byte(header)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Strip the carriage return and new line
 | 
			
		||||
	header = header[:len(header)-2]
 | 
			
		||||
 | 
			
		||||
	// Split on spaces, should be (PROXY <type> <remote addr> <local addr> <remote port> <local port>)
 | 
			
		||||
	parts := strings.Split(header, " ")
 | 
			
		||||
	if len(parts) < 2 {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadHeader{[]byte(header)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Verify the type is known
 | 
			
		||||
	switch parts[1] {
 | 
			
		||||
	case "UNKNOWN":
 | 
			
		||||
		if !p.acceptUnknown || len(parts) != 2 {
 | 
			
		||||
			p.conn.Close()
 | 
			
		||||
			return &ErrBadHeader{[]byte(header)}
 | 
			
		||||
		}
 | 
			
		||||
		p.remoteAddr = p.conn.LocalAddr()
 | 
			
		||||
		p.localAddr = p.conn.RemoteAddr()
 | 
			
		||||
		return nil
 | 
			
		||||
	case "TCP4":
 | 
			
		||||
	case "TCP6":
 | 
			
		||||
	default:
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadAddressType{parts[1]}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(parts) != 6 {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadHeader{[]byte(header)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Parse out the remote address
 | 
			
		||||
	ip := net.ParseIP(parts[2])
 | 
			
		||||
	if ip == nil {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadRemote{parts[2], parts[4]}
 | 
			
		||||
	}
 | 
			
		||||
	port, err := strconv.Atoi(parts[4])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadRemote{parts[2], parts[4]}
 | 
			
		||||
	}
 | 
			
		||||
	p.remoteAddr = &net.TCPAddr{IP: ip, Port: port}
 | 
			
		||||
 | 
			
		||||
	// Parse out the destination address
 | 
			
		||||
	ip = net.ParseIP(parts[3])
 | 
			
		||||
	if ip == nil {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadLocal{parts[3], parts[5]}
 | 
			
		||||
	}
 | 
			
		||||
	port, err = strconv.Atoi(parts[5])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		p.conn.Close()
 | 
			
		||||
		return &ErrBadLocal{parts[3], parts[5]}
 | 
			
		||||
	}
 | 
			
		||||
	p.localAddr = &net.TCPAddr{IP: ip, Port: port}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								modules/proxyprotocol/errors.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								modules/proxyprotocol/errors.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package proxyprotocol
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// ErrBadHeader is an error demonstrating a bad proxy header
 | 
			
		||||
type ErrBadHeader struct {
 | 
			
		||||
	Header []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ErrBadHeader) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Unexpected proxy header: %v", e.Header)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrBadAddressType is an error demonstrating a bad proxy header with bad Address type
 | 
			
		||||
type ErrBadAddressType struct {
 | 
			
		||||
	Address string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ErrBadAddressType) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Unexpected proxy header address type: %s", e.Address)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrBadRemote is an error demonstrating a bad proxy header with bad Remote
 | 
			
		||||
type ErrBadRemote struct {
 | 
			
		||||
	IP   string
 | 
			
		||||
	Port string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ErrBadRemote) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Unexpected proxy header remote IP and port: %s %s", e.IP, e.Port)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrBadLocal is an error demonstrating a bad proxy header with bad Local
 | 
			
		||||
type ErrBadLocal struct {
 | 
			
		||||
	IP   string
 | 
			
		||||
	Port string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ErrBadLocal) Error() string {
 | 
			
		||||
	return fmt.Sprintf("Unexpected proxy header local IP and port: %s %s", e.IP, e.Port)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								modules/proxyprotocol/listener.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								modules/proxyprotocol/listener.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package proxyprotocol
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Listener is used to wrap an underlying listener,
 | 
			
		||||
// whose connections may be using the HAProxy Proxy Protocol (version 1 or 2).
 | 
			
		||||
// If the connection is using the protocol, the RemoteAddr() will return
 | 
			
		||||
// the correct client address.
 | 
			
		||||
//
 | 
			
		||||
// Optionally define ProxyHeaderTimeout to set a maximum time to
 | 
			
		||||
// receive the Proxy Protocol Header. Zero means no timeout.
 | 
			
		||||
type Listener struct {
 | 
			
		||||
	Listener           net.Listener
 | 
			
		||||
	ProxyHeaderTimeout time.Duration
 | 
			
		||||
	AcceptUnknown      bool // allow PROXY UNKNOWN
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Accept implements the Accept method in the Listener interface
 | 
			
		||||
// it waits for the next call and returns a wrapped Conn.
 | 
			
		||||
func (p *Listener) Accept() (net.Conn, error) {
 | 
			
		||||
	// Get the underlying connection
 | 
			
		||||
	conn, err := p.Listener.Accept()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	newConn := NewConn(conn, p.ProxyHeaderTimeout)
 | 
			
		||||
	newConn.acceptUnknown = p.AcceptUnknown
 | 
			
		||||
	return newConn, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close closes the underlying listener.
 | 
			
		||||
func (p *Listener) Close() error {
 | 
			
		||||
	return p.Listener.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Addr returns the underlying listener's network address.
 | 
			
		||||
func (p *Listener) Addr() net.Addr {
 | 
			
		||||
	return p.Listener.Addr()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								modules/proxyprotocol/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								modules/proxyprotocol/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
// Copyright 2020 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package proxyprotocol
 | 
			
		||||
 | 
			
		||||
import "io"
 | 
			
		||||
 | 
			
		||||
var localHeader = append(v2Prefix, '\x20', '\x00', '\x00', '\x00', '\x00')
 | 
			
		||||
 | 
			
		||||
// WriteLocalHeader will write the ProxyProtocol Header for a local connection to the provided writer
 | 
			
		||||
func WriteLocalHeader(w io.Writer) error {
 | 
			
		||||
	_, err := w.Write(localHeader)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -94,45 +94,52 @@ var (
 | 
			
		||||
	LocalURL string
 | 
			
		||||
 | 
			
		||||
	// Server settings
 | 
			
		||||
	Protocol             Scheme
 | 
			
		||||
	Domain               string
 | 
			
		||||
	HTTPAddr             string
 | 
			
		||||
	HTTPPort             string
 | 
			
		||||
	RedirectOtherPort    bool
 | 
			
		||||
	PortToRedirect       string
 | 
			
		||||
	OfflineMode          bool
 | 
			
		||||
	CertFile             string
 | 
			
		||||
	KeyFile              string
 | 
			
		||||
	StaticRootPath       string
 | 
			
		||||
	StaticCacheTime      time.Duration
 | 
			
		||||
	EnableGzip           bool
 | 
			
		||||
	LandingPageURL       LandingPage
 | 
			
		||||
	LandingPageCustom    string
 | 
			
		||||
	UnixSocketPermission uint32
 | 
			
		||||
	EnablePprof          bool
 | 
			
		||||
	PprofDataPath        string
 | 
			
		||||
	EnableAcme           bool
 | 
			
		||||
	AcmeTOS              bool
 | 
			
		||||
	AcmeLiveDirectory    string
 | 
			
		||||
	AcmeEmail            string
 | 
			
		||||
	AcmeURL              string
 | 
			
		||||
	AcmeCARoot           string
 | 
			
		||||
	SSLMinimumVersion    string
 | 
			
		||||
	SSLMaximumVersion    string
 | 
			
		||||
	SSLCurvePreferences  []string
 | 
			
		||||
	SSLCipherSuites      []string
 | 
			
		||||
	GracefulRestartable  bool
 | 
			
		||||
	GracefulHammerTime   time.Duration
 | 
			
		||||
	StartupTimeout       time.Duration
 | 
			
		||||
	PerWriteTimeout      = 30 * time.Second
 | 
			
		||||
	PerWritePerKbTimeout = 10 * time.Second
 | 
			
		||||
	StaticURLPrefix      string
 | 
			
		||||
	AbsoluteAssetURL     string
 | 
			
		||||
	Protocol                   Scheme
 | 
			
		||||
	UseProxyProtocol           bool // `ini:"USE_PROXY_PROTOCOL"`
 | 
			
		||||
	ProxyProtocolTLSBridging   bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
 | 
			
		||||
	ProxyProtocolHeaderTimeout time.Duration
 | 
			
		||||
	ProxyProtocolAcceptUnknown bool
 | 
			
		||||
	Domain                     string
 | 
			
		||||
	HTTPAddr                   string
 | 
			
		||||
	HTTPPort                   string
 | 
			
		||||
	LocalUseProxyProtocol      bool
 | 
			
		||||
	RedirectOtherPort          bool
 | 
			
		||||
	RedirectorUseProxyProtocol bool
 | 
			
		||||
	PortToRedirect             string
 | 
			
		||||
	OfflineMode                bool
 | 
			
		||||
	CertFile                   string
 | 
			
		||||
	KeyFile                    string
 | 
			
		||||
	StaticRootPath             string
 | 
			
		||||
	StaticCacheTime            time.Duration
 | 
			
		||||
	EnableGzip                 bool
 | 
			
		||||
	LandingPageURL             LandingPage
 | 
			
		||||
	LandingPageCustom          string
 | 
			
		||||
	UnixSocketPermission       uint32
 | 
			
		||||
	EnablePprof                bool
 | 
			
		||||
	PprofDataPath              string
 | 
			
		||||
	EnableAcme                 bool
 | 
			
		||||
	AcmeTOS                    bool
 | 
			
		||||
	AcmeLiveDirectory          string
 | 
			
		||||
	AcmeEmail                  string
 | 
			
		||||
	AcmeURL                    string
 | 
			
		||||
	AcmeCARoot                 string
 | 
			
		||||
	SSLMinimumVersion          string
 | 
			
		||||
	SSLMaximumVersion          string
 | 
			
		||||
	SSLCurvePreferences        []string
 | 
			
		||||
	SSLCipherSuites            []string
 | 
			
		||||
	GracefulRestartable        bool
 | 
			
		||||
	GracefulHammerTime         time.Duration
 | 
			
		||||
	StartupTimeout             time.Duration
 | 
			
		||||
	PerWriteTimeout            = 30 * time.Second
 | 
			
		||||
	PerWritePerKbTimeout       = 10 * time.Second
 | 
			
		||||
	StaticURLPrefix            string
 | 
			
		||||
	AbsoluteAssetURL           string
 | 
			
		||||
 | 
			
		||||
	SSH = struct {
 | 
			
		||||
		Disabled                              bool               `ini:"DISABLE_SSH"`
 | 
			
		||||
		StartBuiltinServer                    bool               `ini:"START_SSH_SERVER"`
 | 
			
		||||
		BuiltinServerUser                     string             `ini:"BUILTIN_SSH_SERVER_USER"`
 | 
			
		||||
		UseProxyProtocol                      bool               `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"`
 | 
			
		||||
		Domain                                string             `ini:"SSH_DOMAIN"`
 | 
			
		||||
		Port                                  int                `ini:"SSH_PORT"`
 | 
			
		||||
		User                                  string             `ini:"SSH_USER"`
 | 
			
		||||
@@ -717,6 +724,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
 | 
			
		||||
			HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
 | 
			
		||||
	ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
 | 
			
		||||
	ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second)
 | 
			
		||||
	ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false)
 | 
			
		||||
	GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true)
 | 
			
		||||
	GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second)
 | 
			
		||||
	StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second)
 | 
			
		||||
@@ -770,8 +781,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
 | 
			
		||||
	}
 | 
			
		||||
	LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
 | 
			
		||||
	LocalURL = strings.TrimRight(LocalURL, "/") + "/"
 | 
			
		||||
	LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
 | 
			
		||||
	RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false)
 | 
			
		||||
	PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80")
 | 
			
		||||
	RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol)
 | 
			
		||||
	OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
 | 
			
		||||
	DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
 | 
			
		||||
	if len(StaticRootPath) == 0 {
 | 
			
		||||
@@ -836,6 +849,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
 | 
			
		||||
	SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen")
 | 
			
		||||
	SSH.Port = sec.Key("SSH_PORT").MustInt(22)
 | 
			
		||||
	SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port)
 | 
			
		||||
	SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false)
 | 
			
		||||
 | 
			
		||||
	// When disable SSH, start builtin server value is ignored.
 | 
			
		||||
	if SSH.Disabled {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ func listen(server *ssh.Server) {
 | 
			
		||||
	gracefulServer.PerWriteTimeout = setting.SSH.PerWriteTimeout
 | 
			
		||||
	gracefulServer.PerWritePerKbTimeout = setting.SSH.PerWritePerKbTimeout
 | 
			
		||||
 | 
			
		||||
	err := gracefulServer.ListenAndServe(server.Serve)
 | 
			
		||||
	err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.UseProxyProtocol)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		select {
 | 
			
		||||
		case <-graceful.GetManager().IsShutdown():
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user