Я пытаюсь заставить сервер слушать как IPv4, так и IPv6 в режиме двойного стека.
Мне нужен один и тот же номер порта для серверов IPv4 и IPv6, и я хочу, чтобы он был на случайном выборе порта (используя порт «0»)
когда я связываюсь, каждый сервер получает свой порт, и я хочу, чтобы он был одинаковым.
поэтому я подумал, что это должно быть сделано функцией getaddrinfo.
Но когда я назначаю ему порт «0», он остается равным «0» в результатах addrinfo, что приводит к тому, что каждая привязка дает мне другой номер порта.
Мой вопрос: Есть ли способ указать getaddrinfo выбрать один свободный порт, свободный для всех интерфейсов, а затем связать данный адрес со всеми интерфейсами?
если нет, есть ли другой способ найти номер свободного порта? (без привязки и остановка при сбое)
пожалуйста, обратитесь к следующему коду:
РЕДАКТИРОВАТЬ: теперь код может быть полностью скомпилирован на VS 10.
#ifdef WIN32
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#define closesocket close
#endif
#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
int GetAddressFamily()
{
return AF_UNSPEC;
}
std::string ipaddress(addrinfo* info)
{
std::string retval;
char temp[260];
socklen_t addrlen = (socklen_t)info->ai_addrlen;
int res = ::getnameinfo(info->ai_addr, addrlen, temp, 256, NULL, 0, NI_NUMERICHOST);
if(res){
std::cout<<gai_strerrorA(res)<<std::endl;
}else{
retval = temp;
}
return retval;
}
int getport(addrinfo* info)
{
int retval=0;
if (info->ai_family == AF_INET) {
retval = htons(((struct sockaddr_in*)(info->ai_addr))->sin_port);
}else{
retval = htons(((struct sockaddr_in6*)(info->ai_addr))->sin6_port);
}
return retval;
}
int main()
{
char *hostName = NULL; //GetHostName();
int portNum = 0;
#ifdef WIN32
WSADATA w;
if(0 != WSAStartup(MAKEWORD(2, 2), &w))
{
std::cerr<<" WSAStartup() failed \n";
return -1;
}
#endif
addrinfo hints,*results,*tmp;
memset(&hints,0,sizeof(hints));
hints.ai_family = GetAddressFamily();
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_NUMERICSERV;
if(hostName){
hints.ai_flags |= AI_CANONNAME;
//AI_CANONNAME - fills ai_cannonname in address.
}else{
hints.ai_flags |= AI_PASSIVE;
//AI_PASSIVE - give ADDR_ANY and IN6ADDR_ANY_INIT when hostName is NULL
}
char portbuff[40];
sprintf(portbuff,"%u",portNum);
int res = ::getaddrinfo(hostName, portbuff,&hints, &results);
if(res){
std::cerr<<gai_strerrorA(res)<<std::endl;
}else{
std::vector<int> sockets;
for(tmp = results; tmp ; tmp=tmp->ai_next){
std::cout<<ipaddress(tmp).c_str()<<" : "<<port(tmp)<<std::endl;
int s = socket(tmp->ai_family,tmp->ai_socktype,tmp->ai_protocol);
if(s != -1){
res = bind(s, tmp->ai_addr, (int)tmp->ai_addrlen);
if(res != -1){
sockaddr_storage addr;
socklen_t len =sizeof(addr);
int res = getsockname(s, (struct sockaddr *)&addr, &len);
std::cout<<"Bound to port: ";
if(addr.ss_family == AF_INET){
std::cout<<htons(((sockaddr_in*)&addr)->sin_port)<<std::endl;
}else{
std::cout<<htons(((sockaddr_in6*)&addr)->sin6_port)<<std::endl;
}
sockets.push_back(s);
}
}
}
for(int i=0;i<sockets.size();i++){
closesocket(sockets[i]);
}
}
::freeaddrinfo(results);
return 0;
}
EDIT2: мое решение на данный момент:
Я добавил следующую функцию, которая будет вызываться после первого успешного связывания, и установлю для данного порта список addrinfo:
void setport(addrinfo* info,int port)
{
for(addrinfo* tmp = info; tmp ; tmp=tmp->ai_next){
if (tmp->ai_family == AF_INET) {
((struct sockaddr_in*)(tmp->ai_addr))->sin_port = htons(port);
}else{
((struct sockaddr_in6*)(tmp->ai_addr))->sin6_port = htons(port);
}
}
Должен быть вызван после успешного связывания:
port = getport(result)
//...after bind:
if(port == 0) {
port = printed value after succesful bind
setport(result, port)
}
Я думаю, что ваш лучший вариант — это использовать сокет IPv6 с IPV6_V6ONLY
отключен. Затем этот единственный сокет будет принимать подключения как IPv6, так и IPv4. Клиенты IPv4 будут иметь адрес, установленный на сопоставленный адрес ::ffff:a.b.c.d
,
Там на самом деле нет никаких оснований для использования getaddrinfo
здесь, потому что ты ничего не ищешь. Что-то вроде этого должно сделать это (не проверено):
int s = socket(AF_INET6, SOCK_STREAM, 0);
if (s < 0)
throw std::system_error(errno, generic_category());
const int off = 0;
if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) {
close(s);
throw std::system_error(errno, generic_category());
}
struct sockaddr_in6 addr{};
socklen_t alen = sizeof(addr);
if (bind(s, static_cast<struct sockaddr*>(&addr), alen)) {
close(s);
throw std::system_error(errno, generic_category());
}
getsockname(s, static_cast<struct sockaddr*>(&addr), &alen);
int port = ntohs(addr.sin6_port);
PS. Это хорошая идея всегда установить IPV6_V6ONLY
вариант того, какое значение вы хотите, потому что по умолчанию зависит от ОС.
Других решений пока нет …