sim.js
  1  
  2  
  3  var visited_host = [];
  4  
  5  var private_subnets = [
  6      {'hid':'__none__', 'rid':'private_class_A', 'route':'10.0.0.0/8', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}},
  7      {'hid':'__none__', 'rid':'private_class_B', 'route':'172.16.0.0/12', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}},
  8      {'hid':'__none__', 'rid':'private_class_C', 'route':'192.168.0.0/16', 'gate':'0.0.0.0', 'route_edit':'false', 'gate_edit':'false', 'h':{'type':'internet'}}
  9  ];
 10  
 11  // simulate network
 12  
 13  
 14  function ip_to_int(s)
 15  {
 16      var tab = s.split('.');
 17      tab.forEach((el, idx) => {this[idx] = parseInt(el);});
 18      if (tab.length != 4) return (null);
 19      if (isNaN(tab[0]) || tab[0] < 0 || tab[0] > 223 || isNaN(tab[1]) || tab[1] < 0 || tab[1] > 255 ||
 20  	isNaN(tab[2]) || tab[2] < 0 || tab[2] > 255 || isNaN(tab[3]) || tab[3] < 0 || tab[3] > 255) return (null);
 21      if (tab[0] == 127) { g_sim_logs += "loopback address detected on outside interface\n"; return (null); }   // maybe not the best option to deal with 127/8 loopback addresses...
 22      return ( ( (tab[0] << 24) | (tab[1] << 16) | (tab[2] << 8) | (tab[3]) ) >>> 0);
 23  }
 24  
 25  function mask_to_int(s)
 26  {
 27      if (s.length == 0) return (null);
 28      if (s[0] == '/')
 29      {
 30  	var cidr = parseInt(s.substring(1));
 31  	if (isNaN(cidr) || cidr < 0 || cidr > 32) return (null);
 32  	if (cidr == 32) return ((-1)>>>0);
 33  	return ( ((((1 << cidr)>>>0)-1) << (32-cidr))>>>0 );
 34      }
 35      var tab = s.split('.');
 36      tab.forEach((el, idx) => {this[idx] = parseInt(el);});
 37      if (tab.length != 4) return (null);
 38      if (isNaN(tab[0]) || tab[0] < 0 || tab[0] > 255 || isNaN(tab[1]) || tab[1] < 0 || tab[1] > 255 ||
 39  	isNaN(tab[2]) || tab[2] < 0 || tab[2] > 255 || isNaN(tab[3]) || tab[3] < 0 || tab[3] > 255) return (null);
 40      if (tab[0] != 255 && (tab[1] != 0 || tab[2] != 0 || tab[3] != 0)) return (null);
 41      if (tab[0] == 255 && tab[1] != 255 && (tab[2] != 0 || tab[3] != 0)) return (null);
 42      if (tab[0] == 255 && tab[1] == 255 && tab[2] != 255 && tab[3] != 0) return (null);
 43      // magic trick to check if we have continuity of 1 then 0
 44      var mask = ( ( ( tab[0] << 24) | (( tab[1] ) << 16) | (( tab[2] ) << 8) | ( tab[3] ) ) >>> 0);
 45      if (mask == 0) return (0);
 46      if ( ( ((~mask)+1) & (~mask) ) == 0)
 47  	return (mask);
 48      return (null);
 49  }
 50  
 51  
 52  
 53  // mask on interface
 54  function get_if_mask_str(itf)
 55  {
 56      if (itf['mask_edit'] == 'true')
 57  	return (document.getElementById('mask_'+itf['if']).value);
 58      return (itf['mask']);
 59  }
 60  function get_if_mask(itf)
 61  {
 62      return (mask_to_int(get_if_mask_str(itf)));
 63  }
 64  
 65  // ip on interface
 66  function get_if_ip_str(itf)
 67  {
 68      if (itf['ip_edit'] == 'true')
 69  	return (document.getElementById('ip_'+itf['if']).value);
 70      return (itf['ip']);
 71  }
 72  function get_if_ip(itf)
 73  {
 74      var the_ip = ip_to_int(get_if_ip_str(itf));
 75      // check if ip is not the network or broadcast address
 76      var the_mask = get_if_mask(itf);
 77      if ( (the_ip & (~the_mask)) == 0 ||
 78  	 (the_ip & (~the_mask)) == (~the_mask) )
 79  	return (null);
 80      return (the_ip);
 81  }
 82  
 83  
 84  // route in routes
 85  function get_route_route_str(r)
 86  {
 87      if (r['route_edit'] == 'true')
 88  	return (document.getElementById('route_'+r['rid']).value);
 89      return (r['route']);
 90  }
 91  
 92  // gate in routes
 93  function get_route_gate_str(r)
 94  {
 95      if (r['gate_edit'] == 'true')
 96  	return (document.getElementById('gate_'+r['rid']).value);
 97      return (r['gate']);
 98  }
 99  function get_route_gate(r)
