blob: 40ce9bb261a2c25488cde41351f5f092ca841374 [file] [log] [blame]
Davide Pesavento56a741f2018-02-10 16:30:59 -05001#!/usr/bin/env python3
Alexander Afanasyev9bcbc7c2014-04-06 19:37:37 -07002"""
Davide Pesavento48108e02024-05-13 19:04:25 -04003Copyright (c) 2014-2024, Regents of the University of California,
Junxiao Shie2733332016-11-24 14:11:40 +00004 Arizona Board of Regents,
5 Colorado State University,
6 University Pierre & Marie Curie, Sorbonne University,
7 Washington University in St. Louis,
8 Beijing Institute of Technology,
9 The University of Memphis.
Alexander Afanasyev9bcbc7c2014-04-06 19:37:37 -070010
11This file is part of NFD (Named Data Networking Forwarding Daemon).
12See AUTHORS.md for complete list of NFD authors and contributors.
13
14NFD is free software: you can redistribute it and/or modify it under the terms
15of the GNU General Public License as published by the Free Software Foundation,
16either version 3 of the License, or (at your option) any later version.
17
18NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
19without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
20PURPOSE. See the GNU General Public License for more details.
21
22You should have received a copy of the GNU General Public License along with
23NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
24"""
Chengyu Fanb07788a2014-03-31 12:15:36 -060025
Davide Pesavento48108e02024-05-13 19:04:25 -040026import argparse
27import ipaddress
28import os
29import socket
30import subprocess
Davide Pesaventodecaf502023-08-30 22:50:03 -040031from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
Davide Pesaventob922c882018-02-24 23:55:57 -050032from urllib.parse import urlsplit
Chengyu Fanb07788a2014-03-31 12:15:36 -060033
34
Davide Pesavento56a741f2018-02-10 16:30:59 -050035class NfdStatusHandler(SimpleHTTPRequestHandler):
Davide Pesaventodecaf502023-08-30 22:50:03 -040036 """The handler class to handle HTTP requests."""
37
Chengyu Fanb07788a2014-03-31 12:15:36 -060038 def do_GET(self):
Davide Pesaventob922c882018-02-24 23:55:57 -050039 path = urlsplit(self.path).path
40 if path == "/":
Davide Pesaventodecaf502023-08-30 22:50:03 -040041 self.__serve_report()
42 elif path == "/robots.txt" and self.server.allow_robots:
Davide Pesavento56a741f2018-02-10 16:30:59 -050043 self.send_error(404)
Chengyu Fanb07788a2014-03-31 12:15:36 -060044 else:
Davide Pesavento56a741f2018-02-10 16:30:59 -050045 super().do_GET()
Chengyu Fanb07788a2014-03-31 12:15:36 -060046
Davide Pesaventodecaf502023-08-30 22:50:03 -040047 def __serve_report(self):
48 """Obtain XML-formatted NFD status report and send it back as response body."""
Junxiao Shi9137e9e2016-12-15 21:31:50 +000049 try:
Davide Pesaventodecaf502023-08-30 22:50:03 -040050 proc = subprocess.run(
51 ["nfdc", "status", "report", "xml"], capture_output=True, check=True, text=True, timeout=10
52 )
53 output = proc.stdout
Davide Pesavento56a741f2018-02-10 16:30:59 -050054 except OSError as err:
Davide Pesaventodecaf502023-08-30 22:50:03 -040055 super().log_message(f"error invoking nfdc: {err}")
Davide Pesavento56a741f2018-02-10 16:30:59 -050056 self.send_error(500)
Davide Pesaventodecaf502023-08-30 22:50:03 -040057 except subprocess.SubprocessError as err:
58 super().log_message(f"error invoking nfdc: {err}")
59 self.send_error(504, "Cannot connect to NFD")
Davide Pesavento56a741f2018-02-10 16:30:59 -050060 else:
Junxiao Shi9137e9e2016-12-15 21:31:50 +000061 # add stylesheet processing instruction after the XML document type declaration
Davide Pesavento56a741f2018-02-10 16:30:59 -050062 # (yes, this is a ugly hack)
Davide Pesaventodecaf502023-08-30 22:50:03 -040063 if (pos := output.find(">") + 1) != 0:
Davide Pesavento48108e02024-05-13 19:04:25 -040064 xml = output[:pos] + '<?xml-stylesheet type="text/xsl" href="nfd-status.xsl"?>' + output[pos:]
Davide Pesaventodecaf502023-08-30 22:50:03 -040065 self.send_response(200)
66 self.send_header("Content-Type", "text/xml; charset=UTF-8")
67 self.end_headers()
68 self.wfile.write(xml.encode())
69 else:
70 super().log_message(f"malformed nfdc output: {output}")
71 self.send_error(500)
Chengyu Fanb07788a2014-03-31 12:15:36 -060072
Davide Pesavento56a741f2018-02-10 16:30:59 -050073 # override
74 def log_message(self, *args):
75 if self.server.verbose:
76 super().log_message(*args)
77
78
Davide Pesaventodecaf502023-08-30 22:50:03 -040079class NfdStatusHttpServer(ThreadingHTTPServer):
80 def __init__(self, addr, port, *, robots=False, verbose=False):
Davide Pesavento56a741f2018-02-10 16:30:59 -050081 # socketserver.BaseServer defaults to AF_INET even if you provide an IPv6 address
82 # see https://bugs.python.org/issue20215 and https://bugs.python.org/issue24209
Davide Pesaventodecaf502023-08-30 22:50:03 -040083 if addr.version == 6:
Chengyu Fane25b0f02014-04-05 21:42:40 -060084 self.address_family = socket.AF_INET6
Davide Pesaventodecaf502023-08-30 22:50:03 -040085 self.allow_robots = robots
Chengyu Fanb07788a2014-03-31 12:15:36 -060086 self.verbose = verbose
Davide Pesaventodecaf502023-08-30 22:50:03 -040087 super().__init__((str(addr), port), NfdStatusHandler)
Chengyu Fanb07788a2014-03-31 12:15:36 -060088
Davide Pesavento56a741f2018-02-10 16:30:59 -050089
90def main():
Davide Pesaventodecaf502023-08-30 22:50:03 -040091 def ip_address(arg, /):
92 """Validate IP address."""
Chengyu Fane25b0f02014-04-05 21:42:40 -060093 try:
Davide Pesavento56a741f2018-02-10 16:30:59 -050094 value = ipaddress.ip_address(arg)
95 except ValueError:
Davide Pesaventodecaf502023-08-30 22:50:03 -040096 raise argparse.ArgumentTypeError(f"{arg!r} is not a valid IP address")
Davide Pesavento56a741f2018-02-10 16:30:59 -050097 return value
98
Davide Pesaventodecaf502023-08-30 22:50:03 -040099 def port_number(arg, /):
100 """Validate port number."""
Chengyu Fane25b0f02014-04-05 21:42:40 -0600101 try:
Davide Pesavento56a741f2018-02-10 16:30:59 -0500102 value = int(arg)
103 except ValueError:
104 value = -1
105 if value < 0 or value > 65535:
Davide Pesaventodecaf502023-08-30 22:50:03 -0400106 raise argparse.ArgumentTypeError(f"{arg!r} is not a valid port number")
Davide Pesavento56a741f2018-02-10 16:30:59 -0500107 return value
Chengyu Fane25b0f02014-04-05 21:42:40 -0600108
Davide Pesavento56a741f2018-02-10 16:30:59 -0500109 parser = argparse.ArgumentParser(description="Serves NFD status page via HTTP")
110 parser.add_argument("-V", "--version", action="version", version="@VERSION@")
Davide Pesaventodecaf502023-08-30 22:50:03 -0400111 parser.add_argument("-a", "--address", default="127.0.0.1", type=ip_address, metavar="ADDR",
Davide Pesavento56a741f2018-02-10 16:30:59 -0500112 help="bind to this IP address (default: %(default)s)")
Davide Pesavento48108e02024-05-13 19:04:25 -0400113 parser.add_argument("-p", "--port", default=6380, type=port_number,
Davide Pesavento56a741f2018-02-10 16:30:59 -0500114 help="bind to this port number (default: %(default)s)")
115 parser.add_argument("-f", "--workdir", default="@DATAROOTDIR@/ndn", metavar="DIR",
116 help="server's working directory (default: %(default)s)")
Davide Pesavento48108e02024-05-13 19:04:25 -0400117 parser.add_argument("-r", "--robots", action="store_true", help="allow crawlers and other HTTP bots")
118 parser.add_argument("-v", "--verbose", action="store_true", help="turn on verbose logging")
Davide Pesavento56a741f2018-02-10 16:30:59 -0500119 args = parser.parse_args()
Chengyu Fanb07788a2014-03-31 12:15:36 -0600120
Davide Pesavento56a741f2018-02-10 16:30:59 -0500121 os.chdir(args.workdir)
Chengyu Fanb07788a2014-03-31 12:15:36 -0600122
Davide Pesaventodecaf502023-08-30 22:50:03 -0400123 with NfdStatusHttpServer(args.address, args.port, robots=args.robots, verbose=args.verbose) as httpd:
124 if httpd.address_family == socket.AF_INET6:
125 url = "http://[{}]:{}"
126 else:
127 url = "http://{}:{}"
128 print("Server started at", url.format(*httpd.server_address))
Alexander Afanasyevb47d5382014-05-05 14:35:03 -0700129
Davide Pesaventodecaf502023-08-30 22:50:03 -0400130 try:
131 httpd.serve_forever()
132 except KeyboardInterrupt:
133 pass
Chengyu Fanb07788a2014-03-31 12:15:36 -0600134
Chengyu Fanb07788a2014-03-31 12:15:36 -0600135
Davide Pesavento56a741f2018-02-10 16:30:59 -0500136if __name__ == "__main__":
137 main()