SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีแก้ปัญหา FTP ค้างตอนแสดงรายการไฟล์หลังผ่าน Firewall

ปัญหา FTP ล็อกอินผ่านแต่ค้างตอนดึงรายการไฟล์เกิดจาก Passive Mode ต้องการพอร์ตข้อมูลเพิ่ม เรียนรู้วิธีตั้งค่าช่วงพอร์ต Passive และเพิ่มกฎ Firewall ให้ถูกต้องเพื่อแก้ไขข้อผิดพลาดนี้

ทำไม FTP ล็อกอินได้แต่ค้างตอนแสดงรายการไดเรกทอรี

FTP passive mode ทำงานไม่ได้เมื่อผ่านไฟร์วอลล์ เพราะ FTP ใช้การเชื่อมต่อ TCP สองชุดไม่ใช่ชุดเดียว การเชื่อมต่อที่พอร์ต 21 ทำหน้าที่ส่งข้อมูลล็อกอินและคำสั่ง ดังนั้นชื่อผู้ใช้และรหัสผ่านจึงผ่านการตรวจสอบและดูเหมือนว่าไฟร์วอลล์ทำงานถูกต้องแล้ว จากนั้น ls ชุดแรกจำเป็นต้องมีการเชื่อมต่อชุดที่สองผ่านพอร์ตอื่น แต่ไม่มีกฎในไฟร์วอลล์ที่อนุญาตการเชื่อมต่อดังกล่าว ไคลเอนต์จึงค้างอยู่จนกว่าจะหมดเวลา (timeout)

วิธีแก้ไขคือการกำหนดช่วงพอร์ตที่แน่นอนสำหรับการเชื่อมต่อข้อมูลเหล่านั้น พร้อมกับเพิ่มกฎไฟร์วอลล์ที่อนุญาตช่วงพอร์ตดังกล่าว เซิร์ฟเวอร์ที่อยู่หลัง NAT (network address translation) จำเป็นต้องตั้งค่าเพิ่มเติมอีกหนึ่งอย่างเพื่อให้เซิร์ฟเวอร์ประกาศที่อยู่ IP ที่ถูกต้อง ในอดีต connection tracking helper จะทำหน้าที่นี้ให้โดยอัตโนมัติ แต่ปัจจุบันไม่เป็นเช่นนั้นแล้ว และเหตุผลที่เปลี่ยนไปนั้นเป็นสิ่งที่ควรทราบก่อนที่จะคัดลอกคำแนะนำเก่าๆ มาใช้งาน

ช่องทางควบคุมและช่องทางข้อมูล

FTP (file transfer protocol) ถูกกำหนดไว้ใน RFC 959 ซึ่งเกิดขึ้นก่อนยุคของ NAT และ stateful firewall การเชื่อมต่อจะเปิดช่องทางควบคุม (control connection) หนึ่งช่องทางไปยัง TCP port 21 และคงสถานะเปิดไว้ตลอดทั้งเซสชัน คำสั่งต่างๆ จะถูกส่งขึ้นไปในรูปแบบข้อความธรรมดา (plain text) และได้รับคำตอบกลับมาเป็นรหัสตัวเลข 3 หลักพร้อมข้อความ การเชื่อมต่อนี้จะไม่ใช้ในการรับส่งเนื้อหาของไฟล์

ข้อมูลทุกส่วนจะใช้การเชื่อมต่อ TCP แยกต่างหาก ได้แก่ หนึ่งช่องทางสำหรับการแสดงรายการไดเรกทอรี (LIST), หนึ่งช่องทางสำหรับการดาวน์โหลดแต่ละครั้ง (RETR) และหนึ่งช่องทางสำหรับการอัปโหลดแต่ละครั้ง (STOR) การเชื่อมต่อเหล่านี้จะถูกเปิดขึ้น ใช้งานเพียงครั้งเดียวแล้วปิดลง การยืนยันตัวตนจะเกิดขึ้นบนช่องทางควบคุมทั้งหมด ดังนั้นหากเส้นทางข้อมูลมีปัญหา อาการที่ปรากฏจะเหมือนกันเสมอ คือล็อกอินสำเร็จแต่ค้าง หากไคลเอนต์แสดงคำตอบ 230 แล้วหยุดนิ่งที่ขั้นตอนการแสดงรายการ แสดงว่าปัญหาอยู่ที่ช่องทางข้อมูล ไม่ใช่ที่ข้อมูลประจำตัว (credentials)

โหมด Active: เซิร์ฟเวอร์เชื่อมต่อกลับไปยังไคลเอนต์

ในโหมด Active ไคลเอนต์จะเลือกพอร์ตหนึ่งพอร์ต เปิดรอรับการเชื่อมต่อ และแจ้งให้เซิร์ฟเวอร์ทราบว่าจะให้เชื่อมต่อเข้ามาที่ใด:

PORT 192,168,1,50,195,80

