How can I add a 10 second timeout with tls.Dial ? (There is no tls.DialTimeout to correspond to net.DialTimeout)

huangapple go评论83阅读模式
英文:

How can I add a 10 second timeout with tls.Dial ? (There is no tls.DialTimeout to correspond to net.DialTimeout)

问题

在使用Go语言的tls.Dial时,添加超时的最佳方法是使用contextDialer来实现超时功能。以下是几种可能的方法:

  1. 使用tls.DialWithDialer函数,该函数接受一个net.Dialer参数,可以配置超时:
func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error)

你可以创建一个net.Dialer并设置超时时间,然后将其传递给tls.DialWithDialer函数。

  1. 使用tls.DialContext函数,该函数接受一个context.Context参数,可以在上下文中设置超时:
func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

你可以使用context.WithTimeout函数创建一个带有超时的上下文,并将其传递给tls.DialContext函数。

  1. 你还可以使用net.DialTimeout函数建立初始连接,然后升级连接并继续进行TLS握手。这种方法需要手动处理TLS握手过程,可能会更复杂一些。

希望这些信息对你有帮助!

英文:

What is the best way to add a timeout when using tls.Dial in Go?

I see the net package has net.DialTimeout, but unfortunately, the tls package doesn't have a corresponding function.

I presume I should be using a context or Dialer to implement a timeout, but I'm not an expert in Go and I can't find any good examples.

(1) I found tls.DialWithDialer, but I'm not sure how to create a net.Dialer that is configured with a timeout.

func DialWithDialer(dialer *net.Dialer, network, addr string, config *Config) (*Conn, error)

(2) I also found tls.DialContext, but I'm not sure how to use that to implement a timeout.

func (d *Dialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error)

(3) I presume it might be possible to establish an initial connection using net.DialTimeout and then upgrade the connection and continue with the TLS handshake, but I can't find any examples that show how to do that.

Any help or guidance would be appreciated.

Here is my simple program that connects to a list of servers and prints some info about the certificate chain. When a server is not responding, this program hangs for a long time. All I want to do is time out after 10 seconds.

package main

import (
	"bufio"
	"crypto/tls"
	"fmt"
	"os"
)

func main() {
	port := "443"
	conf := &tls.Config{
		InsecureSkipVerify: true,
	}
	s := bufio.NewScanner(os.Stdin)
	for s.Scan() {
		host := s.Text()
		conn, err := tls.Dial("tcp", host+":"+port, conf)
		if err != nil {
			fmt.Println("Host:", host, "Dial:", err)
			continue
		}
		defer conn.Close()
		certs := conn.ConnectionState().PeerCertificates
		for _, cert := range certs {
			fmt.Println("Host:", host, "Issuer:", cert.Issuer)
		}
	}
}

答案1

得分: 4

根据你的问题提到,有几种选项可以使用DialContext来实现:

package main

import (
	"bufio"
	"context"
	"crypto/tls"
	"fmt"
	"os"
	"time"
)

func main() {
	port := "443"
	conf := &tls.Config{
		InsecureSkipVerify: true,
	}
	s := bufio.NewScanner(os.Stdin)
	for s.Scan() {
		host := s.Text()
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		d := tls.Dialer{
			Config: conf,
		}
		conn, err := d.DialContext(ctx, "tcp", host+":"+port)
		cancel() // 确保始终调用cancel
		if err != nil {
			fmt.Println("Host:", host, "Dial:", err)
			continue
		}

		// 警告:在循环中使用defer可能不会产生预期的结果
		// 连接将保持打开状态,直到函数退出
		defer conn.Close()

		tlsConn := conn.(*tls.Conn)
		certs := tlsConn.ConnectionState().PeerCertificates
		for _, cert := range certs {
			fmt.Println("Host:", host, "Issuer:", cert.Issuer)
		}
	}
}

使用上述方法可以相对简单地允许你的代码用户取消请求(接受一个context并在上述代码中使用context.Background())。如果这对你来说不重要,那么使用带有TimeoutDialer更简单:

conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 10 * time.Second}, "tcp", host+":"+port, conf)
if err != nil {
	fmt.Println("Host:", host, "Dial:", err)
	continue
}

certs := conn.ConnectionState().PeerCertificates
for _, cert := range certs {
	fmt.Println("Host:", host, "Issuer:", cert.Issuer)
}
conn.Close()
英文:

As you mention in your question there are a few options; using DialContext is a common technique:

package main

import (
	"bufio"
	"context"
	"crypto/tls"
	"fmt"
	"os"
	"time"
)

func main() {
	port := "443"
	conf := &tls.Config{
		InsecureSkipVerify: true,
	}
	s := bufio.NewScanner(os.Stdin)
	for s.Scan() {
		host := s.Text()
		ctx, cancel := context.WithTimeout(context.Background(), 10 * time.Second)
		d := tls.Dialer{
			Config: conf,
		}
		conn, err := d.DialContext(ctx,"tcp", host+":"+port)
		cancel() // Ensure cancel is always called
		if err != nil {
			fmt.Println("Host:", host, "Dial:", err)
			continue
		}

		// warning: using defer in a loop may not have the expected result
        // the connection will remain open until the function exists
		defer conn.Close()

		tlsConn := conn.(*tls.Conn)
		certs := tlsConn.ConnectionState().PeerCertificates
		for _, cert := range certs {
			fmt.Println("Host:", host, "Issuer:", cert.Issuer)
		}
	}
}

Using the above approach makes it relatively simple to allow users of your code to cancel the request (accept a context and use it where the above has context.Background()). If this is not important to you then using a Dialer with Timeout is simpler:

conn, err := tls.DialWithDialer(&net.Dialer{Timeout:  10 * time.Second}, "tcp", host+":"+port, conf)
if err != nil {	
	fmt.Println("Host:", host, "Dial:", err)
 	continue
}

certs := conn.ConnectionState().PeerCertificates
for _, cert := range certs {
	fmt.Println("Host:", host, "Issuer:", cert.Issuer)
}
conn.Close()

huangapple
  • 本文由 发表于 2021年9月28日 06:45:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/69353996.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定