-
Notifications
You must be signed in to change notification settings - Fork 93
fix(docker): set fixed MACs on network endpoints #2619
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
27ce772
bc2ebbc
e2c1ce8
131f18b
c3da0b6
0041875
b2263be
fc85e36
93903c0
bab527f
ba55802
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,66 @@ function xml_decode($string) { | |
| return strval(html_entity_decode($string, ENT_XML1, 'UTF-8')); | ||
| } | ||
|
|
||
| function extraParamsWithQuotedValuesMasked($extraParams) { | ||
| return preg_replace('/"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|\'[^\']*\'/', '""', $extraParams); | ||
| } | ||
|
|
||
| function replaceUnquotedExtraParams($extraParams, $callback) { | ||
| $parts = preg_split('/("[^"\\\\]*(?:\\\\.[^"\\\\]*)*"|\'[^\']*\')/', $extraParams, -1, PREG_SPLIT_DELIM_CAPTURE); | ||
| if ($parts === false) { | ||
| return $extraParams; | ||
| } | ||
| foreach ($parts as $i => $part) { | ||
| if ($part === '' || $part[0] === '"' || $part[0] === "'") { | ||
| continue; | ||
| } | ||
| $parts[$i] = $callback($part); | ||
| } | ||
| return implode('', $parts); | ||
| } | ||
|
|
||
| function extractMacAddressParam($extraParams) { | ||
| if (!is_string($extraParams)) { | ||
| return ''; | ||
| } | ||
| $extraParams = extraParamsWithQuotedValuesMasked($extraParams); | ||
| if (preg_match('/(?:^|\s)--mac-address=([^\s\'"]+)/', $extraParams, $match)) { | ||
| return trim($match[1]); | ||
| } | ||
| if (preg_match('/(?:^|\s)--mac-address\s+([^\s\'"]+)/', $extraParams, $match)) { | ||
| return trim($match[1]); | ||
| } | ||
| return ''; | ||
| } | ||
|
|
||
| function removeMacAddressParam($extraParams) { | ||
| if (!is_string($extraParams) || $extraParams === '') { | ||
| return ''; | ||
| } | ||
| $extraParams = replaceUnquotedExtraParams($extraParams, function($part) { | ||
| $part = preg_replace('/(^|\s)--mac-address=[^\s\'"]+/', '$1', $part); | ||
| return preg_replace('/(^|\s)--mac-address\s+[^\s\'"]+/', '$1', $part); | ||
| }); | ||
| return trim($extraParams); | ||
| } | ||
|
|
||
| function hasNetworkParam($extraParams) { | ||
| return is_string($extraParams) && preg_match('/(?:^|\s)--net(?:work)?(?:=|\s+)[^\s\'"]+/', extraParamsWithQuotedValuesMasked($extraParams)); | ||
| } | ||
|
|
||
| function normalizeMacAddress($mac) { | ||
| $mac = strtolower(trim($mac ?? '')); | ||
| if ($mac === '') { | ||
| return ''; | ||
| } | ||
| if (preg_match('/^[0-9a-f]{12}$/', $mac)) { | ||
| $mac = implode(':', str_split($mac, 2)); | ||
| } else { | ||
| $mac = str_replace('-', ':', $mac); | ||
| } | ||
| return preg_match('/^([0-9a-f]{2}:){5}[0-9a-f]{2}$/', $mac) ? $mac : ''; | ||
| } | ||
|
|
||
| function generateTSwebui($url, $serve, $webUI) { | ||
| if (!isset($webUI)) { | ||
| return ''; | ||
|
|
@@ -75,6 +135,9 @@ function postToXML($post, $setOwnership=false) { | |
| $xml->Network = xml_encode($post['contNetwork']); | ||
| } | ||
| $xml->MyIP = xml_encode($post['contMyIP']); | ||
| $extraNetwork = hasNetworkParam($post['contExtraParams'] ?? ''); | ||
| $myMAC = $extraNetwork ? '' : normalizeMacAddress(trim($post['contMyMAC'] ?? '') ?: extractMacAddressParam($post['contExtraParams'] ?? '')); | ||
| $xml->MyMAC = xml_encode($myMAC); | ||
| $xml->Shell = xml_encode($post['contShell']); | ||
| $xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false'; | ||
| $xml->Support = xml_encode($post['contSupport']); | ||
|
|
@@ -85,7 +148,7 @@ function postToXML($post, $setOwnership=false) { | |
| $xml->WebUI = xml_encode(trim($post['contWebUI'])); | ||
| $xml->TemplateURL = xml_encode($post['contTemplateURL']); | ||
| $xml->Icon = xml_encode(trim($post['contIcon'])); | ||
| $xml->ExtraParams = xml_encode($post['contExtraParams']); | ||
| $xml->ExtraParams = xml_encode($myMAC && !$extraNetwork ? removeMacAddressParam($post['contExtraParams']) : $post['contExtraParams']); | ||
| $xml->PostArgs = xml_encode($post['contPostArgs']); | ||
| $xml->CPUset = xml_encode($post['contCPUset']); | ||
| $xml->DateInstalled = xml_encode(time()); | ||
|
|
@@ -149,6 +212,9 @@ function xmlToVar($xml) { | |
| $out['Registry'] = xml_decode($xml->Registry); | ||
| $out['Network'] = xml_decode($xml->Network); | ||
| $out['MyIP'] = xml_decode($xml->MyIP ?? ''); | ||
| $extraParams = xml_decode($xml->ExtraParams ?? ''); | ||
| $extraNetwork = hasNetworkParam($extraParams); | ||
| $out['MyMAC'] = $extraNetwork ? '' : normalizeMacAddress(xml_decode($xml->MyMAC ?? '') ?: extractMacAddressParam($extraParams)); | ||
| $out['Shell'] = xml_decode($xml->Shell ?? 'sh'); | ||
| $out['Privileged'] = xml_decode($xml->Privileged); | ||
| $out['Support'] = xml_decode($xml->Support); | ||
|
|
@@ -159,7 +225,7 @@ function xmlToVar($xml) { | |
| $out['WebUI'] = xml_decode($xml->WebUI); | ||
| $out['TemplateURL'] = xml_decode($xml->TemplateURL); | ||
| $out['Icon'] = xml_decode($xml->Icon); | ||
| $out['ExtraParams'] = xml_decode($xml->ExtraParams); | ||
| $out['ExtraParams'] = $extraParams; | ||
| $out['PostArgs'] = xml_decode($xml->PostArgs); | ||
| $out['CPUset'] = xml_decode($xml->CPUset); | ||
| $out['DonateText'] = xml_decode($xml->DonateText); | ||
|
|
@@ -325,13 +391,29 @@ function xmlToCommand($xml, $create_paths=false) { | |
| $xml = xmlToVar($xml); | ||
| $cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : ''; | ||
| $cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : ''; | ||
| $extraNetwork = hasNetworkParam($xml['ExtraParams']); | ||
| $cmdMyIP = ''; | ||
| if (preg_match('/^container:(.*)/', $xml['Network'])) { | ||
| $cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg($xml['Network']); | ||
| $cmdNetwork = $extraNetwork ? "" : '--net='.escapeshellarg($xml['Network']); | ||
| } else { | ||
| $cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network'])); | ||
| $networkName = strtolower($xml['Network']); | ||
| if ($extraNetwork) { | ||
| $cmdNetwork = ""; | ||
| } elseif (strlen($xml['MyMAC']) && !in_array($networkName, ['host','none'])) { | ||
| $xml['ExtraParams'] = removeMacAddressParam($xml['ExtraParams']); | ||
| $networkEndpoint = ['name='.$networkName]; | ||
| foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) { | ||
| if ($myIP) $networkEndpoint[] = (strpos($myIP,':') !== false ? 'ip6=' : 'ip=').$myIP; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| $networkEndpoint[] = 'mac-address='.$xml['MyMAC']; | ||
| $cmdNetwork = '--network='.escapeshellarg(implode(',', $networkEndpoint)); | ||
| } else { | ||
| $cmdNetwork = '--net='.escapeshellarg($networkName); | ||
|
Comment on lines
+399
to
+411
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, mixed-case Docker network names are valid in Docker Engine. They are matched case-sensitively when passed through docker create --network or docker run --network. Docker network names have minimal validation restrictions compared to container or volume names. Unlike containers/volumes which follow a strict regex [a-zA-Z0-9][a-zA-Z0-9_.-]*, networks historically allowed broader characters (e.g., %, ^) as long as non-empty, though later versions tightened some rules for consistency. Official docs do not prohibit uppercase letters, and community reports confirm mixed-case names like "ThIsIsANeTWoRk" can be created successfully. Network matching by name in --network is case-sensitive because Docker stores and lists network names preserving case (e.g., docker network ls shows exact case), and Linux filesystems (where Docker stores network config under /var/lib/docker/network/files/local) are case-sensitive. When specifying a partial ID, Docker resolves it to the name first, then matches, confirming case-exact name lookup. No sources indicate case-insensitive name matching for networks; contrasts with container DNS resolution (which became case-insensitive in later versions via libnetwork changes). Citations:
🏁 Script executed: # First, let's check the file and see the full context of this function
cd /root && find . -type f -name "Helpers.php" | head -20Repository: unraid/webgui Length of output: 112 🏁 Script executed: # Examine the file with line numbers, showing context around the target lines
if [ -f "./emhttp/plugins/dynamix.docker.manager/include/Helpers.php" ]; then
wc -l "./emhttp/plugins/dynamix.docker.manager/include/Helpers.php"
fiRepository: unraid/webgui Length of output: 120 🏁 Script executed: # Get the full function context - find the function this code belongs to
rg -B 30 'strtolower\(\$xml\[.Network.\]\)' --context 3 ./emhttp/plugins/dynamix.docker.manager/include/Helpers.php | head -80Repository: unraid/webgui Length of output: 387 🏁 Script executed: # Check how $xml['Network'] is used elsewhere in the file before this point
rg '\$xml\[.Network.\]' --context 2 ./emhttp/plugins/dynamix.docker.manager/include/Helpers.phpRepository: unraid/webgui Length of output: 804 🏁 Script executed: # Get broader context of the function - search backwards to find the function signature
sed -n '350,420p' ./emhttp/plugins/dynamix.docker.manager/include/Helpers.php | cat -nRepository: unraid/webgui Length of output: 3339 🏁 Script executed: # Look for the function name and its start
rg 'function.*\{' -B 5 ./emhttp/plugins/dynamix.docker.manager/include/Helpers.php | grep -A 10 'xmlToCommand'Repository: unraid/webgui Length of output: 1301 🏁 Script executed: # Get the complete section around lines 390-420 to see the full logic
sed -n '390,420p' ./emhttp/plugins/dynamix.docker.manager/include/Helpers.php | cat -nRepository: unraid/webgui Length of output: 1893 Preserve the configured network name when emitting CLI arguments. The code lowercases The Proposed fix- $networkName = strtolower($xml['Network']);
+ $networkName = $xml['Network'];
+ $networkMode = strtolower($networkName);
if ($extraNetwork) {
$cmdNetwork = "";
- } elseif (strlen($xml['MyMAC']) && !in_array($networkName, ['host','none'])) {
+ } elseif (strlen($xml['MyMAC']) && !in_array($networkMode, ['host','none'])) {
$xml['ExtraParams'] = removeMacAddressParam($xml['ExtraParams']);
$networkEndpoint = ['name='.$networkName];
foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) {
if ($myIP) $networkEndpoint[] = (strpos($myIP,':') !== false ? 'ip6=' : 'ip=').$myIP;
}
$networkEndpoint[] = 'mac-address='.$xml['MyMAC'];
$cmdNetwork = '--network='.escapeshellarg(implode(',', $networkEndpoint));
} else {
$cmdNetwork = '--net='.escapeshellarg($networkName);
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| if (!strlen($xml['MyMAC']) || preg_match('/^container:(.*)/', $xml['Network']) || $extraNetwork) { | ||
| foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) if ($myIP) $cmdMyIP .= (strpos($myIP,':') !== false ? '--ip6=' : '--ip=').escapeshellarg($myIP).' '; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| $cmdMyIP = ''; | ||
| foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) if ($myIP) $cmdMyIP .= (strpos($myIP,':')?'--ip6=':'--ip=').escapeshellarg($myIP).' '; | ||
| $cmdCPUset = strlen($xml['CPUset']) ? '--cpuset-cpus='.escapeshellarg($xml['CPUset']) : ''; | ||
| $Volumes = ['']; | ||
| $Ports = ['']; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't silently drop invalid Fixed MAC input.
normalizeMacAddress()collapses any non-normalizable value to'', and bothpostToXML()andxmlToVar()persist only that normalized result. If a user types an invalid MAC in the new field, the template quietly clears it instead of preserving the value and letting Docker reject the create. That loses user input and bypasses the intended Docker-owned error path.Proposed fix
Also applies to: 138-151, 215-228
🤖 Prompt for AI Agents