ตัวเลข 4 ชุดแรกคือที่อยู่ IP ของไคลเอนต์ ส่วน 2 ชุดสุดท้ายคือพอร์ตที่ถูกเข้ารหัสไว้เป็น 2 ไบต์: 195 * 256 + 80 = 50000 จากนั้นเซิร์ฟเวอร์จะเปิดการเชื่อมต่อข้อมูล จาก พอร์ต 20 ของตนเอง ไปยัง พอร์ต 50000 บนไคลเอนต์

การเชื่อมต่อดังกล่าวถือเป็นขาเข้าและไม่ได้ถูกร้องขอจากมุมมองของไคลเอนต์ ไฟร์วอลล์ของไคลเอนต์จึงปฏิเสธการเชื่อมต่อนี้ หากไคลเอนต์อยู่หลังเราเตอร์ตามบ้าน ที่อยู่ในคำสั่ง PORT จะเป็นที่อยู่ภายใน (private IP) ซึ่งเซิร์ฟเวอร์ไม่สามารถเข้าถึงได้เลย โหมด Active คือสาเหตุที่ทำให้ FTP มีชื่อเสียงในด้านการใช้งานไม่ได้ผล

Passive mode: ไคลเอนต์เป็นผู้เปิดการเชื่อมต่อทั้งสองฝั่ง

Passive mode จะสลับทิศทางการเชื่อมต่อข้อมูล โดยไคลเอนต์จะส่งคำสั่ง PASV และเซิร์ฟเวอร์จะตอบกลับด้วยที่อยู่ IP และพอร์ตของเซิร์ฟเวอร์เอง:

227 Entering Passive Mode (203,0,113,10,195,80)

ด้วยการเข้ารหัสรูปแบบเดียวกัน ไคลเอนต์จึงเชื่อมต่อไปยัง 203.0.113.10 ที่พอร์ต 50000 ในตอนนี้ไคลเอนต์จะเป็นผู้เปิดการเชื่อมต่อทั้งสองฝั่ง ซึ่งเป็นเหตุผลว่าทำไม passive mode จึงสามารถใช้งานผ่าน NAT ฝั่งไคลเอนต์ได้ และทำไมไคลเอนต์ในปัจจุบันทุกตัวจึงร้องขอโหมดนี้เป็นอันดับแรก

ปัญหาไม่ได้หายไป แต่เพียงแค่ย้ายฝั่งเท่านั้น การเชื่อมต่อขาเข้าที่ไม่ได้ร้องขอจะมาถึงเซิร์ฟเวอร์ของคุณผ่านพอร์ตสูงที่เปลี่ยนไปทุกครั้งที่มีการถ่ายโอนข้อมูล เนื่องจากไฟร์วอลล์นั้นเป็นของคุณ ปัญหานี้จึงกลายเป็นปัญหาของคุณเช่นกัน

EPSV (extended passive mode, RFC 2428) ใช้แนวคิดเดียวกันแต่มีการตอบกลับที่สะอาดกว่า:

229 Entering Extended Passive Mode (|||50000|)

ในการตอบกลับนี้ไม่มีที่อยู่ IP ระบุอยู่ ไคลเอนต์จะนำที่อยู่ IP ที่ใช้สำหรับการเชื่อมต่อควบคุม (control connection) มาใช้ซ้ำ ซึ่งเป็นสิ่งที่ทำให้โหมดนี้ทำงานบน IPv6 ได้และกำจัดข้อผิดพลาดประเภท NAT ไปได้ทั้งหมดหนึ่งกลุ่ม คู่มือของ curl ระบุว่าโดยปกติแล้ว curl จะพยายามใช้ EPSV ก่อน PASV เนื่องจากพอร์ตยังคงถูกเลือกในขณะรันไทม์ EPSV จึงไม่ได้เปลี่ยนแปลงกฎไฟร์วอลล์ของคุณแต่อย่างใด

เหตุใดกฎ firewall ปกติจึงไม่อนุญาตให้ช่องทางข้อมูลผ่านได้

เนื่องจากหมายเลขพอร์ตยังไม่ถูกกำหนดในขณะที่คุณเขียนกฎ เซิร์ฟเวอร์จะสุ่มเลือกพอร์ตสำหรับการถ่ายโอนแต่ละครั้ง หากใช้ค่าเริ่มต้นตามที่ pasv_min_port และ pasv_max_port ระบุไว้ในเอกสารของ vsftpd ว่าเป็น 0 ซึ่งหมายถึง "ใช้พอร์ตใดก็ได้" การเชื่อมต่อข้อมูลจึงสามารถเข้ามาทางพอร์ตใดก็ได้ที่สูงกว่า 1023 กฎ sudo ufw allow 21/tcp จะอนุญาตเฉพาะช่องทางควบคุมเท่านั้น ซึ่งเป็นสาเหตุที่ทำให้การล็อกอินสำเร็จแต่ไม่สามารถแสดงรายการไฟล์ได้ หากแนวคิดเรื่องบริการที่รอรับการเชื่อมต่อบนพอร์ตคงที่ยังไม่ชัดเจน คุณสามารถอ่านพื้นฐานเพิ่มเติมได้ที่ วิธีการทำงานของพอร์ตและ listening socket บน Linux

