上週研究與熬夜很久,對於Docker新手的我來說學習到很多概念,踩坑經驗分享。
結論先說,因爲Docker Desktop的宿主(Host)系統(Windows、macOS)並不支援真正的Port直通,中間仍過一層軟體轉換,只有容器監聽全局的Port才會被軟體轉換,DHCP Servers是監聽interface為主而不是全局,以至於DHCP Server無法正常運作。
如果使用Windows或是macOS,建議只在Docker Network做內部測試,當測試完成一定再放在Linux系統下跑真的Host模式去測試。
Docker Desktop 如何啓用Network Host 模式
在 Setting > Resources > Enable host networking。

容器全域監聽會自動對應在Host上
啓用之後,容器如果全局監聽,也就是「0.0.0.0:<port>」或是「[::]:<port>」就會自動在Host上也監聽一樣的。
也就是說,如果要讓DHCP Server啓動時也在HOST監聽UDP 67,就得讓容器全局監聽UDP 67。

很可惜,Kea DHCP Server並不支援這種模式,Kea DHCP Server只能去監聽特定介面, 即使使用「”*”」這種符號,也不會有個介面卡的IP是0.0.0.0/0。
docker desktop的network host並不是直通,而是透過軟體連接,軟體判斷機制是容器全域監聽Port才會在自動設定在Host上。
Docker Desktop的軟體Host模式
那如果使用軟體Host模式之後,又加上想辦法讓容器全局監聽,這樣DHCP Server是否能正常?不能。
當特定Port監聽流量進來,經過Docker Desktop的軟體Host,對容器來說,會變成來源爲127.0.0.1與隨機port的流量,這意味着NAT的發生。
對於網頁服務來說,來源IP與MAC可能不重要。對於DNS來說來源IP與MAC也不重要。
對於DHCP來說卻很重要,因爲來源IP會影響到DHCP Server要派發的IP。
如果DHCP DISCOVERY使用broadcast,Relay agent IP爲0.0.0.0,那麼DHCP Server若收到,就會此收到此封包的介面作爲Relay agent IP,然後再進DHCP Pool搜尋是否有Pool的Network包含Relay agent IP,決定派發哪個DHCP Pool。

Host模式直通容器的Loopback(127.0.0.1),而DHCP Server無法全局監聽,到此差不多就陣亡了。
在Linux上的Host模式
在Linux系統下的Docker,host模式不需要額外設定,可以Host Port to Container port直通。
這有什麼好處?Container可以正確看到來源IP,像是Adguard Home可以看到誰來問,更進階的Access Control也需要能看到來源IP。
還有本次重點DHCP Server可以正確運作,也可以被正確relay到。
在Docker Desktop環境下使用DHCP Relay
經過DHCP Relay之後,會變成單播封包,同時DHCP Option中也會加上agent ip,只需要能正確Relay到DHCP Server所監聽的介面,就能回應正確的DHCP Offer。
透過wireshark可以看到,從host來的封包,來源爲127.0.0.1 udp 68,目的127.0.0.1 udp 67,mac直接全部丟失。

之前若走NAT,像是軟體HOST模式,來源將會變成隨機Port。依此回覆時UDP 68會對不上隨機Port,自然就無法觸法NAT模式回程,走不出Docker之中。
若多一個容器,專門做UDP封包轉送,像是socat。socat只能做到轉送,DHCP server收到可以正確觸發。由於socat並不是NAT機制,因此DHCP Server回應也就無法回到軟體HOST去。
在Docker Desktop部署可以,但要很努力客製化
先前常用的DHCP Server套件都不合用,在研究過程中,Python能做到很多事情,理論上可以用Python實現全局監聽與派發DHCP,這樣只需要很努力刻出來。
DHCP Server適合環境
DHCP Server適合在穩定的環境,因此VM會比Container更適合。
如果是Server,也可以透過套件安裝,或是使用Linux Networkmode Host 或是macvlan透使DHCP Server Container能正常運作。
DHCP Server部署在Client常見的OS:Windows、macOS並不適合。