Add files via upload
This commit is contained in:
1 parent
b55ab60cd3
commit
b3fcacd2a1
4 files changed
+344
-57
No files matched your search
@@ -0,0 +1,8 @@
|
|||||||
|
# Attribution
|
||||||
|
|
||||||
|
reximemoDNS is based on the open-source sudomemoDNS project by Austin Burk / Team Sudomemo.
|
||||||
|
The original project is distributed under the MIT License; see `LICENSE`.
|
||||||
|
|
||||||
|
RexiMemo-specific changes include the project branding, default DNS targets, RexiMemo host interception, upstream DNS forwarding, and configuration options.
|
||||||
|
|
||||||
|
RexiMemo is not affiliated with or endorsed by Team Sudomemo, Hatena Co., Ltd., or Nintendo.
|
||||||
@@ -1,67 +1,103 @@
|
|||||||

|
# reximemoDNS
|
||||||
### This server will allow you to connect to Sudomemo when your Internet Service Provider does not work with custom DNS.
|
|
||||||
|
A small local DNS helper for connecting Flipnote Studio to **RexiMemo** when a network or ISP does not reliably allow the console to use RexiMemo's public DNS directly.
|
||||||
|
|
||||||
|
It runs on a computer on the same local network as the Nintendo DSi. Flipnote Studio's Nintendo/Hatena hostnames are redirected to RexiMemo, while unrelated DNS lookups are forwarded to a normal upstream resolver.
|
||||||
|
|
||||||
|
## Default RexiMemo routing
|
||||||
|
|
||||||
|
reximemoDNS redirects these hostnames to RexiMemo's legacy service address:
|
||||||
|
|
||||||
|
- `nas.nintendowifi.net`
|
||||||
|
- `flipnote.hatena.com`
|
||||||
|
- `ugomemo.hatena.ne.jp`
|
||||||
|
|
||||||
|
The default RexiMemo server IP is **141.147.76.115**. All other DNS queries are forwarded to **8.8.8.8** by default.
|
||||||
|
|
||||||
|
`conntest.nintendowifi.net` is deliberately not intercepted, matching RexiMemo's server-side DNS configuration.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
The setup process does not differ from what is shown at https://flipnot.es/setup except for the values to enter in your custom DNS settings.
|
1. Put the computer running reximemoDNS and the DSi on the same local network.
|
||||||
|
2. Install the requirements.
|
||||||
|
3. Run reximemoDNS as Administrator/root because DNS uses port 53.
|
||||||
|
4. Enter the **Primary DNS** address printed by the program into the DSi connection settings.
|
||||||
|
5. Use `008.008.008.008` as Secondary DNS.
|
||||||
|
6. Save the connection and open Flipnote Studio → Flipnote Hatena.
|
||||||
|
|
||||||
First, make sure that your console is connected to the same network as this computer.
|
### Install requirements
|
||||||
|
|
||||||
Please submit any issues to Sudomemo Support.
|
|
||||||
|
|
||||||
## Running on Windows:
|
|
||||||
|
|
||||||
Run the .exe provided in this release. You may have to click past a warning from Windows SmartScreen (as it sometimes turns its nose up at applications bundled by PyInstaller) or your firewall (as it needs to listen for DNS requests).
|
|
||||||
|
|
||||||
## Running on anything else:
|
|
||||||
|
|
||||||
Required packages are installable with the following command:
|
|
||||||
|
|
||||||
sudo python3 -m pip install -r requirements.txt
|
|
||||||
|
|
||||||
Required Python version: 3.6+, tested with 3.9.9
|
|
||||||
|
|
||||||
sudo python3 sudomemoDNS.py
|
|
||||||
|
|
||||||
Note: `python3` is assumed to be the name of your python binary, if it is not then it will need to be substituted with the name and/or path of your binary.
|
|
||||||
|
|
||||||
## What you'll see when it starts up
|
|
||||||
|
|
||||||
+===============================+
|
|
||||||
| Sudomemo DNS Server |
|
|
||||||
| Version 1.2.1 |
|
|
||||||
+===============================+
|
|
||||||
|
|
||||||
== Welcome to sudomemoDNS! ==
|
|
||||||
This server will allow you to connect to Sudomemo when your Internet Service Provider does
|
|
||||||
not work with custom DNS.
|
|
||||||
|
|
||||||
== How To Use ==
|
|
||||||
First, make sure that your console is connected to the same network as this computer.
|
|
||||||
|
|
||||||
Then, put these settings in for DNS on your console:
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Primary DNS: XXX.XXX.XXX.XXX (NOTE: This value will be unique when you run the program)
|
|
||||||
Secondary DNS: 008.008.008.008
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
== Getting Help ==
|
|
||||||
Need help? Visit our Discord server or check out https://support.sudomemo.net
|
|
||||||
|
|
||||||
## Building on Windows
|
|
||||||
|
|
||||||
- Install Python 3.6+ (preferably 3.9). Make sure to install the x86 version for 32 bit targets.
|
|
||||||
|
|
||||||
- Install all requirements via the following command as an administrator
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Run
|
||||||
|
|
||||||
|
Windows, from an Administrator terminal:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python reximemoDNS.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Linux/macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo python3 reximemoDNS.py
|
||||||
|
```
|
||||||
|
|
||||||
|
When it starts, it prints the LAN address to enter on the DSi.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The defaults are already set for the current RexiMemo server, but they can be changed with environment variables:
|
||||||
|
|
||||||
|
- `REXIMEMODNS_SERVER_IP` — RexiMemo legacy-service IPv4 address. Default: `141.147.76.115`
|
||||||
|
- `REXIMEMODNS_UPSTREAM_DNS` — resolver used for all non-RexiMemo names. Default: `8.8.8.8`
|
||||||
|
- `REXIMEMODNS_BIND_IP` — local interface/address on which to listen. By default, reximemoDNS detects the computer's LAN IPv4 address.
|
||||||
|
- `REXIMEMODNS_PORT` — DNS listening port. Default: `53`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REXIMEMODNS_SERVER_IP=141.147.76.115 REXIMEMODNS_UPSTREAM_DNS=1.1.1.1 sudo python3 reximemoDNS.py
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:REXIMEMODNS_SERVER_IP="141.147.76.115"
|
||||||
|
$env:REXIMEMODNS_UPSTREAM_DNS="1.1.1.1"
|
||||||
|
python reximemoDNS.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If the program cannot bind to port 53, run it as Administrator/root and make sure another DNS server is not already using that port.
|
||||||
|
|
||||||
|
If no console requests appear in the log, check the computer firewall and confirm the DSi's Primary DNS exactly matches the address printed by reximemoDNS.
|
||||||
|
|
||||||
|
If ordinary websites or the DSi connection test stop resolving while reximemoDNS is running, verify that `REXIMEMODNS_UPSTREAM_DNS` points to a reachable DNS resolver.
|
||||||
|
|
||||||
|
For RexiMemo support, join the Discord server: **https://discord.gg/K5mKWvaruF**
|
||||||
|
|
||||||
|
## Building a Windows executable
|
||||||
|
|
||||||
|
Install Nuitka and its build dependencies:
|
||||||
|
|
||||||
|
```powershell
|
||||||
python -m pip install -r requirements.txt nuitka zstandard
|
python -m pip install -r requirements.txt nuitka zstandard
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run the following (answer yes to any prompts):
|
Then build:
|
||||||
|
|
||||||
```shell
|
```powershell
|
||||||
python -m nuitka --standalone --onefile --windows-icon-from-ico=sudomemoDNS_icon.ico -o sudomemoDNS.exe sudomemoDNS.py
|
python -m nuitka --standalone --onefile -o reximemoDNS.exe reximemoDNS.py
|
||||||
```
|
```
|
||||||
|
|
||||||
- Run `sudomemoDNS.exe`
|
No RexiMemo-specific icon is bundled in this source tree, so the executable uses the default application icon unless you supply one when building.
|
||||||
|
|
||||||
|
## License and attribution
|
||||||
|
|
||||||
|
reximemoDNS is derived from the open-source `sudomemoDNS` project by Austin Burk / Team Sudomemo. The original MIT copyright and license are preserved in `LICENSE`.
|
||||||
|
|
||||||
|
See `NOTICE.md` for attribution and RexiMemo-specific changes.
|
||||||
+1
-2
@@ -1,2 +1 @@
|
|||||||
requests
|
dnslib>=0.9.24
|
||||||
dnslib
|
|
||||||
+244
@@ -0,0 +1,244 @@
|
|||||||
|
# reximemoDNS
|
||||||
|
#
|
||||||
|
# RexiMemo adaptation of the MIT-licensed sudomemoDNS project.
|
||||||
|
# See LICENSE and NOTICE.md for attribution.
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from ipaddress import IPv4Address, ip_address
|
||||||
|
import os
|
||||||
|
from socket import AF_INET, SOCK_DGRAM, socket
|
||||||
|
from sys import platform
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from dnslib import A, DNSLabel, DNSRecord, QTYPE, RCODE, RR
|
||||||
|
from dnslib.server import DNSServer
|
||||||
|
|
||||||
|
|
||||||
|
REXIMEMODNS_VERSION = "1.0.0"
|
||||||
|
DEFAULT_REXIMEMO_SERVER_IP = "141.147.76.115"
|
||||||
|
DEFAULT_UPSTREAM_DNS = "8.8.8.8"
|
||||||
|
|
||||||
|
# Keep this list aligned with RexiMemo's LOCAL_DNS_HOSTS in network_services.py.
|
||||||
|
# conntest.nintendowifi.net is intentionally not intercepted.
|
||||||
|
REXIMEMO_HOSTS = (
|
||||||
|
"nas.nintendowifi.net",
|
||||||
|
"flipnote.hatena.com",
|
||||||
|
"ugomemo.hatena.ne.jp",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_platform():
|
||||||
|
platforms = {
|
||||||
|
"linux": "Linux",
|
||||||
|
"linux1": "Linux",
|
||||||
|
"linux2": "Linux",
|
||||||
|
"darwin": "macOS",
|
||||||
|
"win32": "Windows",
|
||||||
|
}
|
||||||
|
return platforms.get(platform, platform)
|
||||||
|
|
||||||
|
|
||||||
|
def format_ip(address):
|
||||||
|
"""Format an IPv4 address the way Nintendo connection screens display it."""
|
||||||
|
octets = str(address).split(".")
|
||||||
|
return ".".join(f"{int(octet):03d}" for octet in octets)
|
||||||
|
|
||||||
|
|
||||||
|
def _ipv4_env(name, default):
|
||||||
|
value = (os.environ.get(name) or default).strip()
|
||||||
|
try:
|
||||||
|
parsed = ip_address(value)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"[ERROR] {name} must be a valid IPv4 address; got {value!r}") from exc
|
||||||
|
if not isinstance(parsed, IPv4Address):
|
||||||
|
raise SystemExit(f"[ERROR] {name} must be an IPv4 address; got {value!r}")
|
||||||
|
return str(parsed)
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip():
|
||||||
|
configured = (os.environ.get("REXIMEMODNS_BIND_IP") or "").strip()
|
||||||
|
if configured:
|
||||||
|
return _ipv4_env("REXIMEMODNS_BIND_IP", configured)
|
||||||
|
|
||||||
|
probe = socket(AF_INET, SOCK_DGRAM)
|
||||||
|
try:
|
||||||
|
# The destination does not need to be reachable. Connecting a UDP socket
|
||||||
|
# is only used to discover which local IPv4 address the OS would route.
|
||||||
|
probe.connect(("10.255.255.255", 1))
|
||||||
|
return probe.getsockname()[0]
|
||||||
|
except OSError:
|
||||||
|
return "127.0.0.1"
|
||||||
|
finally:
|
||||||
|
probe.close()
|
||||||
|
|
||||||
|
|
||||||
|
def _port_env():
|
||||||
|
raw = (os.environ.get("REXIMEMODNS_PORT") or "53").strip()
|
||||||
|
try:
|
||||||
|
value = int(raw)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"[ERROR] REXIMEMODNS_PORT must be a number; got {raw!r}") from exc
|
||||||
|
if not 1 <= value <= 65535:
|
||||||
|
raise SystemExit("[ERROR] REXIMEMODNS_PORT must be between 1 and 65535")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
MY_IP = get_ip()
|
||||||
|
REXIMEMO_SERVER_IP = _ipv4_env("REXIMEMODNS_SERVER_IP", DEFAULT_REXIMEMO_SERVER_IP)
|
||||||
|
UPSTREAM_DNS = _ipv4_env("REXIMEMODNS_UPSTREAM_DNS", DEFAULT_UPSTREAM_DNS)
|
||||||
|
DNS_PORT = _port_env()
|
||||||
|
SERIAL = int((datetime.utcnow() - datetime(1970, 1, 1)).total_seconds())
|
||||||
|
|
||||||
|
|
||||||
|
def _print_banner():
|
||||||
|
print("+===============================+")
|
||||||
|
print("| RexiMemo DNS Server |")
|
||||||
|
print(f"| Version {REXIMEMODNS_VERSION:<13}|")
|
||||||
|
print("+===============================+\n")
|
||||||
|
|
||||||
|
print("== Welcome to reximemoDNS! ==")
|
||||||
|
print(
|
||||||
|
"This local DNS helper routes Flipnote Studio's Nintendo/Hatena service names "
|
||||||
|
"to RexiMemo while forwarding unrelated DNS normally.\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
print("== How To Use ==")
|
||||||
|
print("First, make sure that your console is connected to the same network as this computer.\n")
|
||||||
|
print("Then, put these settings in for DNS on your console:")
|
||||||
|
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
|
||||||
|
print(f"Primary DNS: {format_ip(MY_IP)}")
|
||||||
|
print(f"Secondary DNS: {format_ip(DEFAULT_UPSTREAM_DNS)}")
|
||||||
|
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
|
||||||
|
|
||||||
|
print("== RexiMemo Routing ==")
|
||||||
|
print(f"RexiMemo server: {REXIMEMO_SERVER_IP}")
|
||||||
|
for hostname in REXIMEMO_HOSTS:
|
||||||
|
print(f" {hostname} -> {REXIMEMO_SERVER_IP}")
|
||||||
|
print(f"Other DNS queries -> {UPSTREAM_DNS}\n")
|
||||||
|
|
||||||
|
print("== Getting Help ==")
|
||||||
|
print("Join the RexiMemo Discord server: https://discord.gg/K5mKWvaruF\n")
|
||||||
|
|
||||||
|
|
||||||
|
class RexiMemoDNSLogger(object):
|
||||||
|
def log_recv(self, handler, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def log_send(self, handler, data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def log_request(self, handler, request):
|
||||||
|
qname = str(request.q.qname).rstrip(".")
|
||||||
|
qtype = QTYPE.get(request.q.qtype, str(request.q.qtype))
|
||||||
|
print(
|
||||||
|
"[INFO] DNS request from %s: %s %s"
|
||||||
|
% (handler.client_address[0], qtype, qname)
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_reply(self, handler, reply):
|
||||||
|
answer_count = len(reply.rr)
|
||||||
|
print(
|
||||||
|
"[INFO] DNS response to %s: %d answer%s"
|
||||||
|
% (handler.client_address[0], answer_count, "" if answer_count == 1 else "s")
|
||||||
|
)
|
||||||
|
|
||||||
|
def log_error(self, handler, error):
|
||||||
|
print("[ERROR] Invalid DNS request from %s: %s" % (handler.client_address[0], error))
|
||||||
|
|
||||||
|
def log_truncated(self, handler, reply):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def log_data(self, dnsobj):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Resolver:
|
||||||
|
def __init__(self):
|
||||||
|
self.reximemo_names = {DNSLabel(name) for name in REXIMEMO_HOSTS}
|
||||||
|
|
||||||
|
def _reximemo_reply(self, request):
|
||||||
|
reply = request.reply()
|
||||||
|
# Mirror RexiMemo's own DNS behavior: A/ANY gets the legacy-service IPv4
|
||||||
|
# address. Other query types get an authoritative empty NOERROR response.
|
||||||
|
if request.q.qtype in (QTYPE.A, QTYPE.ANY):
|
||||||
|
reply.add_answer(
|
||||||
|
RR(
|
||||||
|
rname=request.q.qname,
|
||||||
|
rtype=QTYPE.A,
|
||||||
|
rclass=1,
|
||||||
|
ttl=0,
|
||||||
|
rdata=A(REXIMEMO_SERVER_IP),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def _forward(self, request, handler):
|
||||||
|
try:
|
||||||
|
raw = request.send(
|
||||||
|
UPSTREAM_DNS,
|
||||||
|
53,
|
||||||
|
tcp=(getattr(handler, "protocol", "udp") == "tcp"),
|
||||||
|
timeout=3,
|
||||||
|
)
|
||||||
|
reply = DNSRecord.parse(raw)
|
||||||
|
# Never relay a mismatched DNS transaction as if it answered this query.
|
||||||
|
if reply.header.id != request.header.id:
|
||||||
|
raise ValueError("upstream DNS transaction ID mismatch")
|
||||||
|
return reply
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"[ERROR] Upstream DNS lookup via {UPSTREAM_DNS} failed: {exc}")
|
||||||
|
reply = request.reply()
|
||||||
|
reply.header.rcode = RCODE.SERVFAIL
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def resolve(self, request, handler):
|
||||||
|
if request.q.qname in self.reximemo_names:
|
||||||
|
return self._reximemo_reply(request)
|
||||||
|
return self._forward(request, handler)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
_print_banner()
|
||||||
|
print("[INFO] Starting reximemoDNS...")
|
||||||
|
print("[INFO] Detected operating system:", get_platform())
|
||||||
|
|
||||||
|
current_platform = get_platform()
|
||||||
|
if current_platform in {"Linux", "macOS"}:
|
||||||
|
print("[INFO] DNS uses UDP/TCP port 53, so this normally must run as root.")
|
||||||
|
print("[INFO] If no requests appear, also check whether another service is already using port 53.")
|
||||||
|
elif current_platform == "Windows":
|
||||||
|
print("[INFO] Windows may ask you to allow reximemoDNS through the firewall.")
|
||||||
|
|
||||||
|
resolver = Resolver()
|
||||||
|
logger = RexiMemoDNSLogger()
|
||||||
|
|
||||||
|
try:
|
||||||
|
servers = [
|
||||||
|
DNSServer(resolver=resolver, port=DNS_PORT, address=MY_IP, tcp=True, logger=logger),
|
||||||
|
DNSServer(resolver=resolver, port=DNS_PORT, address=MY_IP, tcp=False, logger=logger),
|
||||||
|
]
|
||||||
|
except PermissionError:
|
||||||
|
print("[ERROR] Permission denied while opening DNS port. Run as Administrator/root.")
|
||||||
|
raise SystemExit(1)
|
||||||
|
except OSError as exc:
|
||||||
|
print(f"[ERROR] Could not bind DNS server to {MY_IP}:{DNS_PORT}: {exc}")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
print(f"[INFO] reximemoDNS is ready on {MY_IP}:{DNS_PORT} (UDP/TCP).")
|
||||||
|
print("[INFO] Waiting for DNS requests from your console...")
|
||||||
|
|
||||||
|
for server in servers:
|
||||||
|
server.start_thread()
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
sleep(0.1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[INFO] Stopping reximemoDNS...")
|
||||||
|
finally:
|
||||||
|
for server in servers:
|
||||||
|
server.stop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user
