Repository: traefik/traefik (language: go)

Issue:
Traefik not repeating DNS lookup for services not present at initialisation in Docker environment
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.containo.us) and didn't find any.

### What did you do?

Set up Traefik as a proxy to another group of services using a file provider in a docker swarm environment. Expected Traefik to fail on DNS lookups for those  prior to the initialisation, but then succeed when those downstream services initialised.

### What did you see instead?

Traefik failed the initial DNS looks up on initialisation, and then continued to fail the DNS lookups for those services even once they had instantiated. The services are available by their name from within the Traefik container. Furthermore, restarting the Traefik service/container caused it to start succeeding on the DNS lookups.

### What version of Traefik are you using?

behaviour present with v2.3.2, v2.3.7 v.2.4.13

### What is your environment & configuration?

```yaml
#dynamic

tcp:

  routers:
    <router_name>:
      entrypoints:
        - "<entrypoint_name>"
      rule: "HostSNI(`*`)"
      service: <service_name>

  services:
    <service_name>:
      loadBalancer:
        servers:
          - address: "<service_name_1>:<port>"
          - address: "<service_name_2>:<port>"
          - address: "<service_name_n>:<port>"
          
#static

entryPoints:
  <entrypoint_name>:
    address: ":<port>"


providers:
  file:
    filename: "/etc/traefik/traefik-dynamic.yml"
    watch: true

serversTransport:
  maxIdleConnsPerHost: -1
```



### If applicable, please paste the log output in DEBUG level

`time="<timestamp" level=error msg="no available server"` - this line repeated ad infinitum, mixed in with other items about other services which are not relevant to this bug.


Discussion / hints:
@pa-exonar-com Thanks for the report, I can confirm the issue while using a hostname as address that has no endpoints when Traefik starts.
Is there any update on this by chance?

Fix patch:
diff --git a/pkg/tcp/proxy.go b/pkg/tcp/proxy.go
index 06dc2884af..22e35e191d 100644
--- a/pkg/tcp/proxy.go
+++ b/pkg/tcp/proxy.go
@@ -14,33 +14,31 @@ import (
 // Proxy forwards a TCP request to a TCP service.
 type Proxy struct {
 	address          string
-	target           *net.TCPAddr
+	tcpAddr          *net.TCPAddr
 	terminationDelay time.Duration
 	proxyProtocol    *dynamic.ProxyProtocol
-	refreshTarget    bool
 }
 
 // NewProxy creates a new Proxy.
 func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dynamic.ProxyProtocol) (*Proxy, error) {
-	tcpAddr, err := net.ResolveTCPAddr("tcp", address)
-	if err != nil {
-		return nil, err
-	}
-
 	if proxyProtocol != nil && (proxyProtocol.Version < 1 || proxyProtocol.Version > 2) {
 		return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version)
 	}
 
-	// enable the refresh of the target only if the address in not an IP
-	refreshTarget := false
-	if host, _, err := net.SplitHostPort(address); err == nil && net.ParseIP(host) == nil {
-		refreshTarget = true
+	// Creates the tcpAddr only for IP based addresses,
+	// because there is no need to resolve the name on every new connection,
+	// and building it should happen once.
+	var tcpAddr *net.TCPAddr
+	if host, _, err := net.SplitHostPort(address); err == nil && net.ParseIP(host) != nil {
+		tcpAddr, err = net.ResolveTCPAddr("tcp", address)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	return &Proxy{
 		address:          address,
-		target:           tcpAddr,
-		refreshTarget:    refreshTarget,
+		tcpAddr:          tcpAddr,
 		terminationDelay: terminationDelay,
 		proxyProtocol:    proxyProtocol,
 	}, nil
@@ -83,10 +81,14 @@ func (p *Proxy) ServeTCP(conn WriteCloser) {
 }
 
 func (p Proxy) dialBackend() (*net.TCPConn, error) {
-	if !p.refreshTarget {
-		return net.DialTCP("tcp", nil, p.target)
+	// Dial using directly the TCPAddr for IP based addresses.
+	if p.tcpAddr != nil {
+		return net.DialTCP("tcp", nil, p.tcpAddr)
 	}
 
+	log.WithoutContext().Debugf("Dial with lookup to address %s", p.address)
+
+	// Dial with DNS lookup for host based addresses.
 	conn, err := net.Dial("tcp", p.address)
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/pkg/tcp/proxy_test.go b/pkg/tcp/proxy_test.go
index e70b36796c..fb8cad1dd2 100644
--- a/pkg/tcp/proxy_test.go
+++ b/pkg/tcp/proxy_test.go
@@ -176,16 +176,20 @@ func TestLookupAddress(t *testing.T) {
 	testCases := []struct {
 		desc          string
 		address       string
-		expectRefresh bool
+		expectAddr    assert.ComparisonAssertionFunc
+		expectRefresh assert.ValueAssertionFunc
 	}{
 		{
-			desc:    "IP doesn't need refresh",
-			address: "8.8.4.4:53",
+			desc:          "IP doesn't need refresh",
+			address:       "8.8.4.4:53",
+			expectAddr:    assert.Equal,
+			expectRefresh: assert.NotNil,
 		},
 		{
 			desc:          "Hostname needs refresh",
 			address:       "dns.google:53",
-			expectRefresh: true,
+			expectAddr:    assert.NotEqual,
+			expectRefresh: assert.Nil,
 		},
 	}
 
@@ -197,16 +201,12 @@ func TestLookupAddress(t *testing.T) {
 			proxy, err := NewProxy(test.address, 10*time.Millisecond, nil)
 			require.NoError(t, err)
 
-			require.NotNil(t, proxy.target)
+			test.expectRefresh(t, proxy.tcpAddr)
 
 			conn, err := proxy.dialBackend()
 			require.NoError(t, err)
 
-			if test.expectRefresh {
-				assert.NotEqual(t, test.address, conn.RemoteAddr().String())
-			} else {
-				assert.Equal(t, test.address, conn.RemoteAddr().String())
-			}
+			test.expectAddr(t, test.address, conn.RemoteAddr().String())
 		})
 	}
 }