Provide options for binding IP and ports

This commit is contained in:
q13x 2022-12-20 11:31:37 -08:00
parent 083762fce1
commit 2e75776ff1
3 changed files with 148 additions and 25 deletions

View File

@ -2,10 +2,11 @@ import { Config } from "./types.js";
export const config: Config = { export const config: Config = {
name: "BasedProxy", name: "BasedProxy",
port: 80, // 443 if using TLS bindHost: "0.0.0.0",
bindPort: 80, // 443 if using TLS
maxPlayers: 20, maxPlayers: 20,
motd: { motd: {
iconURL: null, iconURL: "./icon.webp",
l1: "hi", l1: "hi",
l2: "lol" l2: "lol"
}, },
@ -18,4 +19,8 @@ export const config: Config = {
key: null, key: null,
cert: null cert: null
} }
} }
export const BRANDING: Readonly<string> = Object.freeze("EaglerXProxy")
export const VERSION: Readonly<string> = "1.0.0"
export const NETWORK_VERSION: Readonly<string> = Object.freeze(BRANDING + "/" + VERSION)

View File

@ -2,12 +2,11 @@ import { readFileSync } from "fs";
import * as http from "http" import * as http from "http"
import * as https from "https" import * as https from "https"
import { WebSocketServer } from "ws"; import { WebSocketServer } from "ws";
import { ProxiedPlayer } from "./classes.js"; import { BRANDING, config, NETWORK_VERSION, VERSION } from "./config.js";
import { config } from "./config.js";
import { handlePacket } from "./listener.js"; import { handlePacket } from "./listener.js";
import { Logger } from "./logger.js"; import { Logger } from "./logger.js";
import { BRANDING, NETWORK_VERSION, VERSION } from "./meta.js"; import { disconnect, generateMOTDImage } from "./utils.js";
import { State } from "./types.js"; import { ChatColor, ProxiedPlayer, State } from "./types.js";
import { genUUID } from "./utils.js"; import { genUUID } from "./utils.js";
const logger = new Logger("EagXProxy") const logger = new Logger("EagXProxy")
@ -22,25 +21,20 @@ global.PROXY = {
secure: false, secure: false,
proxyUUID: genUUID(config.name), proxyUUID: genUUID(config.name),
MOTD: { MOTD: {
icon: null, icon: config.motd.iconURL ? await generateMOTDImage(readFileSync(config.motd.iconURL)) : null,
motd: [config.motd.l1, config.motd.l2] motd: [config.motd.l1, config.motd.l2]
}, },
playerStats: {
max: config.maxPlayers,
onlineCount: 0
},
wsServer: null, wsServer: null,
players: new Map(), players: new Map(),
logger: logger, logger: logger,
config: config config: config
} }
PROXY.playerStats.onlineCount = PROXY.players.size
let server: WebSocketServer let server: WebSocketServer
if (PROXY.config.security.enabled) { if (PROXY.config.security.enabled) {
logger.info(`Starting SECURE WebSocket proxy on port ${config.port}...`) logger.info(`Starting SECURE WebSocket proxy on port ${config.bindPort}...`)
if (process.env.REPL_SLUG) { if (process.env.REPL_SLUG) {
logger.warn("You appear to be running the proxy on Repl.it with encryption enabled. Please note that Repl.it by default provides encryption, and enabling encryption may or may not prevent you from connecting to the server.") logger.warn("You appear to be running the proxy on Repl.it with encryption enabled. Please note that Repl.it by default provides encryption, and enabling encryption may or may not prevent you from connecting to the server.")
} }
@ -48,29 +42,54 @@ if (PROXY.config.security.enabled) {
server: https.createServer({ server: https.createServer({
key: readFileSync(config.security.key), key: readFileSync(config.security.key),
cert: readFileSync(config.security.cert) cert: readFileSync(config.security.cert)
}).listen(config.port) }).listen(config.bindPort, config.bindHost)
}) })
} else { } else {
logger.info(`Starting INSECURE WebSocket proxy on port ${config.port}...`) logger.info(`Starting INSECURE WebSocket proxy on port ${config.bindPort}...`)
server = new WebSocketServer({ server = new WebSocketServer({
port: config.port port: config.bindPort,
host: config.bindHost
}) })
} }
PROXY.wsServer = server PROXY.wsServer = server
server.addListener('connection', c => { server.addListener('connection', c => {
connectionLogger.debug(`[CONNECTION] New inbound WebSocket connection from [/${(c as any)._socket.remoteAddress}:${(c as any)._socket.remotePort}]. (${(c as any)._socket.remotePort} -> ${config.port})`) connectionLogger.debug(`[CONNECTION] New inbound WebSocket connection from [/${(c as any)._socket.remoteAddress}:${(c as any)._socket.remotePort}]. (${(c as any)._socket.remotePort} -> ${config.bindPort})`)
const plr = new ProxiedPlayer() const plr = new ProxiedPlayer()
plr.ws = c plr.ws = c
plr.ip = (c as any)._socket.remoteAddress plr.ip = (c as any)._socket.remoteAddress
plr.remotePort = (c as any)._socket.remotePort plr.remotePort = (c as any)._socket.remotePort
plr.state = State.PRE_HANDSHAKE plr.state = State.PRE_HANDSHAKE
plr.queuedEaglerSkinPackets = []
c.on('message', msg => { c.on('message', msg => {
handlePacket(msg as Buffer, plr) handlePacket(msg as Buffer, plr)
}) })
}) })
server.on('listening', () => { server.on('listening', () => {
logger.info(`Successfully started${config.security.enabled ? " [secure]" : ""} WebSocket proxy on port ${config.port}!`) logger.info(`Successfully started${config.security.enabled ? " [secure]" : ""} WebSocket proxy on port ${config.bindPort}!`)
})
process.on('uncaughtException', err => {
logger.error(`An uncaught exception was caught! Exception: ${err.stack ?? err}`)
})
process.on('unhandledRejection', err => {
logger.error(`An unhandled promise rejection was caught! Rejection: ${(err != null ? (err as any).stack : err) ?? err}`)
})
process.on('SIGTERM', () => {
logger.info("Cleaning up before exiting...")
for (const [username, plr] of PROXY.players) {
if (plr.remoteConnection != null) plr.remoteConnection.end()
disconnect(plr, ChatColor.YELLOW + "Proxy is shutting down.")
}
process.exit(0)
})
process.on('SIGINT', () => {
logger.info("Cleaning up before exiting...")
for (const [username, plr] of PROXY.players) {
if (plr.remoteConnection != null) plr.remoteConnection.end()
disconnect(plr, ChatColor.YELLOW + "Proxy is shutting down.")
}
process.exit(0)
}) })

