TUN/TAP Não responde para IP de cliente VPN

1. TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 02/11/2018 - 13:06h

Estou tentando programar uma especie de VPN do zero e estou tendo dificuldades em tratar os pacotes lidos e escritos na interface.
O cliente se conecta com o servidor autentica com PAM, em seguida o servidor responde com um IP disponivel na linha 10.11.0.1/16
O Cliente configura a propria interface com tal IP (ex: 10.11.0.2/32)
Na parte do cliente eu leio os pacotes vindos da interface e envio o pacote para o socket que está ligado ao servidor.
No servidor eu recebo o tamanho do pacote, leio o socket do cliente até completar o tamanho e em seguida escrevo o tamanho do pacote e o pacote no fd da interface do servidor.

O problema está na parte de leitura da interface do servidor, nunca chega os pacotes destinados ao IP vinculado ao cliente, apenas recebo destinado a 10.11.0.1 e 0.0.0.0 então o cliente nunca recebe resposta.

Minha duvida é, se isso seria um problema de programação ou seria alguma regra de iptables ou rota sei la...
Não sou expert em rede nem em programação, mas tenho uma ideia em mente e queria faze-la funcionar, ficaria muito grato se algum experiente me desse uma luz.

Leitura da interface do servidor
ipListStr possue chave contendo o IP do cliente com o FD do socket desse cliente

std::map<std::string, int> ipListStr;


// Loop de saida da interfaçe
size_t plen;
uint_fast8_t pacote[BUFFER];
while (true){
// Lê os pacotes vindo do tunnel
if ((plen=(size_t) read(tunFd,pacote,(size_t) BUFFER)) < 1 ) break;

auto ipheaders = (struct iphdr *) pacote;
in_addr addr{};
addr.s_addr = ipheaders->saddr;
printf("TunOut: %s",inet_ntoa(addr));
addr.s_addr = ipheaders->daddr;
printf(" -> %s [%d]\n",inet_ntoa(addr),(int) plen);

// Deve extrair o IP de destino do pacote
// E enviar para o socket vinculado ao endereço
write(ipListStr.at(inet_ntoa(addr)), &plen, sizeof(plen));
write(ipListStr.at(inet_ntoa(addr)), pacote, plen);
}
close(mainSckt);
}


Leitura do socket do cliente

ssize_t plen;
uint_fast8_t pacote[BUFFER];
while (true){

if( (plen = read(clientFd, pacote, (size_t) BUFFER)) < 1) break;

if( write(tunFd, &plen , sizeof(plen)) < 1) break;
if( write(tunFd, pacote, (size_t) plen) < 1) break;

auto ipheaders = (struct iphdr *) pacote;
in_addr addr{};
addr.s_addr = ipheaders->saddr;
printf("ClientIn: %s",inet_ntoa(addr));
addr.s_addr = ipheaders->daddr;
printf(" -> %s [%d]\n",inet_ntoa(addr),(int) plen);
}



  


2. Re: TUN/TAP Não responde para IP de cliente VPN

Paulo
paulo1205

(usa Ubuntu)

Enviado em 02/11/2018 - 16:03h

O roteamento de pacotes é feito em nível de sistema operacional.

Quando o SO produz (ou recebe de fora, se estiver configurado para fazer roteamento de pacotes) um pacote que tem como destino um determinado IP, ele decide por qual de suas interfaces de rede o pacote será enviado, e então o envia para essa interface. Se a interface escolhida for dos tipos TUN ou TAP, supõe-se que haverá, na mesma máquina, um programa que receberá os dados desse pacote através de um descritor de arquivos. Tal programa deve ler esses dados e geralmente o encaminha para algum outro programa, rodando na mesma máquina (como um hypervisor de máquinas virtuais) ou em máquinas remotas (através de canais de comunicação usando uma rede pública) que fará uma operação no sentido inverso (i.e. jogar dados recebidos num descritor, que então serão repassados a uma interface do SO remoto e tratados de acordo com regras usuais de comunicação por rede, seja entregando a uma aplicação que tem um socket nesse SO remoto, seja decidindo por fazer novo roteamento para alguma outra interface).

Dito isso, seria bom saber como você está criando a interface, e como fica a tabela de roteamento após a interface estar criada e com IP configurado.


3. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 02/11/2018 - 16:47h

paulo1205 escreveu:

O roteamento de pacotes é feito em nível de sistema operacional.

Quando o SO produz (ou recebe de fora, se estiver configurado para fazer roteamento de pacotes) um pacote que tem como destino um determinado IP, ele decide por qual de suas interfaces de rede o pacote será enviado, e então o envia para essa interface. Se a interface escolhida for dos tipos TUN ou TAP, supõe-se que haverá, na mesma máquina, um programa que receberá os dados desse pacote através de um descritor de arquivos. Tal programa deve ler esses dados e geralmente o encaminha para algum outro programa, rodando na mesma máquina (como um hypervisor de máquinas virtuais) ou em máquinas remotas (através de canais de comunicação usando uma rede pública) que fará uma operação no sentido inverso (i.e. jogar dados recebidos num descritor, que então serão repassados a uma interface do SO remoto e tratados de acordo com regras usuais de comunicação por rede, seja entregando a uma aplicação que tem um socket nesse SO remoto, seja decidindo por fazer novo roteamento para alguma outra interface).