Firewall แบบ stateful สามารถติดตามสถานะการเชื่อมต่อได้ และ kernel สามารถอนุญาตให้การเชื่อมต่อใหม่เข้ามาในฐานะ RELATED ของการเชื่อมต่อเดิมที่มีอยู่แล้ว แต่สำหรับ FTP จำเป็นต้องมีกลไกในการอ่านกระแสข้อมูลควบคุมเพื่อดึงหมายเลขพอร์ตออกมาจากบรรทัด 227 หรือ PORT ซึ่งโดยปกติแล้วจะไม่มีการตั้งค่านี้ไว้เป็นค่าเริ่มต้น

เหตุผลที่ FTP connection tracking helper ไม่ใช่คำตอบอีกต่อไป

Kernel module nf_conntrack_ftp คือสิ่งที่คู่มือรุ่นเก่าแนะนำให้คุณใช้ มันทำหน้าที่อ่าน control channel ที่เป็นข้อความธรรมดา ค้นหาพอร์ตที่ประกาศไว้ และลงทะเบียนความคาดหมาย (expectation) เพื่อให้การเชื่อมต่อข้อมูล (data connection) ได้รับอนุญาตโดยไม่ต้องมีกฎระบุพอร์ตนั้นโดยเฉพาะ แต่มี 4 สิ่งที่เปลี่ยนแปลงไปนับตั้งแต่คู่มือเหล่านั้นถูกเขียนขึ้น

การกำหนด helper อัตโนมัติถูกปิดใช้งาน และใน kernel ปัจจุบันไม่สามารถเปิดใช้งานกลับมาได้อีก Kernel ได้ปิดฟีเจอร์นี้เป็นค่าเริ่มต้นตั้งแต่ปี 2016 ต่อมา commit ของ Pablo Neira Ayuso เรื่อง "netfilter: remove nf_conntrack_helper sysctl and modparam toggles" (สิงหาคม 2022) ได้ลบสวิตช์ดังกล่าวออกไปโดยสิ้นเชิง โดยระบุว่า "ผู้ใช้จำเป็นต้องพึ่งพาการกำหนดค่า conntrack helper ผ่าน ruleset โดยตรง" ค่า sysctl nf_conntrack_helper ยังคงมีอยู่ใน Linux 5.19 แต่ถูกลบออกตั้งแต่ Linux 6.0 เป็นต้นไป และเอกสารประกอบ sysctl ของ conntrack ใน kernel ก็ไม่ได้ระบุถึงค่านี้อีกต่อไป การโหลด module เพียงอย่างเดียวจึงไม่ส่งผลใดๆ

คุณสามารถตรวจสอบประเภทของ kernel ที่คุณใช้งานได้โดยรัน sysctl net.netfilter.nf_conntrack_helper หากได้รับคำตอบเป็น sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_helper: No such file or directory หมายความว่า kernel ของคุณไม่มีการกำหนด helper อัตโนมัติเหลือให้เปิดใช้งานอีกแล้ว หากคุณได้รับตัวเลขกลับมา แสดงว่าคุณกำลังใช้ kernel ที่เก่ากว่า 6.0 ซึ่งสวิตช์ดังกล่าวยังคงมีอยู่และมีค่าเริ่มต้นเป็น 0

เครื่องมือจัดการ firewall ต่างๆ ก็ได้เลิกสนับสนุนฟีเจอร์นี้เช่นกัน man ufw-framework บน Ubuntu 24.04 ได้ระบุถึงบรรทัด IPT_MODULES ใน /etc/default/ufw ว่า: "การโหลด module connection tracking (nf_conntrack_*) ในลักษณะนี้ถือว่าเลิกใช้แล้ว (deprecated)" และเสริมว่ากฎของ helper "ต้องจัดการผ่านไฟล์กฎ (RULES FILES)" ส่วน firewalld ได้ระบุถึง AutomaticHelpers ใน firewalld.conf ว่า "เลิกใช้แล้ว (Deprecated) ตัวเลือกนี้จะถูกละเว้นและไม่มีการใช้งานอีกต่อไป" การแนบ helper ในปัจจุบันหมายถึงการเขียนกฎโดยตรงด้วย target CT ซึ่งยุ่งยากกว่าวิธีแก้ไขด้านล่างและจะหยุดทำงานทันทีที่คุณเปิดใช้งาน TLS บทความ iptables and nftables on Ubuntu อธิบายถึงตำแหน่งที่กฎเหล่านี้ถูกจัดเก็บไว้จริง

TLS เป็นจุดสิ้นสุดของประเด็นนี้ Helper ทำงานโดยการอ่าน control channel ในรูปแบบข้อความ หากคุณเข้ารหัสช่องทางดังกล่าว Helper จะเห็นเพียง ciphertext ทำให้ไม่สามารถค้นหาพอร์ตได้ และไม่มีวิธีแก้ไขสำหรับเรื่องนี้ ซึ่งเป็นสิ่งที่ควรจะเป็น เพราะ middlebox ที่สามารถอ่าน control channel ของคุณได้ ก็คือ middlebox ที่สามารถอ่านรหัสผ่านของคุณได้เช่นกัน

