Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
JAliEn
JAliEn-ROOT
Commits
126fed52
Commit
126fed52
authored
Jun 05, 2019
by
Nikola Hardi
Committed by
Volodymyr Yurchenko
Jun 05, 2019
Browse files
Feature DNS resolv
parent
3a214844
Pipeline
#899990
passed with stage
in 2 minutes and 28 seconds
Changes
10
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
CMakeLists.txt
View file @
126fed52
...
...
@@ -33,7 +33,11 @@ set(SRCS
TJAlienResult.cxx
TJAlienResultRewriter.cxx
TJAlienSAXHandler.cxx
TJAlienSystem.cxx
)
TJAlienSystem.cxx
TJAlienCredentials.cxx
TJClientFile.cxx
TJAlienDNSResolver.cxx
)
string
(
REPLACE
".cxx"
".h"
HDRS
"
${
SRCS
}
"
)
include_directories
(
${
CMAKE_CURRENT_SOURCE_DIR
}
)
include_directories
(
${
LIBNAME
}
${
OPENSSL_INCLUDE_DIR
}
)
...
...
LinkDef.h
View file @
126fed52
...
...
@@ -20,4 +20,7 @@
#pragma link C++ class TJAlienResultRewriter;
#pragma link C++ class TJAlienSAXHandler;
#pragma link C++ class TJAlienSystem;
#pragma link C++ class TJAlienCredentials;
#pragma link C++ class TJClientFile;
#pragma link C++ class TJAlienDNSResolver;
#endif
TJAlien.cxx
View file @
126fed52
...
...
@@ -19,6 +19,7 @@
#include
"TJAlienJobStatus.h"
#include
"TJAlienJobStatusList.h"
#include
"TJAlienResultRewriter.h"
#include
"TJAlienDNSResolver.h"
#include
<sstream>
ClassImp
(
TJAlien
)
...
...
@@ -29,6 +30,8 @@ int TJAlien::writeable_flag = 0;
int
TJAlien
::
receive_flag
=
0
;
std
::
string
TJAlien
::
readBuffer
=
""
;
using
std
::
string
;
//______________________________________________________________________________
TJAlien
::
TJAlien
(
const
char
*
gridUrl
,
const
char
*
uId
,
const
char
*
passwd
,
...
...
@@ -73,120 +76,103 @@ TJAlien::~TJAlien()
//______________________________________________________________________________
void
TJAlien
::
CreateConnection
()
{
for
(
int
i
=
0
;
i
<
5
;)
{
// Clear flags
destroy_flag
=
0
;
connection_flag
=
0
;
writeable_flag
=
0
;
receive_flag
=
0
;
readBuffer
=
""
;
// Load certificate
std
::
stringstream
tokencert_s
,
tokenkey_s
;
tokencert_s
<<
tmpdir
<<
"/tokencert_"
<<
getuid
()
<<
".pem"
;
tokenkey_s
<<
tmpdir
<<
"/tokenkey_"
<<
getuid
()
<<
".pem"
;
std
::
string
tokencert
=
tokencert_s
.
str
();
std
::
string
tokenkey
=
tokenkey_s
.
str
();
std
::
string
tokencertpath
=
std
::
getenv
(
"JALIEN_TOKEN_CERT"
)
?
:
tokencert
;
std
::
string
tokenkeypath
=
std
::
getenv
(
"JALIEN_TOKEN_KEY"
)
?
:
tokenkey
;
FILE
*
tokencertfile
=
NULL
;
FILE
*
tokenkeyfile
=
NULL
;
TJAlienCredentialsObject
co
;
TJAlienDNSResolver
dns_jcentral
(
default_server
,
default_WSport
);
string
current_host
;
clearFlags
();
creds
.
loadCredentials
();
if
(
creds
.
has
(
cJBOX_TOKEN
))
{
co
=
creds
.
get
(
cJBOX_TOKEN
);
}
else
if
(
creds
.
has
(
cFULL_GRID_CERT
))
{
co
=
creds
.
get
(
cFULL_GRID_CERT
);
}
else
{
return
;
}
// Try to connect with token certificate if it exists
if
((
tokencertfile
=
fopen
(
tokencertpath
.
c_str
(),
"r"
))
&&
(
tokenkeyfile
=
fopen
(
tokenkeypath
.
c_str
(),
"r"
))
)
{
fclose
(
tokencertfile
);
fclose
(
tokenkeyfile
);
if
(
co
.
kind
==
cJBOX_TOKEN
)
{
ConnectJBox
();
}
ConnectJBox
(
tokencertpath
,
tokenkeypath
);
}
if
(
connection_flag
)
return
;
// In not succeded, establish a connection with full user grid certificate
if
(
!
connection_flag
)
{
std
::
string
usercert
=
sUsercert
.
Data
()[
0
]
!=
'\0'
?
sUsercert
.
Data
()
:
homedir
+
"/.globus/usercert.pem"
;
std
::
string
userkey
=
sUserkey
.
Data
()[
0
]
!=
'\0'
?
sUserkey
.
Data
()
:
homedir
+
"/.globus/userkey.pem"
;
std
::
string
usercertpath
=
std
::
getenv
(
"X509_USER_CERT"
)
?
:
usercert
;
std
::
string
userkeypath
=
std
::
getenv
(
"X509_USER_KEY"
)
?
:
userkey
;
ConnectJBox
(
usercertpath
,
userkeypath
);
}
for
(
int
i
=
0
;
i
<
dns_jcentral
.
lenght
();
i
++
)
{
current_host
=
dns_jcentral
.
get_next_host
();
ConnectJCentral
(
co
.
certpath
,
co
.
keypath
,
current_host
);
if
(
connection_flag
)
{
// If connected directly to JCentral, immediately ask for token
if
(
fHost
==
default_server
)
Token
(
""
,
false
);
if
(
gDebug
>
0
)
{
Info
(
"TJAlien"
,
"Successfully connected to %s"
,
current_host
.
c_str
());
}
Token
(
""
,
false
);
fUser
=
Whoami
();
return
;
}
else
{
Error
(
"TJAlien"
,
"Failed to connect to
any server! Retrying in %d seconds..."
,
++
i
);
sleep
(
i
);
Error
(
"TJAlien"
,
"Failed to connect to
%s - retrying..."
,
current_host
.
c_str
()
);
sleep
(
1
);
}
}
Error
(
"TJAlien"
,
"Failed to connect to any server! Giving up"
);
}
//______________________________________________________________________________
void
TJAlien
::
ConnectJBox
(
std
::
string
certpath
,
std
::
string
keypath
)
void
TJAlien
::
clearFlags
()
{
// Load token config file
char
*
cUserId
=
new
char
[
10
];
sprintf
(
cUserId
,
"%d"
,
getuid
());
char
*
jclientFileLocation
=
new
char
[
100
];
sprintf
(
jclientFileLocation
,
"%s%s%s"
,
P_tmpdir
,
"/jclient_token_"
,
cUserId
);
// Read server:port from config file
// If successful, try to connect
if
(
ReadJClientFile
(
jclientFileLocation
))
MakeWebsocketConnection
(
certpath
,
keypath
);
destroy_flag
=
0
;
connection_flag
=
0
;
writeable_flag
=
0
;
receive_flag
=
0
;
readBuffer
=
""
;
}
delete
cUserId
;
delete
jclientFileLocation
;
//______________________________________________________________________________
void
TJAlien
::
ConnectJBox
()
{
if
(
!
creds
.
has
(
cJBOX_TOKEN
))
{
return
;
}
TJAlienCredentialsObject
c
=
creds
.
get
(
cJBOX_TOKEN
);
TJClientFile
jcf
;
if
(
jcf
.
isValid
)
{
MakeWebsocketConnection
(
c
.
certpath
,
c
.
keypath
,
(
string
)
jcf
.
fHost
,
jcf
.
fWSPort
);
}
else
{
Info
(
"TJAlien"
,
"The JClient file is not valid - not connecting to JBox!"
);
}
}
// Connection failed, try again with central server
if
(
!
connection_flag
)
{
if
(
gDebug
>
1
)
Info
(
"TJAlien"
,
"Trying to connect to default server!"
);
fHost
=
default_server
;
fPort
=
8098
;
fWSPort
=
8097
;
fUser
=
""
;
fPw
=
""
;
MakeWebsocketConnection
(
certpath
,
keypath
);
}
void
TJAlien
::
ConnectJCentral
(
string
certpath
,
string
keypath
,
string
host
)
{
if
(
gDebug
>
1
)
Info
(
"TJAlien"
,
"Trying to connect to server %s"
,
host
.
c_str
());
MakeWebsocketConnection
(
certpath
,
keypath
,
host
,
default_WSport
);
}
//______________________________________________________________________________
void
TJAlien
::
MakeWebsocketConnection
(
std
::
string
certpath
,
std
::
string
keypath
)
void
TJAlien
::
MakeWebsocketConnection
(
string
certpath
,
string
keypath
,
string
host
,
int
WSPort
)
{
// Create the connection to JBox using the parameters read from the token
// returns true if the connection was established
Info
(
"TJAlien"
,
"Connecting to Server %s:%d"
,
fHost
.
Data
(),
fWSPort
);
if
(
gDebug
>
0
)
{
Info
(
"TJAlien"
,
"Connecting to Server %s:%d"
,
host
.
c_str
(),
WSPort
);
Info
(
"TJAlien"
,
"Using cert %s and %s"
,
certpath
.
c_str
(),
keypath
.
c_str
());
}
// Use this for debugging
//lws_set_log_level(1023, NULL);//LLL_DEBUG | LLL_INFO | LLL_ERR | LLL_NOTICE, NULL);
//
lws_set_log_level(1023, NULL);//LLL_DEBUG | LLL_INFO | LLL_ERR | LLL_NOTICE, NULL);
lws_set_log_level
(
gDebug
,
NULL
);
// Reset context variables
context
=
NULL
;
wsi
=
NULL
;
// Clear flags
destroy_flag
=
0
;
connection_flag
=
0
;
writeable_flag
=
0
;
receive_flag
=
0
;
clearFlags
();
// libwebsockets variables
struct
lws_client_connect_info
connect_info
;
...
...
@@ -235,13 +221,13 @@ void TJAlien::MakeWebsocketConnection(std::string certpath, std::string keypath)
if
(
gDebug
>
1
)
Info
(
"TJAlien"
,
"context created"
);
connect_info
.
address
=
fH
ost
;
connect_info
.
port
=
f
WSPort
;
connect_info
.
address
=
h
ost
.
c_str
()
;
connect_info
.
port
=
WSPort
;
connect_info
.
path
=
"/websocket/json"
;
connect_info
.
context
=
context
;
connect_info
.
ssl_connection
=
use_ssl
;
connect_info
.
host
=
fH
ost
;
connect_info
.
origin
=
fH
ost
;
connect_info
.
host
=
h
ost
.
c_str
()
;
connect_info
.
origin
=
h
ost
.
c_str
()
;
connect_info
.
ietf_version_or_minus_one
=
-
1
;
connect_info
.
protocol
=
protocols
[
0
].
name
;
connect_info
.
pwsi
=
&
wsi
;
...
...
@@ -249,7 +235,9 @@ void TJAlien::MakeWebsocketConnection(std::string certpath, std::string keypath)
// Create wsi - WebSocket Instance
lws_client_connect_via_info
(
&
connect_info
);
if
(
wsi
==
NULL
)
{
Error
(
"TJAlien"
,
"WebSocket instance creation error"
);
if
(
gDebug
>
0
)
{
Error
(
"TJAlien"
,
"WebSocket instance creation error"
);
}
return
;
}
...
...
@@ -391,118 +379,6 @@ TJAlienResult* TJAlien::GetCommandResult(json_object *json_response)
return
gridResult
;
}
//______________________________________________________________________________
Bool_t
TJAlien
::
ReadJClientFile
(
const
char
*
jclientFilePath
)
{
// Reads the token generated by JBox to set the connection parameters
// Setting: fHost, fPort, fWSPort, fUser and fPW
// jclientFilePath: the token file
// returns
// - true if all connections paramenters are set
// - false for any error
std
::
ifstream
jclientFile
(
jclientFilePath
);
std
::
string
fileLine
;
Bool_t
result
=
true
;
if
(
jclientFile
.
is_open
())
{
while
(
getline
(
jclientFile
,
fileLine
))
{
if
(
gDebug
>
1
)
Info
(
"TJAlien"
,
"Token file line: %s"
,
fileLine
.
c_str
());
TString
sLine
=
fileLine
;
TObjArray
*
arr
=
sLine
.
Tokenize
(
"= "
);
if
(
arr
->
GetEntries
()
==
2
)
{
TObjString
*
a
=
(
TObjString
*
)
arr
->
At
(
0
);
TObjString
*
b
=
(
TObjString
*
)
arr
->
At
(
1
);
TString
sKey
=
a
->
GetString
();
TString
sValue
=
b
->
GetString
();
if
(
gDebug
>
1
)
Info
(
"TJAlien"
,
"
\"
%s
\"
=
\"
%s
\"
"
,
sKey
.
Data
(),
sValue
.
Data
());
if
(
sKey
.
EqualTo
(
"Host"
))
{
fHost
=
sValue
;
if
(
fHost
==
NULL
||
fHost
.
Length
()
==
0
)
{
Error
(
"TJAlien"
,
"JAliEn connection host field empty"
);
result
=
false
;
}
}
if
(
sKey
.
EqualTo
(
"Port"
))
{
fPort
=
sValue
.
Atoi
();
}
if
(
sKey
.
EqualTo
(
"WSPort"
))
{
fWSPort
=
sValue
.
Atoi
();
}
if
(
sKey
.
EqualTo
(
"Home"
))
{
fHome
=
sValue
;
}
if
(
sKey
.
EqualTo
(
"Usercert"
))
{
sUsercert
=
sValue
;
}
if
(
sKey
.
EqualTo
(
"Userkey"
))
{
sUserkey
=
sValue
;
}
if
(
sKey
.
EqualTo
(
"User"
))
{
fUser
=
sValue
;
if
(
fUser
==
NULL
||
fUser
.
Length
()
==
0
)
{
if
(
gDebug
>
1
)
Error
(
"TJAlien"
,
"JAliEn connection user field is empty"
);
result
=
false
;
}
}
if
(
sKey
.
EqualTo
(
"Passwd"
))
{
fPw
=
sValue
;
if
(
fPw
==
NULL
||
fPw
.
Length
()
==
0
)
{
if
(
gDebug
>
1
)
Error
(
"TJAlien"
,
"JAliEn connection password field is empty"
);
result
=
false
;
}
}
}
else
{
if
(
gDebug
>
1
)
Error
(
"TJAlien"
,
"jclient file does not have "
"the correct structure (%d)"
,
arr
->
GetEntries
());
result
=
false
;
}
delete
arr
;
}
jclientFile
.
close
();
}
else
{
if
(
gDebug
>
1
)
Error
(
"TJAlien"
,
"Error while opening jclient file"
);
result
=
false
;
}
return
result
;
}
//______________________________________________________________________________
void
TJAlien
::
Stderr
()
{
...
...
@@ -671,7 +547,8 @@ void TJAlien::Token(Option_t* options, bool force_restart)
std
::
string
userkey
=
sUserkey
.
Data
()[
0
]
!=
'\0'
?
sUserkey
.
Data
()
:
homedir
+
"/.globus/userkey.pem"
;
std
::
string
usercertpath
=
std
::
getenv
(
"X509_USER_CERT"
)
?
:
usercert
.
c_str
();
std
::
string
userkeypath
=
std
::
getenv
(
"X509_USER_KEY"
)
?
:
userkey
.
c_str
();
ConnectJBox
(
usercertpath
,
userkeypath
);
// ConnectJBox(usercertpath, userkeypath);
ConnectJCentral
(
usercertpath
,
userkeypath
);
if
(
!
connection_flag
)
{
Info
(
"TJAlien"
,
"TJAlien::Token failed to establish the connection to the server"
);
...
...
TJAlien.h
View file @
126fed52
...
...
@@ -79,6 +79,12 @@ typedef char int8_t;
#endif
#define UNUSED(x) (void)(x)
#define DEFAULT_JCENTRAL_SERVER "alice-jcentral.cern.ch"
#include
"TJClientFile.h"
#include
"TJAlienCredentials.h"
using
std
::
string
;
class
TJAlien
:
public
TGrid
{
...
...
@@ -95,15 +101,16 @@ private:
static
std
::
string
readBuffer
;
std
::
string
homedir
;
// local home directory
std
::
string
tmpdir
;
// tmp directory
const
int
default_WSport
=
8097
;
const
std
::
string
default_server
=
"alice-jcentral.cern.ch"
;
Bool_t
ReadJClientFile
(
const
char
*
jclientFilePath
);
const
std
::
string
default_server
=
DEFAULT_JCENTRAL_SERVER
;
Bool_t
WriteTokenFile
();
static
size_t
WriteCallback
(
void
*
contents
,
size_t
size
,
size_t
nmemb
);
void
CreateConnection
();
void
ConnectJBox
(
std
::
string
certpath
,
std
::
string
keypath
);
void
MakeWebsocketConnection
(
std
::
string
certpath
,
std
::
string
keypath
);
void
ConnectJBox
();
void
ConnectJCentral
(
string
certpath
,
string
keypath
,
string
host
=
DEFAULT_JCENTRAL_SERVER
);
void
MakeWebsocketConnection
(
string
certpath
,
string
keypath
,
string
host
,
int
WSPort
);
// Format command to Json structure
json_object
*
CreateJsonCommand
(
TString
*
command
,
TList
*
options
);
...
...
@@ -134,6 +141,9 @@ private:
virtual
TGridResult
*
OpenDataset
(
const
char
*
lfn
,
const
char
*
options
=
""
);
const
char
*
Whoami
();
void
clearFlags
();
TJAlienCredentials
creds
;
public:
TJAlien
(
const
char
*
gridUrl
,
const
char
*
uId
=
0
,
const
char
*
passwd
=
0
,
...
...
TJAlienCredentials.cxx
0 → 100644
View file @
126fed52
#include
"TJAlienCredentials.h"
#include
<sstream>
#include
<cstdlib>
#include
<unistd.h>
string
TJAlienCredentials
::
getTmpDir
()
{
string
tmpdir
;
if
(
getenv
(
"TMPDIR"
)
!=
NULL
)
tmpdir
=
getenv
(
"TMPDIR"
);
else
if
(
getenv
(
"TMP"
)
!=
NULL
)
tmpdir
=
getenv
(
"TMP"
);
else
if
(
getenv
(
"TEMP"
)
!=
NULL
)
tmpdir
=
getenv
(
"TEMP"
);
else
tmpdir
=
P_tmpdir
;
return
tmpdir
;
}
string
TJAlienCredentials
::
getHomeDir
()
{
string
homedir
;
if
(
getenv
(
"HOME"
)
!=
NULL
)
homedir
=
getenv
(
"HOME"
);
else
homedir
=
"~"
;
return
homedir
;
}
string
TJAlienCredentials
::
getTokencertPath
()
{
std
::
stringstream
tokencert_s
;
tokencert_s
<<
tmpdir
<<
"/tokencert_"
<<
getuid
()
<<
".pem"
;
std
::
string
tokencert
=
tokencert_s
.
str
();
std
::
string
tokencertpath
=
std
::
getenv
(
"JALIEN_TOKEN_CERT"
)
?
:
tokencert
;
return
tokencertpath
;
}
string
TJAlienCredentials
::
getTokenkeyPath
()
{
std
::
stringstream
tokenkey_s
;
tokenkey_s
<<
tmpdir
<<
"/tokenkey_"
<<
getuid
()
<<
".pem"
;
std
::
string
tokenkey
=
tokenkey_s
.
str
();
std
::
string
tokenkeypath
=
std
::
getenv
(
"JALIEN_TOKEN_KEY"
)
?
:
tokenkey
;
return
tokenkeypath
;
}
string
TJAlienCredentials
::
getUsercertPath
()
{
std
::
string
usercert
=
jcf
.
sUsercert
.
Data
()[
0
]
!=
'\0'
?
jcf
.
sUsercert
.
Data
()
:
homedir
+
"/.globus/usercert.pem"
;
std
::
string
usercertpath
=
std
::
getenv
(
"X509_USER_CERT"
)
?
:
usercert
;
return
usercertpath
;
}
string
TJAlienCredentials
::
getUserkeyPath
()
{
std
::
string
userkey
=
jcf
.
sUserkey
.
Data
()[
0
]
!=
'\0'
?
jcf
.
sUserkey
.
Data
()
:
homedir
+
"/.globus/userkey.pem"
;
std
::
string
userkeypath
=
std
::
getenv
(
"X509_USER_KEY"
)
?
:
userkey
;
return
userkeypath
;
}
TJAlienCredentials
::
TJAlienCredentials
()
{
tmpdir
=
getTmpDir
();
homedir
=
getHomeDir
();
loadCredentials
();
}
void
TJAlienCredentials
::
loadCredentials
()
{
found_credentials
.
clear
();
loadTokenCertificate
();
loadFullGridCertificate
();
}
void
TJAlienCredentials
::
loadTokenCertificate
()
{
TJAlienCredentialsObject
token_credentials
(
getTokencertPath
(),
getTokenkeyPath
(),
cJBOX_TOKEN
);
if
(
token_credentials
.
exists
())
{
found_credentials
[
cJBOX_TOKEN
]
=
token_credentials
;
}
}
void
TJAlienCredentials
::
loadFullGridCertificate
()
{
TJAlienCredentialsObject
grid_certificate
(
getUsercertPath
(),
getUserkeyPath
(),
cFULL_GRID_CERT
);
if
(
grid_certificate
.
exists
())
{
found_credentials
[
cFULL_GRID_CERT
]
=
grid_certificate
;
}
}
bool
TJAlienCredentials
::
has
(
CredentialsKind
kind
)
{
return
found_credentials
.
count
(
kind
)
==
1
;
}
TJAlienCredentialsObject
TJAlienCredentials
::
get
(
CredentialsKind
kind
)
{
if
(
this
->
has
(
kind
))
{
return
found_credentials
[
kind
];
}
}
TJAlienCredentials.h
0 → 100644
View file @
126fed52
// Author: Nikola Hardi 3/6/2019
#ifndef ROOT_TJAlienCredentials
#define ROOT_TJAlienCredentials
#include
<string>
#include
<map>
#include
"TObject.h"
#include
"TJClientFile.h"
using
std
::
string
;
using
std
::
map
;
enum
CredentialsKind
{
cJBOX_TOKEN
=
0
,
cFULL_GRID_CERT
,
cJOB_TOKEN
,
cOTHER_TOKEN
,
};
struct
TJAlienCredentialsObject
{
string
certpath
;
string
keypath
;
string
source
;
CredentialsKind
kind
;
TJAlienCredentialsObject
()
{}
TJAlienCredentialsObject
(
string
certpath
,
string
keypath
,
CredentialsKind
kind
=
cOTHER_TOKEN
,
string
source
=
""
)
{
this
->
certpath
=
certpath
;
this
->
keypath
=
keypath
;
this
->
kind
=
kind
;
this
->
source
=
source
;
};
bool
exists
()
{
return
fileExists
(
certpath
)
&&
fileExists
(
keypath
);
};
private:
bool
fileExists
(
string
filename
)
{
bool
fileExists
=
false
;
FILE
*
f
=
fopen
(
filename
.
c_str
(),
"r"
);
if
(
f
!=
NULL
)
{
fclose
(
f
);
fileExists
=
true
;
}
else
{
fileExists
=
false
;
}
return
fileExists
;
}
};
class
TJAlienCredentials
:
public
TObject
{
public:
TJAlienCredentials
();
string
getTmpDir
();
string
getHomeDir
();
void
loadCredentials
();
bool
has
(
CredentialsKind
kind
);
TJAlienCredentialsObject
get
(
CredentialsKind
kind
);
private:
void
loadTokenCertificate
();
void
loadFullGridCertificate
();
string
getUsercertPath
();
string
getUserkeyPath
();
string
getTokencertPath
();
string
getTokenkeyPath
();
string
tmpdir
;
string
homedir
;
map
<
CredentialsKind
,
TJAlienCredentialsObject
>
found_credentials
;
TJClientFile
jcf
;
ClassDef
(
TJAlienCredentials
,
0
)
};
#endif
TJAlienDNSResolver.cxx
0 → 100644
View file @
126fed52
#include
"TJAlienDNSResolver.h"
using
std
::
random_device
;
TJAlienDNSResolver
::
TJAlienDNSResolver
(
string
host
,
int
port
)
{
this
->
host
=
host
;
this
->
port
=
std
::
to_string
(
port
);
reset
();
}
string
TJAlienDNSResolver
::
addr2string
(
const
struct
addrinfo
*
ai
)
{
char
dst
[
32
];
const
struct
sockaddr
*
sa
=
ai
->
ai_addr
;