-
Notifications
You must be signed in to change notification settings - Fork 131
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add network diagnostic tool to advanced settings section (#391)
Co-authored-by: Nikhil Narayana <[email protected]> Co-authored-by: Vince Au <[email protected]>
- Loading branch information
1 parent
a22e8af
commit 01957f6
Showing
14 changed files
with
907 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
import type { PortMapping } from "@common/types"; | ||
import { NatType, Presence } from "@common/types"; | ||
import { createPmpClient, createUpnpClient } from "@xmcl/nat-api"; | ||
import { gateway4async } from "default-gateway"; | ||
import Tracer from "nodejs-traceroute"; | ||
import { createServer, request } from "stun"; | ||
|
||
const STUN_SERVER_URL1 = "stun1.l.google.com:19302"; | ||
const STUN_SERVER_URL2 = "stun2.l.google.com:19302"; | ||
|
||
export async function getNetworkDiagnostics(): Promise<{ | ||
address: string; | ||
cgnat: Presence; | ||
natType: NatType; | ||
portMapping: PortMapping; | ||
}> { | ||
let address = ""; | ||
let natType = NatType.FAILED; | ||
let portMapping = { upnp: Presence.FAILED, natpmp: Presence.FAILED }; | ||
let cgnat = Presence.FAILED; | ||
try { | ||
portMapping = await getPortMappingPresence(); | ||
({ address, natType } = await getNatType()); | ||
cgnat = await getCgnatPresence(address); | ||
} catch (err) { | ||
// just return what we have | ||
} | ||
return { address, cgnat, natType, portMapping }; | ||
} | ||
|
||
async function getNatType(): Promise<{ address: string; natType: NatType }> { | ||
const stunServer = createServer({ type: "udp4" }); | ||
const stunResponse1 = await request(STUN_SERVER_URL1, { server: stunServer }); | ||
const stunResponse2 = await request(STUN_SERVER_URL2, { server: stunServer }); | ||
const address1 = stunResponse1.getXorAddress(); | ||
const address2 = stunResponse2.getXorAddress(); | ||
stunServer.close(); | ||
return { address: address1.address, natType: address1.port === address2.port ? NatType.NORMAL : NatType.SYMMETRIC }; | ||
} | ||
|
||
async function getPortMappingPresence(): Promise<PortMapping> { | ||
let upnpPresence = Presence.UNKNOWN; | ||
const upnpClient = await createUpnpClient(); | ||
const upnpPromise = upnpClient | ||
.externalIp() | ||
.then(() => { | ||
upnpPresence = Presence.PRESENT; | ||
}) | ||
.catch(() => { | ||
upnpPresence = Presence.ABSENT; | ||
}) | ||
.finally(() => { | ||
upnpClient.destroy(); | ||
}); | ||
|
||
let natpmpPresence = Presence.UNKNOWN; | ||
const pmpClient = await createPmpClient((await gateway4async()).gateway); | ||
const pmpPromise = new Promise((resolve, reject) => { | ||
// library does not use a timeout for NAT-PMP, so we do it ourselves. | ||
const timeout = setTimeout(() => { | ||
reject("NAT-PMP timeout"); | ||
}, 1800); // same as library UPnP timeout | ||
pmpClient | ||
.externalIp() | ||
.then(resolve) | ||
.catch(reject) | ||
.finally(() => { | ||
clearTimeout(timeout); | ||
}); | ||
}) | ||
.then(() => { | ||
natpmpPresence = Presence.PRESENT; | ||
}) | ||
.catch(() => { | ||
natpmpPresence = Presence.ABSENT; | ||
}) | ||
.finally(() => { | ||
pmpClient.close(); | ||
}); | ||
|
||
await Promise.all([upnpPromise, pmpPromise]); | ||
return { upnp: upnpPresence, natpmp: natpmpPresence } as PortMapping; | ||
} | ||
|
||
async function getCgnatPresence(address: string): Promise<Presence> { | ||
return new Promise((resolve, reject) => { | ||
let hops = 0; | ||
const tracer = new Tracer(); | ||
tracer.on("hop", () => { | ||
hops++; | ||
}); | ||
const timeout = setTimeout(() => { | ||
if (hops > 1) { | ||
resolve(Presence.PRESENT); | ||
} else { | ||
reject("CGNAT timeout"); | ||
} | ||
}, 9000); | ||
tracer.on("close", (code) => { | ||
clearTimeout(timeout); | ||
if (code === 0 && hops > 0) { | ||
resolve(hops === 1 ? Presence.ABSENT : Presence.PRESENT); | ||
} else { | ||
reject(code); | ||
} | ||
}); | ||
tracer.trace(address); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
src/renderer/containers/Settings/NetworkDiagnostic/CgnatCommandSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
import Button from "@mui/material/Button"; | ||
import InputBase from "@mui/material/InputBase"; | ||
import Typography from "@mui/material/Typography"; | ||
import React from "react"; | ||
|
||
const buttonStyle = { marginLeft: "8px", width: "96px" }; | ||
const hiddenIpAddress = "···.···.···.···"; | ||
const inputBaseCss = css` | ||
padding: 4px 8px; | ||
border-radius: 10px; | ||
background-color: rgba(0, 0, 0, 0.4); | ||
font-size: 1em; | ||
margin: 8px 0; | ||
`; | ||
|
||
// This is used to correct an observed 1px vertical misalignment | ||
const AlignCenterDiv = styled.div` | ||
display: flex; | ||
align-items: center; | ||
`; | ||
|
||
const DialogBody = styled.div` | ||
margin-bottom: 1em; | ||
`; | ||
|
||
type CgnatCommandSectionProps = { | ||
address: string; | ||
}; | ||
export const CgnatCommandSection = ({ address }: CgnatCommandSectionProps) => { | ||
const [cgnatCommandHidden, setCgnatCommandHidden] = React.useState(true); | ||
const onCgnatCommandShowHide = () => { | ||
setCgnatCommandHidden(!cgnatCommandHidden); | ||
}; | ||
const tracerouteCommand = window.electron.common.isWindows ? "tracert" : "traceroute"; | ||
const cgnatCommand = `${tracerouteCommand} ${address}`; | ||
const displayedCgnatCommand = `${tracerouteCommand} ${cgnatCommandHidden ? hiddenIpAddress : address}`; | ||
const [cgnatCommandCopied, setCgnatCommandCopied] = React.useState(false); | ||
const onCgnatCommandCopy = React.useCallback(() => { | ||
window.electron.clipboard.writeText(cgnatCommand); | ||
setCgnatCommandCopied(true); | ||
window.setTimeout(() => setCgnatCommandCopied(false), 2000); | ||
}, [cgnatCommand]); | ||
|
||
return ( | ||
<div> | ||
<Typography variant="subtitle2">Run this command</Typography> | ||
<AlignCenterDiv> | ||
<InputBase css={inputBaseCss} disabled={true} value={displayedCgnatCommand} /> | ||
<Button variant="contained" color="secondary" onClick={onCgnatCommandShowHide} style={buttonStyle}> | ||
{cgnatCommandHidden ? "Reveal" : "Hide"} | ||
</Button> | ||
<Button variant="contained" color="secondary" onClick={onCgnatCommandCopy} style={buttonStyle}> | ||
{cgnatCommandCopied ? "Copied!" : "Copy"} | ||
</Button> | ||
</AlignCenterDiv> | ||
<DialogBody>More than one hop to your external IP address indicates CGNAT or Double NAT (or VPN).</DialogBody> | ||
</div> | ||
); | ||
}; |
66 changes: 66 additions & 0 deletions
66
src/renderer/containers/Settings/NetworkDiagnostic/NatTypeSection.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { NatType } from "@common/types"; | ||
import { css } from "@emotion/react"; | ||
import styled from "@emotion/styled"; | ||
import Button from "@mui/material/Button"; | ||
import InputBase from "@mui/material/InputBase"; | ||
import Typography from "@mui/material/Typography"; | ||
import React from "react"; | ||
|
||
const buttonStyle = { marginLeft: "8px", width: "96px" }; | ||
const hiddenIpAddress = "···.···.···.···"; | ||
const inputBaseCss = css` | ||
padding: 4px 8px; | ||
border-radius: 10px; | ||
background-color: rgba(0, 0, 0, 0.4); | ||
font-size: 1em; | ||
margin: 8px 0; | ||
`; | ||
|
||
const DialogBody = styled.div` | ||
margin-bottom: 1em; | ||
`; | ||
|
||
const getIpAddressTitle = (natType: NatType) => { | ||
if (natType === NatType.FAILED) { | ||
return "Failed to determine IP Address"; | ||
} | ||
return "External IP Address"; | ||
}; | ||
|
||
type NatTypeSectionProps = { | ||
address: string; | ||
description: string; | ||
natType: NatType; | ||
title: string; | ||
}; | ||
export const NatTypeSection = ({ address, description, natType, title }: NatTypeSectionProps) => { | ||
const ipAddressTitle = getIpAddressTitle(natType); | ||
const [ipAddressCopied, setIpAddressCopied] = React.useState(false); | ||
const onIpAddressCopy = React.useCallback(() => { | ||
window.electron.clipboard.writeText(address); | ||
setIpAddressCopied(true); | ||
window.setTimeout(() => setIpAddressCopied(false), 2000); | ||
}, [address]); | ||
const [ipAddressHidden, setIpAddressHidden] = React.useState(true); | ||
const onIpAddressShowHide = () => { | ||
setIpAddressHidden(!ipAddressHidden); | ||
}; | ||
return ( | ||
<div> | ||
<Typography variant="subtitle2">{ipAddressTitle}</Typography> | ||
{natType !== NatType.FAILED && ( | ||
<DialogBody> | ||
<InputBase css={inputBaseCss} disabled={true} value={ipAddressHidden ? hiddenIpAddress : address} /> | ||
<Button variant="contained" color="secondary" onClick={onIpAddressShowHide} style={buttonStyle}> | ||
{ipAddressHidden ? "Reveal" : "Hide"} | ||
</Button> | ||
<Button variant="contained" color="secondary" onClick={onIpAddressCopy} style={buttonStyle}> | ||
{ipAddressCopied ? "Copied!" : "Copy"} | ||
</Button> | ||
</DialogBody> | ||
)} | ||
<Typography variant="subtitle2">{title}</Typography> | ||
<DialogBody>{description}</DialogBody> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.