Dito isso, seria bom saber como você está criando a interface, e como fica a tabela de roteamento após a interface estar criada e com IP configurado.


Entendi, na parte do servidor executo assim

ip addr add dev ctun0 10.11.0.1/16 broadcast 10.11.255.255
ip link set dev ctun0 up mtu 1500

iptables -I FORWARD -s 10.11.0.1/16 -j ACCEPT
iptables -I FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.11.0.1/16 -o eth0 -j MASQUERADE

Eu creio que fiz quase tudo certo, no programa eu leio o descritor da interface do servidor, e no conteudo lido eu extraio o IP de destino, só que nunca o destino é o IP de um cliente ( 10.11.0.2 ), é sempre 10.11.0.1
É como se não recebesse respostas para os pacotes escritos pelo cliente.

Não sei se estou escrevendo errado os pacotes do cliente no descritor da interface, primeiro eu escrevo um inteiro informando o tamanho do pacote, depois eu escrevo o pacote, é certo assim ou eu devo escrever o pacote diretamente?

if( write(tunFd, &plen , sizeof(plen)) < 1) break;
if( write(tunFd, pacote, (size_t) plen) < 1) break;



Deveria acontecer assim

Pacote Cliente SRC 10.11.0.2 -> Socket -> Interface do Servidor 10.11.0.1
Pacote lido da interface do Servidor DST 10.11.0.2 -> Socket -> Cliente

Quando leio a interface do servidor nunca recebo um pacote destinado a um IP de cliente.


4. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 02/11/2018 - 17:18h


ClientIP [5]: 10.11.0.2
ClientIn: 10.11.0.2 -> 8.8.8.8 [65]
ClientIn: 10.11.0.2 -> 8.8.8.8 [55]
ClientIn: 10.11.0.2 -> 8.8.8.8 [119]
ClientIn: 10.11.0.2 -> 8.8.4.4 [60]
TunOut: 64.6.177.204 -> 10.11.0.1 [64]
TunOut: 64.6.51.235 -> 10.11.0.1 [64]
ClientIn: 10.11.0.2 -> 8.8.8.8 [60]
ClientIn: 10.11.0.2 -> 8.8.4.4 [60]
ClientIn: 10.11.0.2 -> 8.8.4.4 [65]
TunOut: 64.6.177.203 -> 10.11.0.1 [64]
TunOut: 64.6.174.193 -> 10.11.0.1 [64]
ClientIn: 10.11.0.2 -> 8.8.4.4 [55]
ClientIn: 10.11.0.2 -> 8.8.4.4 [119]
ClientIn: 10.11.0.2 -> 8.8.8.8 [60]



5. Re: TUN/TAP Não responde para IP de cliente VPN

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/11/2018 - 00:16h

Eu não lembro de já ter programado com TUN/TAP, então o que eu vou dizer pode não estar muito certo. Contudo, pela descrição da API, não me parece que você deve de modo nenhum passar o tamanho do pacote pelo descritor que se refere ao túnel, mas tão somente o pacote IP (ou quadro ethernet, no caso de TAP).

Eu não entendi se você trocou os nomes de suas variáveis, mas eu estou assumindo que tunFd é o descritor que lê/escreve dados que o sistema operacional transmite/recebe pela interface, e que clientFd e ipListStr[addr] são descritores associados a algun outro canal de comunicação que utilizam a rede pública para comunicação entre as máquinas por fora da VPN. Se for assim, não faz sentido, no cliente, você preceder a escrita do pacote em tunFd com a escrita do tamanho do pacote no mesmo descritor; escrever o tamanho do pacote que vem a seguir é uma operação que faz sentido no descritor associado à rede pública, na qual pode ocorrer fragmentações e consolidação de múltiplos datagramas.

Em suma: o lado do servidor parece certo, mas o lado do cliente parece estar deixando de ler a informação de tamanho do pacote da rede pública, e, além disso, está tentando escrever a informação que ele não leu (assumir que a quantidade de bytes recebida da rede pública reflete exatamente o tamanho do pacote é arriscado, por causa da possibilidade de fragmentação/agregação ao longo da rede) num lugar onde tal informação é desnecessária (ou mesmo errada).