การกำหนดช่วงพอร์ตแบบ passive บนเซิร์ฟเวอร์

เซิร์ฟเวอร์ FTP ทุกตัวสามารถตั้งค่าให้เลือกพอร์ตแบบ passive จากช่วงที่คุณกำหนดได้ ชื่อตัวเลือกจะแตกต่างกันไปตามซอฟต์แวร์ ดังนั้นโปรดตรวจสอบเอกสารประกอบของเซิร์ฟเวอร์ที่คุณใช้งานจริง

สำหรับ vsftpd ใน /etc/vsftpd.conf:

pasv_enable=YES
pasv_min_port=30000
pasv_max_port=30099

pasv_enable มีค่าเริ่มต้นเป็น YES อยู่แล้ว ตัวเลือกพอร์ตทั้งสองมีค่าเริ่มต้นเป็น 0 ซึ่งเป็นพฤติกรรม "ใช้พอร์ตใดก็ได้" ตามที่อธิบายไว้ข้างต้น ให้ใช้การตั้งค่าด้วย sudo systemctl restart vsftpd จากนั้นตรวจสอบว่าบริการกลับมาทำงานปกติด้วย systemctl status vsftpd โดย vsftpd จะปฏิเสธการเริ่มทำงานหากพบการตั้งค่าที่อ่านไม่เข้าใจ แทนที่จะเพิกเฉยต่อบรรทัดนั้น ดังนั้นหากการรีสตาร์ทล้มเหลว ให้ตรวจสอบ journalctl -u vsftpd -n 20 เพื่อดูบรรทัด 500 OOPS: ที่ระบุตัวเลือกที่คุณเพิ่งพิมพ์ลงไป

สำหรับ ProFTPD ใน proftpd.conf:

PassivePorts 30000 30099

ProFTPD ไม่ได้ระบุค่าเริ่มต้นไว้ในส่วนนี้ หากไม่มีคำสั่งดังกล่าว เคอร์เนลจะเป็นผู้เลือกพอร์ตให้ เอกสารประกอบระบุว่าเมื่อไม่มีพอร์ตว่างในช่วงที่คุณกำหนด เซิร์ฟเวอร์จะเปลี่ยนไปใช้พอร์ตที่เคอร์เนลจัดสรรให้และบันทึกข้อความลงใน log ดังนั้นช่วงพอร์ตที่แคบเกินไปจะทำให้เกิดความล้มเหลวเป็นครั้งคราวแทนที่จะล้มเหลวอย่างชัดเจน ซึ่งจะวินิจฉัยปัญหาได้ยากกว่ามาก ควรใช้พอร์ตที่ไม่มีสิทธิ์พิเศษ (non-privileged ports) ตั้งแต่ 1024 ขึ้นไป

Pure-FTPd ใช้แฟล็ก -p first:last ซึ่งระบุไว้ใน man pure-ftpd ว่า "ใช้เฉพาะพอร์ตในช่วงที่กำหนดตั้งแต่พอร์ตแรกถึงพอร์ตสุดท้ายสำหรับการดาวน์โหลดในโหมด passive" ซึ่ง "ช่วยให้ pure-ftpd ทำงานร่วมกับ packet filter ได้ดียิ่งขึ้น" โดยปกติแล้วแพ็กเกจที่ติดตั้งมาพร้อมกับระบบมักจะรวมแฟล็กนี้ไว้ในไฟล์คอนฟิก ดังนั้นให้ตรวจสอบเอกสารของดิสทริบิวชันที่คุณใช้เพื่อหาชื่อไฟล์ แทนการเดาชื่อไฟล์เอง

คุณต้องใช้พอร์ตจำนวนเท่าใด? คำตอบคือหนึ่งพอร์ตต่อหนึ่งการเชื่อมต่อข้อมูลที่กำลังทำงานอยู่ พอร์ต TCP ที่ปิดไปแล้วจะยังคงอยู่ในสถานะ TIME_WAIT เป็นเวลาสองสามนาทีก่อนที่จะนำกลับมาใช้ใหม่ได้ ดังนั้นควรเผื่อจำนวนพอร์ตไว้มากกว่าช่วงที่มีการใช้งานสูงสุดที่คุณคาดการณ์ไว้หลายเท่า จำนวน 100 พอร์ตถือว่าเพียงพอสำหรับผู้ใช้จำนวนน้อย แต่เซิร์ฟเวอร์สาธารณะที่มีการใช้งานหนาแน่นต้องการพอร์ตมากกว่านั้นมาก vsftpd มีความยืดหยุ่นน้อยกว่า ProFTPD ในเรื่องนี้ เนื่องจากซอร์สโค้ดจะพยายามสุ่มพอร์ตภายในช่วงที่กำหนด หากพอร์ตทั้งหมดถูกใช้งานอยู่ เซิร์ฟเวอร์จะยุติเซสชันด้วย 500 OOPS: vsf_sysutil_bind แทนที่จะยืมพอร์ตภายนอกช่วงที่กำหนดมาใช้ ไม่ว่าจะเป็นเซิร์ฟเวอร์ตัวใด ช่วงพอร์ตที่น้อยเกินไปจะทำให้การรับส่งข้อมูลล้มเหลวในช่วงเวลาที่คุณมีผู้ใช้งานหนาแน่นที่สุด

