Beispiel #1
0
// Start the HTTP Router frontend.
// Takes a channel to notify when the listener is started
// to safely synchronize tests.
func (r *HostRouter) Start(ready chan bool) {
	//FIXME: poor locking strategy
	r.Lock()
	var err error
	r.listener, err = newTimeoutListener("tcp", r.server.Addr, 300*time.Second)
	if err != nil {
		log.Errorf("%s", err)
		r.Unlock()
		return
	}

	listener := r.listener
	if r.Scheme == "https" {
		listener = tls.NewListener(listener, r.server.TLSConfig)
	}

	r.Unlock()

	log.Printf("%s server listening at %s", strings.ToUpper(r.Scheme), r.server.Addr)
	if ready != nil {
		close(ready)
	}

	// This will log a closed connection error every time we Stop
	// but that's mostly a testing issue.
	log.Errorf("%s", r.server.Serve(listener))
}
Beispiel #2
0
// find certs in and is the named directory, and match them up by their base
// name using '.pem' and '.key' as extensions.
func loadCerts(certDir string) (*tls.Config, error) {
	abs, err := filepath.Abs(certDir)
	if err != nil {
		return nil, err
	}

	dir, err := ioutil.ReadDir(abs)
	if err != nil {
		return nil, err
	}

	// [cert, key] pairs
	pairs := make(map[string][2]string)

	for _, f := range dir {
		name := f.Name()
		if strings.HasSuffix(name, ".pem") {
			p := pairs[name[:len(name)-4]]
			p[0] = filepath.Join(abs, name)
			pairs[name[:len(name)-4]] = p
		}
		if strings.HasSuffix(name, ".key") {
			p := pairs[name[:len(name)-4]]
			p[1] = filepath.Join(abs, name)
			pairs[name[:len(name)-4]] = p
		}
	}

	tlsCfg := &tls.Config{
		NextProtos: []string{"http/1.1"},
	}

	for key, pair := range pairs {
		if pair[0] == "" {
			log.Errorf("missing cert for key: %s", pair[1])
			continue
		}
		if pair[1] == "" {
			log.Errorf("missing key for cert: %s", pair[0])
			continue
		}

		cert, err := tls.LoadX509KeyPair(pair[0], pair[1])
		if err != nil {
			log.Error(err)
			continue
		}
		tlsCfg.Certificates = append(tlsCfg.Certificates, cert)
		log.Debugf("loaded X509KeyPair for %s", key)
	}

	if len(tlsCfg.Certificates) == 0 {
		return nil, fmt.Errorf("no tls certificates loaded")
	}

	tlsCfg.BuildNameToCertificate()

	return tlsCfg, nil
}
Beispiel #3
0
func (s *Service) runUDP() {
	buff := make([]byte, 65536)
	conn := s.udpListener

	// for UDP, we can proxy the data right here.
	for {
		n, _, err := conn.ReadFromUDP(buff)
		if err != nil {
			// we can't cleanly signal the Read to stop, so we have to
			// string-match this error.
			if err.Error() == "use of closed network connection" {
				// normal shutdown
				return
			} else if err, ok := err.(net.Error); ok && err.Temporary() {
				log.Warnf("WARN: %s", err.Error())
			} else {
				// unexpected error, log it before exiting
				log.Errorf("ERROR: %s", err.Error())
				atomic.AddInt64(&s.Errors, 1)
				return
			}
		}

		if n == 0 {
			continue
		}

		atomic.AddInt64(&s.Rcvd, int64(n))

		backend := s.udpRoundRobin()
		if backend == nil {
			// this could produce a lot of message
			// TODO: log some %, or max rate of messages
			continue
		}

		n, err = conn.WriteTo(buff[:n], backend.udpAddr)
		if err != nil {
			if err, ok := err.(net.Error); ok && err.Temporary() {
				log.Warnf("WARN: %s", err.Error())
				continue
			}

			log.Errorf("ERROR: %s", err.Error())
			atomic.AddInt64(&s.Errors, 1)
		} else {
			atomic.AddInt64(&s.Sent, int64(n))
		}
	}
}
Beispiel #4
0
// Add or replace a Backend in this service
func (s *Service) add(backend *Backend) {
	s.Lock()
	defer s.Unlock()

	log.Printf("Adding %s backend %s{%s} for %s at %s", backend.Network, backend.Name, backend.Addr, s.Name, s.Addr)
	backend.up = true
	backend.rwTimeout = s.ServerTimeout
	backend.dialTimeout = s.DialTimeout
	backend.checkInterval = time.Duration(s.CheckInterval) * time.Millisecond

	// We may add some allowed protocol bridging in the future, but for now just fail
	if s.Network[:3] != backend.Network[:3] {
		log.Errorf("ERROR: backend %s cannot use network '%s'", backend.Name, backend.Network)
	}

	// replace an existing backend if we have it.
	for i, b := range s.Backends {
		if b.Name == backend.Name {
			b.Stop()
			s.Backends[i] = backend
			backend.Start()
			return
		}
	}

	s.Backends = append(s.Backends, backend)

	backend.Start()
}
Beispiel #5
0
func NewBackend(cfg client.BackendConfig) *Backend {
	b := &Backend{
		Name:      cfg.Name,
		Addr:      cfg.Addr,
		CheckAddr: cfg.CheckAddr,
		Weight:    cfg.Weight,
		Network:   cfg.Network,
		stopCheck: make(chan interface{}),
	}

	// don't want a weight of 0
	if b.Weight == 0 {
		b.Weight = 1
	}

	if b.Network == "" {
		b.Network = "tcp"
	}

	switch b.Network {
	case "udp", "udp4", "udp6":
		var err error
		b.udpAddr, err = net.ResolveUDPAddr(b.Network, b.Addr)
		if err != nil {
			log.Errorf("ERROR: %s", err.Error())
			b.up = false
		}
	}

	return b
}
Beispiel #6
0
func (s *Service) connectTCP(cliConn net.Conn) {
	backends := s.next()

	// Try the first backend given, but if that fails, cycle through them all
	// to make a best effort to connect the client.
	for _, b := range backends {
		srvConn, err := s.dialer.Dial(b.Network, b.Addr)
		if err != nil {
			log.Errorf("ERROR: connecting to backend %s/%s: %s", s.Name, b.Name, err)
			atomic.AddInt64(&b.Errors, 1)
			continue
		}

		b.Proxy(srvConn, cliConn)
		return
	}

	log.Errorf("ERROR: no backend for %s", s.Name)
	cliConn.Close()
}
Beispiel #7
0
// Dial a backend by address.
// This way we can wrap the connection to provide our timeout settings, as well
// as hook it into the backend stats.
// We return an error if we don't have a backend which matches.
// If Dial returns an error, we wrap it in DialError, so that a ReverseProxy
// can determine if it's safe to call RoundTrip again on a new host.
func (s *Service) Dial(nw, addr string) (net.Conn, error) {
	s.Lock()

	var backend *Backend
	for _, b := range s.Backends {
		if b.Addr == addr {
			backend = b
			break
		}
	}
	s.Unlock()

	if backend == nil {
		return nil, DialError{fmt.Errorf("no backend matching %s", addr)}
	}

	srvConn, err := s.dialer.Dial(nw, backend.Addr)
	if err != nil {
		log.Errorf("ERROR: connecting to backend %s/%s: %s", s.Name, backend.Name, err)
		atomic.AddInt64(&backend.Errors, 1)
		return nil, DialError{err}
	}

	conn := &shuttleConn{
		TCPConn:   srvConn.(*net.TCPConn),
		rwTimeout: s.ServerTimeout,
		written:   &backend.Sent,
		read:      &backend.Rcvd,
		connected: &backend.HTTPActive,
	}

	atomic.AddInt64(&backend.Conns, 1)

	// NOTE: this relies on conn.Close being called, which *should* happen in
	// all cases, but may be at fault in the active count becomes skewed in
	// some error case.
	atomic.AddInt64(&backend.HTTPActive, 1)
	return conn, nil
}