For on-demand PDNs connectivity:
- The DNS servers are not updated in resolv.conf and routines like gethostbyname would fail
- The default routes are not setup and socket connection without binding to an interface would fail The purpose of the section is to provide information about:
- DNS resolution using dig
- Binding to an interface to enable connectivity
1. Get Data Connection Manager and wait for service availability
Get the data factory, data connection manager and wait until the service is available.
auto &dataFactory = telux::data::DataFactory::getInstance();
{
std::promise<telux::common::ServiceStatus> p;
dataConnMgr = dataFactory.getDataConnectionManager(
slotId, [&](telux::common::ServiceStatus status) { p.set_value(status); });
if (dataConnMgr) {
std::cout << "\n\nInitializing Data connection manager subsystem on slot " << slotId
<< ", Please wait ..." << std::endl;
subSystemStatus = p.get_future().get();
}
if (subSystemStatus == telux::common::ServiceStatus::SERVICE_AVAILABLE) {
std::cout << "Data sub-system is ready" << std::endl;
} else {
std::cerr << "Unable to initialize data subsystem. Exiting..." << std::endl;
exit(1);
}
}
2. Register listener
Register the listener with the data connection manager for listening to data call status.
dataConnMgr->registerListener(dataListener);
3. Start data call on the mentioned slot Id and profile id and operation type
{
std::promise<telux::common::ErrorCode> p;
telux::data::IpFamilyType ipFamilyType = telux::data::IpFamilyType::IPV4;
dataConnMgr->startDataCall(
profileId, ipFamilyType,
[&](const std::shared_ptr<telux::data::IDataCall> &dataCall,
telux::common::ErrorCode errorCode) {
std::cout << "startCallResponse: errorCode: " << static_cast<int>(errorCode)
<< std::endl;
p.set_value(errorCode);
},
opType);
telux::common::ErrorCode errorCode = p.get_future().get();
if (errorCode != telux::common::ErrorCode::SUCCESS) {
std::cerr << "Failed to start data call. Exiting..." << std::endl;
exit(1);
}
}
4. Wait for the data call to get connected
When the data call is connected, obtain a reference to the data call that was brought up. This information would be sent to the listener.
void onDataCallInfoChanged(const std::shared_ptr<telux::data::IDataCall> &dataCall) override {
std::cout << "\n onDataCallInfoChanged";
logDataCallDetails(dataCall);
if (dataCall->getDataCallStatus() == telux::data::DataCallStatus::NET_CONNECTED) {
p_.set_value(dataCall);
}
}
std::shared_ptr<telux::data::IDataCall> dataCall = dataCallFuture.get();
if (dataCall == nullptr) {
std::cerr << "Could not get data call object. Exiting..." << std::endl;
exit(1);
}
5. Resolve the remote host using the DNS address provided by the data call
Use the DNS address (Ex: primaryDnsAddress) information from the data call object to resolve the domain using dig (Domain Information Groper). dig has multiple modes and accepts a variety of parameters, however, for the purpose of name resolution, we use the "+short" mode where detailed answers are not output by dig, but only the IP addresses are provided. We parse the IP addresses provided by dig to see if it is a valid IP address.
std::string resolve(std::string domain, std::string dnsAddress) {
std::cout << "Resolving " << domain << " using DNS server at " << dnsAddress << std::endl;
FILE *cmd;
std::string ipAddress;
char cipAddress[SIZE_IP_ADDR_BUF] = {0};
std::string command = "/usr/bin/dig @" + dnsAddress + " " + domain + " +short";
std::cout << "Command: " << command << std::endl;
cmd = popen(command.c_str(), "r");
if (cmd) {
sockaddr_in address;
while (NULL != fgets(cipAddress, SIZE_IP_ADDR_BUF, cmd)) {
cipAddress[strlen(cipAddress) - 1] = '\0';
if (inet_pton(AF_INET, cipAddress, &address.sin_addr) == 1) {
ipAddress = cipAddress;
std::cout << ipAddress;
ipAddress.erase(
std::remove(ipAddress.begin(), ipAddress.end(), '\n'), ipAddress.end());
break;
}
}
}
std::cout << "\n\n";
return ipAddress;
}
std::string remoteIp = resolve(domain, dataCall->getIpv4Info().addr.primaryDnsAddress);
if (remoteIp == "") {
std::cerr << "Could not resolve " << domain << ". Exiting..." << std::endl;
exit(1);
}
std::cout << "Resolved " << domain << " to " << remoteIp << std::endl;
6. Connect to the remote host
In addition to the usual connection routine on the client side, we additionally would need to bind to the interface that would allow us to reach the remote host. This is done here by using the setsockopt method.
int connect(std::string ipAddress, std::string outBoundIf, std::string portNumber) {
std::cout << "Connecting to " << ipAddress << " on port " << portNumber << " via " << outBoundIf
<< std::endl;
int sockfd = 0;
sockaddr_in serverIpAddress;
serverIpAddress.sin_family = AF_INET;
serverIpAddress.sin_port = htons(stoi(portNumber));
if (inet_pton(AF_INET, ipAddress.c_str(), &serverIpAddress.sin_addr) <= 0) {
std::cerr << "Cannot parse IP address" << std::endl;
return -1;
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
std::cerr << "Socket creation failed" << std::endl;
return -1;
}
ifreq ifr;
g_strlcpy(ifr.ifr_name, outBoundIf.c_str(), outBoundIf.length());
if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)) < 0) {
std::cerr << "Socket bind failed with " << strerror(errno) << std::endl;
close(sockfd);
return -1;
}
if (connect(sockfd, (sockaddr *)&serverIpAddress, sizeof(serverIpAddress)) < 0) {
std::cerr << "Connect failed: " << strerror(errno) << std::endl;
close(sockfd);
return -1;
}
return sockfd;
}
int sockfd = connect(remoteIp, dataCall->getInterfaceName(), portNumber);
if (sockfd < 0) {
std::cerr << "Could not connect to " << domain << ". Exiting..." << std::endl;
exit(1);
}
std::cout << "Connected to " << domain << std::endl;
7. Clean-up
std::cout << "Cleaning up" << std::endl;
close(sockfd);
dataConnMgr->deregisterListener(dataListener);
dataConnMgr = nullptr;