ช่วงพอร์ตควรอยู่ที่ใด? ให้รันคำสั่ง sysctl net.ipv4.ip_local_port_range ก่อน บน Ubuntu ค่าเริ่มต้นจะแสดงเป็น 32768 60999 ซึ่งเป็นช่วงพอร์ตที่เคอร์เนลจัดสรรสำหรับการเชื่อมต่อขาออก หากช่วงพอร์ตแบบ passive อยู่ภายในช่วงนี้ อาจเกิดการชนกันกับการเชื่อมต่อขาออกที่กำลังใช้งานพอร์ตนั้นอยู่ ดังนั้นควรตั้งค่าช่วงพอร์ตให้อยู่ต่ำกว่าช่วงดังกล่าว ช่วง 30000 ถึง 30099 ถือว่าปลอดภัยสำหรับระบบมาตรฐานทั่วไป แต่ควรตรวจสอบระบบของคุณเองแทนที่จะเชื่อตัวเลขนี้เพียงอย่างเดียว

เปิดช่วงพอร์ตเดียวกันในไฟร์วอลล์

ufw ใช้เครื่องหมายทวิภาค (colon) ในการระบุช่วงพอร์ต และคู่มือระบุว่าการใช้ช่วงหรือรายการ "อาจใช้เพื่อระบุพอร์ตหลายพอร์ตได้ แต่ในกรณีนี้จำเป็นต้องระบุโปรโตคอลด้วย":

sudo ufw allow 21/tcp
sudo ufw allow 30000:30099/tcp
sudo ufw status verbose

ufw status verbose ควรแสดงรายการทั้งสองรายการ หากใช้คำสั่งเดียวกันโดยไม่มี /tcp ระบบจะปฏิเสธและแจ้งข้อผิดพลาดให้คุณระบุ tcp หรือ udp เนื่องจาก ufw จะไม่คาดเดาเอง ไวยากรณ์กฎของ ufw บน VPS ครอบคลุมรายละเอียดส่วนที่เหลือ

firewalld ใช้เครื่องหมายยัติภังค์ (hyphen) ในการระบุช่วงและจำเป็นต้องโหลดการตั้งค่าใหม่:

sudo firewall-cmd --permanent --add-service=ftp
sudo firewall-cmd --permanent --add-port=30000-30099/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

--add-service=ftp จะเปิดพอร์ต 21/tcp และเรียกใช้ ftp helper ตามที่นิยามของบริการที่ติดตั้งมาได้ระบุไว้ แต่มันไม่ได้เปิดช่วงพอร์ต passive ให้คุณ ดังนั้นการทำเพียงเท่านี้จึงไม่ช่วยแก้ปัญหา โซนและบริการของ firewalld บน VPS มีข้อมูลในภาพรวมที่กว้างกว่า

สำหรับ nftables โดยตรง ภายใน input chain ของคุณ:

tcp dport { 21, 30000-30099 } accept

อีกหนึ่งไฟร์วอลล์ที่ต้องคำนึงถึงคือ ผู้ให้บริการส่วนใหญ่มีไฟร์วอลล์เครือข่ายในแผงควบคุม (control panel) ซึ่งอยู่นอกเหนือระบบปฏิบัติการ หากกฎบนเซิร์ฟเวอร์ดูถูกต้องแล้วแต่แพ็กเก็ตยังคงมาไม่ถึง ให้เปิดช่วงพอร์ตเดียวกันในนั้นด้วย

การกำหนดที่อยู่สาธารณะให้เซิร์ฟเวอร์เมื่ออยู่หลัง NAT

ให้รันคำสั่ง ip -4 addr show บนเซิร์ฟเวอร์ หากที่อยู่บนอินเทอร์เฟซเป็นที่อยู่ที่ไคลเอนต์ใช้เชื่อมต่อ ให้ข้ามส่วนนี้ไป แต่หากอินเทอร์เฟซมีที่อยู่เป็น private address (10.x, 172.16 ถึง 172.31.x, 192.168.x) และแพลตฟอร์มทำการแมปที่อยู่สาธารณะเข้ามา เซิร์ฟเวอร์จะไม่ทราบที่อยู่สาธารณะของตนเอง เอกสารของ vsftpd ระบุค่าเริ่มต้นของ pasv_address ว่า "ที่อยู่จะถูกดึงมาจาก socket ที่เชื่อมต่อเข้ามา" ดังนั้นการตอบกลับของ 227 จึงส่งที่อยู่ private address ไปให้ และไคลเอนต์จะถูกส่งไปยังจุดที่เข้าถึงไม่ได้

FileZilla ระบุชื่อการตั้งค่านี้ไว้อย่างชัดเจน:

Server sent passive reply with unroutable address. Using server address instead.

