Linux發行版們應該禁用 IPv4 映射的 IPv6 地址嗎?
從各方面來看,互聯網向 IPv6 的過渡是件很緩慢的事情。不過在最近幾年,可能是由于 IPv4 地址資源的枯竭,IPv6 的使用處于上升態勢。相應的,開發者也有興趣確保軟件能在 IPv4 和 IPv6 下工作。但是,正如近期 OpenBSD 郵件列表中的討論所關注的,一個使得向 IPv6 轉換更加輕松的機制設計同時也可能導致網絡更不安全——并且 Linux 發行版們的默認配置可能并不安全。
地址映射
IPv6 在很多方面看起來可能很像 IPv4,但它是一個不同地址空間的不同的協議。服務器程序想要接受使用二者之中任意一個協議的連接,必須給兩個不同的地址族分別打開一個套接字——IPv4 的 AF_INET 和 IPv6 的AF_INET6。特別是一個程序希望在主機上的使用兩種地址協議的任意接口都接受連接的話,需要創建一個綁定到全零通配符地址(0.0.0.0)的 AF_INET 套接字和一個綁定到 IPv6 等效地址(寫作 ::)的 AF_INET6套接字。它必須在兩個套接字上都監聽連接——或者有人會這么認為。
多年前,在 RFC 3493,IETF 指定了一個機制,程序可以使用一個單獨的 IPv6 套接字工作在兩個協議之上。有了一個啟用這個行為的套接字,程序只需要綁定到 :: 地址從而在所有接口上接受使用這兩個協議的連接。當創建了一個 IPv4 連接到該綁定端口,源地址會像 RFC 2373 中描述的那樣映射到 IPv6。所以,舉個例子,一個使用了這個模式的程序會將一個 192.168.1.1 的傳入連接看作來自 ::ffff:192.168.1.1(這個混合的寫法就是這種地址的通常寫法)。程序也能通過相同的映射方法打開一個到 IPv4 地址的連接。
RFC 要求默認實現這個行為,所以大多數系統這么做了。不過也有些例外,OpenBSD 就是其中之一;在那里,希望在兩種協議下工作的程序能做的只能是創建兩個獨立的套接字。但一個在 Linux 中打開兩個套接字的程序會遇到麻煩:IPv4 和 IPv6 套接字都會嘗試綁定到 IPv4 地址,所以不論是哪個,后者都會失敗。換句話說,一個綁定到 :: 指定端口的套接字的程序會同時綁定到那個端口上的 IPv6 的 :: 和 IPv4 的 0.0.0.0 地址。如果程序之后嘗試綁定一個 IPv4 套接字到 0.0.0.0 的相同端口上時,這個操作會失敗,因為這個端口已經被綁定了。
當然有個辦法可以解決這個問題;程序可以調用 setsockopt() 來打開 IPV6_V6ONLY 選項。一個打開兩個套接字并且設置了 IPV6_V6ONLY 的程序應該可以在所有的系統間移植。
讀者們可能對不是每個程序都能正確處理這一問題沒那么震驚。事實證明,這些程序的其中之一是網絡時間協議Network Time Protocol的 OpenNTPD 實現。Brent Cook 最近給上游 OpenNTPD 源碼提交了一個小補丁,添加了必要的 setsockopt() 調用,它也被提交到了 OpenBSD 中了。不過那個補丁看起來不大可能被接受,最可能的原因是因為 OpenBSD 式的理由(LCTT 譯注:如前文提到的,OpenBSD 并不受這個問題的影響)。
安全擔憂
正如上文所提到,OpenBSD 根本不支持 IPv4 映射的 IPv6 套接字。即使一個程序試著通過將 IPV6_V6ONLY 選項設置為 0 來顯式地啟用地址映射,它的作者也會感到沮喪,因為這個設置在 OpenBSD 系統中無效。這個決定背后的原因是這個映射帶來了一些安全隱憂。攻擊打開的接口的攻擊類型有很多種,但它們最后都會回到規定的兩個途徑到達相同的端口,每個端口都有它自己的控制規則。
任何給定的服務器系統可能都設置了防火墻規則,描述端口的允許訪問權限。也許還會有適當的機制,比如 TCP wrappers 或一個基于 BPF 的過濾器,或一個網絡上的路由器可以做連接狀態協議過濾。結果可能是導致防火墻保護和潛在的所有類型的混亂連接之間的缺口造成同一 IPv4 地址可以通過兩個不同的協議到達。如果地址映射是在網絡邊界完成的,情況甚至會變得更加復雜;參看這個 2003 年的 RFC 草案,它描述了如果映射地址在主機之間傳播,一些隨之而來的其它攻擊場景。
改變系統和軟件正確地處理 IPv4 映射的 IPv6 地址當然可以實現。但那增加了系統的整體復雜度,并且可以確定這個改動沒有實際地完整實現到它應該實現的范圍內。如同 Theo de Raadt 說的:
有時候人們將一個糟糕的想法放進了 RFC。之后他們發現這個想法是不可能的就將它丟回垃圾箱了。結果就是概念變得如此復雜,每個人都得在管理和編碼方面是個全職專家。我們也根本不清楚這些全職專家有多少在實際配置使用 IPv4 映射的 IPv6 地址的系統和網絡。
有人可能會說,盡管 IPv4 映射的 IPv6 地址造成了安全危險,更改一下程序讓它在實現了地址映射的系統上關閉地址映射應該沒什么危害。但 Theo 認為不應該這么做,有兩個理由。第一個是有許多破舊的程序,它們永遠不會被修復。而實際的原因是給發行版們施加了壓力去默認關閉地址映射。正如他說的:“最終有人會理解這個危害是系統性的,并更改系統默認行為使之‘secure by default’。”
Linux 上的地址映射
在 Linux 系統,地址映射由一個叫做 net.ipv6.bindv6only 的 sysctl 開關控制;它默認設置為 0(啟用地址映射)。管理員(或發行版們)可以通過將它設置為 1 來關閉地址映射,但在部署這樣一個系統到生產環境之前最好確認軟件都能正常工作。一個快速調查顯示沒有哪個主要發行版改變這個默認值;Debian 在 2009 年的 “squeeze” 中改變了這個默認值,但這個改動破壞了很多的軟件包(比如任何包含 Java 的程序),在經過了幾次的 Debian 式的討論之后,它恢復到了原來的設置。看上去不少程序依賴于默認啟用地址映射。
OpenBSD 有以“secure by default”的名義打破其核心系統之外的東西的傳統;而 Linux 發行版們則更傾向于難以作出這樣的改變。所以那些一般不愿意收到他們用戶的不滿的發行版們,不太可能很快對 bindv6only 的默認設置作出改變。好消息是這個功能作為默認已經很多年了,但很難找到被利用的例子。但是,正如我們都知道的,誰都無法保證這樣的利用不可能發生。