basic-ftp icon indicating copy to clipboard operation
basic-ftp copied to clipboard

feat: Add flag to force use of local control host address

Open julianosk opened this issue 1 year ago • 1 comments

The assumption in the enterPassiveModeIPv4 function (in src/transfer.ts:48) is that NAT is only used if the "control connection" uses a public IP address and the "data connection" uses a private IP address.

However, in our particular scenario this is not the case. Both the control connection and the data connection use an private IP address but we are using NAT. The correct behaviour in this case would be to ignore the IP address mentioned in the PASV-response and instead use the control IP address.

To ensure we don't change current behaviour, we would like to introduce a configuration option that always uses the IP address of the control connection, regardless of whether the IP address is public or private. This configuration flag, called alwaysUseControlHost, is passed in the contructor and defaults to false.

julianosk avatar Nov 12 '24 15:11 julianosk

funny, I was just preparing a PR for the same problem but a slightly different approach borrowed from lftp. I need this fixed to talk to Bambu printers over a NAT'd link.

/**
 * Prepare a data socket using passive mode over IPv4.
 */
export async function enterPassiveModeIPv4(ftp: FTPContext): Promise<FTPResponse> {
    const res = await ftp.request("PASV")
    const target = parsePasvResponse(res.message)
    if (!target) {
        throw new Error("Can't parse PASV response: " + res.message)
    }
    const controlHost = ftp.socket.remoteAddress
    // PASV over NAT'd connections results in invalid detected target host
    // Borrowing solution from lftp which simply falls back to the provided host
    if (target.host === '0.0.0.0' && controlHost) {
        ftp.log(`Invalid PASV host detected ${target.host}. Falling back to control host.`)
        target.host = controlHost
    }
    // If the host in the PASV response has a local address while the control connection hasn't,
    // we assume a NAT issue and use the IP of the control connection as the target for the data connection.
    // We can't always perform this replacement because it's possible (although unlikely) that the FTP server
    // indeed uses a different host for data connections.
    if (ipIsPrivateV4Address(target.host) && controlHost && !ipIsPrivateV4Address(controlHost)) {
        target.host = controlHost
    }
    await connectForPassiveTransfer(target.host, target.port, ftp)
    return res
}

stewartoallen avatar Jan 29 '25 15:01 stewartoallen