FileZilla จะแก้ไขปัญหานี้และทำงานต่อได้ แต่ไคลเอนต์อื่นอีกหลายตัวไม่สามารถทำได้ พวกมันจะพยายามเชื่อมต่อ 10.0.0.5 แล้วค้างไป

curl ก็ช่วยซ่อนปัญหานี้เช่นกัน ซึ่งเป็นประเด็นสำคัญหากคุณใช้ curl ในการทดสอบ คู่มือระบุว่า --ftp-skip-pasv-ip "ถูกเปิดใช้งานโดยค่าเริ่มต้น (เพิ่มเข้ามาในเวอร์ชัน 7.74.0)" ดังนั้น curl จึงเพิกเฉยต่อที่อยู่ในคำตอบของ 227 และใช้ที่อยู่เดียวกับการเชื่อมต่อควบคุม (control connection) การถ่ายโอนข้อมูลที่ทำงานได้ด้วย curl อาจล้มเหลวในไคลเอนต์แบบกราฟิกได้ด้วยเหตุผลนี้เพียงประการเดียว

ให้กำหนดที่อยู่ไว้อย่างชัดเจน vsftpd ใช้คำสั่ง pasv_address=203.0.113.10 ร่วมกับ pasv_addr_resolve=YES (ค่าเริ่มต้นคือ NO) หากคุณต้องการระบุเป็นชื่อโฮสต์แทน ProFTPD ใช้ MasqueradeAddress ซึ่งรองรับทั้งที่อยู่ IP, ชื่อ DNS หรือชื่ออินเทอร์เฟซ Pure-FTPd ใช้ -P ซึ่งระบุไว้สำหรับกรณีที่ "เซิร์ฟเวอร์อยู่หลังอุปกรณ์ masquerading (NAT)" ส่วน EPSV ช่วยหลีกเลี่ยงปัญหานี้ได้ทั้งหมดเนื่องจากการตอบกลับไม่มีฟิลด์ที่อยู่ แต่คุณไม่สามารถพึ่งพาวิธีนี้ได้เนื่องจากไคลเอนต์เป็นผู้ตัดสินใจว่าจะส่งคำสั่งใด

การเปลี่ยนแปลงด้าน TLS

FTPS คือ FTP ที่ทำงานผ่าน TLS (transport layer security) ไคลเอนต์จะเชื่อมต่อผ่านพอร์ต 21 ตามปกติ จากนั้นส่งคำสั่ง AUTH TLS เพื่อรักษาความปลอดภัยให้กับช่องทางควบคุม (control channel) และส่งคำสั่ง PROT P เพื่อเข้ารหัสช่องทางข้อมูล (data channel) ด้วยเช่นกัน FTP แบบปกติจะส่งรหัสผ่านผ่านเครือข่ายในรูปแบบข้อความที่อ่านได้ ดังนั้นหากจำเป็นต้องใช้ FTP ให้ใช้ FTPS แทน ซอฟต์แวร์ vsftpd มาพร้อมกับค่า ssl_enable ที่ตั้งค่าเริ่มต้นเป็น NO

ผลที่ตามมามี 2 ประการ ประการแรก ตัวช่วยติดตามการเชื่อมต่อ (connection tracking helper) จะไม่สามารถทำงานได้ ซึ่งเป็นประเด็นเดียวกับที่กล่าวไว้ข้างต้นเมื่อมองจากอีกมุมหนึ่ง ประการที่สอง sudo tcpdump -nAi any 'tcp port 21' จะไม่แสดงการตอบกลับ 227 ให้คุณเห็นอีกต่อไป ดังนั้นเมื่อคุณต้องการทราบว่าเซิร์ฟเวอร์ประกาศที่อยู่และพอร์ตใดออกมา ให้ตรวจสอบจาก log ของเซิร์ฟเวอร์โดยตรงแทนการดักจับข้อมูลบนเครือข่าย

การทดสอบการเปลี่ยนแปลงจากภายนอก

ให้รันคำสั่งเหล่านี้จากเครื่องอื่น การทดสอบจากตัวเซิร์ฟเวอร์เองจะข้ามขั้นตอนการตรวจสอบ firewall ที่คุณกำลังพยายามแก้ไขอยู่

sudo ss -ltnp | grep :21
curl -v --disable-epsv --user ftpuser:secret ftp://example.com/
nc -vz example.com 30000

ss ควรแสดงให้เห็นว่า FTP daemon กำลังรอรับการเชื่อมต่ออยู่ที่พอร์ต 21 ในขณะที่เซิร์ฟเวอร์ไม่ได้ใช้งาน จะไม่มีสิ่งใดรอรับการเชื่อมต่อในช่วงพอร์ต passive เนื่องจาก socket เหล่านั้นจะถูกสร้างขึ้นเพื่อการถ่ายโอนข้อมูลและปิดตัวลงหลังจากเสร็จสิ้น

--disable-epsv จะบังคับให้ curl ทำงานผ่านเส้นทาง PASV ซึ่งเป็นจุดที่เผยให้เห็นปัญหาเรื่องที่อยู่ (address) ผลลัพธ์การ trace จะแสดงการตอบกลับของเซิร์ฟเวอร์ ตามด้วยที่อยู่และพอร์ตที่ curl พยายามเชื่อมต่อ:

