// 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)) }
// 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 }
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)) } } }
// 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() }
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 }
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() }
// 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 }