Académique Documents
Professionnel Documents
Culture Documents
UDP
•
Os diversos pacotes que viajam entre dois hosts precisam ser rotulados
para que os pacotes da web possam ser diferenciados de pacotes de email
e para que os dois possam ser separados de outras comunicações de rede
em que a máquina estiver engajada. Isso se chama multiplexação.
•
Qualquer dano que possa ocorrer a um fluxo viajando separadamente de
um host para outro precisa ser reparado. Pacotes ausentes precisam ser
retransmitidos até chegar. Pacotes chegando fora de ordem têm de ser rea
grupados na ordem correta. E, para concluir, pacotes duplicados precisam
ser descartados para não termos repetição nas informações do fluxo de
dados. Isso é conhecido como fornecer um transporte confiável.
Este livro dedicará um capítulo a cada um dos dois principais protocolos usados
acima do IP.
47
48
Programação de Redes com Python
O segun do, o Transmission Cont rol Protocol (TCP), resolve os dois problemas. Além
de incorp orar números de
porta usan do as mesmas regras do UDP, ele oferece
fluxo s de dados ordenados e confiáveis que ocultam dos aplicativos o fato de qu e
o fluxo contínuo de dados foi na verdade dividido em pacotes e então reagrupado
na outra extremidade. Você aprenderá sobre o uso do TCP no capítulo 3.
É bom ressaltar que alguns aplicativos raros e especializados, como os que lida m
com o compartilhamento multimídia entre todos os hosts de uma LAN, optam
por não usar nenhum desses protocolos e preferem criar um protocolo totalmente
novo baseado no IP que, junto ao TCP e ao UDP, constitui uma nova maneira de
estabelecer comunicações através de uma rede IP. Não só isso é comum, como,
por ser uma operação de baixo nível, é improvável que seja escrita em Python;
logo, você não examinará a engenharia de protocolos neste livro. A abordagem
mais próxima da construção de pacotes brutos acima do IP neste livro fica na
seção "Fragmentação no UDP " quase no fim do capítulo 1, que constrói pacotes
e recebe uma resposta usando o ICMP.
Devo admitir desde já que é improvável que você use o UDP nos aplicativos que
criar. Se achar que o UDP combina exatamente com seu aplicativo, talvez seja
melhor examinar as filas de mensagens (Capítulo 8). No entanto, a oportunida
de que o UDP lhe dará ao expor a multiplexação de pacotes brutos é um passo
importante antes de você estar pronto para conhecer o TCP no capítulo 3.
Números de porta
O problema de conseguir diferenciar os vários sinais que estão compartilhando o
mesmo canal é de ordem geral, tanto na teoria das redes de computadores quanto
na de sinais eletromagnéticos. A solução que permite que várias comunicações
compartilhem um meio ou mecanismo é conhecida como esquema de multiple
xação. Em uma descoberta famosa, ficou provado que sinais de rádio podem ser
separados uns dos outros com o uso de frequências distintas. No mundo digital
dos pacotes, os projetistas do UDP preferiram distinguir comunicações diferentes
�
usando a técni a imper eita de rotular cada pacote UDP com um par de números
�
de porta de 16 bits sem Sinal no intervalo de O a 65.536. A porta de origem identifi
ca o recesso ou programa específico que enviou 0 pacote a partir da máquina
�
de ngem, enquanto a porta de destino especifica o aplicativo do endereço IP de
�
destino para o qual a comunicação deve ser distribuída.
49
�a c:mada de rede IP, só o que fica visível são os pacotes fazendo seu trajeto em
d1reçao a um hos t específico.
IP de origeM � IP de destino
Mas as pilhas de rede das duas máquinas que estão se comunicando- que têm
de contr �lar a c?nversa entre vários aplicativos diferentes- veem a comunicação
de mane1ra multo mais específica entre um par 'endereço IP e número de porta'
em cada máquina.
Para dar mais materialidade à ideia, suponhamos que você instalasse um servidor
DNS (Capítulo 4) em uma de suas máquinas com o endereço IP 192.168.1.9. Para
permitir que outros computadores encontrem o serviço, o servidor pedirá ao
sistema operacional permissão para receber pacotes que chegarem à porta UDP
com o número de porta DNS padrão: a porta 53. Supondo que um processo que
ainda não esteja sendo execuútdo solicite esse número de porta, ela será conce
dida ao servidor DNS.
Agora, suponhamos que uma máquina cliente com o endereço 192.168.130 quisesse
emitir uma consulta para o servidor. Ela criará uma solicitação na memória e então
solicitará ao sistema operacional que envie esse bloco de dados como um pacote
UDP. Já que é preciso alguma maneira de identificar o cliente quando o pacote
retornar e como o cliente não solicitou explicitamente um número de porta, o
sistema operacional atribuirá a ele um número aleatório- digamos, porta 44137.
tará �o sist�ma
Uma vez que tiver formulado uma resposta, o servidor DNS solici
esses dois endereços Invertidos
operacional que retorne um pacote UDP contendo
emitente.
para que a resposta retorne diretamente para o
.1.30:44137)
OrigeM (192.168.1.9:53) �Destino (192.168
Mas como um programa cliente pode descobrir o número de porta ao qual deve
se conectar? Há três abordagens gerais.
Configuração manual- Para todas as situações não abordadas nos dois casos
•
•
As portas conhecidas (0- 1023) são para os protocolos mais importantes e
amplamente usados. Em muitos sistemas operacionais Unix, programas
comuns dos usuários não podem escutar nessas portas. No passado, isso
impedia que graduandos problemáticos executassem programas que se
disfarçavam de serviços importantes do sistema em máquinas multiusuário
universitárias. Atualmente, o mesmo cuidado é aplicável quando empresas
de hospedagem fornecem contas de linha de comando do Linux.
•
Geralmente as portas registradas (1024-49151) não são tratadas como especiais
pelos sistemas operacionais - qualquer usuário pode escrever um programa
que use a porta 5432 e finja ser um banco de dados PostgreSQL, por exemplo-,
mas elas podem ser registradas pela IANA para protocolos específicos, e a IANA
recomenda evitar seu uso para algo que não seja o protocolo atribuído a elas.
•
Os números de porta restantes (49152-65535) estão livres para qualquer
tipo de uso. Como você verá, eles fazem parte do pool que os sistemas ope
racionais pesquisam para gerar números de porta arbitrários quando um
cliente não se importa com a porta atribuída para sua conexão de saída.
51
�
Na cr ação de pro�rama s que acei tem núm eros de por ta a par tir de entrada
s do
usuáno como na hnha de coma ndo ou em arqui vos de configuraça -0, e amigave1
. , · ,
p er miti r para port as conh ecida s não só núm eros de port a numéricos como tam-
bém no mes legíve is pelas pesso as. Esses nomes são padrão e esta-0 d' , .
Ispomveis
,
_
�
atra ves da f unçao getservb nal'le() do modulo padrão socket em Python . Se quiser
,
, .
saber qual e a porta a traves do Ser viço de Nome s de D omín io, é possíve l fazê-lo
dest a forma:
Soquetes
Em vez de tentar i nventar sua própria API para a programação de redes, Python
tomou uma decisão interessante. A biblioteca padrão Python fornece uma in
terface baseada em objetos para todas as chamadas comuns de baixo nível que
normalmente são usadas na execução de tarefas de rede em sistemas operacionais
compatíveis com o P OSIX. As chamadas têm até os mesmos nomes das operações
subjacentes que encapsulam. A intenção da linguagem Python de expor as chama
das de sistema tradicionais que todos conhecíamos é uma das razões que a tornou
essa lufada de ar fresco para aqueles de nós que trabalhavam em linguagens de
baixo nível no início dos anos 90. Por fim, havia chegado uma linguagem de alto
nível que nos permitia fazer chamadas de baixo nível quando precisássemos, sem
nos forçar a usar uma API ostensivamente "mais bonita" específica da linguagem,
mas desajeitada e com menos recursos. Era muito mais fácil lembrar-se de um
único conjunto de chamadas que f uncionava tanto em C quanto em Python.
52 Programação de Redes com Python
Tanto em sistemas Windows q u a n to em sísremas POSIX (como o Linux e o
Mac OS X), as chamadas de rede subjacentes giram em corno da ideia de uma
extremidade de comunicaçã o chamada soquete. O si s te ma operacional usa i ntei
ros para identificar soquetes, mas a ling u age m P)rthon re ro rna pt�ra o código um
objeto socket.socket que é mais corwcnicntc. Ele ar ma zen a o inteiro (você pode
chamar o m é ro do fileno() para acessá-lo) c o usa auwmaticamcnre sempre que
chamamos um de �cu métodos para solicitar que uma chamada de sistema seja
executada no ·oquct c .
supondo-se um ambicnre POSJX, acesse esse inteiro e use-o parn exe c u ttlr
ch:1mad.1s n;Jo relacionadas a redes como os.read() e os.wrtte() no descritor de
arquivo, fazendo coisas referentes a arquivos com o que na verd ade é uma
extremidade de comun i c açã o de rede. No emamo, já que os códigos deste
livro também foram projetados para funcionar no Windows, você só executar�
opcraçiks de soquete em seus soquetes.
#!/usrjbinfenv python3
# PrograMação de Redes coM Python
# https://github.coM/brandon-rhodes/fopnp/blob/M/py3/chapter02/udp_local.py
# cliente e servidor UDP eM localhost
MAX_BYTES = 65535
def server(port):
whHe True:
data, address = sock.recvfroM(MAX_BYTES)
text = data.decode('asctt')
prtnt('The cltent at {} says {!r}' .forMat(address, text))
data = text.encode('asctt')
sock.sendto(data, address)
def cltent(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
tf naMe__ == '__Matn__' :
__
functton = chotces[args.role]
functton(args.p)
Você deve poder executar esse script em seu computador, mesmo se não estiver
atualmente no intervalo de uma rede, porqúe tanto o servidor quanto o cliente
usam o endereço IP localhost que fica disponível havendo ou não conexão a uma
rede. Primeiro, tente iniciar o servidor.
Após exibir essa linha de saída, o servidor esperará o recebimento de uma mensagem.
No código-fonte, é possível ver que foram necessárias três etapas para o servidor
funcionar.
54 Programação de Redes com Python
Primeiro ele criou um soquete si mples com uma ch<nnada a sock et(). Es e nov o
soque te ainda não está v inculado a um CJH.lcrcço JP ou n ú me ro de p orta . não está
co nect ado a n ada , c lan çará um a ex cçiio se \·ocê remar usá-lo p a ra se com u nica r.
No entan to. fo i p elo menos mnrcado como sendo de um tipo especifico : sua famí
lia é a AF_INE T, a famflia de p ro to co los da Internet. e e l e u a o tipo de datag ram a
SOCK_OGRAM, 0 que ignífica que u�ará o UDP em uma rede IP Observe que datagrama
(e nã o pacote) é o termo oficial p�na um bloco de transmissão de dt1dos de nív el
de aplícativ porque a pilha de rede do s i stema peracional não garante que um
único pacote s en do transmitid o re prese nt ar á realmente um üni o dat<�gram a.
(Consulte a próx im a seção, na qual insisto no u o de uma correspondência u m
·para�um entTc datagram as e pacotes para que você possa encontrar a unidade
de rransmiss5o m á x ima I MTU].)
Em seguida, esse ser vidor simples usa o comando bi.nd() para solicirar um ende
reço de rede UDP, q ue é uma tupi a Python combinando um endereço I P str (você
verá posteriormenre que um nome de host também é aceitá ve l ) c um número de
porta UDP i.nt. Esta etapa poderia falhar com uma exce çã o c outro programa já
estivesse usando essa porra UDP e o scripr do servidor não p udesse obtê-la. Tente
executar outra cópia do s ervidor- ele r eclama rá assim:
É claro que há uma pequena chance de você ver essa exceção na primeira vez
que executar o servidor porque a porta UDP 1060 pode já estar sendo usada em
sua máquina. Enfrentei um dilema para escolher o número de porta para esse
primeiro exemplo. Ele tinha que estar acima de 1023 ou talvez você não conse
guisse executar o script se não fosse administrador do sistema- e embora goste
de meus exemplos de scripts, não quero encorajar ninguém a executá-los como
administrador do sistema! Poderia ter deixado o sistema operacional escolher o
número da porta (como fiz para o cliente, o que veremos em breve), fazer o servidor
exibi-lo e pedir que você o digitasse no cliente como um de seus argumentos de
linha de comando. No entanto, nesse caso não teria de lhe mostrar a sintaxe para
que você mesmo pedisse um número de porta específico. Para concluir, considerei
usar uma porta do intervalo "efêmero" de numeração alta já descrito, mas essas
são precisamente as portas que já podem aleatoriamente estar sendo usadas por
algum outro aplicativo em sua máquina, como seu navegador web ou cliente SSH.
ss
Logo, minha única opção parecia ser uma porta do intervalo reservado porém
pouco conhecido que fica acima da porta 1023. Examinei a lista e lembrei que
você, gentil leitor, não deve estar executando o SAP BusinessObjects Polestar no
laptop ou desktop onde está executando meus scripts Python. Se estiver, tente dar
ao ser vidor uma opção -p para selecionar um número de porta diferente.
É bom ressaltar que o programa Python sempre pode usar o método getsocknaMe()
uma tupla contendo o endereço e a porta IP atuais
de um soquete para recuperar
aos quais o soquete está vinculado.
Uma vez que o soquete for vinculado com sucesso, o servidor estará pronto para
começar a receber solicitações! Ele entrará em um loop e executará recvfrol'l() re
petidamente, dizendo à rotina que receberá mensagens até um tamanho máximo
de 65.535 bytes- um valor que é o maior tamanho que um datagrama UDP pode
ter, para que você veja sempre o conteúdo completo de cada datagrama. Até você
enviar uma mensagem com um cliente, a chamada a recvfrol'l() ficará esperando
infinitamente.
(A propósito, espero que não seja confuso o fato de este exemplo- como alguns
outros do livro- combinar na mesma listagem o código do servidor e do cliente,
que são selecionados por argumentos de linha de comando. Geralmente prefiro
esse estilo, já que ele mantém a lógica do servidor e do cliente próximas na página,
facilit ando ver que fragmentos de código do servidor estão relacionados a quais
fragmentos de código do cliente.)
Enquanto 0 servidor está sendo executado, abra outra janela de comando em seu
sistema e tente executar o cliente duas vezes seguidas:
Em segundo lugar, já que o cliente sabe que está esperando uma resposta do servidor,
ele simplesmente chama o método recv() do soquete sem se importar com a versão
recvfro�() que também retorna um endereço. Como você pode ver pela saída, tanto
o servidor quanto o cliente conseguem ver as mensagens um do outro; sempre que
o cliente é executado, um trajeto de ida e volta completo relacionado à solicitação
e à resposta está sendo feito entre dois soquetes UDP. Sucesso!
Se você executar o cliente agora, ele enviará seu datagrama e então travará , espe
rando uma resposta .
Suponhamos que você fosse um invasor que quisesse forjar uma resposta do
servidor interferindo na comunicação e enviando seu datagrama antes de o ser
vidor ter a chance de enviar sua resposta. Já que o cliente informou ao sistema
operacional que deseja receber todos os datagramas e não está fazendo verificações
de integridade do resultado, deve acreditar que a resposta falsa veio do ser vidor.
Você pode enviar esse pacote usando uma sessão rápida no prompt do Python.
$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:18)
[GCC 4.8.2] on ltnux
Type "help", "copyright", "credtts" or "Hcense" for More tnfomatton.
>>> tMport socket
>>> sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> sock.sendto('FAKE'.encode('asctt'), ('127.0.0.1 ', 39692))
4
Você pode trazer o ser vidor de volta agora digitando fg e deixar que seja executado
(ele verá o pacote do cliente enfileirado e em espera e enviará sua resposta para o ago
ra fechado soquete do cliente). Pressione Ctrl+C como sempre para interrompê-lo.
58 Programação de Redes com Python
Observe que o cliente está vulnerável n quem quer que possa endereçar um pa
cote UDP para ele. Este não é um caso em que um invasor intermediário tem 0
controle da rede e pode forjar pacotes a partir de endereços falsos , uma situaç ão
que só pode ser evitada com o uso de criptografia (Capítulo 6). Em vez disso,
um emitente desprivilcgiado agindo totalmente dentro das regras e enviando um
pacote com um endereço de retorno legítimo tem, portanto, seus dados aceitos.
Um cliente de rede na escuta que aceite ou registre cada pacote que receber, sem
levar em consideração se o pacote foi endereçado de maneira correta , é tecnica
mente conhecido como cliente promíscuo. Às vezes, os criamos deliberadamente,
como quando monitoramos a rede querendo ver todos os pacotes que chegam em
uma interface. No caso em questão, no entanto, a promiscuidade é um problema.
Só uma boa e bem escrita criptografia deve convencer seu código de que ele se
comunicou com o servidor correto. Caso contrário, há duas verificações rápidas
que você p ode fazer. Em primeiro lugar, projete ou use protocolos que incluam
um identificador exclusivo ou uma ID de solicitação que se repita na resposta. Se
a resposta tiver a ID que você está esperando, então- contanto que o intervalo
de IDs seja suficientemente grande para impedir que alguém envie rapidamente
milhares ou milhões de pacotes contendo cada ID possível- quem viu a solicitação
deve pelo menos tê-la finalizado. Em segundo lugar, compare o endereço do pacote
de resposta com o endereço que usou como sendo do destinatário (lembre-se, as
tuplas em Python podem ser comparados simplesmente com ==) ou use connect()
como mostrado na próxima seção para proibir que outros endereços lhe enviem
pacotes. Consulte as próximas seções, "Conectando soquetes UDP" e "IDs de
solicitação" para ver mais detalhes.
Já que o cliente e o servidor das seções anteriores estavam ambos sendo executados
na mesma máquina e se comunicando através de sua interface de loopback- que
não é uma placa de rede física que poderia apresentar falha no sinal- não havia
como os pacotes serem perdidos e, portanto, você não viu as inconveniências do
UDP na listagem 2.1. De que forma o código se tornaria mais complicado se os
pacotes pudessem ser perdidos?
#!/usr/bin/env python3
MAX_BYTES = 65535
sock.bind((interface, port))
print('Listening at', sock.getsockna�e())
whHe True:
text= data.decode('ascii')
hostna�e = sys.argv[2]
sock.connect((hostnaMe, port))
print('Client socket naMe is{}'.forMat(sock.getsocknaMe()))
sock.send(data)
'.forMat(delay))
print('Waiting up to{} seconds for a reply
sock.se ttiMeout(delay)
try:
60 Programação de Redes com P
ython
data sock.recv(MAX_BYTES)
=
except socket.tiMeout:
delay *= 2 # espera ainda Mais pela próxiMa solicitação
if delay > 2.6:
raise Runt iMeError('I think the server is down')
else:
break # terMinaMos e não podeMos interroMper o loop
• t
. if _naMe_ == _Matn_
•
I
·
Sempre que uma solicitação for recebida, o servidor usará o método randoM() de
escolha aleatória para decidir se ela será respondida a fim de que o cliente não
precise ser executado o dia todo enquanto você espera u m pacote ser descartado.
Qualquer que seja a decisão tomada, ele exibirá uma mensagem na tela para que
você aco mpanh e sua atividade
.
Como criar um clien te UDP "real'; u m cliente que tenha que lidar com o fato de
que os pacotes podem ser perdidos?
Capítulo 2 • UDP
61
Em prim eiro lugar, a falta de confiança no UDP f:az
o eI'Iente ter que executar sua
solicitação dentro de um loop. Ele tem de estar preparado
para esperar infinitamen-
te por uma resposta ou ser arbitrário e decidir quan d° esp
erou "demais" e prectsa
envtar outra soltcttaçao. Essa decisão difícil é necessa, na
.
. · · - ·
•
O servidor está inativo e não responde a ninguém.
Lo��' u � cliente �DP tem d � definir uma programação segundo a qual ele envie
sohcttaçoes duphcadas se nver esperado um período de tempo razoável sem
receber uma resposta. É claro que fazendo isso ele pode acabar desperdiçando 0
tempo do servidor porque a primeira resposta pode estar para chegar e a segunda
cópia da solicitação faria o servidor executar um trabalho duplicado desnecessário.
Ao mesmo tempo, no entanto, o cliente deve decidir se deve reenviar a solicitação
ou talvez espere para sempre.
Quando uma chamada está esperando uma operação de rede terminar, dizemos
que ela está bloqueando o chamador. O termo bloqueio é usado para descrever uma
chamada como recv() que faz o cliente esperar até novos dados chegarem. Quando
você chegar ao capítulo 7, no qual a arquitetura do servidor é discutida, verá que
a diferença entre chamadas de rede bloqueantes e não bloqueantes é bem maior!
Este cliente específico começa com uma espera modesta de u m décimo de segun
do. Em minha rede caseira, em que geralmente os tempos de ping são de alguns
milissegundos, isso raramente faria o cliente enviar uma solicitação duplicada só
pelo fato de a resposta estar demorando para retornar.
Portanto, esse cliente us a uma técni c;-t conhecida como recuo e"ponenc ial, em que
suas tentativas se torn(lm cada vez menos freq uentes. Isso ajuda na imp ortante
meta de sobrev iver::� alguma solicitações ou respostas perdidas, ao mesmo tempo
em que possibilita que uma rede congestionada se recupere lentamente à medida
que todos os clientes ativos recuarem em suas demandas e gradualmente enviarem
Ao executar o cliente da listagem 2.2, forneça para ele o nome de host da máquina
em que está executando o script do servidor, como mostrado anteriormente. Em
alguns casos, o cliente terá sorte e obterá uma resposta imediata.
No entanto, com frequência ele perceberá que uma ou mais de suas solicitações
não resultaram em respostas e terá que tentar novamente. Se você observar suas
repetidas tentativas com cuidado, poderá ver até mesmo o recuo exponencial
ocorrendo em tempo real, à medida que as instruções de exibição ecoadas para
a tela forem ficando cada vez mais lentas e o tempo de retardo aumentar.
63
Waiting up to 0.4 seconds for a reply
Waiting up to 0.8 seconds for a reply
The above exception was the direct cause of the following exception:
Se estiver escrevendo um código daemon que faça novas tentativas o dia inteiro, não
adote um recuo exponencial estrito, ou aumentará rapidamente o retardo até um valor
de, por exemplo, duas horas, e provavelmente perderá todo o período de meia hora
durante o qual o proprietário do laptop senta-se em uma cafeteria e você poderia
disponibilizar a rede. Em vez disso, selecione algum retardo máximo- como cinco
minutos- e uma vez que o recuo exponencial tiver alcançado esse limite, mantenha-o
aí para que você tenha sempre a garantia de tentar uma atualização quando o usuário
estiver na rede por cinco minutos após um longo tempo desconectado.
Se o sistema operacional permitir que seu processo seja notifi cado sobre eventos
como a rede voltou à atividade, você terá opções melhores do que lidar com timers
e adivinhar quando a rede voltou. Mas infelizmente esse tipo de mecanismo es
pecífico do sistema não faz parte do escopo deste livro; logo, voltemos ao UDP e
a mais algumas questões que ele suscita.
Mas o cliente UDP remoto da listagem 2.2 também usa uma nova chamada que
ainda não discuti: a operação de soquete connect(). É fácil ver o que ela faz. Em
vez de ser preciso usar sendto() com uma tupla de endereço explícita sempre que
a
quiser m os enviar algo ao servidor, a chamada a connect() permite que o siste�
envtar
operacional saiba antecipadamente o endereço remoto ao qual queremos
ter
pacotes para podermos fornecer apenas dados para a chamada a send() sem
que repetir o endereço do servidor.
65
Cont udo, connect( ) faz outra coisa importante que não fica clara na leitura da
problema do cliente ser promíscuo! Se você executar nes
lis tage m 2.2: resolve o
se cliente o teste que executou na seção "Promiscuidade': verá que o cliente da
listagem 2.2 não é suscetível a receber pacotes de outros servidores. Isso ocorre
devido ao segundo e menos óbvio efeito do uso de connect ( ) na configuração do
melhor destino de um soquete UDP: uma vez que você executar connect( ) , o sistema
operacional descartará qualquer pacote recebido em sua porta cujo endereço de
retorn o não coincidir com o endereço ao qual você se conectou.
Há, portanto, duas maneiras de criar clientes UDP que tomem cuidado com os
end ereços de retorno dos pacotes recebidos.
• Você pode usar sendto( ) para direcionar cada pacote para um destino
específico e então usar recvfrof'l() para receber as respostas e verificar cui
dadosamente cada endereço de retorno na lista de servidores em que tem
solicitações pendentes.
•
Ou pode usar connec t ( ) para conectar seu soquete logo após criá-lo e se
comunicar com sendO e recv( ) . O sistema operacional filtrará os pacotes
indesejados. Isso só funciona na comunicação com um servidor de cada vez
porque executar connect ( ) novamente no mesmo soquete não adiciona u m
segundo endereço d e destino. O primeiro endereço é totalmente removido
para que nenhuma resposta adicional do endereço anterior seja distribuída
para o programa.
Após conectar um soquete UDP usando connect( ) , você pode empregar seu método
getpeernaf'le( ) para lembrar o endereço ao qual o conectou. Tome cuidado ao chamar
esse método em u m soquete que ainda não esteja conectado. Em vez de retornar
0.0.0.0 ou alguma outra resposta padrão, a chamada lançará u m socket . error.
próprio filtrar pacotes indesej ados usando o endereço de retorno - não é uma
medid a de segurança! Quando alguém na rede tem más intenções, geralmente
é fácil o computador forj ar pacotes com o endereço de retorno do servidor para
que as respostas falsas passem tranquilament e pelo filtro de endereços.
66 Programação de Redes com Python
O envio de pacotes com o end ereço de reto rno de ou tro computador se cha ma
spoofinge é uma das primei ras coisas que os projetistas de protoco los têm qu e
considerar ao projeta r protocolos que devam ter segurança contra interferênci a.
Consulte o capítul o 6 para ver mais informações sobre isso.
A primeira vantagem é que esse esquema nos protege de ficar confusos com res
postas duplicadas de solicitações que foram repetidas várias vezes por um cliente
executando um Ioop de recuo exponencial.
É fácil visualizar como ocorreria a dupl icação. Você envia a sol icitação A. Cansa
-se de esperar uma resposta, logo, repete a solicitação A . E m seguida, finalmente
recebe um retorno, a resposta A. Você supõe que a primeira cópia foi perdida,
portanto, apenas dá continuidade a seus afazeres.
As IDs de solicitação nos protegem disso. Se você desse a cada cópia da solicitação
A a ID #42496 e à solicitação B a I D # 16916, ao esperar a resposta de B o loop do
programa poderia simplesmente descartar respostas cujas IDs não fossem iguais a
# 16916 até finalmente receber u ma que coincidisse. Esse esquema protege contra
respostas duplicadas, que ocorrem não só quando repetimos a solicitação, mas
também n a rara situação em que uma redundância na estrutura da rede gera
acidentalmente duas cópias do pacote em algum ponto entre o servidor e o cliente.
rod os os pacotes que você enviar e poderão enviar respostas falsas - esperando
que suas resposta s cheguem antes das do servidor - a qualqu er solicitação que
lhes interesse! Contu do, no caso em que os invasores não puderem observar o
tráfego e tiverem que enviar cegamente pacotes UDP para o servidor, um número
de ID de solicitação de tamanho adequado pode tornar bem menos provável que
o clien te aceite suas respostas.
Observe que os exemplos de I D de solicitação que usei nessa história não são
sequ enciais nem fáceis de adivinhar. Essas características indicam que o invasor
não terá ide ia de qual seria um número sequencial possível. Se você começar com
O ou 1 e contar em ordem crescente, facilitará muito o trabalho do invasor. Em vez
disso, tente usar o módulo randol'l para gerar inteiros de valor alto. Se sua ID for
um número aleatório entre O e N, a chance do invasor o enganar com um pacote
válido - mesmo se souber o endereço e a porta do servidor - será no máximo
de 1/N e talvez muito menor se ele tiver de tentar alcançar todos os números de
porta que poderiam existir em sua máquina.
Mas é claro que nada disso é segurança real - apenas protege contra ataques de
spoofing ingênuos de pessoas que não podem observar o tráfego de rede. A se
gurança real protege mesmo se os invasores puderem observar o tráfego e inserir
suas próprias mensagens sempre que quiserem. No capítulo 6, você examinará
como a segurança real f unciona.
Vinculando-se a interfaces
Até agora, você viu duas opções para o endereço IP usado na chamada a bi.nd() que
o servidor faz. Você pode usar ' 127. e. e . 1 ' para indicar que deseja pacotes apenas
de outros programas sendo executados na mesma máqu ina ou usar uma string
vazia ( •)
' como curinga para indicar que deseja receber pacotes que chegarem
ao servidor via qualquer uma de suas interfaces de rede.
ces
Há uma terceira opção. Podemos fornecer o endereço IP de uma das interfa
ss, e o servidor
externas da máqu ina, como sua conexão Ethernet _ou placa wirele
ter notado que a
só escutará pacotes destinados a esses endereços IPs. Você deve
chamada a bi.nd(), o que
listagem 2.2 permite fornecer uma string de servidor à
agora .nos per mit irá fazer alguns testes.
te o servidor des-
,
E se nos vmcu 1asse mos apenas a uma interface externa? Execu
·
Se, porém, você tentar se conectar ao serviço através da interface de Joopback execu
tando o script do cliente na mesma máquina, os pacotes nunca serão distribuídos.
Tente executar o cliente novamente na mesma máquina, mas desta vez use seu
endereço IP externo.
e estao send o
execu tad os e tente executar doi
servid ores na mes ma máq uina . \lj s
oce conectará um
,...
ao Ioopback.
$ python udp_reMo te . py server 12
7.0.0.1
Listening a t ( ' 127 . 0 . 0 . 1 ' , 1060)
O mais estranho, porém, é que você também não poderá executar um servidor
no endereço IP curinga.
Esse código falhará porque o endereço curinga inclui 12ZO.O.l e, portanto, entra
em conflito com o endereço que o processo do primeiro servidor obteve. Mas e se,
em vez de tentar executar o segundo servidor em relação a todas as interfaces IP,
você o executasse apenas e m relação a uma interface I P externa - uma interface
que a primeira cópia do servidor não esteja escutando? Vamos tentar.
Funciono u! Agora há dois servidores sendo executados nessa máquina com o mesmo
número de porta UDP, u m deles vinculado à interface de loopback que só vê o sistema
internamente e outro olhando para fora em busca de pacotes que cheguem à rede
0
à qual a placa wireless foi conectada . Se você estiver em uma máquina com várias
interfaces remotas, poderá iniciar ainda mais servidores, um em cada interface remota.
70 Programação de Redes com Python
Um a vez qu e estiver com e _s cs s e r vido res �e ndo cxccm a do . renrc env i a r <l r .a e1 ·s
�
g n p
al u s a cotes com s e u chc n tc { I DP . Vocc v e r á q ue só u rn scn·ido r rc c h c CMia
sol ic i t açã o c e m ca d a caso e l e se r;í o sen· i dor q u e C( n ém o codercço TP sr cc ífi co
ão ' D P.
ao q u a l foi d i rec i o n a d o o p aco t e ela sol i cit a ç
pode ficar remado a se vincu lar a uma su b - red e privada em com u n icaç1io com
se u s outros servidores e achar, portanto, que ev i ta rá pacotes falsos q u e cheguem
a seu endereço JP pú blico d a I n te rn e t .
Infelizmenre, a vida não é tão simples. Na verdade, para pacotes de entrada en
dereçados a uma i nterface terem permissão para c hega r a ou tra interface, isso
vai depender do sistema operacional escolhido e de como ele foi configurado.
Pode ser que seu sistema aceite normalmente pacotes que aleguem ser de outros
servidores de sua rede se eles vierem da conexão pública da Internet! Consulte a
documentação do sistema operacional, ou o administrador do sistema, para saber
mais sobre seu caso específico. A configuração e a execução de um firewall em sua
máquina também podem fornecer proteção se seu sistema operacional não o fizer.
Fragmentação no UDP
Até agora neste capítulo venho falando como se o UDP permitisse que você, como
usuário, enviasse datagramas brutos empacotados como pacotes IP contendo
apenas algumas inform ações adicionai s - uma porta tanto para o emitente quan
to para o destinatário. Mas isso deve ter gerado dúvidas porque as listagens de
o
program a anteriores sugerem q u e um pacote UDP pode ter até 64kB, enquant
você deve saber que sua placa E thernet ou wireless só pode manipu lar pacotes
de cerca de 1500 bytes.
71
A verd ade é que, embora o UDP envie pequenos datagramas como pacotes IP
individuais, ele tem de dividir datagramas UDP maiores em vários pacotes IP
pequenos para que eles possam percorrer a rede (como discutido brevemente
no capítulo 1). Isso significa que pacotes grandes têm mais probabilidade de ser
descartados, já que, se uma de suas partes não conseguir chegar ao destino, 0 pa
cote inteiro pode nunca ser reagrupado e distribuído para o sistema operacional
que está escutando.
Exceto pela maior chance de falha, o processo de fragmentar pacotes UDP maio
res para que eles se ajustem à conexão deve ser invisível para seu aplicativo. No
entanto, há três maneiras de ele ser relevante.
•
Se os pacotes ICMP forem erroneamente bloqueados por um firewall que
normalmente permitiria que seu host autodetectasse a MTU entre você e
o host remoto (situação comum no fim dos anos 90), seus pacotes UDP
maiores podem desaparecer sem que você tome conhecimento. A MTU é a
"unidade de transmissão máxima"" ou o "maior tamanho de pacote" que
todos os dispositivos de rede entre dois hosts suportam.
Se seu protocolo puder fazer suas próprias escolhas sobre como dividirá dados
entre diferentes datagramas e você quiser poder autoajustar esse tamanho de
acordo com a MTU entre dois hosts, alguns sistemas operacionais permitem a
desativação da fragmentaç ão e o recebimento de uma mensagem de erro se um
pacote UDP for grande demais. Você precisaria então ter o cuidado de modelar
datagramas em confor midade com a unidad e mínima .
O Linux é um sistema operacio nal que dá suporte a esta última opção. Examine
a listagem 23, que envia uma mensagem grande para um dos servidores recém
-designados - o u para qualqu er outro serviço UDP da Internet.
# ! /usr/bin/env python3
parse r . add_arguMent( ' host ' , help= ' the host to which to target the packet ' )
parser. add_arguMen t( ' - p ' , Metavar= ' PORT ' , type=int, default=1060,
O mais estranho é que a interface de Ioopback de meu Iaptop, que deveria poder
suportar pacotes tão grandes quanto minha RAM, também impõe uma MTU.
•
SO_BROADCAST - Permite que pacotes UDP de broadcast sejam enviados e re
cebidos, o que abordarei na próxima seção.
•
so_DONTROUTE - Só envia pacotes que sejam endereçados a hosts de sub-redes
às quais esse computador esteja conectado diretamente. Neste momento, por
exemplo, se essa opção de soquete estivesse ativada, meu lapto� só enviar a �
.
pacotes para as redes 127.0.0.0/8 e 192.1685.0/24 e não os enviana para mais
ninguém porque os pacotes teriam que ser roteados através de um gateway.
•
so TYPE _ Quando passada para getsockopt(), essa opção retorna se um soquete
Broadcast
Se o UDP tem um superpoder, seria sua capacidade de dar suporte ao broadcast. Em
vez de enviar um da tagrama para algum outro host específico, você pode endereçá
-lo a uma sub-rede inteira à qual sua máquina estiver conectada e fazer a placa de
rede física enviar o datagrama por broadcast para que todos os hosts conectados o
vejam sem que ele tenha de ser copiado separadamente em cada um deles.
#! /usr/bin/env python3
# cliente e servidor UDP para o envio de Mensagens broadcast eM uMa LAN local
BUFSIZE = 65535
' f -naMe
l. -
== ' Main
- -
':
parser = argparse. ArguMentPa rser( descriptton= ' Send 1 receive UDP broadcast ' )
pars e r . add_arguMent( ' role ' 1 chotces=chotces 1 help= ' which role to take ' )
parser . add_arguMent ( ' host ' I help= ' tnterface the server ltstens at; '
' network the cltent sends to ' )
pa rse r . add_arguMent ( ' - p ' , Metavar= ' port ' , type=int, default=1060,
Ao testar o servidor e o cl iente, a primeira coisa que notamos é que eles se com
por tam exatamente como clientes e servidores normais se usarmos o cliente para
enviar pacotes endereçados ao endereço IP de um servidor específico. A ativação
do broadcast para u m soquete UDP não desativa ou altera sua habilidade comum
de enviar e receber pacotes endereçados especificamente.
Em segu ida , enqua nto esses servidores estão sendo executados, use o cliente para
s um servid or recebe
enviar mens agens para cada servid or. Você verá que apena
cada mensagem.
. 168 . 5 . 10
$ pyt hon udp_broadcas t . py cli ent 192
76 Programação de Redes com Python
u m pacote UDP. Não deixe de colocar o nome entre aspas ao passá-lo para seu
cliente, já que os caracteres < e > são especiais para qualquer shell POSIX normal.
•
Quando estamos implementando u m protocolo que já existe e usa o UDP.
•
Quando estamos projetando um fluxo de mídia de tempo crítico cuja
redundância permite a perda ocasional de pacotes e não queremos que os
dados atuais travem esperando dados antigos de vários segundos atrás que
ainda não foram distribuídos (como ocorre com o TCP).
77
Qua ndo o mul ticas t não con fiável de sub-redes de .
LAN é um o' tImo
•
. . padrao
-
para seu aphcattvo e o UDP o supo rta perfe itamente.
� �
E m ca�os diferent d sas três situações, você deve exam inar os últimos capítu
los
� �
deste hvro ara ter tdeta s s bre com o cons truir a com unicação para seu aplicativ
o.
_
Há u m anugo dttad o que dtz que, quan do temos um protocolo UDP funciona
ndo
para nosso aplicativo, provavelment e apenas reinven tamos (mal) 0 TCP.
Resum o
O User Datagram Protocol permite que programas de nível de usuário enviem
pacotes individuais através de uma rede IP. Normalmente, um programa cliente
envia um pacote a um servidor, que então responde usando o endereço de retorno
embu tido em cada pacote UDP.
Já que o UDP opera acima do comportamento real dos pacotes de rede, ele não
é confiável. Pacotes podem ser descartados devido a uma falha no meio de trans
missão de rede o u porque um segmento de rede está muito ocupado. Os clientes
têm que compensar isso retransmitindo uma solicitação até receber uma resposta.
Para evitar tornar uma rede ocupada ainda pior, os clientes devem usar o recuo
exponencial ao se deparar com falhas repetidas e também devem aumentar seu
tempo de espera inicial se notarem que as transmissões de ida e volta com o
servidor estão demorando mais do que o esperado por quem as gerou.
Entre a s opções de soq uet e di spon íveis para soquetes UDP, a m a is poderosa é o
broadca s t , q u e n os pern1 i tc enviar pacotes para todos os hosts d a s u b-rede sem ser
preciso envia r pa ra cada host i n d ivid ual men te. Isso pode aj u d a r na programação
de jogos de LA N loca i s ou em o u tro tipo de com p u tação coopera tiva , e é uma
das pou ca s razões para selecionarmos o UDP para novos apl ica tivos.
' r