《Python网络编程基础》第二章 读书笔记。

第二章 UDP

UDP称为用户数据报协议,TCP称为传输控制协议,计算机有多个端口,使用了多路复用技术,两台终端通信,必须保证数据的可靠性。

2.1端口号

IP只不过把数据送到了指定了主机,双方进行通信必须要有端口号,知名端口(0~1023),普通程序无法监听该端口。注册端口(1024~49151),一般建议指定服务时才使用的端口,剩下(49152到65535)当发送数据包的时候,没有指定的话,操作系统会随机生成一个。

In [1]: import socket                                                                                             

In [2]: socket.getservbyname("domain")                                                                            
Out[2]: 53

In [3]:  

  通过getservbyname获取dns的服务器的端口号.

2.2套接字

Python标准库通过对兼容posix操作系统网络操作的底层函数进行了封装,并为所有普通的原丝调用提供了一个简单的基于对象的接口。

套接字时一个通信的端点,操作系统使用整数来标识套接字,而Python则使用socket.socket对象来表示套接字,该对象内部维护了操作系统标识套接字的整数(fileno()方法可以看到)

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

import argparse, socket
from datetime import datetime

MAX_BYTES = 65535

def server(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 绑定端口
    sock.bind(('localhost', port))
    print("sock file no is {}".format(sock.fileno()))
    print("Listening at {}".format(sock.getsockname()))

    while True:
        data, address = sock.recvfrom(MAX_BYTES)
        text = data.decode('ascii')
        print('The client at {} says {!r}'.format(address, text))
        text = 'Your data was {} bytes long'.format(len(data))
        data = text.encode('ascii')
        sock.sendto(data, address)

def client(port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    text = 'The time is {}'.format(datetime.now())
    data = text.encode('ascii')
    sock.connect(('127.0.0.1', port))
    sock.send(data)
    print('The OS assigned me the address {}'.format(sock.getsockname()))
    data, address = sock.recvfrom(MAX_BYTES)
    text = data.decode('ascii')
    print('The server {} replied {!r}'.format(address, text))

if __name__ == '__main__':
    choices = {'client': client, 'server': server}
    paser = argparse.ArgumentParser(description='Send and receive UDP locally')
    paser.add_argument('role', choices=choices, help='which role to play')
    paser.add_argument('-p', metavar='PORT', type=int, default=1060,
                       help='UDP port (default 1060)')
    args = paser.parse_args()
    function = choices[args.role]
    function(args.p)

  上面写了示例代码:作为服务端,首先定义一个socket端口,里面填写参数,然后申请一个ip端口进行绑定,接着死循环通过recvfrom接受信息,接受的是发送者的ip端口与内容,通过sendto把信息发送给发送者

     client就比较简单,申请一个socket,通过sendto发送消息,通过recvfrom接受消息,少了一个bind死循环的需求。

 

2.2.1 混杂客户端与垃圾回复。

书中示例了当服务器运行以后,暂停服务,可以端发送请求以后一直在等待,如果你知道客户端的端口,你可以可以向等待的客户端发送信息,客户端将接受到攻击者的请求。

解决的方案,可以在传输的过程中设置一个唯一的id进行认证,或者检查发送者的ip,或者通过connect()来阻止其他地址向客户端发送数据包。

 

2.2.2 不可靠性、退避、阻塞和超时

 书中通过编写服务器丢包或者延迟的情况下【实际就是丢包】,请求处理情况,通过sock.settimeout的方法设定超时时间。采用指数退让的方式,处理超时。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import argparse, random, socket

MAX_BYTES = 65535

def server(interface, port):
    # 创建sock
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 给空格默认绑定0.0.0.0
    sock.bind((interface, port))
    print("Listening at", sock.getsockname())
    while True:
        data, address = sock.recvfrom(MAX_BYTES)
        # 丢一般的包不返回
        if random.random() < 0.5:
            print('Pretending to drop packet from {}'.format(address))
            continue
        text = data.decode('ascii')
        print("The client at {} says {!r}".format(address, text))
        message = 'Your data was {} bytes long'.format(len(data))
        sock.sendto(message.encode('ascii'), address)

def client(hostname, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.connect((hostname, port))
    print('Client socket name is {}'.format(sock.getsockname()))
    delay = .1
    text = 'This is another message'
    data = text.encode('ascii')
    # 一直重发,除非超时到2秒以上。
    while True:
        sock.send(data)
        print("Waiting up to {} seconds for a reply".format(delay))
        # 设置超时时间
        sock.settimeout(delay)
        try:
            data = sock.recv(MAX_BYTES)
        except socket.timeout as exc:
            delay *= 2
            if delay > 2.0:
                raise RuntimeError('I think the server is down') from exc
        else:
            break
    print("The server says {!r}".format(data.decode('ascii')))

if __name__ == '__main__':
    choices = {'client': client, 'server': server}
    parser = argparse.ArgumentParser(description='Send and receive UDP,'
                                     ' pretending 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=1060,
                        help='UDP port (default 1060)')
    args = parser.parse_args()
    function = choices[args.role]
    function(args.host, args.p)

  以上完整代码

 

2.2.3 连接套接字

书中介绍了显式绑定与隐式绑定。显式绑定bind()调用发生在服务器端,用来指定服务器使用的ip地址和端口;隐式绑定则发生在客户端,当客户端第一次尝试使用一个套接字时,操作系统会为其随机分配一个临时端口。

当客户端sock通过connect设置连接服务器,客户端只要使用send与recv就可以接收与发送消息,不需要使用sendto与recvfrom,并且使用connect()将其与目标地址连接,操作系统将非connect信息的消息过滤掉。

当使用了connect以后的sock对象,可以使用方法getpeername()得到连接的地址,如果没有connect,调用该方法会抛出socket.error

connect()的调用最后需要两点提示:

第一点,使用connect()连接UDP套接字,没有在网络上传输任何信息,知识简单的将连接的地址写入了操作系统的内存,以供之后调用send()和recv()的时候使用。

第二点,调用connect(),甚至通过返回地址手动过滤不需要的数据包,并不能够确保安全。

 

2.2.4 请求id:好主意

书中主要介绍了一种思路,当你多次发送请求的时候,如何确保响应就是你这个请求的,你可以在请求的时候带上一个id,返回的时候也把这个id带上,通过查寻响应id就能知道,这个响应对应的请求是哪一个。

但如果攻击者拦截到发送信息了,那一切都是白搭。

 

2.3绑定接口

本章我看的比较模糊,书中主要介绍了通过bind绑定网络接口,前面已经设置过了""与127.0.0.1,当然也可以设置指定的外部ip接口地址,比如以太网连接或者无线网卡,服务器只会监听传输至该ip的数据包。

书中演示了当你绑定网卡的网卡ip与端口时,无法送过127.0.0.1的端口发送信息,同一网络的其他计算机设备可以向该端口进行通信,即使本金也可以与该ip进行通讯。

书中最后演示了,跑两个服务器脚本,同时监听网卡的ip端口,与127.0.0.1的端口。

 

2.4 UDP分组

这一章节,翻译的也很差,我看的也不是很懂,就知道了发送大的udp数据的时候,以太网或者无线网卡只能处理1500B的数据包。所以,需要在IP网络层对数据包进行分拆。

书中的一个demo,说linux支持

原文:如果协议可以对如何针对不同的数据报进行数据分割做出选择,而我们又希望能够在两台主机间的实际MTU的基础上自动调整数据包大小,那么就可以通过操作系统关闭分组功能。这样一来,在UDP数据包过大时就会收到一条错误信息。然后,我们可以谨慎的修改数据报,使得其大小不超过限制。

#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter02/big_sender.py
# Send a big UDP datagram to learn the MTU of the network path.

import argparse, socket, sys

# Inlined constants, because Python 3.6 has dropped the IN module.

class IN:
    IP_MTU = 14
    IP_MTU_DISCOVER = 10
    IP_PMTUDISC_DO = 2

if sys.platform != 'linux':
    print('Unsupported: Can only perform MTU discovery on Linux',
          file=sys.stderr)
    sys.exit(1)

def send_big_datagram(host, port):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 设置socket数据
    sock.setsockopt(socket.IPPROTO_IP, IN.IP_MTU_DISCOVER, IN.IP_PMTUDISC_DO)
    sock.connect((host, port))
    try:
        sock.send(b'#' * 999999)
    except socket.error:
        print('Alas, the datagram did not make it')
        # 读取socket数据
        max_mtu = sock.getsockopt(socket.IPPROTO_IP, IN.IP_MTU)
        print('Actual MTU: {}'.format(max_mtu))
    else:
        print('The big datagram was sent!')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Send UDP packet to get MTU')
    parser.add_argument('host', help='the host to which to target the packet')
    parser.add_argument('-p', metavar='PORT', type=int, default=1060,
                        help='UDP port (default 1060)')
    args = parser.parse_args()
    send_big_datagram(args.host, args.p)

  这是源码,就看到了setsockopt,与getsockopt选项,具体里面的参数,书中没有介绍。我也不懂。

 

2.5 套机字选项

通过s.getsockopt()与setsockopt()选项可以对设置的sock对象进行参数设置与参数读取。

 

2.6 广播

通过像广播地址发送消息,同网段所有的机器只要开了该端口接收消息,都能收到,有意思的家伙。

 

posted @ 2020-12-08 18:03  就是想学习  阅读(178)  评论(0编辑  收藏  举报