1 module symmetry.linux.namespace;
2 import symmetry.sildoc;
3 
4 version(Posix):
5 
6 extern(C) @nogc nothrow int setns(int fd, int nstype);
7 					 
8 import symmetry.linux.process : CloneFlag, posixCloneFlags;
9 import symmetry.linux.file : fdopen;
10 import symmetry.linux.cgroups: ChildConfig;
11 import symmetry.linux.mount : fnMount, unmount;
12 import core.sys.linux.mount : MountType, UmountFlag;
13 
14 private auto system(string s)
15 {
16 	import std.process: executeShell;
17 	return executeShell("sudo " ~ s);
18 }
19 
20 void setNamespace(string path, CloneFlag[] cloneFlags)
21 {
22 	import core.sys.posix.fcntl: O_RDONLY;
23 	if (path.length ==0)
24 		return;
25 	auto flags = posixCloneFlags(cloneFlags);
26 	setNamespace_(path,flags);
27 }
28 
29 version(Posix)
30 void setNamespace_(string path, int flags)
31 {
32 	import core.sys.posix.fcntl: O_RDONLY;
33 	if (path.length ==0)
34 		return;
35 	auto fd = fdopen(path, O_RDONLY);
36 	setns(fd,flags);
37 }
38 
39 void createNetworkNamespace(string name)
40 {
41 	import std.format : format;
42 	import std.exception : enforce;
43 	deleteNetworkNamespace(name);
44 	auto ret = system(format!"ip netns add %s"(name));
45 	enforce(ret.status ==0, ret.output);
46 }
47 
48 void deleteNetworkNamespace(string name)
49 {
50 	import std.format : format;
51 	import std.exception : enforce;
52 	auto ret = system(format!"ip netns del %s"(name));
53 }
54 
55 void createVethPair(string name, string peerName)
56 {
57 	import std.format : format;
58 	import std.exception : enforce;
59 	auto ret = system(format!"ip link add %s type veth peer name %s"(name, peerName));
60 	enforce(ret.status ==0, ret.output);
61 }
62 
63 void deleteVethPair(string name)
64 {
65 	import std.format : format;
66 	import std.exception : enforce;
67 	auto ret = system(format!"ip link delete %s"(name));
68 }
69 
70 void addVethPairToNamespace(string peerName, string namespace)
71 {
72 	import std.exception : enforce;
73 	import std.format : format;
74 	auto ret = system(format!"ip link set %s netns %s"(peerName, namespace));
75 	enforce(ret.status ==0, ret.output);
76 }
77 
78 void addAddressesIP4(string addresses, string deviceName, string namespace = null)
79 {
80 	import std.exception : enforce;
81 	import std.format : format;
82 	auto ns = (namespace.length > 0) ? "-n " ~ namespace ~ " ": "";
83 	auto ret = system(format!"ip %saddr add %s dev %s"(ns,addresses,deviceName));
84 	enforce(ret.status ==0, ret.output);
85 }
86 
87 void addAddressesIP4Peer(string nameSpace, string addresses, string deviceName)
88 {
89 	import std.exception : enforce;
90 	import std.format : format;
91 	auto ret = system(format!"ip netns exec %s ip addr add %s dev %s"(nameSpace,addresses,deviceName));
92 	enforce(ret.status ==0, ret.output);
93 }
94 
95 // return addresses in JSON format
96 string showAddresses(string nameSpace = null)
97 {
98 	import std.exception : enforce;
99 	import std.format : format;
100 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
101 	auto ret = system(format!"ip -j %saddr show"(ns));
102 	enforce(ret.status ==0, ret.output);
103 	return ret.output;
104 }
105 	
106 void setLinkUpPeer(string nameSpace, string deviceName)
107 {
108 	import std.exception : enforce;
109 	import std.format : format;
110 	auto ret = system(format!"ip netns exec %s ip link set %s up"(nameSpace, deviceName));
111 	enforce(ret.status ==0, ret.output);
112 	ret = system("dhcpcd");
113 	enforce(ret.status ==0, ret.output);
114 }
115 
116 void setLinkUp(string deviceName, string nameSpace = null)
117 {
118 	import std.exception : enforce;
119 	import std.format : format;
120 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
121 	auto ret = system(format!"ip %slink set %s up"(ns,deviceName));
122 	enforce(ret.status ==0, ret.output);
123 }
124 
125 // return link info in JSON format
126 string showLink(string nameSpace = null)
127 {
128 	import std.exception : enforce;
129 	import std.format : format;
130 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
131 	auto ret = system(format!"ip -j %slink show"(ns));
132 	enforce(ret.status ==0, ret.output);
133 	return ret.output;
134 }
135 
136 // return route info in JSON format
137 string showRoute(string nameSpace = null)
138 {
139 	import std.exception : enforce;
140 	import std.format : format;
141 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
142 	auto ret = system(format!"ip -j %sroute show"(ns));
143 	enforce(ret.status ==0, ret.output);
144 	return ret.output;
145 }
146 
147 // return route get info in JSON format
148 string getRoute(string destinationIP, string nameSpace = null)
149 {
150 	import std.exception : enforce;
151 	import std.format : format;
152 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
153 	auto ret = system(format!"ip -j %sroute get %s"(ns,destinationIP));
154 	enforce(ret.status ==0, ret.output);
155 	return ret.output;
156 }
157 
158 // return route get info in JSON format
159 string addRoute(string route, string device, string nameSpace = null)
160 {
161 	import std.exception : enforce;
162 	import std.format : format;
163 	auto ns = (nameSpace.length > 0) ? "-n " ~ nameSpace ~ " ": "";
164 	auto ret = system(format!"ip -j %sroute add %s dev %s"(ns,route,device));
165 	enforce(ret.status ==0, ret.output);
166 	return ret.output;
167 }
168 
169 string executeNamespace(string nameSpace, string cmd)
170 {
171 	import std.exception : enforce;
172 	import std.format : format;
173 	auto ret = system(format!"ip netns exec %s %s"(nameSpace,cmd));
174 	enforce(ret.status ==0, ret.output);
175 	return ret.output;
176 }
177 
178 void addDefaultRoutePeer(string nameSpace, string defaultGateway)
179 {
180 	import std.exception : enforce;
181 	import std.format : format;
182 	auto ret = system(format!"ip netns exec %s ip route add default via %s"(nameSpace,defaultGateway));
183 	enforce(ret.status ==0, ret.output);
184 }
185 
186 void setHostForwarding(bool enableForwarding = true)
187 {
188 	import std.file : write;
189 	// Enable IP-forwarding.
190 	write("/proc/sys/net/ipv4/ip_forward",enableForwarding ? "1\n" : "0\n");
191 }
192 
193 
194 void allowForwarding(string hostDeviceName, string clientDeviceName)
195 {
196 	import std.exception : enforce;
197 	import std.format : format;
198 
199 	// Allow forwarding between eth0 and v-eth1.
200 	auto ret = system(format!"iptables -A FORWARD -i %s -o %s -j ACCEPT"(hostDeviceName, clientDeviceName));
201 	enforce(ret.status ==0, ret.output);
202 	ret = system(format!"iptables -A FORWARD -o %s -i %s -j ACCEPT"(hostDeviceName,clientDeviceName)); 
203 	enforce(ret.status ==0, ret.output);
204 }
205 
206 void flushNatRules()
207 {
208 	import std.exception : enforce;
209 	import std.file : write;
210 	import std.format : format;
211 	// Flush nat rules.
212 	auto ret = system("iptables -t nat -F");
213 	enforce(ret.status ==0, ret.output);
214 }
215 
216 void enableMasquerading(string peerAddress, string interfaceName)
217 {
218 	import std.exception : enforce;
219 	import std.file : write;
220 	import std.format : format;
221 	// Enable masquerading of 10.200.1.0.
222 	auto ret = system(format!"iptables -t nat -A POSTROUTING -s %s/255.255.255.0 -o %s -j MASQUERADE"(peerAddress,interfaceName));
223 	enforce(ret.status ==0, ret.output);
224 }
225 
226 void setPolicyDropDefault()
227 {
228 	import std.exception : enforce;
229 	import std.file : write;
230 	import std.format : format;
231 	// set policy DROP by default
232 	auto ret = system("iptables -P FORWARD DROP");
233 	enforce(ret.status ==0, ret.output);
234 }
235 
236 void flushForwardRules()
237 {
238 	import std.exception : enforce;
239 	import std.file : write;
240 	import std.format : format;
241 	// set policy DROP by default
242 	auto ret = system("iptables -F FORWARD");
243 	enforce(ret.status ==0, ret.output);
244 }
245 
246 @SILdoc("Share internet access between host and namespace")
247 void setHostForwarding(string hostDeviceName, string clientDeviceName, string address)
248 {
249 	import std.exception : enforce;
250 	import std.file : write;
251 	import std.format : format;
252 
253 	setHostForwarding(true);
254 	setPolicyDropDefault();
255 	flushForwardRules();
256 	flushNatRules();
257 	enableMasquerading(address,hostDeviceName);
258 	allowForwarding(hostDeviceName,clientDeviceName);
259 }
260 
261 
262 void containNetwork(string namespace, string hostDeviceName, string deviceName, string peerName, string addresses = "10.200.1.1/24")
263 {
264 	import std.exception : enforce;
265 	import std.format : format;
266 	import std..string : lastIndexOf;
267 
268 	createNetworkNamespace(namespace);
269 	createVethPair(deviceName,peerName);
270 	addVethPairToNamespace(peerName,namespace);
271 	addAddressesIP4(addresses,deviceName);
272 	setLinkUp(deviceName);
273 	addAddressesIP4Peer(namespace,addresses,peerName);
274 	setLinkUpPeer(namespace,peerName);
275 	setLinkUpPeer(namespace,"lo");
276 	addDefaultRoutePeer(namespace,"10.200.1.1");
277 	auto address = addresses[0..addresses.lastIndexOf(".")] ~ ".0";
278 	setHostForwarding(hostDeviceName, deviceName, address);
279 }
280 
281 void mounts(ChildConfig config)
282 {
283 	import std.exception : enforce;
284 	import std.file : tempDir, rmdir,chdir;
285 	import std.path : baseName;
286 	import std.format : format;
287 	import std.stdio: stderr, writeln, writefln;
288 
289 	stderr.writeln("=> remounting everyting with MS_PRIVATE...");
290 	fnMount(null,"/",null, [MountType.rec, MountType.private_], null);
291 	stderr.writeln("=> making a temp directory and a bind mount there...");
292 	auto mountDir = tempDir();
293 	enforce(mountDir != ".", "failed making a temp directory");
294 	fnMount(config.mountDir,mountDir,null,[MountType.bind, MountType.private_], null);
295 	auto innerMountDir = format!"%s/oldroot"(mountDir);
296 	stderr.writeln("done");
297 	stderr.writeln("=> pivoting root");
298 	auto oldRootDir = format!"%s/"(innerMountDir.baseName());
299 	auto oldRoot = oldRootDir;
300 	chdir("/");
301 	unmount(oldRoot,[UmountFlag.detach]);
302 	rmdir(oldRoot);
303 	stderr.writefln("done");
304 }
305 
306 
307 void teardownChroot(ChildConfig childConfig)
308 {
309 	import std.format : format;
310 	foreach(m;	[	"proc", "sys", "sys/firmware/efi/efivars", "dev/pts", "dev/shm", "dev", "run", "tmp"])
311 	{
312 		unmount(format!"%s/%s"(childConfig.mountDir,m));
313 	}
314 }
315 
316 void setupChroot(ChildConfig childConfig)
317 {
318 	import std.format : format;
319 	fnMount("proc",format!"%s/proc"(childConfig.mountDir), "proc",[MountType.noSUID,MountType.noExec, MountType.noDev]);
320 	fnMount("sys",format!"%s/sys"(childConfig.mountDir), "sysfs",[MountType.noSUID,MountType.noExec, MountType.noDev,MountType.readOnly]);
321 	try
322 	{
323 		fnMount("efivars",format!"%s/sys/firmware/efi/efivars"(childConfig.mountDir), "efivars",[MountType.noSUID,MountType.noExec, MountType.noDev]);
324 	}
325 	catch(Exception e)
326 	{
327 	}
328 
329 	fnMount("udev",format!"%s/dev"(childConfig.mountDir), "devtmpfs",[MountType.noSUID],"mode=0755");
330 	fnMount("devpts",format!"%s/dev/pts"(childConfig.mountDir), "devpts",[MountType.noSUID,MountType.noExec],"mode=0620,gid=5");
331 	fnMount("shm",format!"%s/dev/shm"(childConfig.mountDir), "tmpfs",[MountType.noSUID,MountType.noDev],"mode=1777");
332 	fnMount("/run",format!"%s/run"(childConfig.mountDir), "--bind",[]);
333 	fnMount("tmp",format!"%s/tmp"(childConfig.mountDir), "tmpfs",[MountType.strictAccessTime,MountType.noDev,MountType.noSUID],"mode=1777");
334 }