Skip to content

Commit 4c2f61e

Browse files
authored
Merge pull request #6 from brentru/updated-dns
DNS Client Implementation and misc. improvements
2 parents f20cb39 + 9c8d28e commit 4c2f61e

File tree

6 files changed

+272
-44
lines changed

6 files changed

+272
-44
lines changed

README.rst

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ Introduction
1515

1616
Pure-Python interface for WIZNET 5k ethernet modules.
1717

18-
NOTE: This library does not currently contain a DNS client. You will need to include a server ip address and destination port in your code.
19-
2018
Dependencies
2119
=============
2220
This driver depends on:
@@ -67,24 +65,21 @@ wifitest.adafruit.com.
6765
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET
6866
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
6967
70-
# Name address for wifitest.adafruit.com
71-
SERVER_ADDRESS = (('104.236.193.178'), 80)
72-
7368
cs = digitalio.DigitalInOut(board.D10)
7469
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
7570
7671
# Initialize ethernet interface with DHCP
77-
eth = WIZNET(spi_bus, cs)
72+
eth = WIZNET5K(spi_bus, cs, debug=True)
7873
7974
print("DHCP Assigned IP: ", eth.pretty_ip(eth.ip_address))
8075
8176
socket.set_interface(eth)
8277
83-
# Create a new socket
84-
sock = socket.socket()
78+
host = 'wifitest.adafruit.com'
79+
port = 80
8580
86-
print("Connecting to: ", SERVER_ADDRESS[0])
87-
sock.connect(SERVER_ADDRESS)
81+
addr_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)
82+
sock = socket.socket(addr_info[0], addr_info[1], addr_info[2])
8883
8984
print("Connected to ", sock.getpeername())
9085

adafruit_wiznet5k/adafruit_wiznet5k.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747

4848
from adafruit_bus_device.spi_device import SPIDevice
4949
import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp
50+
import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns
5051

5152

5253
__version__ = "0.0.0-auto.0"
@@ -206,14 +207,28 @@ def set_dhcp(self, response_timeout=1):
206207
_gw_addr = (_dhcp_client.gateway_ip[0], _dhcp_client.gateway_ip[1],
207208
_dhcp_client.gateway_ip[2], _dhcp_client.gateway_ip[3])
208209

