Vous êtes sur la page 1sur 32

CAPÍTULO 2

UDP

0 capítulo anterior descreveu o hardware de rede moderno como suportando a


transmissão de mensagens curtas chamadas pacotes, que geralmente não têm mais
do que alguns milhares de bytes. Como essas minúsculas mensagens individuais
podem ser combinadas para formar as conversas que ocorrem entre um navega­
dor web e o servidor ou entre um cliente de email e o servidor de email do ISP?

O IP só é responsável por tentar distribuir cada pacote para a máquina correta.


Dois recursos adicionais costumam ser necessários se aplicativos separados tiverem
de manter comunicações, e é tarefa dos protocolos existentes acima do Internet
Protocol fornecer esses recursos.


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.

O primeiro, o User Datagram Protocol (UDP), é documentado neste capítulo. Ele só


resolve o primeiro dos dois problemas descritos anteriormente. O UDP fornece
números de porta, como descrito na próxima seção, para que os pacotes destinados
a diferentes serviços na mesma máquina possam ser desmultiplexados apropria­
damente. No entanto, programas de rede que usarem o UDP ainda devem tomar
cuidado com a perda de pacotes, a duplicação e a ordenação.

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.

OrigeM (IP : núMero da porta) �Destino (IP : núMero da porta)

Os pacotes recebidos per tencentes a uma comunicação específica sempre terão


os mesmos quatro valores para essas coordenadas, e as respostas encaminhadas
na direção oposta terão simplesmente os dois números IP e os dois números de
por ta trocados em seus campos de origem e destino.

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.

Logo, o pacote será encaminhado para a porta 53 com estes endereços:

OrigeM (192.168.1.30:44137) �Destino (192.168.1.9:53)

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

o simples; só um endereço IP e uma


P ortanto, o eSquema UDP é realmente muit .
dire cion ame nto de um paco te para seu desu no.
por ta são necessários para o
--

50 Programação de Redes com Python

Mas como um programa cliente pode descobrir o número de porta ao qual deve
se conectar? Há três abordagens gerais.

• Convençdo -A Autoridade para Atribuição de Números na Internet (IANA,


Internet Assigned Numbcrs Authority) projetou muitos números de port a
como as portas oficiais conhecidas de serviços específicos. É por isso que
o DNS deveria ser encontrado na porta UDP 53 no exemplo anterior.

Cmzfiguração automática - Com frequência, os endereços IP de serviços
cruciais como o DNS são conhecidos quando um computador se conecta
pela primeira vez com uma rede, usando um protocolo como o DHCP.
Combinando esses endereços IP com números de porta conhecidos, os
programas podem alcançar esses ser viços essenciais.

Configuração manual- Para todas as situações não abordadas nos dois casos

anteriores, a inter venção manual de um administrador ou usuário terá que


distribuir o endereço IP ou o nome de host correspondente de um serviço.
Esse tipo de configuração manual ocorrerá, por exemplo, sempre que você
digitar o nome de um ser vidor web em seu navegador web.

Ao tomar decisões sobre a definição de números de porta, como no caso de 53


para o DNS, a I ANA considera-os como pertencentes a três intervalos- e isso se
aplica a números de porta tanto do UDP quanto do TCP.


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:

>>> i�port socket


>>> socket.getservbynaMe('doMatn')
53

Co mo você verá no capítulo 4, os nomes de porta também podem ser decodifica­


dos pela função mais complicada getaddri.nfo(), que é fornecida pelo módulo socket.

Geralmente, o banco de dados de nomes de serviço e números de porta conhecidos


é mantido no arquivo /etc/servi.ces em máquinas Linux e Mac OS X, e você pode
consultá-lo à vontade. As primeiras páginas do arquivo estão cheias de protocolos
antigos que ainda têm números reser vados apesar de há muitos anos não terem
um pacote de nenhum lugar do mundo sendo endereçado para eles. Uma cópia
atualizada (e normalmente muito mais extensa) também é mantida pela IANA
emwww.i.ana.org/assi.gnMents/port-nuMbers.

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 .

Nota Em �i:,tl'llHlS POSJX, o inteiro que identifica um soquete rewrnado por


fileno() t�1mbém é um descritor de arquivo retirado do p o ol de inteiros que
rcprc ·cni<Hll arquivo� Vocé pode se deparar com um código que,
a b er tos .

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.

Qual a aparência dos soquetes ao funcionarem? Examine a listagem 2.1, que


