Ticket #3014: twisted-ipv6.patch

File twisted-ipv6.patch, 15.7 KB (added by philmayers, 6 years ago)

Handles scope ID on connectTCP addresses; may need some more work server side

  • twisted/internet/abstract.py

    diff --git a/twisted/internet/abstract.py b/twisted/internet/abstract.py
    index 64f77b0..c15158f 100644
    a b from twisted.python import log, reflect, failure 
    1616from twisted.persisted import styles
    1717from twisted.internet import interfaces, main
    1818
     19import socket
    1920
    2021class FileDescriptor(object):
    2122    """An object which can be operated on by select().
    def isIPAddress(addr): 
    380381        return True
    381382    return False
    382383
     384def isIPv6Address(addr):
     385    if '%' in addr:
     386        addr, scope = addr.split('%', 1)
     387    try:
     388        s = socket.inet_pton(socket.AF_INET6, addr)
     389    except Exception, ex:
     390        print ex
     391        return False
     392    else:
     393        return True
     394
    383395
    384396__all__ = ["FileDescriptor"]
  • twisted/internet/tcp.py

    diff --git a/twisted/internet/tcp.py b/twisted/internet/tcp.py
    index 23d56b8..e141c3d 100644
    a b class BaseClient(Connection): 
    602602        fdesc._setCloseOnExec(s.fileno())
    603603        return s
    604604
    605     def resolveAddress(self):
    606         if abstract.isIPAddress(self.addr[0]):
    607             self._setRealAddress(self.addr[0])
    608         else:
    609             d = self.reactor.resolve(self.addr[0])
    610             d.addCallbacks(self._setRealAddress, self.failIfNotConnected)
    611605
    612     def _setRealAddress(self, address):
    613         self.realAddress = (address, self.addr[1])
    614         self.doConnect()
    615606
    616607    def doConnect(self):
    617608        """I connect the socket.
    class Client(BaseClient): 
    684675    def __init__(self, host, port, bindAddress, connector, reactor=None):
    685676        # BaseClient.__init__ is invoked later
    686677        self.connector = connector
     678        self.bindAddress = bindAddress
     679        self.reactor = reactor
    687680        self.addr = (host, port)
    688681
    689         whenDone = self.resolveAddress
    690682        err = None
    691683        skt = None
    692684
     685        self._start(host, port, True)
     686
     687    def _start(self, host, port, resolve=False):
     688
     689        if abstract.isIPAddress(host):
     690            self.addressFamily = socket.AF_INET
     691            self.realAddress = (host, port)
     692
     693        elif abstract.isIPv6Address(host):
     694            # we need to pass this through getaddrinfo to handle
     695            # scope ID, but we don't want it to block, hence
     696            # the flags..
     697            self.addressFamily = socket.AF_INET6
     698            replies = socket.getaddrinfo(host, port,
     699                    self.addressFamily,
     700                    self.socketType,
     701                    0,
     702                    socket.AI_NUMERICHOST   # numeric hosts - no DNS!
     703                    )
     704            if not replies:
     705                err = error.DNSResolutionError('couldnt resolve v6 scope '+host)
     706                self.failIfNotConnected(err)
     707                return
     708            # FIXME: what to do about multiple replies?
     709            self.realAddress = replies[0][4]
     710
     711        elif resolve:
     712            # resolve is only set on 1st call from "init"; 2nd
     713            # call is the answer, we set ourselves as callback
     714            # FIXME: currently this only does IPv4 lookups
     715            d = self.reactor.resolve(host)
     716            d.addCallback(self._start, port).addErrback(self.failIfNotConnected)
     717            return
     718
     719        else:
     720            # wtf?
     721            err = error.DNSResolutionError('couldnt resolve '+self.addr[0])
     722            self.failIfNotConnected(err)
     723            return
     724
     725        # create our socket
    693726        try:
    694727            skt = self.createInternetSocket()
    695728        except socket.error, se:
    696729            err = error.ConnectBindError(se[0], se[1])
    697             whenDone = None
    698         if whenDone and bindAddress is not None:
     730            self._finishInit(None, skt, err, self.reactor)
     731            return
     732
     733        if self.bindAddress is not None:
    699734            try:
    700                 skt.bind(bindAddress)
     735                skt.bind(self.bindAddress)
    701736            except socket.error, se:
    702737                err = error.ConnectBindError(se[0], se[1])
    703                 whenDone = None
    704         self._finishInit(whenDone, skt, err, reactor)
     738                self._finishInit(None, skt, err, self.reactor)
     739                return
     740
     741        self._finishInit(self.doConnect, skt, None, self.reactor)
    705742
    706743    def getHost(self):
    707744        """Returns an IPv4Address.
    708745
    709746        This indicates the address from which I am connecting.
    710747        """
    711         return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
     748        addr = self.socket.getsockname()
     749        return address.IPv4Address('TCP', addr[0], addr[1], 'INET')
    712750
    713751    def getPeer(self):
    714752        """Returns an IPv4Address.
    715753
    716754        This indicates the address that I am connected to.
    717755        """
    718         return address.IPv4Address('TCP', *(self.realAddress + ('INET',)))
     756        return address.IPv4Address('TCP', self.realAddress[0], self.realAddress[1], 'INET')
    719757
    720758    def __repr__(self):
    721759        s = '<%s to %s at %x>' % (self.__class__, self.addr, unsignedID(self))
    class Server(Connection): 
    770808
    771809        This indicates the server's address.
    772810        """
    773         return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
     811        addr = self.socket.getsockname()
     812        return address.IPv4Address('TCP', addr[0], addr[1], 'INET')
    774813
    775814    def getPeer(self):
    776815        """Returns an IPv4Address.
    777816
    778817        This indicates the client's address.
    779818        """
    780         return address.IPv4Address('TCP', *(self.client + ('INET',)))
     819        return address.IPv4Address('TCP', self.client[0], self.client[1], 'INET')
    781820
    782821class Port(base.BasePort, _SocketCloser):
    783822    """
    class Port(base.BasePort, _SocketCloser): 
    827866        self.factory = factory
    828867        self.backlog = backlog
    829868        self.interface = interface
     869        if interface and abstract.isIPv6Address(interface):
     870            self.addressFamily = socket.AF_INET6
    830871
    831872    def __repr__(self):
    832873        if self._realPortNumber is not None:
    class Port(base.BasePort, _SocketCloser): 
    872913        self.startReading()
    873914
    874915
    875     def _buildAddr(self, (host, port)):
    876         return address._ServerFactoryIPv4Address('TCP', host, port)
     916    def _buildAddr(self, addr):
     917        return address._ServerFactoryIPv4Address('TCP', addr[0], addr[1])
    877918
    878919
    879920    def doRead(self):
    class Port(base.BasePort, _SocketCloser): 
    10051046
    10061047        This indicates the server's address.
    10071048        """
    1008         return address.IPv4Address('TCP', *(self.socket.getsockname() + ('INET',)))
     1049        addr = self.socket.getsockname()
     1050        return address.IPv4Address('TCP', addr[0], addr[1], 'INET')
    10091051
    10101052class Connector(base.BaseConnector):
    10111053    def __init__(self, host, port, factory, timeout, bindAddress, reactor=None):
  • new file twisted/internet/test/test_ipv6.py

    diff --git a/twisted/internet/test/test_ipv6.py b/twisted/internet/test/test_ipv6.py
    new file mode 100644
    index 0000000..b1aa1d5
    - +  
     1# Copyright (c) 2008-2010 Twisted Matrix Laboratories.
     2# See LICENSE for details.
     3
     4"""
     5Tests for implementations of IPv6
     6"""
     7
     8__metaclass__ = type
     9
     10import socket
     11
     12from zope.interface import implements
     13from zope.interface.verify import verifyObject
     14
     15from twisted.internet.test.reactormixins import ReactorBuilder
     16from twisted.internet.error import DNSLookupError
     17from twisted.internet.interfaces import IResolverSimple, IConnector, IListeningPort
     18from twisted.internet.address import IPv4Address
     19from twisted.internet.defer import succeed, fail, maybeDeferred
     20from twisted.internet.protocol import ServerFactory, ClientFactory, Protocol, DatagramProtocol
     21from twisted.python import log
     22
     23class Stop(ClientFactory):
     24    """
     25    A client factory which stops a reactor when a connection attempt fails.
     26    """
     27    def __init__(self, reactor):
     28        self.reactor = reactor
     29
     30
     31    def clientConnectionFailed(self, connector, reason):
     32        self.reactor.stop()
     33
     34class v6TestsBuilder(ReactorBuilder):
     35    def _freePort(self, interface='::1'):
     36        probe = socket.socket(socket.AF_INET6)
     37        try:
     38            probe.bind((interface, 0))
     39            ip, port, flow, scope = probe.getsockname()
     40            return (ip, port)
     41        finally:
     42            probe.close()
     43
     44
     45    def test_clientConnectionFailedStopsReactor(self):
     46        host, port = self._freePort()
     47
     48        reactor = self.buildReactor()
     49
     50        reactor.connectTCP('::1', port, Stop(reactor))
     51        reactor.run()
     52
     53
     54    def test_addresses(self):
     55        """
     56        A client's transport's C{getHost} and C{getPeer} return L{IPv4Address}
     57        instances which give the dotted-quad string form of the local and
     58        remote endpoints of the connection respectively.
     59        """
     60        host, port = self._freePort()
     61        reactor = self.buildReactor()
     62
     63        serverFactory = ServerFactory()
     64        serverFactory.protocol = Protocol
     65        server = reactor.listenTCP(0, serverFactory, interface='::1')
     66        serverAddress = server.getHost()
     67
     68        addresses = {'host': None, 'peer': None}
     69        class CheckAddress(Protocol):
     70            def makeConnection(self, transport):
     71                addresses['host'] = transport.getHost()
     72                addresses['peer'] = transport.getPeer()
     73                reactor.stop()
     74
     75        clientFactory = Stop(reactor)
     76        clientFactory.protocol = CheckAddress
     77        reactor.connectTCP('::1', server.getHost().port, clientFactory, bindAddress=('::1', port))
     78
     79        reactor.run() # self.runReactor(reactor)
     80
     81        self.assertEqual(
     82            addresses['host'],
     83            IPv4Address('TCP', '::1', port))
     84        self.assertEqual(
     85            addresses['peer'],
     86            IPv4Address('TCP', '::1', serverAddress.port))
     87
     88    def test_mixed4to6(self):
     89        reactor = self.buildReactor()
     90
     91        addresses = {'server': None, 'client': None, 'cserver': None}
     92
     93        class SvProto(Protocol):
     94            def connectionMade(self):
     95                addresses['client'] = self.transport.getPeer()
     96                addresses['server'] = self.transport.getHost()
     97
     98        serverFactory = ServerFactory()
     99        serverFactory.protocol = SvProto
     100        host, sport = self._freePort()
     101        server = reactor.listenTCP(sport, serverFactory, interface='::')
     102
     103        class ClProto(Protocol):
     104            def makeConnection(self, transport):
     105                addresses['cserver'] = transport.getPeer()
     106                reactor.stop()
     107
     108        clientFactory = Stop(reactor)
     109        clientFactory.protocol = ClProto
     110
     111        host, cport = self._freePort()
     112        server = reactor.connectTCP('127.0.0.1', sport, clientFactory, bindAddress=('', cport))
     113
     114        reactor.run()
     115
     116        self.assertEqual(
     117            addresses['client'],
     118            IPv4Address('TCP', '::ffff:127.0.0.1', cport))
     119        self.assertEqual(
     120            addresses['cserver'],
     121            IPv4Address('TCP', '127.0.0.1', sport))
     122
     123
     124
     125class UDP6ServerTestsBuilder(ReactorBuilder):
     126    def _freePort(self, interface='::1'):
     127        probe = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
     128        try:
     129            probe.bind((interface, 0))
     130            ip, port, flow, scope = probe.getsockname()
     131            return (ip, port)
     132        finally:
     133            probe.close()
     134
     135    def test_interface(self):
     136        reactor = self.buildReactor()
     137        port = reactor.listenUDP(0, DatagramProtocol(), interface='::')
     138        self.assertTrue(verifyObject(IListeningPort, port))
     139
     140    def test_traffic(self):
     141        reactor = self.buildReactor()
     142
     143        dgs = []
     144        class Accum(DatagramProtocol):
     145            def datagramReceived(self, data, addr):
     146                dgs.append((addr, data))
     147                self.transport.write('r'+data, addr)
     148
     149        class Xmit(DatagramProtocol):
     150            def perform(self, addr, port):
     151                self.transport.write('msg', (addr, port))
     152            def datagramReceived(self, data, addr):
     153                dgs.append((addr, data))
     154                reactor.stop()
     155
     156        ip, lport = self._freePort()
     157        p = reactor.listenUDP(lport, Accum(), interface='::1')
     158       
     159        ip, xport = self._freePort()
     160        xmit = Xmit()
     161        p = reactor.listenUDP(xport, xmit, interface='::1')
     162
     163        reactor.callLater(0, xmit.perform, '::1', lport)
     164        reactor.callLater(10, reactor.stop)
     165        reactor.run()
     166
     167        self.assertEqual(
     168                dgs,
     169                [(('::1', xport), 'msg'), (('::1', lport), 'rmsg')]
     170                )
     171
     172    def test_mixed4to6(self):
     173        reactor = self.buildReactor()
     174
     175        dgs = []
     176        class Accum(DatagramProtocol):
     177            def datagramReceived(self, data, addr):
     178                dgs.append((addr, data))
     179                self.transport.write('r'+data, addr)
     180
     181        class Xmit(DatagramProtocol):
     182            def perform(self, addr, port):
     183                self.transport.write('msg', (addr, port))
     184            def datagramReceived(self, data, addr):
     185                dgs.append((addr, data))
     186                reactor.stop()
     187
     188        ip, lport = self._freePort()
     189        p = reactor.listenUDP(lport, Accum(), interface='::')
     190       
     191        ip, xport = self._freePort()
     192        xmit = Xmit()
     193        p = reactor.listenUDP(xport, xmit)
     194
     195        reactor.callLater(0, xmit.perform, '127.0.0.1', lport)
     196        reactor.callLater(10, reactor.stop)
     197        reactor.run()
     198
     199        self.assertEqual(
     200                dgs,
     201                [(('::ffff:127.0.0.1', xport), 'msg'), (('127.0.0.1', lport), 'rmsg')]
     202                )
     203
     204globals().update(v6TestsBuilder.makeTestCaseClasses())
     205globals().update(UDP6ServerTestsBuilder.makeTestCaseClasses())
  • twisted/internet/udp.py

    diff --git a/twisted/internet/udp.py b/twisted/internet/udp.py
    index 3a21453..c5672c0 100644
    a b class Port(base.BasePort): 
    6262        self.interface = interface
    6363        self.setLogStr()
    6464        self._connectedAddr = None
     65        if interface and abstract.isIPv6Address(interface):
     66            self.addressFamily = socket.AF_INET6
    6567
    6668    def __repr__(self):
    6769        if self._realPortNumber is not None:
    class Port(base.BasePort): 
    127129            else:
    128130                read += len(data)
    129131                try:
     132                    addr = (addr[0], addr[1])
    130133                    self.protocol.datagramReceived(data, addr)
    131134                except:
    132135                    log.err()
    class Port(base.BasePort): 
    160163                    raise
    161164        else:
    162165            assert addr != None
    163             if not addr[0].replace(".", "").isdigit() and addr[0] != "<broadcast>":
     166            if abstract.isIPAddress(addr[0]) or abstract.isIPv6Address(addr[0]):
     167                pass
     168            elif addr[0] != "<broadcast>":
    164169                warnings.warn("Please only pass IPs to write(), not hostnames",
    165170                              DeprecationWarning, stacklevel=2)
    166171            try:
    class Port(base.BasePort): 
    188193        """
    189194        if self._connectedAddr:
    190195            raise RuntimeError, "already connected, reconnecting is not currently supported (talk to itamar if you want this)"
    191         if not abstract.isIPAddress(host):
     196        if not abstract.isIPAddress(host) and not abstract.isIPv6Address(host):
    192197            raise ValueError, "please pass only IP addresses, not domain names"
    193198        self._connectedAddr = (host, port)
    194199        self.socket.connect((host, port))
    class Port(base.BasePort): 
    242247
    243248        This indicates the address from which I am connecting.
    244249        """
    245         return address.IPv4Address('UDP', *(self.socket.getsockname() + ('INET_UDP',)))
    246 
     250        addr = self.socket.getsockname()
     251        return address.IPv4Address('UDP', addr[0], addr[1], 'INET_UDP')
    247252
    248253
     254# FIXME: implement ipv6 multicast...
    249255class MulticastMixin:
    250256    """
    251257    Implement multicast functionality.