< 227 Entering Passive Mode (203,0,113,10,117,52)

117 * 256 + 52 = 30004 ซึ่งอยู่ในช่วงที่กำหนดไว้ หากบรรทัดดังกล่าวแสดงที่อยู่ภายใน (private address) หมายความว่า pasv_address ยังไม่ได้ถูกตั้งค่า หากพอร์ตอยู่นอกช่วงที่คุณกำหนด แสดงว่าเซิร์ฟเวอร์ไม่ได้อ่านการเปลี่ยนแปลงค่าคอนฟิกูเรชัน ให้ตรวจสอบว่าคุณได้แก้ไขไฟล์ที่ service กำลังใช้งานอยู่จริงหรือไม่

nc สามารถตอบคำถามเรื่อง firewall ได้ด้วยตัวเอง หากได้รับ Connection refused ทันที หมายความว่าแพ็กเก็ตส่งถึงเซิร์ฟเวอร์แล้วแต่ไม่พบสิ่งใดรอรับการเชื่อมต่อ ซึ่งเป็นผลลัพธ์ที่ถูกต้องสำหรับพอร์ต passive ที่ไม่ได้ใช้งาน: กฎของคุณทำงานได้ปกติ หากการเชื่อมต่อค้างจนกระทั่ง nc ยอมแพ้ แสดงว่ามีบางอย่างดรอปแพ็กเก็ตไปโดยไม่มีการแจ้งเตือน ซึ่งก็คือ firewall ไม่ว่าจะเป็นตัวที่อยู่บนเครื่องเองหรือตัวที่อยู่ในแผงควบคุมของผู้ให้บริการ ความแตกต่างนี้เป็นแบบเดียวกับที่อธิบายไว้ใน การปฏิเสธการเชื่อมต่อเทียบกับการหมดเวลาสำหรับ SSH และสามารถนำไปใช้ได้กับทุกพอร์ต

คุณยังควรใช้งาน FTP อยู่หรือไม่?

สำหรับงานใหม่ คำตอบคือไม่ SFTP (SSH file transfer protocol) ทำงานอยู่ภายในช่องทางเชื่อมต่อ SSH เดียวบนพอร์ต 22 โดยไม่มีช่องทางที่สอง ไม่ต้องตั้งค่า passive range ไม่ต้องตั้งค่า NAT และไม่ต้องดูแล daemon เพิ่มเติม เพราะ OpenSSH มีฟังก์ชันนี้ให้ใช้งานอยู่แล้ว sftp user@example.com สามารถทำงานได้บนเครื่องที่คุณไม่ได้ตั้งค่าบริการโอนย้ายไฟล์ใดๆ ไว้เลย หากต้องการให้ผู้อื่นเข้าถึงไฟล์โดยไม่ให้สิทธิ์อื่นเพิ่มเติม sshd_config จะใช้ ForceCommand internal-sftp ร่วมกับ ChrootDirectory ไดเรกทอรีดังกล่าวต้องมีเจ้าของเป็น root และผู้ใช้ต้องไม่มีสิทธิ์เขียนไฟล์ มิฉะนั้น sshd จะปฏิเสธการเชื่อมต่อและบันทึกบรรทัด bad ownership or modes for chroot directory ลงใน log

FTP ยังคงมีความจำเป็นในกรณีที่ฝั่งตรงข้ามไม่สามารถเปลี่ยนแปลงได้ เครื่องสแกนและเครื่องพิมพ์มัลติฟังก์ชันมักมาพร้อมกับเฟิร์มแวร์ที่รองรับเฉพาะ FTP เท่านั้น อุปกรณ์ในห้องปฏิบัติการและอุปกรณ์อุตสาหกรรมมักรันบนอิมเมจแบบคงที่ซึ่งไม่มีใครยอมรับรองการเปลี่ยนแปลง คู่ค้าทางธุรกิจอาจเปิดช่องทาง FTPS ไว้และไม่ยอมเพิ่มโปรโตคอลใหม่ให้ซัพพลายเออร์เพียงรายเดียว ในกรณีเหล่านี้ การตั้งค่า passive range ควบคู่ไปกับกฎ firewall ที่สอดคล้องกันคือทั้งหมดที่ต้องทำ และควรเลือกใช้ FTPS แทน FTP แบบปกติ การออกแบบที่ใช้ช่องทางเชื่อมต่อสองช่องทางเป็นการตัดสินใจตั้งแต่ปี 1985 ซึ่งปัจจุบันต้องทำงานในสภาพแวดล้อมที่ไม่ได้คาดการณ์ไว้แต่แรก เรื่องราวนี้ถูกกล่าวถึงไว้ใน ประวัติของโปรโตคอลการโอนย้ายไฟล์

FAQ

ทำไม FTP ล็อกอินได้แต่ค้างตอนแสดงรายการไฟล์?

การล็อกอินใช้เพียงการเชื่อมต่อควบคุม (control connection) ผ่านพอร์ต 21 ซึ่ง firewall ของคุณอนุญาตอยู่แล้ว แต่การแสดงรายการไฟล์ต้องใช้การเชื่อมต่อ TCP ชุดที่สองผ่านพอร์ตอื่น ซึ่งการเชื่อมต่อนี้ถูกบล็อกไว้ ให้กำหนดช่วงพอร์ตแบบ passive (passive port range) บนเซิร์ฟเวอร์ FTP และเปิดพอร์ตช่วงเดียวกันใน firewall แล้วรายการไฟล์จะแสดงผลได้ตามปกติ อาการค้างขณะแสดงรายการไฟล์เป็นปัญหาที่ช่องทางรับส่งข้อมูล (data channel) ไม่ใช่ปัญหาเรื่องรหัสผ่าน

ต้องเปิดพอร์ตใดบ้างสำหรับ FTP โหมด passive?

พอร์ต 21 สำหรับช่องทางควบคุม และช่วงพอร์ตที่คุณกำหนดไว้สำหรับการรับส่งข้อมูลแบบ passive ไม่มีช่วงพอร์ตมาตรฐานเพราะคุณเป็นผู้กำหนดเอง ช่วงพอร์ตเช่น 30000 ถึง 30099 สามารถใช้งานได้ โดยให้ปรับขนาดตามจำนวนการโอนถ่ายข้อมูลพร้อมกันสูงสุดของคุณ และหลีกเลี่ยงช่วงพอร์ตขาออกของ kernel ซึ่งตรวจสอบได้ด้วย sysctl net.ipv4.ip_local_port_range หากผู้ให้บริการของคุณมี network firewall ในแผงควบคุม ให้เปิดพอร์ตช่วงเดียวกันนั้นด้วย

ยังจำเป็นต้องใช้ nf_conntrack_ftp หรือไม่?

ไม่จำเป็น และใน kernel ปัจจุบันคุณไม่สามารถพึ่งพามันได้ การกำหนด helper อัตโนมัติถูกปิดใช้งานโดยค่าเริ่มต้น และตั้งแต่ Linux 6.0 เป็นต้นมา สวิตช์ net.netfilter.nf_conntrack_helper ถูกถอดออกไปแล้ว ทำให้ sysctl รายงานว่าไม่มีไฟล์ดังกล่าว คู่มือของ ufw ระบุว่าการโหลดโมดูลเหล่านั้นโดยไม่มีเงื่อนไขถือเป็นวิธีที่ล้าสมัย (deprecated) และ firewalld ก็เพิกเฉยต่อ AutomaticHelpers โดยสิ้นเชิง นอกจากนี้ helper ยังต้องอ่านช่องทางควบคุมที่เป็นข้อความธรรมดา (plain text) ดังนั้นมันจะหยุดทำงานทันทีที่คุณเปิดใช้งาน FTPS ให้กำหนดช่วงพอร์ตแบบ passive แทนจะดีกว่า

ทำไม FTP client ถึงแจ้งว่า passive reply มีที่อยู่ที่ไม่สามารถระบุเส้นทางได้ (unroutable address)?

เซิร์ฟเวอร์ตอบกลับ PASV ด้วยที่อยู่ที่มันเห็นบนอินเทอร์เฟซของตัวเอง ซึ่งที่อยู่นั้นเป็นที่อยู่ภายใน (private address) ปัญหานี้เกิดขึ้นเมื่อแพลตฟอร์มแมปที่อยู่สาธารณะเข้ากับที่อยู่ภายใน ให้กำหนดที่อยู่สาธารณะอย่างชัดเจน: pasv_address ใน vsftpd, MasqueradeAddress ใน ProFTPD หรือ -P ใน Pure-FTPd ส่วน FileZilla จะแก้ปัญหานี้ด้วยการนำที่อยู่ที่เชื่อมต่ออยู่แล้วมาใช้ซ้ำ และบันทึก log ว่า "Using server address instead" ซึ่งเป็นเหตุผลว่าทำไม client บางตัวถึงใช้งานได้แม้ตั้งค่าผิดพลาด ในขณะที่ตัวอื่นกลับค้าง

ควรใช้ FTTP หรือ SFTP?

ใช้ SFTP สำหรับงานที่คุณควบคุมได้ทั้งสองฝั่ง เพราะใช้การเชื่อมต่อเดียวผ่าน SSH บนพอร์ต 22 ไม่ต้องเปิดช่องทางรับส่งข้อมูลเพิ่ม และมันทำงานอยู่แล้ว ส่วน FTPS คือ FTP ที่ครอบด้วย TLS จึงยังคงการออกแบบสองช่องทางและปัญหา firewall ทั้งหมดที่ตามมา ให้เลือกใช้ FTPS ก็ต่อเมื่ออีกฝั่งรองรับเฉพาะโปรโตคอลนี้เท่านั้น ห้ามใช้ FTP แบบธรรมดาผ่านอินเทอร์เน็ต เพราะรหัสผ่านจะถูกส่งผ่านเครือข่ายในรูปแบบข้อความที่อ่านได้ทันที