209-
_dns_addr = (_dhcp_client.dns_server_ip[0], _dhcp_client.dns_server_ip[1],
210+
self._dns = (_dhcp_client.dns_server_ip[0], _dhcp_client.dns_server_ip[1],
210211
_dhcp_client.dns_server_ip[2], _dhcp_client.dns_server_ip[3])
211-
self.ifconfig = ((_ip, _subnet_mask, _gw_addr, _dns_addr))
212+
self.ifconfig = ((_ip, _subnet_mask, _gw_addr, self._dns))
212213
return 0
213-
# Reset SRC_Port
214214
self._src_port = 0
215215
return 1
216216

217+
def get_host_by_name(self, hostname):
218+
"""Convert a hostname to a packed 4-byte IP Address.
219+
Returns a 4 bytearray.
220+
"""
221+
if self._debug:
222+
print("* Get host by name")
223+
if isinstance(hostname, str):
224+
hostname = bytes(hostname, 'utf-8')
225+
self._src_port = int(time.monotonic())
226+
# Return IP assigned by DHCP
227+
_dns_client = dns.DNS(self, self._dns)
228+
ret = _dns_client.gethostbyname(hostname)
229+
self._src_port = 0
230+
return ret
231+
217232
@property
218233
def max_sockets(self):
219234
"""Returns max number of sockets supported by chip."""
@@ -289,10 +304,11 @@ def remote_port(self):
289304
def ifconfig(self):
290305
"""Returns the network configuration as a tuple."""
291306
# set subnet and gateway addresses
307+
self._pbuff = bytearray(8)
292308
for octet in range(0, 4):
293309
self._pbuff += self.read(REG_SUBR+octet, 0x04)
294310
for octet in range(0, 4):
295-
self._pbuff[3+octet] += self.read(REG_GAR+octet, 0x04)
311+
self._pbuff += self.read(REG_GAR+octet, 0x04)
296312

297313
params = (self.ip_address, self._pbuff[0:3], self._pbuff[3:7], self._dns)
298314
return params
@@ -430,7 +446,6 @@ def socket_available(self, socket_num, sock_type=SNMR_TCP):
430446
"""
431447
if self._debug:
432448
print("* socket_available called with protocol", sock_type)
433-
print("* #", socket_num)
434449
assert socket_num <= self.max_sockets, "Provided socket exceeds max_sockets."
435450

436451
if sock_type == 0x02:
@@ -556,15 +571,18 @@ def socket_open(self, socket_num, dest, port, conn_mode=SNMR_TCP):
556571
return 1
557572

558573
def socket_close(self, socket_num):
559-
"""Closes a socket.
560-
561-
"""
562-
assert self.link_status, "Ethernet cable disconnected!"
574+
"""Closes a socket."""
563575
if self._debug:
564576
print("*** Closing socket #%d" % socket_num)
565577
self._write_sncr(socket_num, CMD_SOCK_CLOSE)
566578
self._read_sncr(socket_num)
567-
self._write_snir(socket_num, 0xFF)
579+
580+
def socket_disconnect(self, socket_num):
581+
"""Disconnect a TCP connection."""
582+
if self._debug:
583+
print("*** Disconnecting socket #%d" % socket_num)
584+
self._write_sncr(socket_num, CMD_SOCK_DISCON)
585+
self._read_sncr(socket_num)
568586

569587
def socket_read(self, socket_num, length):
570588
"""Reads data from a socket into a buffer.
@@ -673,7 +691,7 @@ def socket_write(self, socket_num, buffer):
673691
# check data was transferred correctly
674692
while(self._read_socket(socket_num, REG_SNIR)[0] & SNIR_SEND_OK) != SNIR_SEND_OK:
675693
if self.socket_status(socket_num) == SNSR_SOCK_CLOSED:
676-
self.socket_close(socket_num)
694+
#self.socket_close(socket_num)
677695
return 0
678696
time.sleep(0.01)
679697

adafruit_wiznet5k/adafruit_wiznet5k_dhcp.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
DHCP_SERVER_PORT = const(67)
7373
# DHCP Lease Time, in seconds
7474
DEFAULT_LEASE_TIME = const(900)
75-
BROADCAST_SERVER_ADDR = 255, 255, 255, 255
75+
BROADCAST_SERVER_ADDR = '255.255.255.255'
7676

7777
# pylint: enable=bad-whitespace
7878
_BUFF = bytearray(317)
@@ -120,7 +120,7 @@ def __init__(self, eth, mac_address, timeout=1, timeout_response=1):
120120
def send_dhcp_message(self, state, time_elapsed):
121121
"""Assemble and send a DHCP message packet to a socket.
122122
:param int state: DHCP Message state.
123-
:param float time_elapsed: Number of seconds elapsed since client attempted to renew lease.
123+
:param float time_elapsed: Number of seconds elapsed since renewal.
124124
125125
"""
126126
# OP
@@ -265,7 +265,7 @@ def parse_dhcp_response(self, response_timeout):
265265
# Subnet Mask
266266
self.subnet_mask = _BUFF[269:273]
267267
# DNS Server
268-
self.dns_server_ip = _BUFF[285:289]
268+
self.dns_server_ip = _BUFF[281:285]
269269

270270
return msg_type, xid
271271

@@ -283,7 +283,7 @@ def request_dhcp_lease(self):
283283
while self._dhcp_state != STATE_DHCP_LEASED:
284284
if self._dhcp_state == STATE_DHCP_START:
285285
self._transaction_id += 1
286-
self._sock.connect((BROADCAST_SERVER_ADDR, DHCP_SERVER_PORT))
286+
self._sock.connect(((BROADCAST_SERVER_ADDR), DHCP_SERVER_PORT))
287287
self.send_dhcp_message(STATE_DHCP_DISCOVER,
288288
((time.monotonic() - start_time) / 1000))
289289
self._dhcp_state = STATE_DHCP_DISCOVER
@@ -322,4 +322,6 @@ def request_dhcp_lease(self):
322322

323323
self._transaction_id += 1
324324
self._last_check_lease_ms = time.monotonic()
325+
# close the socket, we're done with it
326+
self._sock.close()
325327
return result
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# The MIT License (MIT)
2+
#
3+
# (c) Copyright 2009-2010 MCQN Ltd
4+
# Modified by Brent Rubell for Adafruit Industries, 2020
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in
14+
# all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
# THE SOFTWARE.
23+
"""
24+
`adafruit_wiznet5k_dns`
25+
================================================================================
26+
27+
Pure-Python implementation of the Arduino DNS client for WIZnet 5k-based
28+
ethernet modules.
29+
30+
* Author(s): MCQN Ltd, Brent Rubell
31+
32+
"""
33+
import time
34+
from random import getrandbits
35+
from micropython import const
36+
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket
37+
from adafruit_wiznet5k.adafruit_wiznet5k_socket import htons
38+
39+
# pylint: disable=bad-whitespace
40+
41+
QUERY_FLAG = const(0x00)
42+
OPCODE_STANDARD_QUERY = const(0x00)
43+
RECURSION_DESIRED_FLAG = 1<<8
44+
45+
TYPE_A = const(0x0001)
46+
CLASS_IN = const(0x0001)
47+
DATA_LEN = const(0x0004)
48+
49+
# Return codes for gethostbyname
50+
SUCCESS = const(1)
51+
TIMED_OUT = const(-1)
52+
INVALID_SERVER = const(-2)
53+
TRUNCATED = const(-3)
54+
INVALID_RESPONSE = const(-4)
55+
56+
DNS_PORT = const(0x35) # port used for DNS request
57+
# pylint: enable=bad-whitespace
58+
59+
class DNS:
60+
"""W5K DNS implementation.
61+
62+
:param iface: Network interface
63+
"""
64+
def __init__(self, iface, dns_address):
65+
self._iface = iface
66+
socket.set_interface(iface)
67+
self._sock = socket.socket(type=socket.SOCK_DGRAM)
68+
self._sock.settimeout(1)
69+
70+
self._dns_server = dns_address
71+
self._host = 0
72+
self._request_id = 0 # request identifier
73+
self._pkt_buf = bytearray()
74+
75+
def gethostbyname(self, hostname):
76+
"""Translate a host name to IPv4 address format.
77+
:param str hostname: Desired host name to connect to.
78+
79+
Returns the IPv4 address as a bytearray if successful, -1 otherwise.
80+
"""
81+
if self._dns_server is None:
82+
return INVALID_SERVER
83+
self._host = hostname
84+
# build DNS request packet
85+
self._build_dns_header()
86+
self._build_dns_question()
87+
88+
# Send DNS request packet
89+
self._sock.connect((self._dns_server, DNS_PORT))
90+
self._sock.send(self._pkt_buf)
91+
92+
# wait and retry 3 times for a response
93+
retries = 0
94+
addr = -1
95+
while (retries < 3) and (addr == -1):
96+
addr = self._parse_dns_response()
97+
retries += 1
98+
99+
self._sock.close()
100+
return addr
101+
102+
def _parse_dns_response(self):
103+
"""Receives and parses DNS query response.
104+
Returns desired hostname address if obtained, -1 otherwise.
105+
106+
"""
107+
# wait for a response
108+
start_time = time.monotonic()
109+
packet_sz = self._sock.available()
110+
while packet_sz <= 0:
111+
packet_sz = self._sock.available()
112+
if (time.monotonic() - start_time) > 1.0:
113+
# timed out!
114+
return -1
115+
time.sleep(0.05)
116+
# store packet in buffer
117+
self._pkt_buf = self._sock.recv(packet_sz)[0]
118+
119+
# Validate request identifier
120+
if not int.from_bytes(self._pkt_buf[0:2], 'l') == self._request_id:
121+
return -1
122+
# Validate flags
123+
if not int.from_bytes(self._pkt_buf[2:4], 'l') == 0x8180:
124+
return -1
125+
# Validate Answer RRs (>=1)
126+
an_count = int.from_bytes(self._pkt_buf[6:8], 'l')
127+
if not an_count >= 1:
128+
return -1
129+
130+
# iterate over ANCOUNT since answer may not be type A
131+
while an_count > 0:
132+
ans_type = int.from_bytes(self._pkt_buf[41:43], 'l')
133+
ans_class = int.from_bytes(self._pkt_buf[43:45], 'l')
134+
ans_len = int.from_bytes(self._pkt_buf[49:51], 'l')
135+
if ans_type == TYPE_A and ans_class == CLASS_IN:
136+
if ans_len != 4:
137+
# invalid size ret.'d
138+
return -1
139+
# return the address
140+
return self._pkt_buf[51:55]
141+
# not the correct answer type or class
142+
an_count += 1
143+
144+
def _build_dns_header(self):
145+
"""Builds DNS header."""
146+
# generate a random, 16-bit, request identifier
147+
self._request_id = getrandbits(16)
148+
149+
# ID, 16-bit identifier
150+
self._pkt_buf.append(self._request_id >> 8)
151+
self._pkt_buf.append(self._request_id & 0xFF)
152+
153+
# Flags (0x0100)
154+
self._pkt_buf.append(0x01)
155+
self._pkt_buf.append(0x00)
156+
157+
# QDCOUNT
158+
self._pkt_buf.append(0x00)
159+
self._pkt_buf.append(0x01)
160+
# ANCOUNT
161+
self._pkt_buf.append(0x00)
162+
self._pkt_buf.append(0x00)
163+
# NSCOUNT
164+
self._pkt_buf.append(0x00)
165+
self._pkt_buf.append(0x00)
166+
#ARCOUNT
167+
self._pkt_buf.append(0x00)
168+
self._pkt_buf.append(0x00)
169+
170+
def _build_dns_question(self):
171+
"""Build DNS question"""
172+
host = self._host.decode('utf-8')
173+
host = host.split(".")
174+
# write out each section of host
175+
for i, _ in enumerate(host):
176+
# append the sz of the section
177+
self._pkt_buf.append(len(host[i]))
178+
# append the section data
179+
self._pkt_buf += host[i]
180+
# end of the name
181+
self._pkt_buf.append(0x00)
182+
# Type A record
183+
self._pkt_buf.append(htons(TYPE_A) & 0xFF)
184+
self._pkt_buf.append(htons(TYPE_A) >> 8)
185+
# Class IN
186+
self._pkt_buf.append(htons(CLASS_IN) & 0xFF)
187+
self._pkt_buf.append(htons(CLASS_IN) >> 8)

0 commit comments

Comments
 (0)