Bug 132522 - can't get directory listing when IPv6 link is up
Summary: can't get directory listing when IPv6 link is up
Status: RESOLVED FIXED
Alias: None
Product: kftpgrabber
Classification: Applications
Component: general (show other bugs)
Version: unspecified
Platform: openSUSE Linux
: NOR normal
Target Milestone: ---
Assignee: Jernej Kos
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2006-08-16 23:41 UTC by Éric Bischoff
Modified: 2006-08-18 14:41 UTC (History)
0 users

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Éric Bischoff 2006-08-16 23:41:57 UTC
Version:           0.7.99 (using KDE KDE 3.4.2)
Installed from:    SuSE RPMs
Compiler:          gcc 4.0.2 20050901 
OS:                Linux

When trying to connect from my machine (IPv4 address 213.41.243.33, IpV6 address 2001:7a8:b321::1) to ftp.u-strasbg.fr (2001:660:2402::6) as anonymous user in active FTP mode, the remote directory listing does not appear. Looking at the log window gives the explanation:

[21:28:31] PORT 2001:7a8:b321::1,12,229
[21:28:31] 501 Illegal PORT command

Then I shut down the IPv6 link with
   ip addr del 2001:7a8:b321::1 dev dsl0
and it goes much better:

[21:55:24] PORT 213,41,243,33,4,26
[21:55:24] 200 PORT command successful

I get the impression there is something wrong with syntax of PORT command with IPv6 addresses.

In the same situation (same hosts, I mean) all the other FTP clients seem to have no problem.
Comment 1 Éric Bischoff 2006-08-17 01:04:12 UTC
0.7.0-beta1 from the archive downloaded on the site has not the problem.

The problem only occurs with the version from SVN repository (numbered 0.7.99).

Both are compiled by me the same way.

That makes me think this bug has probably been introduced very recently...
Comment 2 Jernej Kos 2006-08-17 09:47:19 UTC
So if I understand you correctly, you are connecting to this server via it's IPv6 address ? If so, I will have to implement the commands needed to support IPv6 connections - I'll do that later today.
Comment 3 Éric Bischoff 2006-08-17 13:01:19 UTC
I didn't ask to use the IPv6 link ;-). Some software component chose to use the IPv6 connectivity instead of the IPv4 one, I don't know why.

I have just asked the host name "ftp.u-strasbg.fr" in kftpgrabber. This host name happens to have both A (IPv4 DNS) and AAAA (IPv6) DNS entries:

# dig -t AAAA ftp.u-strasbg.fr
(...)
;; ANSWER SECTION:
ftp.u-strasbg.fr.       3238    IN      CNAME   anubis.u-strasbg.fr.
anubis.u-strasbg.fr.    3238    IN      AAAA    2001:660:2402::6
(...)

# dig -t A ftp.u-strasbg.fr
(...)
;; ANSWER SECTION:
ftp.u-strasbg.fr.       3182    IN      CNAME   anubis.u-strasbg.fr.
anubis.u-strasbg.fr.    3182    IN      A       130.79.200.5
(...)

I don't know why it chose the AAAA record instead of the A one. Anyway, that makes kftpgrabber unusable, unless you think at shutting down your IPv6 connectivity.

I hope that helps.
Comment 4 Jernej Kos 2006-08-17 13:04:24 UTC
SVN commit 573853 by kostko:

Added support for EPRT.

CCBUG: 132522

 M  +44 -23    ftpsocket.cpp  
 M  +1 -1      ftpsocket.h  
 M  +1 -0      socket.cpp  


--- trunk/extragear/network/kftpgrabber/src/engine/ftpsocket.cpp #573852:573853
@@ -719,21 +719,43 @@
     {
       if (currentState == NegotiateActive) {
         if (!socket()->isResponse("2")) {
-          // Negotiation failed, reset since active is the last fallback
-          socket()->resetCommandClass(Failed);
+          if (socket()->getConfigInt("feat.eprt")) {
+            socket()->setConfig("feat.eprt", 0);
+          } else {
+            // Negotiation failed, reset since active is the last fallback
+            socket()->resetCommandClass(Failed);
+            return;
+          }
+        } else {
+          currentState = HaveConnection;
+          socket()->nextCommandAsync();
           return;
         }
-        
-        currentState = HaveConnection;
-        socket()->nextCommandAsync();
-      } else {
-        // Setup the socket and set the apropriate port command
-        currentState = NegotiateActive;
-        
-        QString address = socket()->setupActiveTransferSocket();
-        if (!address.isNull())
-          socket()->sendCommand("PORT " + address);
       }
+      
+      // Setup the socket and set the apropriate port command
+      currentState = NegotiateActive;
+      
+      KNetwork::KSocketAddress address = socket()->setupActiveTransferSocket();
+      if (address.address()) {
+        if (socket()->getConfigInt("feat.eprt")) {
+          QString ianaFamily = QString::number(address.ianaFamily());
+          
+          socket()->sendCommand("EPRT |" + ianaFamily + "|" + address.nodeName() + "|" + address.serviceName() + "|"); 
+        } else if (address.ianaFamily() == 1) {
+          QString format = address.nodeName().replace(".", ",");
+          
+          format.append(",");
+          format.append(QString::number((unsigned char) address.address()->sa_data[0]));
+          format.append(",");
+          format.append(QString::number((unsigned char) address.address()->sa_data[1]));
+          
+          socket()->sendCommand("PORT " + format);
+        } else {
+          socket()->emitEvent(Event::EventMessage, i18n("Incompatible address family for PORT, but EPRT not supported, aborting!"));
+          socket()->resetCommandClass(Failed);
+        }
+      }
     }
 };
 
@@ -773,7 +795,7 @@
   m_transferSocket->connect(realHost, QString::number(port));
 }
 
-QString FtpSocket::setupActiveTransferSocket()
+KNetwork::KSocketAddress FtpSocket::setupActiveTransferSocket()
 {
   if (!m_serverSocket)
     m_serverSocket = new KNetwork::KServerSocket();
@@ -800,7 +822,7 @@
     if (!found) {
       emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
       resetCommandClass(Failed);
-      return QString::null;
+      return KNetwork::KSocketAddress();
     }
   } else {
     m_serverSocket->setAddress("0");
@@ -808,13 +830,13 @@
     if (!m_serverSocket->listen()) {
       emitEvent(Event::EventMessage, i18n("Unable to establish a listening socket."));
       resetCommandClass(Failed);
-      return QString::null;
+      return KNetwork::KSocketAddress();
     }
   }
   
   KNetwork::KSocketAddress serverAddr = m_serverSocket->localAddress();
   KNetwork::KSocketAddress controlAddr = localAddress();
-  QString request;
+  KNetwork::KSocketAddress request;
   
   if (KFTPCore::Config::portForceIp()) {
     // Force a specified IP/hostname to be used in PORT
@@ -824,21 +846,20 @@
     if (resolverResults.error() < 0) {
       // Well, we are unable to resolve the name, so we should use what we got
       // from control socket
-      request.append(controlAddr.nodeName().replace(".", ","));
+      request = controlAddr;
     } else {
       // The name has been resolved and we have the address, so we should
       // use it
-      request.append(resolverResults[0].address().nodeName().replace(".", ","));
+      request = resolverResults[0].address();
     }
   } else {
     // Just use our IP we bound to when connecting to the remote server
-    request.append(controlAddr.nodeName().replace(".", ","));
+    request = controlAddr;
   }
   
-  request.append(",");
-  request.append(QString::number((unsigned char) serverAddr.address()->sa_data[0]));
-  request.append(",");
-  request.append(QString::number((unsigned char) serverAddr.address()->sa_data[1]));
+  // Set the proper port
+  request.address()->sa_data[0] = serverAddr.address()->sa_data[0];
+  request.address()->sa_data[1] = serverAddr.address()->sa_data[1];
   
   emitEvent(Event::EventMessage, i18n("Waiting for data connection on port %1...").arg(serverAddr.serviceName()));
   QObject::connect(m_serverSocket, SIGNAL(readyAccept()), this, SLOT(slotDataAccept()));