Isso à parte, algumas outras coisas no seu código me chamaram a atenção:

  • Por que converter in_addr para string a cada operação sobre os elementos de ipListStr? Tal conversão é um desperdício de tempo, e pode ser completamente evitada se você criar uma versão do operador < que aceite operandos do tipo in_addr (e trocar o seu mapa de std::map<std::string, socket_t> por std::map<in_addr, socket_t>).

  • A conversão de tipos ao estilo de C é considerada inadequada num programa em C++. Considere escrever “reinterpret_cast<struct iphdr *>(pacote)” em lugar de “(struct iphdr *)pacote” (eu sei que é mais longo, mas também é mais seguro — se não neste caso, certamente noutros, se você se habituar com essa cultura).

  • Seus programas não estão levando portabilidade em consideração. Quem disse que size_t terão a mesma quantidade e o mesmo ordenamento de bytes no servidor e num cliente que rode numa outra máquina? Se você vai transmitir pela rede o tamanho de um bloco de informações que virá em seguida, seria bom usar um tipo com tamanho fixo (uint32_t, por exemplo) e um ordenamento de bytes bem definido (por exemplo, usando a função/macro htonl() antes de transmitir, e ntohl() após receber).


6. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 03/11/2018 - 13:46h

paulo1205 escreveu:

Eu não lembro de já ter programado com TUN/TAP, então o que eu vou dizer pode não estar muito certo. Contudo, pela descrição da API, não me parece que você deve de modo nenhum passar o tamanho do pacote pelo descritor que se refere ao túnel, mas tão somente o pacote IP (ou quadro ethernet, no caso de TAP).

Eu não entendi se você trocou os nomes de suas variáveis, mas eu estou assumindo que tunFd é o descritor que lê/escreve dados que o sistema operacional transmite/recebe pela interface, e que clientFd e ipListStr[addr] são descritores associados a algun outro canal de comunicação que utilizam a rede pública para comunicação entre as máquinas por fora da VPN. Se for assim, não faz sentido, no cliente, você preceder a escrita do pacote em tunFd com a escrita do tamanho do pacote no mesmo descritor; escrever o tamanho do pacote que vem a seguir é uma operação que faz sentido no descritor associado à rede pública, na qual pode ocorrer fragmentações e consolidação de múltiplos datagramas.

Em suma: o lado do servidor parece certo, mas o lado do cliente parece estar deixando de ler a informação de tamanho do pacote da rede pública, e, além disso, está tentando escrever a informação que ele não leu (assumir que a quantidade de bytes recebida da rede pública reflete exatamente o tamanho do pacote é arriscado, por causa da possibilidade de fragmentação/agregação ao longo da rede) num lugar onde tal informação é desnecessária (ou mesmo errada).


Isso à parte, algumas outras coisas no seu código me chamaram a atenção:

  • Por que converter in_addr para string a cada operação sobre os elementos de ipListStr? Tal conversão é um desperdício de tempo, e pode ser completamente evitada se você criar uma versão do operador < que aceite operandos do tipo in_addr (e trocar o seu mapa de std::map<std::string, socket_t> por std::map<in_addr, socket_t>).

  • A conversão de tipos ao estilo de C é considerada inadequada num programa em C++. Considere escrever “reinterpret_cast<struct iphdr *>(pacote)” em lugar de “(struct iphdr *)pacote” (eu sei que é mais longo, mas também é mais seguro — se não neste caso, certamente noutros, se você se habituar com essa cultura).

  • Seus programas não estão levando portabilidade em consideração. Quem disse que size_t terão a mesma quantidade e o mesmo ordenamento de bytes no servidor e num cliente que rode numa outra máquina? Se você vai transmitir pela rede o tamanho de um bloco de informações que virá em seguida, seria bom usar um tipo com tamanho fixo (uint32_t, por exemplo) e um ordenamento de bytes bem definido (por exemplo, usando a função/macro htonl() antes de transmitir, e ntohl() após receber).


Obrigado pelas informações, até consegui fazer funcionar mas ainda não é da forma que eu preciso
Utilizando ifr.ifr_flags = IFF_TUN | IFF_NO_PI na interface do servidor fez com que eu recebesse respostas para os pacotes escritos pelo cliente.


ClientIn: 10.11.0.2 -> 8.8.4.4 [58]
TunOut: 8.8.4.4 -> 10.11.0.2 [133]


O cliente recebe o pacote no caso a resposta do dns e escreve na propria interface.
Bom.. funcionou, mas parece que só está funcionando com pacotes pequenos, uma resposta maior contendo HTTP por exemplo falha para o cliente.


ClientIn: 10.11.0.2 -> 8.8.4.4 [58]
TunOut: 8.8.4.4 -> 10.11.0.2 [133]
ClientIn: 10.11.0.2 -> 31.220.104.164 [52] // Talvez TCP
ClientIn: 10.11.0.2 -> 31.220.104.164 [482] // Requisição HTTPs
TunOut: 31.220.104.164 -> 10.11.0.2 [52] // Resposta TCP
TunOut: 31.220.104.164 -> 10.11.0.2 [1500] // Resposta HTTPs
TunOut: 31.220.104.164 -> 10.11.0.2 [1500] // Resposta HTTPs
TunOut: 31.220.104.164 -> 10.11.0.2 [334] // Resposta HTTPs


