[Server] Listen and serve on a unix socket
Allow to use a unix socket as a listener. To specify an endpoint type we use an optional configuration field 'net', as there's no way to distinguish a relative socket path from a hostname. Signed-off-by: Anton Tiurin <noxiouz@yandex.ru>master
							parent
							
								
									ced8a0378b
								
							
						
					
					
						commit
						ad80cbe1ea
					
				|  | @ -21,6 +21,7 @@ import ( | ||||||
| 	_ "github.com/docker/distribution/registry/auth/silly" | 	_ "github.com/docker/distribution/registry/auth/silly" | ||||||
| 	_ "github.com/docker/distribution/registry/auth/token" | 	_ "github.com/docker/distribution/registry/auth/token" | ||||||
| 	"github.com/docker/distribution/registry/handlers" | 	"github.com/docker/distribution/registry/handlers" | ||||||
|  | 	"github.com/docker/distribution/registry/listener" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/azure" | 	_ "github.com/docker/distribution/registry/storage/driver/azure" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/filesystem" | 	_ "github.com/docker/distribution/registry/storage/driver/filesystem" | ||||||
| 	_ "github.com/docker/distribution/registry/storage/driver/inmemory" | 	_ "github.com/docker/distribution/registry/storage/driver/inmemory" | ||||||
|  | @ -67,14 +68,26 @@ func main() { | ||||||
| 		go debugServer(config.HTTP.Debug.Addr) | 		go debugServer(config.HTTP.Debug.Addr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if config.HTTP.TLS.Certificate == "" { | 	server := &http.Server{ | ||||||
| 		context.GetLogger(app).Infof("listening on %v", config.HTTP.Addr) | 		Handler: handler, | ||||||
| 		if err := http.ListenAndServe(config.HTTP.Addr, handler); err != nil { | 	} | ||||||
|  | 
 | ||||||
|  | 	ln, err := listener.NewListener(config.HTTP.Net, config.HTTP.Addr) | ||||||
|  | 	if err != nil { | ||||||
| 		context.GetLogger(app).Fatalln(err) | 		context.GetLogger(app).Fatalln(err) | ||||||
| 	} | 	} | ||||||
| 	} else { | 	defer ln.Close() | ||||||
|  | 
 | ||||||
|  | 	if config.HTTP.TLS.Certificate != "" { | ||||||
| 		tlsConf := &tls.Config{ | 		tlsConf := &tls.Config{ | ||||||
| 			ClientAuth:   tls.NoClientCert, | 			ClientAuth:   tls.NoClientCert, | ||||||
|  | 			NextProtos:   []string{"http/1.1"}, | ||||||
|  | 			Certificates: make([]tls.Certificate, 1), | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		tlsConf.Certificates[0], err = tls.LoadX509KeyPair(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key) | ||||||
|  | 		if err != nil { | ||||||
|  | 			context.GetLogger(app).Fatalln(err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if len(config.HTTP.TLS.ClientCAs) != 0 { | 		if len(config.HTTP.TLS.ClientCAs) != 0 { | ||||||
|  | @ -99,18 +112,16 @@ func main() { | ||||||
| 			tlsConf.ClientCAs = pool | 			tlsConf.ClientCAs = pool | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		context.GetLogger(app).Infof("listening on %v, tls", config.HTTP.Addr) | 		ln = tls.NewListener(ln, tlsConf) | ||||||
| 		server := &http.Server{ | 		context.GetLogger(app).Infof("listening on %v, tls", ln.Addr()) | ||||||
| 			Addr:      config.HTTP.Addr, | 	} else { | ||||||
| 			Handler:   handler, | 		context.GetLogger(app).Infof("listening on %v", ln.Addr()) | ||||||
| 			TLSConfig: tlsConf, |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 		if err := server.ListenAndServeTLS(config.HTTP.TLS.Certificate, config.HTTP.TLS.Key); err != nil { | 	if err := server.Serve(ln); err != nil { | ||||||
| 		context.GetLogger(app).Fatalln(err) | 		context.GetLogger(app).Fatalln(err) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| func usage() { | func usage() { | ||||||
| 	fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>") | 	fmt.Fprintln(os.Stderr, "usage:", os.Args[0], "<config>") | ||||||
|  |  | ||||||
|  | @ -54,6 +54,9 @@ type Configuration struct { | ||||||
| 		// Addr specifies the bind address for the registry instance.
 | 		// Addr specifies the bind address for the registry instance.
 | ||||||
| 		Addr string `yaml:"addr,omitempty"` | 		Addr string `yaml:"addr,omitempty"` | ||||||
| 
 | 
 | ||||||
|  | 		// Net specifies the net portion of the bind address. A default empty value means tcp.
 | ||||||
|  | 		Net string `yaml:"net,omitempty"` | ||||||
|  | 
 | ||||||
| 		Prefix string `yaml:"prefix,omitempty"` | 		Prefix string `yaml:"prefix,omitempty"` | ||||||
| 
 | 
 | ||||||
| 		// Secret specifies the secret key which HMAC tokens are created with.
 | 		// Secret specifies the secret key which HMAC tokens are created with.
 | ||||||
|  |  | ||||||
|  | @ -61,6 +61,7 @@ var configStruct = Configuration{ | ||||||
| 	}, | 	}, | ||||||
| 	HTTP: struct { | 	HTTP: struct { | ||||||
| 		Addr   string `yaml:"addr,omitempty"` | 		Addr   string `yaml:"addr,omitempty"` | ||||||
|  | 		Net    string `yaml:"net,omitempty"` | ||||||
| 		Prefix string `yaml:"prefix,omitempty"` | 		Prefix string `yaml:"prefix,omitempty"` | ||||||
| 		Secret string `yaml:"secret,omitempty"` | 		Secret string `yaml:"secret,omitempty"` | ||||||
| 		TLS    struct { | 		TLS    struct { | ||||||
|  |  | ||||||
|  | @ -810,6 +810,7 @@ configuration may contain both. | ||||||
| ```yaml | ```yaml | ||||||
| http: | http: | ||||||
| 	addr: localhost:5000 | 	addr: localhost:5000 | ||||||
|  | 	net: tcp | ||||||
| 	prefix: /my/nested/registry/ | 	prefix: /my/nested/registry/ | ||||||
| 	secret: asecretforlocaldevelopment | 	secret: asecretforlocaldevelopment | ||||||
| 	tls: | 	tls: | ||||||
|  | @ -838,7 +839,20 @@ The `http` option details the configuration for the HTTP server that hosts the r | ||||||
|       yes |       yes | ||||||
|     </td> |     </td> | ||||||
|     <td> |     <td> | ||||||
|       The <code>HOST:PORT</code> for which the server should accept connections. |      The address for which the server should accept connections. The form depends on a network type (see <code>net</code> option): | ||||||
|  |      <code>HOST:PORT</code> for tcp and <code>FILE</code> for a unix socket. | ||||||
|  |     </td> | ||||||
|  |   </tr> | ||||||
|  |   <tr> | ||||||
|  |     <td> | ||||||
|  |       <code>net</code> | ||||||
|  |     </td> | ||||||
|  |     <td> | ||||||
|  |       no | ||||||
|  |     </td> | ||||||
|  |     <td> | ||||||
|  |      The network which is used to create a listening socket. Known networks are <code>unix</code> and <code>tcp</code>. | ||||||
|  |      The default empty value means tcp. | ||||||
|     </td> |     </td> | ||||||
|   </tr> |   </tr> | ||||||
|     <tr> |     <tr> | ||||||
|  | @ -1293,4 +1307,3 @@ middleware: | ||||||
| >**Note**: Cloudfront keys exist separately to other AWS keys.  See | >**Note**: Cloudfront keys exist separately to other AWS keys.  See | ||||||
| >[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs) | >[the documentation on AWS credentials](http://docs.aws.amazon.com/AWSSecurityCredentials/1.0/AboutAWSCredentials.html#KeyPairs) | ||||||
| >for more information. | >for more information. | ||||||
| 
 |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | package listener | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"net" | ||||||
|  | 	"os" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
 | ||||||
|  | // connections. It's used by ListenAndServe and ListenAndServeTLS so
 | ||||||
|  | // dead TCP connections (e.g. closing laptop mid-download) eventually
 | ||||||
|  | // go away.
 | ||||||
|  | // it is a plain copy-paste from net/http/server.go
 | ||||||
|  | type tcpKeepAliveListener struct { | ||||||
|  | 	*net.TCPListener | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { | ||||||
|  | 	tc, err := ln.AcceptTCP() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	tc.SetKeepAlive(true) | ||||||
|  | 	tc.SetKeepAlivePeriod(3 * time.Minute) | ||||||
|  | 	return tc, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewListener announces on laddr and net. Accepted values of the net are
 | ||||||
|  | // 'unix' and 'tcp'
 | ||||||
|  | func NewListener(net, laddr string) (net.Listener, error) { | ||||||
|  | 	switch net { | ||||||
|  | 	case "unix": | ||||||
|  | 		return newUnixListener(laddr) | ||||||
|  | 	case "tcp", "": // an empty net means tcp
 | ||||||
|  | 		return newTCPListener(laddr) | ||||||
|  | 	default: | ||||||
|  | 		return nil, fmt.Errorf("unknown address type %s", net) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newUnixListener(laddr string) (net.Listener, error) { | ||||||
|  | 	fi, err := os.Stat(laddr) | ||||||
|  | 	if err == nil { | ||||||
|  | 		// the file exists.
 | ||||||
|  | 		// try to remove it if it's a socket
 | ||||||
|  | 		if !isSocket(fi.Mode()) { | ||||||
|  | 			return nil, fmt.Errorf("file %s exists and is not a socket", laddr) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if err := os.Remove(laddr); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} else if !os.IsNotExist(err) { | ||||||
|  | 		// we can't do stat on the file.
 | ||||||
|  | 		// it means we can not remove it
 | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return net.Listen("unix", laddr) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isSocket(m os.FileMode) bool { | ||||||
|  | 	return m&os.ModeSocket != 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func newTCPListener(laddr string) (net.Listener, error) { | ||||||
|  | 	ln, err := net.Listen("tcp", laddr) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue