I'm using pure-ftpd 1.0.46 on Ubuntu 18.04 as the server, with TLS and certificates signed by Let's Encrypt. I have no issues connecting and transferring over WAN. I'm using passive mode, the ports are properly forwarded, and I've configured pure-ftpd to respond with its WAN IP.
However, I can not make a connection over LAN, as it complains about TLS connection termination. I've tried several clients and they all have some kind of timeout error. I suspect part of the problem may be my DNS setup. I have a domain name that points to my WAN, and that is what I usually use to connect to the server. But I have my local DNS server respond with the LAN IP for the ftp machine. So when I'm at home, my laptop makes the ftp connection to 192.168.1.2, but the PASV response is my WAN IP 74.x.x.x. In theory, my router should support NAT loopback, which sends traffic originating in the LAN and destined for the WAN IP back to the local IP to which its forwarded. This seems to work fine for http, and I assumed since the client initiates the passive mode data tcp connection, it should work for that as well. But maybe there's some routing detail I'm missing. Could this be the problem?
Client log:
Status: Connecting to 192.168.1.2:21...
Status: Connection established, waiting for welcome message...
Trace: CFtpControlSocket::OnReceive()
Response: 220---------- Welcome to Pure-FTPd [privsep] [TLS] ----------
Response: 220-You are user number 9 of 50 allowed.
Response: 220-Local time is now 16:01. Server port: 21.
Response: 220-This is a private system - No anonymous login
Response: 220-IPv6 connections are also welcome on this server.
Response: 220 You will be disconnected after 15 minutes of inactivity.
Trace: CFtpLogonOpData::ParseResponse() in state 1
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 2
Command: AUTH TLS
Trace: CFtpControlSocket::OnReceive()
Response: 234 AUTH TLS OK.
Trace: CFtpLogonOpData::ParseResponse() in state 2
Status: Initializing TLS...
Trace: CTlsSocketImpl::Handshake()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: TLS handshake: About to send CLIENT HELLO
Trace: TLS handshake: Sent CLIENT HELLO
Trace: CTlsSocketImpl::OnSend()
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: TLS handshake: Received SERVER HELLO
Trace: TLS handshake: Processed SERVER HELLO
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: TLS handshake: Received CERTIFICATE
Trace: TLS handshake: Processed CERTIFICATE
Trace: TLS handshake: Received SERVER KEY EXCHANGE
Trace: TLS handshake: Processed SERVER KEY EXCHANGE
Trace: TLS handshake: Received SERVER HELLO DONE
Trace: TLS handshake: Processed SERVER HELLO DONE
Trace: TLS handshake: About to send CLIENT KEY EXCHANGE
Trace: TLS handshake: Sent CLIENT KEY EXCHANGE
Trace: TLS handshake: About to send FINISHED
Trace: TLS handshake: Sent FINISHED
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: TLS handshake: Received NEW SESSION TICKET
Trace: TLS handshake: Processed NEW SESSION TICKET
Trace: TLS handshake: Received FINISHED
Trace: TLS handshake: Processed FINISHED
Trace: TLS Handshake successful
Trace: Protocol: TLS1.2, Key exchange: ECDHE-RSA, Cipher: AES-256-GCM, MAC: AEAD
Status: Verifying certificate...
Status: TLS connection established.
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 5
Command: USER *****
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 331 User ******* OK. Password required
Trace: CFtpLogonOpData::ParseResponse() in state 5
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 5
Command: PASS **********
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 230 OK. Current directory is /
Trace: CFtpLogonOpData::ParseResponse() in state 5
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 9
Command: OPTS UTF8 ON
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 200 OK, UTF-8 enabled
Trace: CFtpLogonOpData::ParseResponse() in state 9
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 10
Command: PBSZ 0
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 200 PBSZ=0
Trace: CFtpLogonOpData::ParseResponse() in state 10
Trace: CControlSocket::SendNextCommand()
Trace: CFtpLogonOpData::Send() in state 11
Command: PROT P
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 200 Data protection level set to "private"
Trace: CFtpLogonOpData::ParseResponse() in state 11
Status: Logged in
Trace: Measured latency of 3 ms
Trace: CFtpControlSocket::ResetOperation(0)
Trace: CControlSocket::ResetOperation(0)
Trace: CFtpLogonOpData::Reset(0) in state 14
Trace: CFileZillaEnginePrivate::ResetOperation(0)
Status: Retrieving directory listing...
Trace: CControlSocket::SendNextCommand()
Trace: CFtpListOpData::Send() in state 0
Trace: CFtpChangeDirOpData::Send() in state 0
Trace: CFtpChangeDirOpData::Send() in state 1
Command: PWD
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 257 "/" is your current location
Trace: CFtpChangeDirOpData::ParseResponse() in state 1
Trace: CFtpControlSocket::ResetOperation(0)
Trace: CControlSocket::ResetOperation(0)
Trace: CFtpChangeDirOpData::Reset(0) in state 1
Trace: CFtpListOpData::SubcommandResult(0) in state 1
Trace: CControlSocket::SendNextCommand()
Trace: CFtpListOpData::Send() in state 2
Trace: CFtpRawTransferOpData::Send() in state 1
Command: TYPE I
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 200 TYPE is now 8-bit binary
Trace: CFtpRawTransferOpData::ParseResponse() in state 1
Trace: CControlSocket::SendNextCommand()
Trace: CFtpRawTransferOpData::Send() in state 2
Command: PASV
Trace: CTlsSocketImpl::OnRead()
Trace: CFtpControlSocket::OnReceive()
Response: 227 Entering Passive Mode (75,85,*,*,46,161)
Trace: CFtpRawTransferOpData::ParseResponse() in state 2
Trace: CControlSocket::SendNextCommand()
Trace: CFtpRawTransferOpData::Send() in state 4
Trace: Destination IP of data connection does not match peer IP of control connection. Not binding source address of data connection.
Command: MLSD
Trace: CTransferSocket::OnConnect
Trace: CTlsSocketImpl::Handshake()
Trace: Trying to resume existing TLS session.
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: TLS handshake: About to send CLIENT HELLO
Trace: TLS handshake: Sent CLIENT HELLO
Trace: CTlsSocketImpl::OnSend()
Trace: CTlsSocketImpl::OnSend()
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: CTlsSocketImpl::OnRead()
Trace: CTlsSocketImpl::ContinueHandshake()
Trace: CTlsSocketImpl::Failure(-110)
Error: GnuTLS error -110: The TLS connection was non-properly terminated.
Status: Server did not properly shut down TLS connection
Trace: CTlsSocketImpl::OnSocketEvent(): close event received
Trace: CTransferSocket::OnClose(106)
Error: Transfer connection interrupted: ECONNABORTED - Connection aborted
Trace: CTransferSocket::TransferEnd(3)
Trace: CFtpControlSocket::TransferEnd()
Trace: CFtpControlSocket::ResetOperation(10)
Trace: CControlSocket::ResetOperation(10)
Trace: CFtpRawTransferOpData::Reset(10) in state 6
Trace: CFtpControlSocket::ResetOperation(10)
Trace: CControlSocket::ResetOperation(10)
Trace: CFtpListOpData::Reset(10) in state 3
Error: Directory listing aborted by user
Trace: CFileZillaEnginePrivate::ResetOperation(10)
Status: Disconnected from server
Trace: CRealControlSocket::DoClose(66)
Trace: CControlSocket::DoClose(66)
Trace: CFtpControlSocket::ResetOperation(66)
Trace: CControlSocket::ResetOperation(66)
Trace: CFileZillaEnginePrivate::ResetOperation(66)
Trace: CRealControlSocket::DoClose(66)
Trace: CControlSocket::DoClose(66)
Trace: CControlSocket::DoClose(66)
Trace: CFileZillaEnginePrivate::ResetOperation(0)