Até deu certo, mas não funciona 100%, passa por exemplo um pedaço do HTML só e o conteudo fica pela metade, nunca completa mesmo retentando.


Leitura e Escrita do cliente

// Lê a interface e envia pro socket
int readFromTo(int from, int to){
// Pacote
uint8_t pacote[BUFFER];
// Recebe o Pacote
ssize_t rlen = read(from,pacote,BUFFER);
if ( rlen > 0 ) write(to,pacote,(size_t) rlen);

return (int) rlen;
}

// IO Com POLL ( INTERFACE <-> SOCKET )
void vpnIo(){
if ( tunFd > 0 && sockFd > 0 ){
struct pollfd fds[2];
fds[0].fd = sockFd;
fds[1].fd = tunFd;
fds[0].events = POLLIN;
fds[1].events = POLLIN;

// IO Cliente Servidor
int evento;
while (true){
if ( ( evento = poll(fds,2,1000)) > 0 ) {
if (fds[0].revents & POLLIN) { if ( readFromTo(sockFd,tunFd) < 1 ) break; }
if (fds[1].revents & POLLIN) { if ( readFromTo(tunFd,sockFd) < 1 ) break; }
}
else if ( evento < 0 ) break;
}
}
closeVpn();
exit(0);
}



Leitura e Escrita no servidor


[...]

/// Entrada do cliente

while (true){
if( (plen = read(clientFd, pacote, (size_t) BUFFER)) < 1) break;
if( write(tunFd, pacote, (size_t) plen) < 1) break;
}

printf("Cliente fechado [FD %d | ID %d]: %s\n",clientFd, clientSessionId, clientIp);

// Zera os Fds
ipList[clientSessionId].fd = 0;
ipListStr[inet_addr(clientIp)] = 0;
close(clientFd);

[...]
// tunOut
// Loop de saida da interfaçe
size_t plen;
uintt8_t pacote[BUFFER];
while (true){
// Lê os pacotes vindo do tunnel
if ((plen=(size_t) read(tunFd,pacote,(size_t) BUFFER)) < 1 ) break;
// E enviar para o socket vinculado ao endereço de destino
auto pheader = reinterpret_cast<struct iphdr *>(pacote);
write(ipListStr[pheader->daddr], pacote, plen);
}



7. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 03/11/2018 - 18:57h

Minha ideia é simples, não entendo onde estou errando.
Depois que que esse IO funcionar pretendo comprimir a conversa entre o cliente e servidor.

Seria como um VTun ou OpenVPN mas sem a parte da criptografia.
No programa do cliente será possível customizar a conexão com SSL por exemplo caso precise de criptografia.

Estou disposto a pagar para ver a minha ideia funcionando ( se não for muito caro rs... )

Eu tenho um programa rodando no servidor que serve para receber 'injeção', SSL, SSH, OpenVPN tudo tratado na mesma porta e podendo fazer um sobre outro ( SSH sob SSL depois de HTTP, OpenVPN depois HTTP e tal... )

A ideia é um app para Android como um HTTP Injector por exemplo, mas com um protocolo simples e mais leve e que possa gerar ganhos de velocidade e economia de processamento com a compressão sem criptografia, ou até puro mesmo tendo em vista que usuários desse programa estarão focados em injeção para enganar firewalls...


8. Re: TUN/TAP Não responde para IP de cliente VPN

Paulo
paulo1205

(usa Ubuntu)

Enviado em 05/11/2018 - 02:23h

(Faço novamente o disclaimer de que eu posso estar falando alguma bobagem, já que não sou perito nos assuntos abaixo. Eu sinceramente acho que não estou errado, mas aceitarei de bom grado correções a quaisquer impropriedades que eu venha a dizer.)

Sem ver o código todo (e sem testar do meu próprio lado — até porque, como eu disse antes, não tenho experiência com programação de TUN/TAP), fica difícil para mim apontar especificamente qual o problema. Contudo, eu continuo suspeitando da maneira como você está transmitindo e recebendo as mensagens pelo socket da rede pública e, de novo, por causa do problema de fragmentação e remontagem de datagramas (UDP) e segmentos (TCP).

A primeira observação é que, pelo que pude entender da documentação, cada chamada a read() ou a write() sobre o descritor associado à interface TUN/TAP “recebe” ou “transmite” um pacote inteiro nessa interface. Nesses descritores, não deve haver escritas parciais de dados (a não ser que você mesmo cuide de fazer a fragmentação de IP, colocando os devidos cabeçalhos IP em cada unidade fragmentada).

