TCPConn_bind.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. //go:build !windows
  2. // +build !windows
  3. /*
  4. * Copyright (c) 2015, Psiphon Inc.
  5. * All rights reserved.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. *
  20. */
  21. package psiphon
  22. import (
  23. "context"
  24. "math/rand"
  25. "net"
  26. "syscall"
  27. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  28. )
  29. // tcpDial is the platform-specific part of DialTCP
  30. func tcpDial(ctx context.Context, addr string, config *DialConfig) (net.Conn, error) {
  31. // Get the remote IP and port, resolving a domain name if necessary
  32. host, port, err := net.SplitHostPort(addr)
  33. if err != nil {
  34. return nil, errors.Trace(err)
  35. }
  36. ipAddrs, err := LookupIP(ctx, host, config)
  37. if err != nil {
  38. return nil, errors.Trace(err)
  39. }
  40. if len(ipAddrs) < 1 {
  41. return nil, errors.TraceNew("no IP address")
  42. }
  43. // When configured, attempt to synthesize IPv6 addresses from
  44. // an IPv4 addresses for compatibility on DNS64/NAT64 networks.
  45. // If synthesize fails, try the original addresses.
  46. if config.IPv6Synthesizer != nil {
  47. for i, ipAddr := range ipAddrs {
  48. if ipAddr.To4() != nil {
  49. synthesizedIPAddress := config.IPv6Synthesizer.IPv6Synthesize(ipAddr.String())
  50. if synthesizedIPAddress != "" {
  51. synthesizedAddr := net.ParseIP(synthesizedIPAddress)
  52. if synthesizedAddr != nil {
  53. ipAddrs[i] = synthesizedAddr
  54. }
  55. }
  56. }
  57. }
  58. }
  59. // Iterate over a pseudorandom permutation of the destination
  60. // IPs and attempt connections.
  61. //
  62. // Only continue retrying as long as the dial context is not
  63. // done. Unlike net.Dial, we do not fractionalize the context
  64. // deadline, as the dial is generally intended to apply to a
  65. // single attempt. So these serial retries are most useful in
  66. // cases of immediate failure, such as "no route to host"
  67. // errors when a host resolves to both IPv4 and IPv6 but IPv6
  68. // addresses are unreachable.
  69. //
  70. // Retries at higher levels cover other cases: e.g.,
  71. // Controller.remoteServerListFetcher will retry its entire
  72. // operation and tcpDial will try a new permutation; or similarly,
  73. // Controller.establishCandidateGenerator will retry a candidate
  74. // tunnel server dials.
  75. permutedIndexes := rand.Perm(len(ipAddrs))
  76. lastErr := errors.TraceNew("unknown error")
  77. for _, index := range permutedIndexes {
  78. dialer := &net.Dialer{
  79. Control: func(_, _ string, c syscall.RawConn) error {
  80. var controlErr error
  81. err := c.Control(func(fd uintptr) {
  82. socketFD := int(fd)
  83. setAdditionalSocketOptions(socketFD)
  84. if config.BPFProgramInstructions != nil {
  85. err := setSocketBPF(config.BPFProgramInstructions, socketFD)
  86. if err != nil {
  87. controlErr = errors.Tracef("setSocketBPF failed: %s", err)
  88. return
  89. }
  90. }
  91. if config.DeviceBinder != nil {
  92. _, err := config.DeviceBinder.BindToDevice(socketFD)
  93. if err != nil {
  94. controlErr = errors.Tracef("BindToDevice failed: %s", err)
  95. return
  96. }
  97. }
  98. })
  99. if controlErr != nil {
  100. return errors.Trace(controlErr)
  101. }
  102. return errors.Trace(err)
  103. },
  104. }
  105. conn, err := dialer.DialContext(
  106. ctx, "tcp", net.JoinHostPort(ipAddrs[index].String(), port))
  107. if err != nil {
  108. lastErr = errors.Trace(err)
  109. continue
  110. }
  111. return &TCPConn{Conn: conn}, nil
  112. }
  113. return nil, lastErr
  114. }