Ticket #3014: twisted-ipv6.patch

File twisted-ipv6.patch, 15.7 KB (added by philmayers, 4 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.