Nos canais de dados ligados à rede pública, é inseguro assumir que a quantidade de bytes devolvida por read() no socket() vai devolver exatamente a mesma quantidade de bytes transmitida pelo outro lado quando ele chamou write() com aqueles dados. Várias entidades no meio do caminho, e mesmo os próprios sistemas operacionais do transmissor e do receptor, podem fazer agregações ou dividir aquilo que deveria ser transmitido como parte de uma ou mais chamadas a write(). Isso, aliás, frequentemente acontece: buffers são usados para diminuir a quantidade de operações lentas (tais como interromper o processamento do programa para mandar dados para a interface de rede).

Aliás, eu diria que é bem grande de acontecer pelo menos um tipo de fragmentação de informação. Suponha que o SO escreva diretamente 1500 octetos na interface tun0. Seu programa vai receber esses 1500 octetos e vai reencaminhá-los pela rede pública para o outro lado. Contudo, quando ele fizer isso diretamente (como seu programa atual está fazendo), serão 1500 octetos de payload, aos quais o SO teria de acrescentar os cabeçalhos IP e UDP/TCP da própria rede pública, para que possam trafegar por ela. Se a interface pública também tiver um MTU de 1500 (ou menor), e supondo que o transporte seja por TCP, você necessariamente teria de ter mais de um segmento TCP para acomodar tal payload.

Uma forma simples, mas não necessariamente muito elegante, de evitar esse problema é alterar seu protocolo para os dados que trafegam pela rede pública, precedendo cada pacote enviado pela rede pública com a quantidade de bytes contida nesse pacote. Essa informação seria muito mais confiável para o outro lado do que o valor de retorno da chamada a read(). Assim sendo, você poderia ficar com código parecido com o seguinte.

// Ao transmitir dados recebidos do tun para a rede pública.
ssize_t read_from_tun=read(tunFd, buffer, sizeof buffer);
if(read_from_tun==-1)
throw std::runtime_error(std::string("failure reading from tun: ")+strerror(errno));
if(read_from_tun==0)
throw std::logical_error("a zero-length packet should never occur!");