mostra um servidor e cliente UDP simples. É possível ver que ela faz uma única
chamada da biblioteca padrão Python, à função socket.socket(), e que todas as
outras chamadas são feitas a métodos do objeto de soquete que a função retorna.

Listagem 2.1 -Servidor e cliente UDP na interface de loopback

#!/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

iMport argparse, socket


froM datetiMe iMport datetiMe

MAX_BYTES = 65535

def server(port):

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)


sock.bind(('127.a.a.1', port))
print('Listening at {}'.forMat(sock.getsocknaMe()))
53

whHe True:
data, address = sock.recvfroM(MAX_BYTES)

text = data.decode('asctt')
prtnt('The cltent at {} says {!r}' .forMat(address, text))

text = 'Your data was {} bytes long' .forMat(len(data))

data = text.encode('asctt')
sock.sendto(data, address)

def cltent(port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

text = 'The tiMe ts {}' .forMat(datettMe.now())


data = text.encode('asctt')

sock.sendto(data, ('127.0.0.1', port))


prtnt('The OS asstgned Me the address {}'.forMat(sock.getsocknaMe()))

data, address = sock.recvfroM(MAX_BYTES) # Danger!


text = data.decode('asctt')

prtnt('The server {} replted {!r}' .forMat(address, text))

tf naMe__ == '__Matn__' :
__

chotces = {'cltent': cltent, 'server': server}

parser = argparse.ArguMentParser(descrtptton='Send and recetve UDP locally')


parser.add_arguMent('role', chotces=chotces, help='whtch role to play')
parser.add_arguMent('·p', Metavar='PORT', type=tnt, default=1060,
help='UDP port (default 1060)')
args = parser.parse_args()

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.

$ python udp_local.py server


Ltstentng at ('127.0.0.1', 1060)

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:

$ python udp_local.py server


Traceback (�ost recent call last):

OSError: [Errno 98] Address already in use

É 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.

Quando um datagrama chegar, recvfrol'l() retornará o endereço do cliente que o


enviou assim como o conteúdo na forma de bytes. Usando o recurso Python de
conversão de bytes diretamente para strings, você pode exibir a mensagem no
console e então retornar um datagrama de resposta para o cliente.

Portanto, iniciaremos nosso cliente e examinaremos o resultado. O código do


cliente também é mostrado na listagem 2.1.

(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:

$ python udp_local . py client


lhe OS assigned l'le the address ( ' 0.8.0 . 0 ' 1 46056)
46 bytes long'
lhe server ('127.0 . 0.1 ' 1 1860) replied 'Vour data was

$ python udp_local.py client


39288)
The OS assi gned l'le the addr ess ( ' 0 . 0.0•0'1
o o 1' 1 1068) replied ' Vour
lh e server ('l27 .o.o. data was 46 bytes long'
56 Programação de Redes com Python

Na janela de comando do servidor, você deve ver informações de cada conexão


que ele atende.

', 46056) says 'The t1�e 1s 2014-06-05 10:34:53.448338'


The cl1ent at ('127.0.0.1
39288) says 'The tt�e ts 2014-06-05 10:34:54.065836'
The cl1ent at ('127.0.0.1',

seja um pouco mais simples do que o do servidor­


Embora 0 código do cliente
linhas de código de rede- ele introduz dois conceitos novos. Em primeiro
só há
a sendto() fornece tanto uma mensagem quanto um
lugar, a chamada do cliente
endereço de destino. Essa chamada simples é tudo que é necessário para um da­
tagrama ser envia do ao servidor! Mas é claro que você precisa de um endereço
IP e um número de porta, na extremidade do cliente, se deseja conversar. Logo,
0 sistema operacional os atribui automaticamente, como é possível ver
na saída

da chamada a getsockna�e(). Como prometido, os números de porta do cliente


pertence m ao intervalo da IANA para números de porta "efêmeros': (Pelo menos
posso vê-los aqui, em meu laptop, no Linux; em um sistema operacional diferente ,
você pode obter outro resultado.)

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!

Quando não precisar mais do servidor, você pode interrompê-lo pressionando


Ctrl+C no terminal onde ele está sendo executado.

Clientes promíscuos e respostas indesejadas


O programa cliente da listagem 2.1 é na verdade perigoso! Se você examinar seu

código-fonte, verá que, embora recvfro�() retorne o endereço do datagrama rece­


bido, o código nunca verifica o endereço de destino do datagrama para saber se
é realmente uma resposta do servidor.

Você detectará esse problema retardando a resposta


do servidor para saber se
outro emitente pode enviar uma resposta na qual o cliente ingênu confie Em
o .
um sistema operacional menos precavido como o Windows, provav
elmente será
preciso adicionar uma chamada de longa duraç
ão a ti.f!le.sleep() entre o recebi­
mento e o envio para simular um servidor que
demore muito para responder.
Capítulo 2 • UDP
57

No Mac OS X e no Linux, no entanto, só é preciso


suspender o ser vidor com
Ctrl+Z, quando ele tiver instalado seu soquete , par
a st·m u1ar um ser vt·dor que
demore para responder.

Portanto, inicie um novo ser vidor e então suspenda-o usando


Ctrl+Z.
$ python udp_local.py server
Listening at ('127.0.0.1', 1060)
"Z

(1] + 9370 suspended python udp_local.py server


$

Se você executar o cliente agora, ele enviará seu datagrama e então travará , espe­
rando uma resposta .

$ python udp_local.py client


The OS assigned Me the address ( '0.0.0.0', 39692)

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

O cliente voltará imediatamente e interpretará essa resposta de terceiros como


I

sendo a que estava esperando.

The server ('127.0.0.1', 37821) replted 'FAKE'

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.

Desconfiança, recuo, bloqueio e tempo limite

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?

Examine a listagem 2.2. E m vez de sempre responder solicitações de clientes, esse


servidor seleciona aleatoriamente responder apenas metade das solicitações recebi­
das, o que nos permite ver como construir confiabilidade n o código do cliente sem
esperar o que poderiam ser horas para que um pacote descartado ocorra na rede!
Capítulo 2 • UDP
59
Lista gem 2.2- Servidor e cliente UDP em máquinas diferentes

#!/usr/bin/env python3

# Progra�ação de Redes co� Python


# https://github.co�/br andon-rhodes/fopnp/blob/M/py3/chapter02
/udp_reMote.py
# Cliente e servidor UDP para coMunicação de rede

iMport argparse, randoM, socket, sys

MAX_BYTES = 65535

def server(interface, port):

sock= socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

sock.bind((interface, port))
print('Listening at', sock.getsockna�e())
whHe True:

data, address = sock.recvfro�(MAX_BYTES)

if randoM.randoM() < 0.5:

print('Pretending to drop packet fro�{}'.for�at(address))


continue

text= data.decode('ascii')

print('The client at{} says{!r}' .for�at(address, text))


Message = 'Your data was{} bytes long'.for�at(len(data))
sock.sendto(�essage.encode('ascii'), address)

def client(hostna�e, port):


sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

hostna�e = sys.argv[2]
sock.connect((hostnaMe, port))
print('Client socket naMe is{}'.forMat(sock.getsocknaMe()))

delay = 0.1 # segundos


text = 'This is another Message'
data= text.encode('ascii')
whHe True:

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

print('The server says {!r}' .forMat(data.decode('ascii')))

• t
. if _naMe_ == _Matn_

I
·

choices = {'client': client, 'server': server}


parser = argparse.ArguMentParser(description='Send and receive UDP,'
' pretendtng packets are often dropped')
parser.add_arguMent('role', choices=choices, help='which role to take')
parser.add_arguMent('host', help='interface the server listens at;'
'host the client sends to')
parser.add_arguMent('-p', Metavar='PORT', type=int, default=1666,
help='UDP port (default 1666)')
args = parser.parse_args()
function = choices[args.role]
function(args.host, args.p)

O servidor do exemplo anterior disse ao sistema operacional que só queria pacotes


que chegassem de outros processos da mesma máquina através da interface pri­
vada 12ZO.O.l, mas você pode tornar esse servidor mais generoso especificando seu
endereço IP como uma string vazia. Isso significa "qualquer interface local'; o que
em meu laptop Linux significa solicitar ao sistema operacional o endereço IP 0.0.0.0.

$ python udp_reMote.py server ""


listening at ('0.9.6.0', te60)

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
.
. · · - ·

. . . porque geralmente não



h á como o c tente d tstmgmr os três evento s muito d't .
I erentes descntos a
segmr:
.

A respo sta está demorando muito para retorna


r, mas chegara, em breve.

A respo sta nun ca chegará porque ela ' ou a solt'ct'taça r


o, 101· perd'd

-
1 a.


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.

Assim, e m vez de permitir que o sistema operacional deixe-o em pausa infinita


na chamada a recv(), primeiro esse cliente executa um setti.l'leout() no soquete. Isso
informará ao sistema que o cliente não quer ficar travado dentro de uma operação
de soquete por mais do que delay segundos e que ele quer que a chamada seja
interrompida com uma exceção socket. ti.l'l�out após ter esperado durante o tempo
especificado.

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.

Um recurso importante desse programa cliente é o que ocorre se o tempo limite


for alcançado. Ele não começa simplesmente a enviar solicitações repetidas em
intervalos fixos! Já que a principal causa da perda de pacotes é o congestionamento
- como sabe qualquer pessoa que tente enviar dados normais upstream através
de um modem DSL ao mesmo tempo em que o upload de fotos ou ví deos está
62 Programação de Redes com Python

ocorrendo-, a última coisa que vo cê vai querer fazer é responder a um paco te


possivelmente descar tado enviando ainda mais deles.

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

menos pacotes. Embora existam algoritmos de recuo exponencial mais elaborados


_por exemplo, a versão do algoritmo na Ethernet adiciona alguma aleatoriedad e
para que duas placas de rede rivais não recuem usando a mesma programação-,
o efeito básico pode ser obtido de maneira bem simples se dobrarmos o retardo
sempre que uma resposta não for recebida.

Observe que se as solicitações estiverem sendo feitas a um servidor que fique,


digamos, 200 milissegundos parado, esse algoritmo ingênuo sempre enviará pelo
menos duas cópias de cada solicitação, porque nunca saberá que as solicitações
feitas a esse servidor levam mais de 0,1 segundo. Se você estiver criando um cliente
UDP que fique ativo por muito tempo, considere fazê-lo lembrar quanto tempo
as últimas solicitações demoraram para ser concluídas e assim espere para tentar
novamente somente após o servidor ter tempo suficiente para responder.

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.

$ python udp_reMote.py client guinness

Client socket naMe is ('127.0.0.1', 45420)

Waiting up to 0.1 seconds for a reply

The server says 'Vour data was 23 bytes long'

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.

$ python udp_reMote.py client guinness

Client socket naMe is ('127.0.0.1', 58414)

Waiting up to 0.1 seconds for � reply

Waiting up to 0.2 seconds for a reply


>

63
Waiting up to 0.4 seconds for a reply
Waiting up to 0.8 seconds for a reply

The server says 'Your data was 23 bytes long'

no terminal em que está executando o servidor se as solicitações


Você pode ver
do ou se está havendo um descarte de pacote em sua rede. Quando
estão chegan
pude examinar o console do servidor e ver que todos os
executei o teste anterior,
am chegado.
pacotes tinh
Pretendi ng to drop packet froM ('192.168.5.10', 53322)
Pretendi ng to drop packet froM ('192.168.5.10', 53322)

Pretending to drop packet froM ('192.168.5.10', 53322)

Pretending to drop packet froM ('192.168.5.10', 53322)

The client at ('192.168.5.10', 53322) says, 'This is another Message'

E se o servidor estiver totalmente inativo? Infelizmente, o UDP não nos dá uma


forma de distinguir um servidor que está inativo de uma rede que está em condição
tão precária a ponto de descartar todos os pacotes ou suas respostas. Suponho
que não devamos culpar o UDP por esse problema. Afinal, até mesmo no mundo
em que vivemos, não temos como distinguir algo que não podemos detectar de
algo que não existe! Logo, o melhor que o cliente pode fazer é desistir quando
tiver feito tentativas suficientes. Encerre o processo do servidor e tente executar
o cliente novamente.

$ python udp_reMote.py client guinness


Client socket naMe is ('127.9.9.1', 58414)

Waiting up to 9.1 seconds for a reply

Waiting up to 0.2 seconds for a reply

Waiting up to 0.4 seconds for a reply

Waiting up to 9.8 seconds for a reply

Waiting up to 1.6 seconds for a reply

Traceback (Most recent call last):

socket.tiMeout: tiMed out

The above exception was the direct cause of the following exception:

Traceback (MOst recent call last):

RuntiMeError: I think the server is down


64 Programação de Redes com Python
É claro que só faz sent�do desisti� se �eu programa estiver tentando executar algu­
ma tarefa breve e prec�se produztr sa1da ou retornar algum tipo de resultado
para
0 usuário. Se você estt ver escrevendo um programa daemon que seja executado
0 dia inteiro - como, diga
mos, um ícone de clima no canto da tela que exib
aa
temperatura e a prev isão do tempo acessadas em um serviço UD P remo to -,en­
i
tão seria bom que o código f zesse novas tentativa s "infinita mente': Afi nal, uma
máquina desktop ou laptop pode ficar fora da rede por períodos longos e talvez
seu código tenha que esperar pacientemente por horas ou dias até que 0 servidor
de previsões possa ser contatado novamente.

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.

Conectando soquetes UDP


A listagem 2.2, que você examinou na seção anterior, introduziu outro conceito
novo que precisa de explicação. Já discuti a vinculação- tanto a chamada explí­
cita a btnd() que um servidor usa para ob ter o endereço que deseja usar quanto a
vinculação implícita que ocorre quando o cliente tenta pela primeira vez usar um
soquete e recebe do sistema operacional um número de porta efêmero aleatório .

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.

Duas últimas observações devem ser feitas sobre a chamada a connect( ).

Em primeiro lugar, executar connec t ( ) em u m soquete UDP não envia nenhuma


informação pela rede ou aciona algo que avise ao servidor que pacotes podem
estar chegando. A operação apenas grava o endereço na memória do sistema
operacional para uso quando posteriormente você chamar sendO e recv ( ).

Em segundo lugar, lembre-se de que executar connect( ) ou até mesmo você


-

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.

l Os de solicitação: uma boa ideia


As mensagens enviadas nas listagens 2.1 e 2.2 eram textos ASCII simples. Mas se
você projetar um esquema para gerar solicitações e respostas UDP, deve considerar
a inclusão de um número sequencial em cada sol icitação e assegurar que a resposta
use o mesmo número. No lado do servidor, apenas copie o número de cada solicita­
ção na resposta correspondente. Isso apresenta pelo menos duas grandes vantagens.

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.

No entanto, e se as duas solicitações chegarem ao servidor e as respostas apenas


demorarem um pouco para voltar? Você recebeu uma das duas respostas, mas
será que a outra está para chegar? Se agora enviar uma solicitação B para o ser­
vidor e começar a escutar, receberá quase imediatamente a resposta A duplicada,
confundindo-se e achando que ela é a resposta à pergunta que fez na solicitação
B. A partir de então, pode ficar totalmente dessincronizado e interpretar cada res­
posta como sendo correspondente a uma solicitação diferente da que acha que é!

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.

A outra finalidade à qual as IDs de solicitação servem, como mencionado na seção


"Promiscuidade': é fornecer u m obstáculo ao spoofing, pelo menos no caso em
que os invasores não podem ver os pacotes. É claro que, se puderem ver, não há
o que fazer: eles verão o endereço IP, o n úmero da porta e a ID de solicitação de
Capítulo 2 • UDP
67

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
·

a operacional informe como sendo o


sa trorma, usando o que quer que o sistem .
endereço IP externo de seu sistema:
68 Programação de Redes com Python

$ python udp_re�te . py server 192 . 168 . 5 . 139


Ltstening at ( ' 192. 168 . 5 . 139 ' , 1969)

A conexão a esse endereço I P a pa rti r de outra máquina deve continuar funcio­


nando normal mente.

$ python udp_reMote . py cltent guinness


Client socket naMe is ( ' 192 . 168 . 5 . 18 ' , 35884)
Waiting up to 8.1 seconds for a reply

The server says ' Your data was 23 bytes'

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.

$ python udp_reMote . py client 127 . 9 . 9 . 1


Client socket naMe is ( ' 127 . 8 . 8 . 1 ' , 69251)
Waiting up to 0.1 seconds for a reply

Traceback (Most recent call las t ) :

socket. erro r : [ E r rno 111] Connection refused

Na verdade, pelo menos em meu sistema operacional, o resultado é ainda melhor do


que os pacotes nunca serem d istribuídos. Já que o sistema operacional pode ver se
uma de suas portas está aberta sem enviar um pacote pela rede, ele responde ime­
diatamente que é impossível estabelecer uma conexão com essa porta! Mas é bom
ressaltar que esse recurso do UDP retornar "Connection refused" é um superpoder
'
do loopback que você nunca verá na rede real. Nela, o pacote deve simplesmente
ser enviado sem indicação de se há uma porta de destino para recebê-lo.

Tente executar o cliente novamente na mesma máquina, mas desta vez use seu
endereço IP externo.

$ python udp_reMote . py client 192. 168 . 5 . 130


Client socket naMe is ( ' 192 . 168 . 5 . 130 ' , 34919)
Waiting up to 0.1 seconds for a reply

lhe server says ' Vour data was 23 bytes '

Viu o que aconteceu? Os programas sendo executados localmente têm permissão


para enviar solicitações originárias de qualquer um dos endereços IP da máquina
- mesmo se estiverem usando esse endereço I P apenas para conversar com outro '
serviço na mesma máquina!
69
·
Logo, vmcu 1ar-se a u ma inter
f:ace IP talv� 1·
ar com Im ite
· os hosts externos qu
con vers voc ê. Mas com c _
e poderão
erteza nao lim ita
ra, conversas com ou tros clie
mesma má qui na, contanto que e1 ntes na
es sat.b am o en
de reço IP ao qual devem se co
. nectar
O que acon tecena se você tent
·

asse executa r dois


serv·d
1 ores ao mesmo
tempo?
Interrompa todos os scripts qu _

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)

Ago ra que o en de reço está ocup


. ad o, nao - e posstvel execu tar um
,
segu ndo servi-
nesse
,

dor ender eço, porq u e o Siste


ma operaciOna 1 nao sa bena
·

. que processo deve


-

receber os paco tes que chegara m


ne I e.
$ python udp_reMot e. py server 127 . 0
•0.1
Traceback (Most recent call last) :

OSError: [Errno 98] Address already in use

O mais estranho, porém, é que você também não poderá executar um servidor
no endereço IP curinga.

$ python udp_reMote . py server


Traceback (Most recent call last) :

OSError: [Errno 98] Address already in use

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.

$ python udp_reMote. py server 192 . 168 . 5 . 130


Listening at ( ' 192 . 168. 5 . 130 ' , 1060)

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 ç

A l ição q u e t u d o ÍS,!,O no dá é que u m a pi lha de rede J P n u nca con iclc r .1 u m,J


po r t a UDP com o uma entidad e a utônoma que esteja i me i ra mc m c d i�pon ivcl nu
e n t ã o em u ·o em u m m omcn t o _cspccí fico. Ela traba l h a com "nome: ele :-nq ucrc,
..

UDP q u e . <lo s e m pre u m par v mcu lando urna i mcrface J P - mes m o se fo r a i n ­


tcrf;:lce curi nga - a u m n ú m cro de pon() DP. . ão c es nomes d e soquetes que
.
não devem e n t ra r e m con n r tn e n t re os servidor q u e cstà �cu r.mdo. e m V(7.

d a s port as UD P que csr; i o em uso.


É necessário dar u m ú lt i m o aviso. já que a discussão a merior mos t r o u qu� v in u l. 1 r
seu servidor à i n t erface 1 27.0.0.1 forneceria p roteção conrra pacorl: pos.sivdmcnrc
maliciosos gerados nn rede e x te rn (J , você po d e achar que \·i ncu br-sc ;1 u m a ( 111 ;.
ca i nrcr face externa o p rote ge rá de pacotes de esrraga-prnzcrcs de ou r r as redes
ex te rn as. Por exe m plo, em u m s e rv i do r gr a n d e com vcírias plnc:1.s de rede, você

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 você estiver considerando a eficiência, pode querer limitar seu protocolo


a pacotes pequenos para tornar a retransmissão menos provável e limitar o
tempo necessário para a pilha IP remota reagrupar o pacote UDP e entregá­
-lo ao aplicativo em espera.


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.

Listagem 2.3 - Enviando um pacote UDP grande

# ! /usr/bin/env python3

# Progra�ção de Redes coM Python


# https : / /github. coM/brandon- rhodes/fopnp/blob/M/py3/chapter92/big_sender . py

# Envia UM datagraMa UDP g rande para saber a MTU do caMinho de rede.


72 Programação de Redes com Python

i�port IN, argparse, socket

if not hasattr(IN, ' I P_MTU ' ) :


raise RuntiMeError( ' cannot perforM MTU dtscovery on this coMbinatton '
• of operattng syste� and Python distribution ' )

def send_big_datagra�(host, po rt) :


sock = socket . socket(socket. AF_INET, socket. SOCK_DGRAM)
sock. setsockopt(socket . IPPROTO_IP, IN. IP_MTU_DISCOVER , I N . I P_PMTUDISC_DO)

sock. connec t ( ( host, port))


try:
sock . send(b ' # ' * 65000)
except socket. error :

print( 'Alas , the datagraM did not Make i t ' )

MaX_Mtu = sock . getsockopt( socket . I PPROTO_IP, I N . IP_MTU)

print( 'Actual MTU: {} ' . forMat(Max_Mtu) )


else:

print( ' The big datagraM was sen t ! ' )

if __ naMe __ == ' __ Main


__ ':

parser = argparse .ArguMentParser(description=' Send UDP packet to get MTU ' )

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,

help= ' UDP port (default 1060) ' )

args = parser. parse_arg s ( )

send_big_datag raM(args . host, args . p)

Se eu executar esse programa em relação a um servidor em outro local de minha


rede doméstica , verei que minha rede wireless permite pacotes físicos que não sejam
maiores do que os 1500 bytes normalmente suportados por redes de tipo Ethernet.

$ python big_sender . py guinness


Ala s , the datagraM did not Make it

Actual MTU: 1500

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.

$ python big_sende r . py 127 . 0 . 0 . 1

Ala s , the datagraM did not Make it

Actual MTU: 65535


Capítulo 2 • UDP
73

Mas o recurso de verificar a MTU não está dispon ível em qua


.
detalhes na docu ment açao de seu sistema operacional.
_
1quer 1ugar; procure

Opções dos soquetes


!
A inter face de soqu ete P OS X dá supo rte a todos os tipos de opções que controlam
os compor tame ntos especificas dos soquetes de rede. A opção IP MTU_DISCOVER qu
. - e
você viu na hstagem 23 é apen as a pont a do iceberg. As opções são acessadas
através dos métodos de soque tes Pytho n getsockopt() e setsockopt() , e devemos usar
as que estiverem listada s na documentação do sistema operacional para essas duas
chamadas de sistema . No Linux, tente visualizar no manua l as páginas socket(7),
udp (7) e - quando passar para o próxim o capítulo tcp(7). -
Ao ativar opções de soquetes, primeiro você tem de nomear o grupo de opções
em que elas residem e, então, como argumento subsequente, nomear a opção que
deseja ativar. Consulte o manual de seu sistema operacional para ver os nomes dos
grupos. Como nas chamadas Python getattr() e setattr() , a chamada set recebe
apenas um argumento a mais do que get.

value = s . getsockopt(socket.SOL_SOCKET, socket .SO_BROADCAST)


s.setsockopt(socket.SOL_SOCKET , socket.SO_BROADCAST, value)

Muitas opções são específicas de determinados sistemas operacionais e eles po­


dem ser exigentes em como suas opções são apresentadas. Aqui estão algumas
das opções mais comuns:


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

é de tipo SOCK_DGRAM e pode ser usado para o UDP ou se é de tipo SOCK_STREAH


e dá suporte à semântica do TCP (Capítulo 3).

0 próximo capítulo introduzirá algumas opções de soquetes adicionais aplicáveis


especificamente a soquetes TCP.
74 Programação de Redes com Python

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.

É preciso mencionar imediatamente que o broadcast já é considerado ultrapassado


porque foi desenvolvida uma técnica mais sofisticada chamada multicast que per­
mite que os sistemas operacionais modernos se beneficiem melhor da inteligência
construída em muitas redes e dispositivos de interface de rede. Além disso, o
multicast pode operar com hosts que não estejam na sub-rede local. Mas se você
quiser uma maneira simples de manter algo como clientes de jogos ou placares
automatizados atualizados na LAN local e quiser que os clientes sobrevivam a
um descarte ocasional de pacote, então, o broadcast UDP é u m a escolha fácil.

A listagem 2.4 mostra o exemplo de um servidor que pode receber pacotes de


broadcast e u m cliente que pode enviá-los. Se você examinar cuidadosamente, verá
que na verdade há apenas uma diferença entre essa listagem e as técnicas usadas
nas listagens anteriores. Antes de usar esse objeto de soquete, estamos chamando
seu método setsockopt ( ) para ativar o broadcast. Fora isso, tanto o servidor quanto
o cliente usam o soquete normalmente.

Listagem 2.4 - Broadcast UDP

#! /usr/bin/env python3

# PrograMação de Redes coM Python

# https : //github. coM/brandon - rhodesjfopnp/blob/M/py3/chapter02/udp_broadcast . py

# cliente e servidor UDP para o envio de Mensagens broadcast eM uMa LAN local

iMport argparse, socket

BUFSIZE = 65535

def server(interface, port) :

sock = socket . socket(socket .AF_INET, socket. SOCK_DGRAM)


sock. bind( (interface, port))

print ( ' listening for datagraMs at {} ' . forMat(sock. getsocknaMe( ) ) )


whi.le True:
Capítulo 2 • UDP 75
text = data . decode( ' ascii ' )
pri nt ( ' The cli ent at {} say s : { ! r} '
. forMat(add res sl text ) )

def client(networkl port) :


sock = socket . sock et(s ocke t. AF_I NET1 sock et. SOCK_DGR
AM)
sock . s etsocko pt( socket . SOL_SOCKET 1 sock et. SO_BROADCA
ST 1 1 )
text = ' B roadcast datagraM ! '

sock . sendto(text . encode ( ' ascii ' ) 1 (netwo rk 1 port))

' f -naMe
l. -
== ' Main
- -
':

choices = { ' cltent ' : cltent 1 ' server ' : server}

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,

help= ' UDP port (default 1060) ' )

args = parse r . parse_args()


functton = chotce s [ a r g s . role]

functton(a rgs . host, a rgs . p )

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.

A mágica acontece quando examinamos as configurações da rede local e usamos


seu "endereço IP de broadcast" como o destino do cliente. P r imeiro, traga u m ou
dois servidores para sua rede, usando comandos como o seguinte:

$ python udp_broadcast . py server " "


Listening for broadcasts at ( ' 0 . 0 . 0 . 0 ' , 1060)

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

No entanto, quando usamos o endereço de broadcast da rede local, subitamente


vemos que todos os servidores de broadcast recebem o pacote ao mesmo tempo!
(Mas nenhum servidor com u m o vê - execute algu mas cópias do servidor comum
udp_rel'lote . py ao encaminhar broadcasts para constatar.) Atu a l m ente, em m i nha
rede local, o comando i.fconfig informa que o endereço de broadcast é :

$ python udp_b roadcas t . py cltent 192 . 168 . 5 . 255

E os dois servidores relatam imediatamen te que estão vendo a mensagem. Se


seu sistema operacional tornar difícil determ i nar o endereço de broadcast e você
não se importar em fazer um broadcast a partir de cada porta de rede de seu
host, Python permite o uso do nome de host especial <broadcast> no envio com
1 1

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.

$ python udp_broadcast . py cHent "<broadcast>"

Se houvesse alguma maneira independente de plataforma de conhecer cada sub­


-rede conectada e seu endereço de broadcast, eu mostraria. Infelizmente, você terá
que consultar a documentação de seu sistema operacional se qu iser fazer algo
mais específico do que usar a string especial <broadcast> I 1 •

Quando usar o UDP


Você deve estar se perguntando se o UDP é eficiente no envio de pequenas mensa­
gens. Na verdade, o UDP só é eficiente quando o host envia uma única mensagem
d e cada vez e então espera uma resposta. Se seu aplicativo costuma enviar várias
mensagens ao mesmo tempo, o uso d e uma fila de mensagens inteligente como
a do 0MQ será mais eficiente porque definirá u m timer de período menor per­
mitindo o agrupamento de várias mensagens pequenas n a mesma transmissão,
provavelmente em uma conexão TCP, qu e é melhor do que nós na tarefa de dividir
os dados em fragmentos!

No entanto, há algumas boas razões para usar o UDP.


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.

A pilha de rede POSIX dá acesso ao UDP através do conceito de "soquete': que é


uma extremidade de comunicação que pode ter um endereço IP e um número de
porta UDP - esses dois elementos em conjunto são chamados de nome ou endereço
do soquete - e envia e recebe datagramas. Python oferece essas operações de rede
primitivas por intermédio do módulo interno socket.

O servidor precisa se vincular (com o uso de bi.nd()) a um endereço e uma porta


antes de poder receber os pacotes que chegarem. Programas clientes UDP podem
apenas começar a enviar e o sistema operacional escolherá um numero de porta
para eles automaticamente.

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.

As IDs de solicitação são cruciais no combate ao problema da duplicação de


respostas, em que uma resposta que achamos que tivesse sido perdida acaba che­
gando posteriormente e pode ser confundida com a resposta dada à solicitação
atual. Quando selecionadas aleatoriamente, as IDs de solicitação também podem
ajudar a proteger contra ataques i ngênuos de spoofing.
78 Programação de Redes com Python

No uso de oquctcs, é i rn po r t a n t e d i s t i ngu i r o aro d e vincular - e m que adota mos


u m a por r a UDP especí fica pa ra uso próprio - do a ro q u e o c l i e n te exec u t a se
co11ectando, que l i nl i t a toda s as respos t a s recebidas para q u e elas só possa m v i r
do servidor cspccf fico com o q u a l queremos nos com u n icar.

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

Vous aimerez peut-être aussi