--- trunk/extragear/network/kftpgrabber/src/engine/ftpsocket.h #573852:573853
@@ -95,7 +95,7 @@
     void resetCommandClass(ResetCode code = Ok);
     
     void setupPassiveTransferSocket(const QString &host, int port);
-    QString setupActiveTransferSocket();
+    KNetwork::KSocketAddress setupActiveTransferSocket();
     
     QFile *getTransferFile() { return &m_transferFile; }
     void checkTransferEnd();
--- trunk/extragear/network/kftpgrabber/src/engine/socket.cpp #573852:573853
@@ -68,6 +68,7 @@
   
   // Fill in some default values
   setConfig("feat.epsv", 1);
+  setConfig("feat.eprt", 1);
   setConfig("feat.pasv", 1);
   setConfig("feat.size", 1);
   setConfig("ssl.prot_mode", 'C');
Comment 5 Éric Bischoff 2006-08-17 14:37:20 UTC
It works for me:

[13:27:18] EPRT |2|2001:7a8:b321::1|3397|
[13:27:18] 200 EPRT command successful

Thank you very much for the EPRT support.

I still can't get anything from SVN version to go through the data connection, neither in active nor in passive mode. That's probably a second, independant, bug, and this second bug is probably linked to IPv6 too.
Comment 6 Éric Bischoff 2006-08-17 15:41:16 UTC
Nope, that's not linked to IPv6, it happens with IPv4 too.

