如何在Go语言中为HTTP客户端设置套接字选项(IP_TOS)?

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

How to set socket option (IP_TOS) for http client in go language?

问题

我是一个对Go语言新手。我计划使用Go语言开发一个HTTP客户端/服务器。在浏览HTTP客户端包支持的功能列表时,我没有找到在包中设置套接字选项的方法(也许我只是不知道如何使用)。在调用HTTP客户端连接之前,我需要在fd中设置DSCP选项(IP_TOS)。尽管我找到了使用系统调用设置套接字选项的方法,但我没有找到从HTTP包中获取fd的方法。

在HTTP服务器端,可以设置套接字选项(IP_TOS)。
代码片段:

  1. tcpListener,err := net.ListenTCP("tcp4", addr)
  2. if err != nil {
  3. //fmt.Println("error in listen", err.error())
  4. log.Fatal("net.ListenTCP()", err)
  5. }
  6. //获取监听套接字的fd
  7. f, _ := tcpListener.File()
  8. err = syscall.SetsockoptInt(int(f.Fd()), syscall.SOL_SOCKET, syscall.IP_TOS, 128)

在HTTP客户端端无法获取套接字fd和设置套接字选项(IP_TOS):
(我想在调用NewRequest之前设置IP_TOS)

  1. client := &http.Client{
  2. Transport : tr,
  3. //Timeout: time.Duration(10) * time.Second,
  4. }
  5. request, err := http.NewRequest("POST", url, body)
  6. if err != nil {
  7. panic(err)
  8. }
  9. response, err := client.Do(request)

谢谢!

英文:

I am a newbie to go language. I plan to use go language for developing an http client/Server.
While browsing through the list of features supported in http client package I do not find a way to set socket option in the package
(May be I just do not know how to use it). I am in a need to set DSCP option (IP_TOS) in the fd before calling http client connection.
(Although I find syscall option to set socket options, I do not find a way to get fd from http package).

In http server side, able to set socket option (IP_TOS).
code excerpt:

  1. tcpListener,err := net.ListenTCP("tcp4", addr)
  2. if err != nil {
  3. //fmt.Println("error in listen", err.error())
  4. log.Fatal("net.ListenTCP()", err)
  5. }
  6. //get lisenet socket fd
  7. f, _ := tcpListener.File()
  8. err = syscall.SetsockoptInt(int(f.Fd()), syscall.SOL_SOCKET, syscall.IP_TOS, 128)

In http client side, not able to get socket fd and set socket option (IP_TOS):
(I want to set IP_TOS before calling NewRequest)

  1. client := &http.Client{
  2. Transport : tr,
  3. //Timeout: time.Duration(10) * time.Second,
  4. }
  5. request, err := http.NewRequest("POST", url, body)
  6. if err != nil {
  7. panic(err)
  8. }
  9. response, err := client.Do(request)

Thanks !!

答案1

得分: 7

你可以为自己的*http.Client创建自己的http.RoundTripperDialContext

  1. dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
  2. conn, err := (&net.Dialer{
  3. Timeout: 30 * time.Second,
  4. KeepAlive: 30 * time.Second,
  5. }).DialContext(ctx, network, addr)
  6. if err != nil {
  7. return nil, err
  8. }
  9. tcpConn, ok := conn.(*net.TCPConn)
  10. if !ok {
  11. err = errors.New("conn is not tcp")
  12. return nil, err
  13. }
  14. f, err := tcpConn.File()
  15. if err != nil {
  16. return nil, err
  17. }
  18. err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  19. if err != nil {
  20. return nil, err
  21. }
  22. return conn, nil
  23. }
  24. tr := &http.Transport{
  25. Proxy: http.ProxyFromEnvironment,
  26. DialContext: dial,
  27. MaxIdleConns: 100,
  28. IdleConnTimeout: 90 * time.Second,
  29. TLSHandshakeTimeout: 10 * time.Second,
  30. ExpectContinueTimeout: 1 * time.Second,
  31. }
  32. c := &http.Client{
  33. Transport: tr,
  34. }
  35. resp, err := c.Get("https://google.com/")
  36. if err != nil {
  37. log.Fatalf("GET error: %v", err)
  38. }
  39. log.Printf("got %q", resp.Status)

