github.com/xraypb/xray-core@v1.6.6/transport/internet/quic/dialer.go (about) 1 package quic 2 3 import ( 4 "context" 5 "io" 6 "sync" 7 "time" 8 9 "github.com/lucas-clemente/quic-go" 10 "github.com/lucas-clemente/quic-go/logging" 11 "github.com/lucas-clemente/quic-go/qlog" 12 "github.com/xraypb/xray-core/common" 13 "github.com/xraypb/xray-core/common/net" 14 "github.com/xraypb/xray-core/common/task" 15 "github.com/xraypb/xray-core/transport/internet" 16 "github.com/xraypb/xray-core/transport/internet/stat" 17 "github.com/xraypb/xray-core/transport/internet/tls" 18 ) 19 20 type connectionContext struct { 21 rawConn *sysConn 22 conn quic.Connection 23 } 24 25 var errConnectionClosed = newError("connection closed") 26 27 func (c *connectionContext) openStream(destAddr net.Addr) (*interConn, error) { 28 if !isActive(c.conn) { 29 return nil, errConnectionClosed 30 } 31 32 stream, err := c.conn.OpenStream() 33 if err != nil { 34 return nil, err 35 } 36 37 conn := &interConn{ 38 stream: stream, 39 local: c.conn.LocalAddr(), 40 remote: destAddr, 41 } 42 43 return conn, nil 44 } 45 46 type clientConnections struct { 47 access sync.Mutex 48 conns map[net.Destination][]*connectionContext 49 cleanup *task.Periodic 50 } 51 52 func isActive(s quic.Connection) bool { 53 select { 54 case <-s.Context().Done(): 55 return false 56 default: 57 return true 58 } 59 } 60 61 func removeInactiveConnections(conns []*connectionContext) []*connectionContext { 62 activeConnections := make([]*connectionContext, 0, len(conns)) 63 for i, s := range conns { 64 if isActive(s.conn) { 65 activeConnections = append(activeConnections, s) 66 continue 67 } 68 69 newError("closing quic connection at index: ", i).WriteToLog() 70 if err := s.conn.CloseWithError(0, ""); err != nil { 71 newError("failed to close connection").Base(err).WriteToLog() 72 } 73 if err := s.rawConn.Close(); err != nil { 74 newError("failed to close raw connection").Base(err).WriteToLog() 75 } 76 } 77 78 if len(activeConnections) < len(conns) { 79 newError("active quic connection reduced from ", len(conns), " to ", len(activeConnections)).WriteToLog() 80 return activeConnections 81 } 82 83 return conns 84 } 85 86 func (s *clientConnections) cleanConnections() error { 87 s.access.Lock() 88 defer s.access.Unlock() 89 90 if len(s.conns) == 0 { 91 return nil 92 } 93 94 newConnMap := make(map[net.Destination][]*connectionContext) 95 96 for dest, conns := range s.conns { 97 conns = removeInactiveConnections(conns) 98 if len(conns) > 0 { 99 newConnMap[dest] = conns 100 } 101 } 102 103 s.conns = newConnMap 104 return nil 105 } 106 107 func (s *clientConnections) openConnection(ctx context.Context, destAddr net.Addr, config *Config, tlsConfig *tls.Config, sockopt *internet.SocketConfig) (stat.Connection, error) { 108 s.access.Lock() 109 defer s.access.Unlock() 110 111 if s.conns == nil { 112 s.conns = make(map[net.Destination][]*connectionContext) 113 } 114 115 dest := net.DestinationFromAddr(destAddr) 116 117 var conns []*connectionContext 118 if s, found := s.conns[dest]; found { 119 conns = s 120 } 121 122 if len(conns) > 0 { 123 s := conns[len(conns)-1] 124 if isActive(s.conn) { 125 conn, err := s.openStream(destAddr) 126 if err == nil { 127 return conn, nil 128 } 129 newError("failed to openStream: ").Base(err).WriteToLog() 130 } else { 131 newError("current quic connection is not active!").WriteToLog() 132 } 133 } 134 135 conns = removeInactiveConnections(conns) 136 newError("dialing quic to ", dest).WriteToLog() 137 rawConn, err := internet.DialSystem(ctx, dest, sockopt) 138 if err != nil { 139 return nil, newError("failed to dial to dest: ", err).AtWarning().Base(err) 140 } 141 142 quicConfig := &quic.Config{ 143 ConnectionIDLength: 12, 144 KeepAlivePeriod: 0, 145 HandshakeIdleTimeout: time.Second * 8, 146 MaxIdleTimeout: time.Second * 300, 147 Tracer: qlog.NewTracer(func(_ logging.Perspective, connID []byte) io.WriteCloser { 148 return &QlogWriter{connID: connID} 149 }), 150 } 151 152 udpConn, _ := rawConn.(*net.UDPConn) 153 if udpConn == nil { 154 udpConn = rawConn.(*internet.PacketConnWrapper).Conn.(*net.UDPConn) 155 } 156 sysConn, err := wrapSysConn(udpConn, config) 157 if err != nil { 158 rawConn.Close() 159 return nil, err 160 } 161 162 conn, err := quic.DialContext(context.Background(), sysConn, destAddr, "", tlsConfig.GetTLSConfig(tls.WithDestination(dest)), quicConfig) 163 if err != nil { 164 sysConn.Close() 165 return nil, err 166 } 167 168 context := &connectionContext{ 169 conn: conn, 170 rawConn: sysConn, 171 } 172 s.conns[dest] = append(conns, context) 173 return context.openStream(destAddr) 174 } 175 176 var client clientConnections 177 178 func init() { 179 client.conns = make(map[net.Destination][]*connectionContext) 180 client.cleanup = &task.Periodic{ 181 Interval: time.Minute, 182 Execute: client.cleanConnections, 183 } 184 common.Must(client.cleanup.Start()) 185 } 186 187 func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.MemoryStreamConfig) (stat.Connection, error) { 188 tlsConfig := tls.ConfigFromStreamSettings(streamSettings) 189 if tlsConfig == nil { 190 tlsConfig = &tls.Config{ 191 ServerName: internalDomain, 192 AllowInsecure: true, 193 } 194 } 195 196 var destAddr *net.UDPAddr 197 if dest.Address.Family().IsIP() { 198 destAddr = &net.UDPAddr{ 199 IP: dest.Address.IP(), 200 Port: int(dest.Port), 201 } 202 } else { 203 addr, err := net.ResolveUDPAddr("udp", dest.NetAddr()) 204 if err != nil { 205 return nil, err 206 } 207 destAddr = addr 208 } 209 210 config := streamSettings.ProtocolSettings.(*Config) 211 212 return client.openConnection(ctx, destAddr, config, tlsConfig, streamSettings.SocketSettings) 213 } 214 215 func init() { 216 common.Must(internet.RegisterTransportDialer(protocolName, Dial)) 217 }