That's probably an inter-thread communication problem. In kftpgrabber/src/engine/thread.cpp, method Thread::run(),
    // Deliver internal events while any
    while (m_internalEvents.count() > 0) {
count() returns 0 even though an event has been queued by the method Thread::queueInternalEvent().
Comment 7 Jernej Kos 2006-08-18 14:41:30 UTC
SVN commit 574216 by kostko:

Removed internal thread events and used manual polling instead (at least until ported to Qt 4). Better handling of transfer socket closures.

BUG: 132522

 M  +40 -41    ftpsocket.cpp  
 M  +2 -2      ftpsocket.h  


--- trunk/extragear/network/kftpgrabber/src/engine/ftpsocket.cpp #574215:574216
@@ -87,10 +87,32 @@
   slotControlTryRead();
 
   if (m_transferSocket) {
-    if (getCurrentCommand() == Commands::CmdPut)
-      slotDataTryWrite();
-    else
-      slotDataTryRead();
+    if (m_transferConnecting && m_transferSocket->state() == Connected) {
+      m_transferConnecting = false;
+      slotDataConnected();
+    } else if (!m_transferConnecting) {
+      if (getCurrentCommand() == Commands::CmdPut)
+        slotDataTryWrite();
+      else {
+        bool input;
+        m_transferSocket->socketDevice()->poll(&input, 0, 0, 0);
+        
+        if (input)
+          slotDataTryRead();
+      }
+    }
+  } else if (m_serverSocket) {
+    bool input;
+    m_serverSocket->socketDevice()->poll(&input, 0, 0, 0);
+    
+    if (input) {
+      KNetwork::KActiveSocketBase *socket = m_serverSocket->accept();
+      
+      if (socket) {
+        slotDataAccept(static_cast<KNetwork::KStreamSocket*>(socket));
+        m_transferConnecting = false;
+      }
+    }
   }
   
   // Check for timeouts
@@ -200,9 +222,6 @@
   timeoutWait(false);
   
   if (m_transferSocket && code != Ok) {
-    QObject::disconnect(m_transferSocket, SIGNAL(connected(const KResolverEntry&)), this, SLOT(slotDataConnected()));
-    QObject::disconnect(m_transferSocket, SIGNAL(gotError(int)), this, SLOT(slotDataError()));
-  
     // Invalidate the socket
     closeDataTransferSocket();
     
@@ -761,9 +780,7 @@
 
 void FtpSocket::initializeTransferSocket()
 {
-  QObject::connect(m_transferSocket, SIGNAL(connected(const KResolverEntry&)), this, SLOT(slotDataConnected()));
-  QObject::connect(m_transferSocket, SIGNAL(gotError(int)), this, SLOT(slotDataError()));
-  
+  m_transferConnecting = true;
   m_transferEnd = 0;
   m_transferBytes = 0;
   m_transferBufferSize = 4096;
@@ -862,19 +879,13 @@
   request.address()->sa_data[1] = serverAddr.address()->sa_data[1];
   
   emitEvent(Event::EventMessage, i18n("Waiting for data connection on port %1...").arg(serverAddr.serviceName()));
-  QObject::connect(m_serverSocket, SIGNAL(readyAccept()), this, SLOT(slotDataAccept()));
   
   return request;
 }
 
-void FtpSocket::slotDataAccept()
+void FtpSocket::slotDataAccept(KNetwork::KStreamSocket *socket)
 {
-  ENGINE_ENSURE_THREAD(slotDataAccept());
-  
-  if (!m_serverSocket)
-    return;
-  
-  m_transferSocket = static_cast<KNetwork::KStreamSocket*>(m_serverSocket->accept());
+  m_transferSocket = socket;
   initializeTransferSocket();
   
   // Socket has been accepted so the server is not needed anymore
@@ -903,9 +914,6 @@
 
 void FtpSocket::closeDataTransferSocket()
 {
-  QObject::disconnect(m_transferSocket, SIGNAL(connected(const KResolverEntry&)), this, SLOT(slotDataConnected()));
-  QObject::disconnect(m_transferSocket, SIGNAL(gotError(int)), this, SLOT(slotDataError()));
-    
   if (m_dataSsl) {
     m_dataSsl->close();
     delete m_dataSsl;
@@ -936,29 +944,10 @@
 
 void FtpSocket::slotDataConnected()
 {
-  ENGINE_ENSURE_THREAD(slotDataConnected());
-
   emitEvent(Event::EventMessage, i18n("Data connection established."));
   nextCommand();
 }
 
-void FtpSocket::slotDataError()
-{
-  ENGINE_ENSURE_THREAD(slotDataError());
-
-  if (!m_transferSocket)
-    return;
-    
-  if (isFatalError(m_transferSocket->error())) {
-    emitEvent(Event::EventMessage, i18n("Data connection failed (%1).").arg(errorString(m_transferSocket->error())));
-    
-    // We received an error, reset with failure
-    resetCommandClass(Failed);
-  } else if (m_transferSocket->error() == RemotelyDisconnected) {
-    transferCompleted();
-  }
-}
-
 void FtpSocket::variableBufferUpdate(Q_LONG size)
 {
   if (size > m_transferBufferSize - 64) {
@@ -1051,8 +1040,18 @@
   } else
     size = m_transferSocket->readBlock(m_transferBuffer, m_transferBufferSize);
   
-  if (size <= 0)
+  // Check if the connection has been closed
+  if (m_transferSocket->error() != NoError) {
+    if (m_transferSocket->error() != WouldBlock) {
+      transferCompleted();
+      return;
+    }
+  }
+  
+  if (size <= 0) {
+    transferCompleted();
     return;
+  }
     
   timeoutPing();
 
--- trunk/extragear/network/kftpgrabber/src/engine/ftpsocket.h #574215:574216
@@ -120,6 +120,7 @@
     char *m_transferBuffer;
     int m_transferBufferSize;
     int m_transferEnd;
+    bool m_transferConnecting;
     
     KSSL *m_controlSsl;
     KSSL *m_dataSsl;
@@ -129,9 +130,8 @@
     void slotControlTryRead();
     void slotError();
     
-    void slotDataAccept();
+    void slotDataAccept(KNetwork::KStreamSocket *socket);
     void slotDataConnected();
-    void slotDataError();
     void slotDataTryRead();
     void slotDataTryWrite();
 };