编辑: 如果你需要在连接实际发生之前设置选项,你可以在DialContext中尝试以下代码,但这种方法不太可移植,不安全,不考虑上下文,并且可能会在以后出现问题:

  1. tcpAddr, err := net.ResolveTCPAddr(network, addr)
  2. if err != nil {
  3. return nil, err
  4. }
  5. sa := &syscall.SockaddrInet4{
  6. Port: tcpAddr.Port,
  7. Addr: [4]byte{tcpAddr.IP[0], tcpAddr.IP[1], tcpAddr.IP[2], tcpAddr.IP[3]},
  8. }
  9. fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
  10. if err != nil {
  11. return nil, err
  12. }
  13. err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  14. if err != nil {
  15. return nil, err
  16. }
  17. err = syscall.Connect(fd, sa)
  18. if err != nil {
  19. return nil, err
  20. }
  21. file := os.NewFile(uintptr(fd), "")
  22. conn, err := net.FileConn(file)
  23. if err != nil {
  24. return nil, err
  25. }
  26. return conn, nil

编辑 2: 在Go 1.11中,你可以这样做:

  1. dialer := &net.Dialer{
  2. Control: func(network, address string, c syscall.RawConn) error {
  3. return c.Control(func(fd uintptr) {
  4. err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  5. if err != nil {
  6. log.Printf("control: %s", err)
  7. return
  8. }
  9. })
  10. },
  11. }
  12. // ...
  13. tr := &http.Transport{
  14. Proxy: http.ProxyFromEnvironment,
  15. DialContext: dialer.DialContext,
  16. MaxIdleConns: 100,
  17. IdleConnTimeout: 90 * time.Second,
  18. TLSHandshakeTimeout: 10 * time.Second,
  19. ExpectContinueTimeout: 1 * time.Second,
  20. }
英文:

You can create your own DialContext for your own http.RoundTripper of your own *http.Client:

  1. dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
  2. conn, err := (&net.Dialer{
  3. Timeout: 30 * time.Second,
  4. KeepAlive: 30 * time.Second,
  5. }).DialContext(ctx, network, addr)
  6. if err != nil {
  7. return nil, err
  8. }
  9. tcpConn, ok := conn.(*net.TCPConn)
  10. if !ok {
  11. err = errors.New("conn is not tcp")
  12. return nil, err
  13. }
  14. f, err := tcpConn.File()
  15. if err != nil {
  16. return nil, err
  17. }
  18. err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  19. if err != nil {
  20. return nil, err
  21. }
  22. return conn, nil
  23. }
  24. tr := &http.Transport{
  25. Proxy: http.ProxyFromEnvironment,
  26. DialContext: dial,
  27. MaxIdleConns: 100,
  28. IdleConnTimeout: 90 * time.Second,
  29. TLSHandshakeTimeout: 10 * time.Second,
  30. ExpectContinueTimeout: 1 * time.Second,
  31. }
  32. c := &http.Client{
  33. Transport: tr,
  34. }
  35. resp, err := c.Get("https://google.com/")
  36. if err != nil {
  37. log.Fatalf("GET error: %v", err)
  38. }
  39. log.Printf("got %q", resp.Status)

EDIT: If you need to set the option before the connection actually happens, you can try this in your DialContext, but this is fairly non-portable, unsafe, doesn't account for context, and will probably break sooner or later:

  1. tcpAddr, err := net.ResolveTCPAddr(network, addr)
  2. if err != nil {
  3. return nil, err
  4. }
  5. sa := &syscall.SockaddrInet4{
  6. Port: tcpAddr.Port,
  7. Addr: [4]byte{tcpAddr.IP[0], tcpAddr.IP[1], tcpAddr.IP[2], tcpAddr.IP[3]},
  8. }
  9. fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
  10. if err != nil {
  11. return nil, err
  12. }
  13. err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  14. if err != nil {
  15. return nil, err
  16. }
  17. err = syscall.Connect(fd, sa)
  18. if err != nil {
  19. return nil, err
  20. }
  21. file := os.NewFile(uintptr(fd), "")
  22. conn, err := net.FileConn(file)
  23. if err != nil {
  24. return nil, err
  25. }
  26. return conn, nil

EDIT 2: In Go 1.11 you'll be able to do this:

  1. dialer := &net.Dialer{
  2. Control: func(network, address string, c syscall.RawConn) error {
  3. return c.Control(func(fd uintptr) {
  4. err := syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TOS, 128)
  5. if err != nil {
  6. log.Printf("control: %s", err)
  7. return
  8. }
  9. })
  10. },
  11. }
  12. // ...
  13. tr := &http.Transport{
  14. Proxy: http.ProxyFromEnvironment,
  15. DialContext: dialer.DialContext,
  16. MaxIdleConns: 100,
  17. IdleConnTimeout: 90 * time.Second,
  18. TLSHandshakeTimeout: 10 * time.Second,
  19. ExpectContinueTimeout: 1 * time.Second,
  20. }

huangapple
  • 本文由 发表于 2016年11月11日 16:40:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/40544096.html
匿名

发表评论

匿名网友

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

确定