100  {
101      return (ip_to_int(get_route_gate_str(r)));
102  }
103  
104  
105  
106  function ip_match_if(ip, itf)
107  {
108      var iip, imask;
109      if ((iip = get_if_ip(itf)) === null) { g_sim_logs += 'on interface '+itf['if']+': invalid IP address\n'; return (0); }
110      if ((imask = get_if_mask(itf)) === null) { g_sim_logs += 'on interface '+itf['if']+': invalid netmask\n'; return (0); }
111  //    my_console_log("## "+iip+" & "+imask+" == "+ip+" & "+imask);
112      if (iip == ip) { g_sim_logs += "duplicate IP ("+get_if_ip_str(itf)+")\n"; return (0); } // ip_match_if is called only on output, not on arrival
113      if ((iip & imask) == (ip & imask))
114      {
115  	// if ip match the interface network, check that the ip is not the network addr or broadcast ?
116  	return (1);
117      }
118      return (0);
119  }
120  
121  
122  function ip_match_route(ip, r)
123  {
124      var str, rip, rmask;
125      str = get_route_route_str(r);
126      if (str == 'default') str = '0.0.0.0/0';
127  //    my_console_log("ip_match_route route :"+JSON.stringify(r));
128      if (r['h']['type'] == "internet" && str == '0.0.0.0/0')
129      { g_sim_logs += 'invalid default route on internet '+r['hid']+'\n'; return (0); }
130      var tab = str.split('/');
131  //    my_console_log("ip_match_route check : "+str+" againt ip "+ip);
132      if (tab.length != 2)
133      { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); }
134      if ((rip = ip_to_int(tab[0])) === null)
135      { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); }
136      if ((rmask = mask_to_int('/'+tab[1])) === null)
137      { g_sim_logs += 'invalid route on host '+r['hid']+'\n'; return (0); }
138      if ((rip & rmask) == (ip & rmask)) return (1);
139      return (0);
140  }
141  
142  
143  
144  
145  function rec_route(ip_dest, local_target, input_itf, h)   // return array of dest itf
146  {
147      var i, nbif, nb_routes, ret, j;
148      var itf_ip;
149      
150      if (input_itf != null)
151  	my_console_log(" ** to "+ip_dest+" / host "+h['id']+" input itf "+input_itf['if']+" / to match local target "+local_target);
152      else
153  	my_console_log(" ** to "+ip_dest+" / host "+h['id']);
154  
155      // loop detection here
156      if (visited_host.includes(h)) { g_sim_logs += "on "+h['id']+' : loop detected\n'; return ([]); }
157      visited_host.push(h);
158  
159      // if switch : rec_route to all links
160      if (h['type'] == 'switch')
161      {
162  	g_sim_logs += 'on switch '+h['id']+': pass to all connections\n';
163  	ret = [];
164  	links.forEach(l => {if (l['e1']['hid'] == h['id']) ret = ret.concat(rec_route(ip_dest, local_target, l['e2'], l['h2']));
165  			    else if (l['e2']['hid'] == h['id']) ret = ret.concat(rec_route(ip_dest, local_target, l['e1'], l['h1']))});
166  	return (ret);
167      }
168      
169      // on a host, is my current gate ip == the ip of the input itf ?
170      if (input_itf != null)
171      {
172  	if ((itf_ip = get_if_ip(input_itf)) === null) { g_sim_logs += 'on '+h['id']+': invalid IP on input interface '+input_itf['if']+'\n'; return ([]); }
173  	if (itf_ip != local_target) { g_sim_logs += 'on '+h['id']+' : packet not for me\n'; return ([]); }
174      }
175  
176      // accepted on host
177      g_sim_logs += 'on '+h['id']+' : packet accepted\n';
178  
179      // internet does no route private addresses, so "internet interface" reject private subnets
180      if (h['type'] == 'internet')
181      {
182  	if (ip_match_route(ip_dest, private_subnets[0]) || ip_match_route(ip_dest, private_subnets[1]) || ip_match_route(ip_dest, private_subnets[2]))
183  	{ g_sim_logs += 'private subnets not routed over internet\n'; return ([]); }
184      }
185      
186     // arrived ?   check not only input itf, in case another itf is the target;  do not check if input_itf is null (departure host)
187      if (input_itf != null)
188      {
189  	ret = [];
190  	ifs.forEach(itf => {if (itf['hid'] == h['id'] && (itf_ip = get_if_ip(itf)) !== null && ip_dest === itf_ip) ret.push(itf);});
191  	if (ret.length > 0) { g_sim_logs += 'on '+h['id']+': destination IP reached\n'; my_console_log("  destination reached !"); return (ret); } // keep multiple : means that 2 interfaces have the same ip
192      }
193  
194      // ip_dest match an interface ?
195      my_console_log('on '+h['id']+': check '+ip_dest+" against all interfaces");
196      nbif = 0; ret = [];
197      for (i = 0; i < ifs.length; i++)
198      {
199  	if (ifs[i]['hid'] == h['id'])
200  	{
201  	    my_console_log("   chk with itf "+ifs[i]['if']);
202  	    if (ip_match_if(ip_dest, ifs[i]))
203  	    {
204  		my_console_log("   match itf "+ifs[i]['if']);
205  		nbif ++;
206  		g_sim_logs += 'on '+h['id']+': send to '+ifs[i]['if']+'\n';
207  		links.forEach(l => {if (l['if1'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_dest, l['e2'], l['h2']));
208  				    else if (l['if2'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_dest, l['e1'], l['h1']))});
209  	    }
210  	}
211      }
212       // force fail if multiple ifs on same subnet
213      if (nbif > 1) { g_sim_logs += 'on '+h['id']+': error on destination ip - multiple interface match\n'; return ([]); }
214      if (nbif == 1) return (ret);
215  
216      // else nbif == 0, no interface match, explore routes
217      my_console_log("  no itf for ip destination, go through gate");
218      g_sim_logs += 'on '+h['id']+': destination does not match any interface. pass through routing table\n';
219  
220      ret = [];
221      nb_routes = 0;
222      for (j = 0; j < routes.length; j++)
223      {
224  	if (routes[j]['hid'] == h['id'])
225  	{
226  	    if (ip_match_route(ip_dest, routes[j]))
227  	    {
228  		g_sim_logs += 'on '+h['id']+' : route match '+get_route_route_str(routes[j])+'\n';
229  		nb_routes ++;
230  		var ip_gate = get_route_gate(routes[j]);
231  		if (ip_gate === null) { g_sim_logs += "on "+h['id']+": invalid gate IP, route "+get_route_route_str(routes[j])+"\n"; return ([]);}
232  		nbif = 0;
233  		for (i = 0; i < ifs.length; i++)
234  		{
235  		    if (ifs[i]['hid'] == h['id'])
236  		    {
237  			if (ip_match_if(ip_gate, ifs[i]))
238  			{
239  			    my_console_log("   gate ip match itf "+ifs[i]['if']);
240  			    g_sim_logs += 'on '+h['id']+': send to gateway '+get_route_gate_str(routes[j])+' through interface '+ifs[i]['if']+'\n';
241  			    nbif ++;
242  			    links.forEach(l => {if (l['if1'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_gate, l['e2'], l['h2']));
243  						else if (l['if2'] == ifs[i]['if']) ret = ret.concat(rec_route(ip_dest, ip_gate, l['e1'], l['h1']))});
244  			}
245  		    }
246  		}
247  		if (nbif > 1) { g_sim_logs += 'on '+h['id']+' : error on gate ip - multiple interface match\n'; return ([]); }
248  	    }
249  	}
250  	if (nb_routes > 0)  // only first route is explored.
251  	{
252  	    if (nbif == 0) g_sim_logs += 'on '+h['id']+' : route match but no interface for gateway '+get_route_gate_str(routes[j])+'\n';
253  	    return (ret);
254  	}
255      }
256      // no match, fail
257      g_sim_logs += 'on '+h['id']+': destination does not match any route\n';
258      return ([]);
259  }
260  
261  
262  
263  function sim_reach(g)
264  {
265      my_console_log("check reach : "+g['id1']+" -> "+g['id2']);
266      var ret = [];
267      var i;
268      var itf_ip;
269      for (i = 0; i < ifs.length; i++)
270      {
271  	if (g['id2'] == ifs[i]['hid'])
272  	{
273  	    visited_host = [];
274  	    if ((itf_ip = get_if_ip(ifs[i])) === null)
275  		g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n";
276  	    else
277  	    {
278  		g_sim_logs +="forward way : "+g['id1']+" -> "+g['id2']+" ("+get_if_ip_str(ifs[i])+")\n";
279  		ret = ret.concat(rec_route(itf_ip, 0, null, g['h1']));
280  	    }
281  	    if (ret.length > 0)
282  		break;     // if one interface matches, that's enough - if ret > 1 it's because another host matches
283  	}
284      }
285      if (ret.length <= 0) return ({text:'KO - No forward way, try again ...', status:0});
286      if (ret.length > 1) return ({text:'KO - Multiple destination hosts match ... ', status:0});
287      if (ret[0]['hid'] != g['id2']) return ({text:'KO - Correct IP reached but on wrong host, try again ...', status:0});
288      
289      // now reverse way
290  
291      my_console_log("check reach : "+g['id2']+" -> "+g['id1']);
292      ret = [];
293      for (i = 0; i < ifs.length; i++)
294      {
295  	if (g['id1'] == ifs[i]['hid'])
296  	{
297  	    visited_host = [];
298  	    if ((itf_ip = get_if_ip(ifs[i])) === null)
299                  g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n";
300              else
301              {
302                  g_sim_logs +="reverse way : "+g['id2']+" -> "+g['id1']+" ("+get_if_ip_str(ifs[i])+")\n";
303                  ret = ret.concat(rec_route(itf_ip, 0, null, g['h2']));
304              }
305              if (ret.length > 0)
306                  break;     // if one interface matches, that's enough - if ret > 1 it's because another host matches
307  	}
308      }
309      if (ret.length <= 0) return ({text:'KO - No reverse way, try again ...', status:0});
310      if (ret.length > 1) return ({text:'KO - Multiple origin hosts match ... ', status:0});
311      if (ret[0]['hid'] != g['id1']) return ({text:'KO - Correct IP reached but on wrong host, try again ...', status:0});
312      
313      return ({text:'OK - Congratulations !!', status:1});
314  }
315  
316  
317  function sim_reach_if(g)
318  {
319      my_console_log("check reach interface : "+g['if_id1']+" -> "+g['if_id2']);
320      var ret = [];
321      var i;
322      var itf_ip;
323      for (i = 0; i < ifs.length; i++)
324      {
325  	if (g['if_id2'] == ifs[i]['if'])
326  	{
327  	    visited_host = [];
328  	    if ((itf_ip = get_if_ip(ifs[i])) === null)
329  		g_sim_logs += "on inerface "+ifs[i]['if']+": invalid IP\n";
330  	    else
331  	    {
332  		g_sim_logs +="forward way : "+g['if_id1']+" -> "+g['if_id2']+" ("+get_if_ip_str(ifs[i])+")\n";
333  		ret = ret.concat(rec_route(itf_ip, 0, null, g['h1']));
334  	    }
335  	    if (ret.length > 0)
336  		break;     // if one interface matches, that's enough - if ret > 1 it's because another host matches
337  	}
338      }
339      if (ret.length <= 0) return ({text:'KO - No forward way, try again ...', status:0});
340      if (ret.length > 1) return ({text:'KO - Multiple destination hosts match ... ', status:0});
341      if (ret[0]['if'] != g['if_id2']) return ({text:'KO - Correct IP reached but wrong interface, try again ...', status:0});
342      
343      // now reverse way
344  
345      my_console_log("check reach interface : "+g['if_id2']+" -> "+g['if_id1']);
346      ret = [];
347      for (i = 0; i < ifs.length; i++)
348      {
349  	if (g['if_id1'] == ifs[i]['if'])
350  	{
351  	    visited_host = [];
352  	    if ((itf_ip = get_if_ip(ifs[i])) === null)
353                  g_sim_logs += "on interface "+ifs[i]['if']+": invalid IP\n";
354              else
355              {
356                  g_sim_logs +="reverse way : "+g['if_id2']+" -> "+g['if_id1']+" ("+get_if_ip_str(ifs[i])+")\n";
357                  ret = ret.concat(rec_route(itf_ip, 0, null, g['h2']));
358              }
359              if (ret.length > 0)
360                  break;     // if one interface matches, that's enough - if ret > 1 it's because another host matches
361  	}
362      }
363      if (ret.length <= 0) return ({text:'KO - No reverse way, try again ...', status:0});
364      if (ret.length > 1) return ({text:'KO - Multiple origin hosts match ... ', status:0});
365      if (ret[0]['if'] != g['if_id1']) return ({text:'KO - Correct IP reached but wrong interface, try again ...', status:0});
366      
367      return ({text:'OK - Congratulations !!', status:1});
368  }
369  
370  
371  
372  function sim_goal(g)
373  {
374      if (g['type'] == 'reach')
375  	return (sim_reach(g));
376      if (g['type'] == 'reach_if')
377  	return (sim_reach_if(g));
378  }