uint32_t packet_len=htonl(static_cast<uint32_t>(read_from_tun);
if(write(publicNetFd, &packet_len, sizeof packet_len)!=sizeof packet_len)
throw std::runtime_error("protocol failure: cannot send packet length");

for(ssize_t n_written, total_written=0; total_written<read_from_tun; total_written+=n_written){
n_written=write(publicNetFd, buffer+total_written, read_from_tun-total_written);
if(n_written==-1)
throw runtime_error(std::string("cannot send data: ")+strerror(errno));
}

// Ao receber dados da rede pública e enviar para o tun.
uint32_t packet_len;
if(read(publicNetFd, &packet_len, sizeof packet_len)!=sizeof packet_len)
throw std::runtime_error("protocol failure: cannot receive packet length");

size_t real_packet_len=static_cast<size_t>(ntohl(packet_len));
size_t total_read=0;
while(total_read<real_packet_len){
ssize_t n_read=read(publicNetFd, buffer+total_read, real_packet_len-total_read);
if(n_read==-1 && errno!=EAGAIN)
throw runtime_error(std::string("error while receiving data: ")+strerror(errno));
if(n_read==0)
goto end_of_data;
total_read+=n_read;
}

if(write(tunFd, buffer, real_packet_len)==-1)
throw std::runtime_error(std::string("failure writing to tun: ")+strerror(errno));


Eventualmente você pode pensar em protocolos mais interessantes (usando, por exemplo, o tamanho do datagrama/fragmento contido no próprio cabeçalho IP do pacote). Mas eu não sei se vale o esforço, pelo menos até o túnel estar realmente funcionando.

Outro ponto que pode vir a ser útil é você colocar um MTU na interface de túnel (pelo menos no lado do cliente) suficientemente menor do que o MTU do caminho, de forma a evitar a transformação de um único pacote em múltiplos datagramas/segmentos ao trafegar pela rede pública. Usar compressão também pode vir a ser útil.


9. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 05/11/2018 - 09:15h

paulo1205 escreveu:

(Faço novamente o disclaimer de que eu posso estar falando alguma bobagem, já que não sou perito nos assuntos abaixo. Eu sinceramente acho que não estou errado, mas aceitarei de bom grado correções a quaisquer impropriedades que eu venha a dizer.)

Sem ver o código todo (e sem testar do meu próprio lado — até porque, como eu disse antes, não tenho experiência com programação de TUN/TAP), fica difícil para mim apontar especificamente qual o problema. Contudo, eu continuo suspeitando da maneira como você está transmitindo e recebendo as mensagens pelo socket da rede pública e, de novo, por causa do problema de fragmentação e remontagem de datagramas (UDP) e segmentos (TCP).

A primeira observação é que, pelo que pude entender da documentação, cada chamada a read() ou a write() sobre o descritor associado à interface TUN/TAP “recebe” ou “transmite” um pacote inteiro nessa interface. Nesses descritores, não deve haver escritas parciais de dados (a não ser que você mesmo cuide de fazer a fragmentação de IP, colocando os devidos cabeçalhos IP em cada unidade fragmentada).

Nos canais de dados ligados à rede pública, é inseguro assumir que a quantidade de bytes devolvida por read() no socket() vai devolver exatamente a mesma quantidade de bytes transmitida pelo outro lado quando ele chamou write() com aqueles dados. Várias entidades no meio do caminho, e mesmo os próprios sistemas operacionais do transmissor e do receptor, podem fazer agregações ou dividir aquilo que deveria ser transmitido como parte de uma ou mais chamadas a write(). Isso, aliás, frequentemente acontece: buffers são usados para diminuir a quantidade de operações lentas (tais como interromper o processamento do programa para mandar dados para a interface de rede).

Aliás, eu diria que é bem grande de acontecer pelo menos um tipo de fragmentação de informação. Suponha que o SO escreva diretamente 1500 octetos na interface tun0. Seu programa vai receber esses 1500 octetos e vai reencaminhá-los pela rede pública para o outro lado. Contudo, quando ele fizer isso diretamente (como seu programa atual está fazendo), serão 1500 octetos de payload, aos quais o SO teria de acrescentar os cabeçalhos IP e UDP/TCP da própria rede pública, para que possam trafegar por ela. Se a interface pública também tiver um MTU de 1500 (ou menor), e supondo que o transporte seja por TCP, você necessariamente teria de ter mais de um segmento TCP para acomodar tal payload.

Uma forma simples, mas não necessariamente muito elegante, de evitar esse problema é alterar seu protocolo para os dados que trafegam pela rede pública, precedendo cada pacote enviado pela rede pública com a quantidade de bytes contida nesse pacote. Essa informação seria muito mais confiável para o outro lado do que o valor de retorno da chamada a read(). Assim sendo, você poderia ficar com código parecido com o seguinte.

// Ao transmitir dados recebidos do tun para a rede pública.
ssize_t read_from_tun=read(tunFd, buffer, sizeof buffer);
if(read_from_tun==-1)
throw std::runtime_error(std::string("failure reading from tun: ")+strerror(errno));
if(read_from_tun==0)
throw std::logical_error("a zero-length packet should never occur!");

uint32_t packet_len=htonl(static_cast<uint32_t>(read_from_tun);
if(write(publicNetFd, &packet_len, sizeof packet_len)!=sizeof packet_len)
throw std::runtime_error("protocol failure: cannot send packet length");

for(ssize_t n_written, total_written=0; total_written<read_from_tun; total_written+=n_written){
n_written=write(publicNetFd, buffer+total_written, read_from_tun-total_written);
if(n_written==-1)
throw runtime_error(std::string("cannot send data: ")+strerror(errno));
}

// Ao receber dados da rede pública e enviar para o tun.
uint32_t packet_len;
if(read(publicNetFd, &packet_len, sizeof packet_len)!=sizeof packet_len)
throw std::runtime_error("protocol failure: cannot receive packet length");

size_t real_packet_len=static_cast<size_t>(ntohl(packet_len));
size_t total_read=0;
while(total_read<real_packet_len){
ssize_t n_read=read(publicNetFd, buffer+total_read, real_packet_len-total_read);
if(n_read==-1 && errno!=EAGAIN)
throw runtime_error(std::string("error while receiving data: ")+strerror(errno));
if(n_read==0)
goto end_of_data;
total_read+=n_read;
}

if(write(tunFd, buffer, real_packet_len)==-1)
throw std::runtime_error(std::string("failure writing to tun: ")+strerror(errno));


Eventualmente você pode pensar em protocolos mais interessantes (usando, por exemplo, o tamanho do datagrama/fragmento contido no próprio cabeçalho IP do pacote). Mas eu não sei se vale o esforço, pelo menos até o túnel estar realmente funcionando.

Outro ponto que pode vir a ser útil é você colocar um MTU na interface de túnel (pelo menos no lado do cliente) suficientemente menor do que o MTU do caminho, de forma a evitar a transformação de um único pacote em múltiplos datagramas/segmentos ao trafegar pela rede pública. Usar compressão também pode vir a ser útil.


Cara muito obrigado, consegui fazer funcionar, graças as suas dicas pude refinar o código e reduzir varias linhas desnecessárias, funcionando perfeitamente sem quebrar nenhum pacote mesmo sem fazer essse esquema de passar o tamanho antes de enviar, creio que o próprio socket já evite isso.


10. Re: TUN/TAP Não responde para IP de cliente VPN

Paulo
paulo1205

(usa Ubuntu)

Enviado em 05/11/2018 - 10:53h

Qual foi a solução final para você?


11. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 05/11/2018 - 11:57h

paulo1205 escreveu:

Qual foi a solução final para você?


Primeiro aplicando a flag IFF_NO_PI ao ifreq na hora da configuração da interface do lado do servidor, permitiu compatibilizar a conversa com a interface do cliente.
Depois daí, a leitura no descritor da interface do servidor estava retornando pacotes em resposta a requisição do cliente (destino 10.11.0.2)
Funcionou, porém na parte do cliente a rede estava mal configurada, configurei a rota 0.0.0.0/0 -> 10.11.0.2 e todo o trafego passou pela VPN. funcionou tudo.
Só que ainda tem um problema, agora é lentidão, utilizando outro meio pronto como OpenVPN e SSH com o mesmo cliente e o mesmo servidor a velocidade é ilimitada praticamente, agora com meu protocolo mal consegue 100KB/s..
Tem alguma sugestão para isso?
Na parte do cliente não faço nenhuma analise de pacote, o IO é simplesmente ler a interface e enviar para o socket, na parte do servidor é a mesma coisa, lê o socket e escreve na interface, lê a interface extrai o IPv4 de destino e envia para o descritor vinculado a ele.

O socket é TCP, configuro com TCP_NODELAY e QUICK_ACK

Processo de leitura do cliente no servidor

// Depois de autenticado inicia o IO
// O IO irá monitorar apenas a entrada do cliente
ssize_t plen;
uint16_t pacote[BUFFER];
char fonte[INET_ADDRSTRLEN];
char desti[INET_ADDRSTRLEN];
while (true){
if((plen = read(clientFd, pacote, static_cast<size_t>(BUFFER))) < 1) break;
// Extrai os IPs
inet_ntop(AF_INET,pacote+DST_OFFSET4,desti, INET_ADDRSTRLEN);
inet_ntop(AF_INET,pacote+SRC_OFFSET4,fonte, INET_ADDRSTRLEN);
if (!strncmp(fonte,clientIp,INET_ADDRSTRLEN)){
// Se o destino for o IP de outro usuário da VPN
// Talvez seja util, poderão se conectar
if ( ipListStr[desti] ) write(ipListStr[desti],pacote,plen);
// Escreve na rede
else write(TUNFD, pacote, plen);
}
// Debugar
if (PRINTLVL>1){
uint16_t ipversao = pacote[0] >> 4;
int protocolo = pacote[PROTO_OFFSET];
printf("ClientOut: %s -> %s [%d|%d|%s]\n"
,fonte,desti
,ipversao,static_cast<int>(plen)
,protocolo==PROTO_TCP ? "TCP" : protocolo==PROTO_UDP ? "UDP" :
protocolo==PROTO_ICMP ? "ICMP" : protocolo==PROTO_IGMP ? "IGMP" : "OUTRO");
}
}


Processo de leitura da interface no servidor

// Dados dos pacotes
ssize_t plen;
uint16_t pacote[BUFFER];
char fonte[INET_ADDRSTRLEN];
char desti[INET_ADDRSTRLEN];

// Loop de saida da interfaçe
while (true){
if ( (plen = read(TUNFD, pacote, (size_t) BUFFER)) < 1 ) break;
inet_ntop(AF_INET,pacote+DST_OFFSET4, desti, INET_ADDRSTRLEN);
if ( ipListStr[desti] )
write(ipListStr[desti],pacote,plen);

// Debugar
if (PRINTLVL>1){
int protocolo = pacote[PROTO_OFFSET];
inet_ntop(AF_INET,pacote+SRC_OFFSET4,fonte, INET_ADDRSTRLEN);
printf("IfaceOut: %s -> %s [%d|%d|%d|%s]\n"
,fonte,desti
,ipListStr[desti]
,pacote[0] >> 4,static_cast<int>(plen)
,protocolo==PROTO_TCP ? "TCP" : protocolo==PROTO_UDP ? "UDP" :
protocolo==PROTO_ICMP ? "ICMP" : protocolo==PROTO_IGMP ? "IGMP" : "OUTRO");
}
}

printf("Interface Fechada!\n");
close(TUNFD);
close(SERVERFD);


Abertura da interface no servidor

// Abre a interface
int tun_alloc(char * dev){
struct ifreq ifr{};
int fd = open("/dev/net/tun",O_RDWR), err;
if ( fd > 0 ){
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
strncpy(ifr.ifr_name,dev,IFNAMSIZ);
if ((err = ioctl(fd,TUNSETIFF,(void*)&ifr)) < 0) return err;
}
return fd;
}


Entrada e Saida na parte do Cliente

int readFromTo(int from, int to){
// Pacote
uint8_t pacote[BUFFER];
// Recebe o Pacote
ssize_t rlen = read(from,pacote,BUFFER);
if ( rlen > 0 ) write(to,pacote,(size_t) rlen);
return (int) rlen;
}
// IO Com POLL ( INTERFACE <-> SOCKET )
void vpnIo(){
if ( TUNFD > 0 && SOCKFD > 0 ){
// Twakes no socket
setsockopt(SOCKFD, SOL_TCP, TCP_NODELAY, &UM, sizeof(UM));
setsockopt(SOCKFD, SOL_TCP, TCP_QUICKACK, &UM, sizeof(UM));
setsockopt(SOCKFD, SOL_TCP, TCP_CORK, &ZERO, sizeof(ZERO));
struct pollfd fds[2];
fds[0].fd = SOCKFD;
fds[1].fd = TUNFD;
fds[0].events = POLLIN;
fds[1].events = POLLIN;
// IO Cliente Servidor
int evento;
while (true){
if ( ( evento = poll(fds,2,1000)) > 0 ) {
if (fds[0].revents & POLLIN) { if ( readFromTo(SOCKFD,TUNFD) < 1 ) break; }
if (fds[1].revents & POLLIN) { if ( readFromTo(TUNFD,SOCKFD) < 1 ) break; }
}
else if ( evento < 0 ) break;
}
}
close(TUNFD);
close(SOCKFD);
}


Configuração da interface em Kotlin no cliente

// Depois de autenticado recebe o IP dado pelo servidor
// e configura a interface com tal IP
ifface = builder.addAddress(String(resposta.copyOf(len)).split(':')[1], 32) // 10.11.*.*
.addRoute("0.0.0.0", 0)
.addDnsServer(dnsPrim) // 8.8.8.8
.addDnsServer(dnsSecu) // 8.8.4.4
.setBlocking(true)
.setMtu(1500)
.establish()



12. Re: TUN/TAP Não responde para IP de cliente VPN

Eduardo Campos
3dUcP

(usa Manjaro Linux)

Enviado em 07/11/2018 - 13:57h

paulo1205 escreveu:

Qual foi a solução final para você?


Boa tarde, cara eu fiz os esquemas de passar o tamanho do pacote lido na interface para o socket e funcionou perfeitamente!!
A lentidão que relatei era por fragmentação de pacote, depois de calcular os tamanhos ficou perfeito!!
Agora é só eu implementar a compressão e criptografia, na criptografia utilizarei SSL.


// Lê tudo
static inline ssize_t read_n(int fd, uint8_t * pacote, size_t ler){
ssize_t lido = 0;
ssize_t atual = 0;
while (lido < ler){
if ((atual=read(fd,pacote+lido,ler-lido)) < 1) return atual;
lido += atual;
}
return lido;
}
// Escreve tudo
static inline ssize_t write_n(int fd, uint8_t * pacote, size_t escrever){
ssize_t atual = 0;
ssize_t escrito = 0;
while (escrito < escrever){
if ((atual=write(fd,pacote+escrito,escrever-escrito)) < 1 ) return atual;
escrito += atual;
}
return escrito;
}

.....

while (true){
if ( (plen = read(TUNFD, pacote, DFTBUFFERSZ)) < 1 ) break;
// Tratar apenas IPv4
if ( pacote[0] >> 4 == 4 ){
// Modo Servidor
if ( SERVERMODE ) {
getIPv4(pacote+DST_OFFSET4,desti);
// Se o destino for cliente
if ( ipListStr[desti] != TUNFD && ipListStr[desti] ){
write(ipListStr[desti], &plen, sizeof(plen));
write_n(ipListStr[desti], pacote, static_cast<size_t>(plen));
}
// Se o destino for TUNFD
else if ( ipListStr[desti] == TUNFD ) {
write_n(ipListStr[desti], pacote, static_cast<size_t>(plen));
}
}
// Modo cliente
else {
// Envia o tamanho do pacote para o socket
if (write(SERVERFD, &plen , sizeof(plen)) < 1) break;
// Envia o pacote para o socket
if (write_n(SERVERFD, pacote, static_cast<size_t>(plen)) < 1) break;
}
// Print
if (PRINTLVL>1)
printPackV4(pacote, static_cast<int>(plen), false);
}
}

.......

ssize_t plen;
char fonte[IPv4LEN];
char desti[IPv4LEN];
uint8_t pacote[DFTBUFFERSZ];
while (true){
// Primeiro recebe o tamanho do pacote
if(read(sockfd, &plen, sizeof(ssize_t)) < 1) break;
// Lê até completar a tamanho
if(read_n(sockfd, pacote, static_cast<size_t>(plen)) < 1) break;
// Tratar apenas IPv4
if ( pacote[0] >> 4 == 4 ){
// Modo Servidor
if (SERVERMODE) {
// Extrai os IPs
getIPv4(pacote+SRC_OFFSET4,fonte);
getIPv4(pacote+DST_OFFSET4,desti);
if (!strncmp(fonte,clientIp,IPv4LEN)){
if ( ipListStr[desti] != TUNFD && ipListStr[desti] ) {
write(ipListStr[desti],&plen, sizeof(plen));
write_n(ipListStr[desti], pacote, static_cast<size_t>(plen));
}
else write_n(TUNFD, pacote, static_cast<size_t>(plen));
}
}
// Modo cliente
else if (write(TUNFD, pacote, static_cast<size_t>(plen)) < 1) break;
if (PRINTLVL>1)
printPackV4(pacote, static_cast<int>(plen), true);
}
}








Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts