1# This test runs a Bittorrent tracker on one machine, and verifies
2# that two client machines can download the torrent using
3# `transmission'. The first client (behind a NAT router) downloads
4# from the initial seeder running on the tracker. Then we kill the
5# initial seeder. The second client downloads from the first client,
6# which only works if the first client successfully uses the UPnP-IGD
7# protocol to poke a hole in the NAT.
8
9import ./make-test-python.nix (
10 { pkgs, ... }:
11
12 let
13
14 # Some random file to serve.
15 file = pkgs.hello.src;
16
17 internalRouterAddress = "192.168.3.1";
18 internalClient1Address = "192.168.3.2";
19 externalRouterAddress = "80.100.100.1";
20 externalClient2Address = "80.100.100.2";
21 externalTrackerAddress = "80.100.100.3";
22
23 download-dir = "/var/lib/transmission/Downloads";
24 transmissionConfig =
25 { pkgs, ... }:
26 {
27 environment.systemPackages = [ pkgs.transmission_3 ];
28 services.transmission = {
29 enable = true;
30 settings = {
31 dht-enabled = false;
32 message-level = 2;
33 inherit download-dir;
34 };
35 };
36 };
37 in
38
39 {
40 name = "bittorrent";
41 meta = with pkgs.lib.maintainers; {
42 maintainers = [
43 domenkozar
44 rob
45 bobvanderlinden
46 ];
47 };
48
49 nodes = {
50 tracker =
51 { pkgs, ... }:
52 {
53 imports = [ transmissionConfig ];
54
55 virtualisation.vlans = [ 1 ];
56 networking.firewall.enable = false;
57 networking.interfaces.eth1.ipv4.addresses = [
58 {
59 address = externalTrackerAddress;
60 prefixLength = 24;
61 }
62 ];
63
64 # We need Apache on the tracker to serve the torrents.
65 services.httpd = {
66 enable = true;
67 virtualHosts = {
68 "torrentserver.org" = {
69 adminAddr = "foo@example.org";
70 documentRoot = "/tmp";
71 };
72 };
73 };
74 services.opentracker.enable = true;
75 };
76
77 router =
78 { pkgs, nodes, ... }:
79 {
80 virtualisation.vlans = [
81 1
82 2
83 ];
84 networking.nat.enable = true;
85 networking.nat.internalInterfaces = [ "eth2" ];
86 networking.nat.externalInterface = "eth1";
87 networking.firewall.enable = true;
88 networking.firewall.trustedInterfaces = [ "eth2" ];
89 networking.interfaces.eth0.ipv4.addresses = [ ];
90 networking.interfaces.eth1.ipv4.addresses = [
91 {
92 address = externalRouterAddress;
93 prefixLength = 24;
94 }
95 ];
96 networking.interfaces.eth2.ipv4.addresses = [
97 {
98 address = internalRouterAddress;
99 prefixLength = 24;
100 }
101 ];
102 services.miniupnpd = {
103 enable = true;
104 externalInterface = "eth1";
105 internalIPs = [ "eth2" ];
106 appendConfig = ''
107 ext_ip=${externalRouterAddress}
108 '';
109 };
110 };
111
112 client1 =
113 { pkgs, nodes, ... }:
114 {
115 imports = [ transmissionConfig ];
116 environment.systemPackages = [ pkgs.miniupnpc ];
117
118 virtualisation.vlans = [ 2 ];
119 networking.interfaces.eth0.ipv4.addresses = [ ];
120 networking.interfaces.eth1.ipv4.addresses = [
121 {
122 address = internalClient1Address;
123 prefixLength = 24;
124 }
125 ];
126 networking.defaultGateway = internalRouterAddress;
127 networking.firewall.enable = false;
128 };
129
130 client2 =
131 { pkgs, ... }:
132 {
133 imports = [ transmissionConfig ];
134
135 virtualisation.vlans = [ 1 ];
136 networking.interfaces.eth0.ipv4.addresses = [ ];
137 networking.interfaces.eth1.ipv4.addresses = [
138 {
139 address = externalClient2Address;
140 prefixLength = 24;
141 }
142 ];
143 networking.firewall.enable = false;
144 };
145 };
146
147 testScript =
148 { nodes, ... }:
149 ''
150 start_all()
151
152 # Wait for network and miniupnpd.
153 router.systemctl("start network-online.target")
154 router.wait_for_unit("network-online.target")
155 router.wait_for_unit("miniupnpd")
156
157 # Create the torrent.
158 tracker.succeed("mkdir ${download-dir}/data")
159 tracker.succeed(
160 "cp ${file} ${download-dir}/data/test.tar.bz2"
161 )
162 tracker.succeed(
163 "transmission-create ${download-dir}/data/test.tar.bz2 --private --tracker http://${externalTrackerAddress}:6969/announce --outfile /tmp/test.torrent"
164 )
165 tracker.succeed("chmod 644 /tmp/test.torrent")
166
167 # Start the tracker. !!! use a less crappy tracker
168 tracker.systemctl("start network-online.target")
169 tracker.wait_for_unit("network-online.target")
170 tracker.wait_for_unit("opentracker.service")
171 tracker.wait_for_open_port(6969)
172
173 # Start the initial seeder.
174 tracker.succeed(
175 "transmission-remote --add /tmp/test.torrent --no-portmap --no-dht --download-dir ${download-dir}/data"
176 )
177
178 # Now we should be able to download from the client behind the NAT.
179 tracker.wait_for_unit("httpd")
180 client1.systemctl("start network-online.target")
181 client1.wait_for_unit("network-online.target")
182 client1.succeed("transmission-remote --add http://${externalTrackerAddress}/test.torrent >&2 &")
183 client1.wait_for_file("${download-dir}/test.tar.bz2")
184 client1.succeed(
185 "cmp ${download-dir}/test.tar.bz2 ${file}"
186 )
187
188 # Bring down the initial seeder.
189 tracker.stop_job("transmission")
190
191 # Now download from the second client. This can only succeed if
192 # the first client created a NAT hole in the router.
193 client2.systemctl("start network-online.target")
194 client2.wait_for_unit("network-online.target")
195 client2.succeed(
196 "transmission-remote --add http://${externalTrackerAddress}/test.torrent --no-portmap --no-dht >&2 &"
197 )
198 client2.wait_for_file("${download-dir}/test.tar.bz2")
199 client2.succeed(
200 "cmp ${download-dir}/test.tar.bz2 ${file}"
201 )
202 '';
203 }
204)