Skip to content

Commit

Permalink
Add two more notes-to-self files
Browse files Browse the repository at this point in the history
blocking-read-hack.py: This demonstrates a really weird approach to
solving python-triogh-174. See:
  python-trio#174 (comment)

ntp-example.py: A fully-worked example of using UDP from Trio,
inspired by
  python-trio#472 (comment)
This should move into the tutorial eventually.
  • Loading branch information
njsmith committed May 21, 2018
1 parent 39f9f74 commit 21729b8
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 0 deletions.
45 changes: 45 additions & 0 deletions notes-to-self/blocking-read-hack.py
@@ -0,0 +1,45 @@
import trio
import os
import socket
import errno

bad_socket = socket.socket()

class BlockingReadTimeoutError(Exception):
pass

async def blocking_read_with_timeout(fd, count, timeout):
print("reading from fd", fd)
cancel_requested = False

async def kill_it_after_timeout(new_fd):
print("sleeping")
await trio.sleep(timeout)
print("breaking the fd")
os.dup2(bad_socket.fileno(), new_fd, inheritable=False)
# MAGIC
print("setuid(getuid())")
os.setuid(os.getuid())
nonlocal cancel_requested
cancel_requested = True

new_fd = os.dup(fd)
print("working fd is", new_fd)
try:
async with trio.open_nursery() as nursery:
nursery.start_soon(kill_it_after_timeout, new_fd)
try:
data = await trio.run_sync_in_worker_thread(os.read, new_fd, count)
except OSError as exc:
if cancel_requested and exc.errno == errno.ENOTCONN:
# Call was successfully cancelled. In a real version we'd
# integrate properly with trio's cancellation tools; here
# we'll just raise an arbitrary error.
raise BlockingReadTimeoutError from None
print("got", data)
nursery.cancel_scope.cancel()
return data
finally:
os.close(new_fd)

trio.run(blocking_read_with_timeout, 0, 10, 2)
91 changes: 91 additions & 0 deletions notes-to-self/ntp-example.py
@@ -0,0 +1,91 @@
# If you want to use IPv6, then:
# - replace AF_INET with AF_INET6 everywhere
# - use the hostname "2.pool.ntp.org"
# (see: https://news.ntppool.org/2011/06/continuing-ipv6-deployment/)

import trio
import struct
import datetime

def make_query_packet():
"""Construct a UDP packet suitable for querying an NTP server to ask for
the current time."""

# The structure of an NTP packet is described here:
# https://tools.ietf.org/html/rfc5905#page-19
# They're always 48 bytes long, unless you're using extensions, which we
# aren't.
packet = bytearray(48)

# The first byte contains 3 subfields:
# first 2 bits: 11, leap second status unknown
# next 3 bits: 100, NTP version indicator, 0b100 == 4 = version 4
# last 3 bits: 011, NTP mode indicator, 0b011 == 3 == "client"
packet[0] = 0b11100011

# For an outgoing request, all other fields can be left as zeros.

return packet

def extract_transmit_timestamp(ntp_packet):
"""Given an NTP packet, extract the "transmit timestamp" field, as a
Python datetime."""

# The transmit timestamp is the time that the server sent its response.
# It's stored in bytes 40-47 of the NTP packet. See:
# https://tools.ietf.org/html/rfc5905#page-19
encoded_transmit_timestamp = ntp_packet[40:48]

# The timestamp is stored in the "NTP timestamp format", which is a 32
# byte count of whole seconds, followed by a 32 byte count of fractions of
# a second. See:
# https://tools.ietf.org/html/rfc5905#page-13
seconds, fraction = struct.unpack("!II", encoded_transmit_timestamp)

# The timestamp is the number of seconds since January 1, 1900 (ignoring
# leap seconds). To convert it to a datetime object, we do some simple
# datetime arithmetic:
base_time = datetime.datetime(1900, 1, 1)
offset = datetime.timedelta(seconds=seconds + fraction / 2**32)
return base_time + offset

async def main():
print("Our clock currently reads (in UTC):", datetime.datetime.utcnow())

# Look up some random NTP servers.
# (See www.pool.ntp.org for information about the NTP pool.)
servers = await trio.socket.getaddrinfo(
"pool.ntp.org", # host
"ntp", # port
family=trio.socket.AF_INET, # IPv4
type=trio.socket.SOCK_DGRAM, # UDP
)

# Construct an NTP query packet.
query_packet = make_query_packet()

# Create a UDP socket
udp_sock = trio.socket.socket(
family=trio.socket.AF_INET, # IPv4
type=trio.socket.SOCK_DGRAM, # UDP
)

# Use the socket to send the query packet to each of the servers.
print("-- Sending queries --")
for server in servers:
address = server[-1]
print("Sending to:", address)
await udp_sock.sendto(query_packet, address)

# Read responses from the socket.
print("-- Reading responses (for 10 seconds) --")
with trio.move_on_after(10):
while True:
# We accept packets up to 1024 bytes long (though in practice NTP
# packets will be much shorter).
data, address = await udp_sock.recvfrom(1024)
print("Got response from:", address)
transmit_timestamp = extract_transmit_timestamp(data)
print("Their clock read (in UTC):", transmit_timestamp)

trio.run(main)

0 comments on commit 21729b8

Please sign in to comment.