111
types.ts
View File

@ -1,8 +1,9 @@
import { randomUUID } from "crypto" import { randomUUID } from "crypto"
import { WebSocketServer } from "ws" import { Client } from "minecraft-protocol"
import { ProxiedPlayer } from "./classes.js" import { WebSocketServer, WebSocket } from "ws"
import { BRANDING, VERSION, NETWORK_VERSION } from "./config.js"
import { EaglerSkinPacketId, SkinId } from "./eaglerPacketDef.js"
import { Logger } from "./logger.js" import { Logger } from "./logger.js"
import { BRANDING, NETWORK_VERSION, VERSION } from "./meta.js"
export type UUID = ReturnType<typeof randomUUID> export type UUID = ReturnType<typeof randomUUID>
@ -13,7 +14,7 @@ export enum State {
} }
export type MOTD = { export type MOTD = {
icon?: Int8Array, // 16384 icon?: Buffer, // 16384
motd: [string, string] motd: [string, string]
} }
@ -32,7 +33,6 @@ export type ProxyGlobals = {
proxyUUID: UUID, proxyUUID: UUID,
MOTD: MOTD, MOTD: MOTD,
playerStats: PlayerStats,
wsServer: WebSocketServer, wsServer: WebSocketServer,
players: Map<string, ProxiedPlayer>, players: Map<string, ProxiedPlayer>,
logger: Logger, logger: Logger,
@ -41,7 +41,8 @@ export type ProxyGlobals = {
export type Config = { export type Config = {
name: string, name: string,
port: number, bindPort: number,
bindHost: string,
maxPlayers: number, maxPlayers: number,
motd: { motd: {
iconURL?: string, iconURL?: string,
@ -57,4 +58,102 @@ export type Config = {
key: string, key: string,
cert: string cert: string
} }
}
export enum ChatColor {
BLACK = "§0",
DARK_BLUE = "§1",
DARK_GREEN = "§2",
DARK_CYAN = "§3",
DARK_RED = "§4",
PURPLE = "§5",
GOLD = "§6",
GRAY = "§7",
DARK_GRAY = "§8",
BLUE = "§9",
BRIGHT_GREEN = "§a",
CYAN = "§b",
RED = "§c",
PINK = "§d",
YELLOW = "§e",
WHITE = "§f",
// text styling
OBFUSCATED = '§k',
BOLD = '§l',
STRIKETHROUGH = '§m',
UNDERLINED = '§n',
ITALIC = '§o',
RESET = '§r'
}
export type ChatExtra = {
text: string,
bold?: boolean,
italic?: boolean,
underlined?: boolean,
strikethrough?: boolean,
obfuscated?: boolean,
color?: ChatColor | 'reset'
}
export type Chat = {
text?: string,
bold?: boolean,
italic?: boolean,
underlined?: boolean,
strikethrough?: boolean,
obfuscated?: boolean,
color?: ChatColor | 'reset',
extra?: ChatExtra[]
}
export class ProxiedPlayer {
public username: string
public uuid: string
public clientBrand: string
public state: State
public ws: WebSocket
public ip: string
public remotePort: number
public remoteConnection: Client
public skin: {
type: "CUSTOM" | "BUILTIN",
skinId?: number,
customSkin?: Buffer
}
public queuedEaglerSkinPackets: UnpackedChannelMessage[]
}
export enum ChannelMessageType {
CLIENT = 0x17,
SERVER = 0x3f
}
export type UnpackedChannelMessage = {
channel: string,
data: Buffer,
type: ChannelMessageType
}
export type DecodedCFetchSkin = {
id: EaglerSkinPacketId.C_FETCH_SKIN,
uuid: UUID
}
export type DecodedSSkinFetchBuiltin = {
id: EaglerSkinPacketId.S_SKIN_DL_BI,
uuid: UUID,
skinId: SkinId
}
export type DecodedSSkinDl = {
id: EaglerSkinPacketId.S_SKIN_DL,
uuid: UUID,
skin: Buffer
}
export type DecodedCSkinReq = {
id: EaglerSkinPacketId.C_REQ_SKIN,
uuid: UUID,
url: string
} }