* commited the latest mirror failure detection code
[ntk/apt.git] / methods / connect.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: connect.cc,v 1.10.2.1 2004/01/16 18:58:50 mdz Exp $
4 /* ######################################################################
5
6 Connect - Replacement connect call
7
8 This was originally authored by Jason Gunthorpe <jgg@debian.org>
9 and is placed in the Public Domain, do with it what you will.
10
11 ##################################################################### */
12 /*}}}*/
13 // Include Files /*{{{*/
14 #include "connect.h"
15 #include <apt-pkg/error.h>
16 #include <apt-pkg/fileutl.h>
17
18 #include <stdio.h>
19 #include <errno.h>
20 #include <unistd.h>
21 #include <sstream>
22
23 // Internet stuff
24 #include <netinet/in.h>
25 #include <sys/socket.h>
26 #include <arpa/inet.h>
27 #include <netdb.h>
28
29 #include "rfc2553emu.h"
30 #include <apti18n.h>
31 /*}}}*/
32
33 static string LastHost;
34 static int LastPort = 0;
35 static struct addrinfo *LastHostAddr = 0;
36 static struct addrinfo *LastUsed = 0;
37
38 // RotateDNS - Select a new server from a DNS rotation /*{{{*/
39 // ---------------------------------------------------------------------
40 /* This is called during certain errors in order to recover by selecting a
41 new server */
42 void RotateDNS()
43 {
44 if (LastUsed != 0 && LastUsed->ai_next != 0)
45 LastUsed = LastUsed->ai_next;
46 else
47 LastUsed = LastHostAddr;
48 }
49 /*}}}*/
50 // DoConnect - Attempt a connect operation /*{{{*/
51 // ---------------------------------------------------------------------
52 /* This helper function attempts a connection to a single address. */
53 static bool DoConnect(struct addrinfo *Addr,string Host,
54 unsigned long TimeOut,int &Fd,pkgAcqMethod *Owner)
55 {
56 // Show a status indicator
57 char Name[NI_MAXHOST];
58 char Service[NI_MAXSERV];
59
60 Name[0] = 0;
61 Service[0] = 0;
62 getnameinfo(Addr->ai_addr,Addr->ai_addrlen,
63 Name,sizeof(Name),Service,sizeof(Service),
64 NI_NUMERICHOST|NI_NUMERICSERV);
65 Owner->Status(_("Connecting to %s (%s)"),Host.c_str(),Name);
66
67 /* If this is an IP rotation store the IP we are using.. If something goes
68 wrong this will get tacked onto the end of the error message */
69 if (LastHostAddr->ai_next != 0)
70 {
71 std::stringstream ss;
72 ioprintf(ss, _("[IP: %s %s]"),Name,Service);
73 Owner->SetIP(ss.str());
74 }
75
76 // Get a socket
77 if ((Fd = socket(Addr->ai_family,Addr->ai_socktype,
78 Addr->ai_protocol)) < 0)
79 return _error->Errno("socket",_("Could not create a socket for %s (f=%u t=%u p=%u)"),
80 Name,Addr->ai_family,Addr->ai_socktype,Addr->ai_protocol);
81
82 SetNonBlock(Fd,true);
83 if (connect(Fd,Addr->ai_addr,Addr->ai_addrlen) < 0 &&
84 errno != EINPROGRESS)
85 return _error->Errno("connect",_("Cannot initiate the connection "
86 "to %s:%s (%s)."),Host.c_str(),Service,Name);
87
88 /* This implements a timeout for connect by opening the connection
89 nonblocking */
90 if (WaitFd(Fd,true,TimeOut) == false) {
91 Owner->SetFailReason("Timeout");
92 return _error->Error(_("Could not connect to %s:%s (%s), "
93 "connection timed out"),Host.c_str(),Service,Name);
94 }
95
96 // Check the socket for an error condition
97 unsigned int Err;
98 unsigned int Len = sizeof(Err);
99 if (getsockopt(Fd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
100 return _error->Errno("getsockopt",_("Failed"));
101
102 if (Err != 0)
103 {
104 errno = Err;
105 if(errno == ECONNREFUSED)
106 Owner->SetFailReason("ConnectionRefused");
107 return _error->Errno("connect",_("Could not connect to %s:%s (%s)."),Host.c_str(),
108 Service,Name);
109 }
110
111 return true;
112 }
113 /*}}}*/
114 // Connect - Connect to a server /*{{{*/
115 // ---------------------------------------------------------------------
116 /* Performs a connection to the server */
117 bool Connect(string Host,int Port,const char *Service,int DefPort,int &Fd,
118 unsigned long TimeOut,pkgAcqMethod *Owner)
119 {
120 if (_error->PendingError() == true)
121 return false;
122
123 // Convert the port name/number
124 char ServStr[300];
125 if (Port != 0)
126 snprintf(ServStr,sizeof(ServStr),"%u",Port);
127 else
128 snprintf(ServStr,sizeof(ServStr),"%s",Service);
129
130 /* We used a cached address record.. Yes this is against the spec but
131 the way we have setup our rotating dns suggests that this is more
132 sensible */
133 if (LastHost != Host || LastPort != Port)
134 {
135 Owner->Status(_("Connecting to %s"),Host.c_str());
136
137 // Free the old address structure
138 if (LastHostAddr != 0)
139 {
140 freeaddrinfo(LastHostAddr);
141 LastHostAddr = 0;
142 LastUsed = 0;
143 }
144
145 // We only understand SOCK_STREAM sockets.
146 struct addrinfo Hints;
147 memset(&Hints,0,sizeof(Hints));
148 Hints.ai_socktype = SOCK_STREAM;
149 Hints.ai_protocol = 0;
150
151 // Resolve both the host and service simultaneously
152 while (1)
153 {
154 int Res;
155 if ((Res = getaddrinfo(Host.c_str(),ServStr,&Hints,&LastHostAddr)) != 0 ||
156 LastHostAddr == 0)
157 {
158 if (Res == EAI_NONAME || Res == EAI_SERVICE)
159 {
160 if (DefPort != 0)
161 {
162 snprintf(ServStr,sizeof(ServStr),"%u",DefPort);
163 DefPort = 0;
164 continue;
165 }
166 return _error->Error(_("Could not resolve '%s'"),Host.c_str());
167 }
168
169 if (Res == EAI_AGAIN)
170 {
171 Owner->SetFailReason("TmpResolveFailure");
172 return _error->Error(_("Temporary failure resolving '%s'"),
173 Host.c_str());
174 }
175 return _error->Error(_("Something wicked happened resolving '%s:%s' (%i)"),
176 Host.c_str(),ServStr,Res);
177 }
178 break;
179 }
180
181 LastHost = Host;
182 LastPort = Port;
183 }
184
185 // When we have an IP rotation stay with the last IP.
186 struct addrinfo *CurHost = LastHostAddr;
187 if (LastUsed != 0)
188 CurHost = LastUsed;
189
190 while (CurHost != 0)
191 {
192 if (DoConnect(CurHost,Host,TimeOut,Fd,Owner) == true)
193 {
194 LastUsed = CurHost;
195 return true;
196 }
197 close(Fd);
198 Fd = -1;
199
200 // Ignore UNIX domain sockets
201 do
202 {
203 CurHost = CurHost->ai_next;
204 }
205 while (CurHost != 0 && CurHost->ai_family == AF_UNIX);
206
207 /* If we reached the end of the search list then wrap around to the
208 start */
209 if (CurHost == 0 && LastUsed != 0)
210 CurHost = LastHostAddr;
211
212 // Reached the end of the search cycle
213 if (CurHost == LastUsed)
214 break;
215
216 if (CurHost != 0)
217 _error->Discard();
218 }
219
220 if (_error->PendingError() == true)
221 return false;
222 return _error->Error(_("Unable to connect to %s %s:"),Host.c_str(),ServStr);
223 }
224 /*}}}*/