Author SHA1 Message Date
KNEMC 58913254e0 区分了ENDXSID和ENDSID。移除了多余的路由表对象的isLoopback(),因为是否是环回是链路的属性,不是路由表的。 2026-08-31 10:57:08 +08:00
KNEMC 02d7bcd93b update JFreeChart 2026-08-30 21:46:01 +08:00
KNEMC 1e52e82d6d Merge branch 'master' of https://git.code.cq.cn/KNEMC/KLALB
# Conflicts:
#	.classpath
#	src/org/kne/cloud/network/klalb/KLALBControllerConfigItem.java
2026-08-30 21:42:29 +08:00
KNEMC 3023fe5b2a ntp和udp URI的GUI现在可以不写协议 2026-08-30 21:40:27 +08:00
SerinaNya 7896ee6638 🙈 add omo-slim files to .gitignore 2026-08-28 17:00:40 +08:00
SerinaNya ed94423855 🚑 recover fastcopy.dll 2026-08-28 16:53:37 +08:00
SerinaNya c3172a04b1 refactor(web)!: remove obsolete interfaces endpoint 2026-08-27 23:19:53 +08:00
SerinaNya cc803f47a7 feat(web): add /api/node-info endpoint for on-demand node detail queries
- Expose GET /api/node-info?address=<ipv6> endpoint in KLALBWebServer
- Return local deviceName and deviceDescription immediately for self node
- Perform asynchronous full node-info query (NODE_INFO_FULL_REQ) via
  KLALBRoutingProtocolAPIClient for remote nodes with 3s timeout fallback
2026-08-26 21:31:48 +08:00
KNEMC 3bb95988fc 增加enableTUN checkbox 2026-08-26 21:10:41 +08:00
KNEMC e87e3bb953 remove existed klalb-config.json 2026-08-26 20:28:31 +08:00
KNEMC 41db17f1ec remove klalb-config.json 2026-08-26 20:22:23 +08:00
SerinaNya 61868fe93e feat(config): switch webPort to URI-style webListen address
- replace integer webPort with MultiProtocolSocketAddress webListen
  (default: http://0.0.0.0:4665) in controller config
- rename Swing UI item to "Web API 监听地址" and validate as full URI
- update WebServer binding to honor configured host and port
- normalize legacy webPort integers to http://0.0.0.0:<port> on load
  and keep JSON API backward compatibility
- update i18n keys in zh_CN and en_US resource bundles
- update root klalb-config.json and AGENTS.md documentation
2026-08-26 19:42:26 +08:00
SerinaNya 9cea5ed493 feat(gui): add enable-Web-API toggle and web port settings
- new "Web API settings" section after security settings in the Swing
  options panel: checkbox bound to webUI plus a port text field bound
  to webPort (empty -> default 4665; non-numeric or out of 0-65535
  shows an invalid-port warning and aborts the save)
- load handler fills both controls from the current config item
- add i18n keys to klalb_zh_CN / klalb_en_US bundles
- include previously missing webUI/webPort fields in
  KLALBControllerConfigItem equals/hashCode
2026-08-26 00:36:24 +08:00
SerinaNya e0296e0ccd feat(srv6)!: replace openlines query with tiny/full node-info API
- routing-protocol JSON API: nodeinfotinyreq/resp returns device name
  only and is never gated; nodeinfofullreq/resp returns external
  endpoints + device name + description, where denyExternalEndpointQuery
  hides only the endpoint list (name/description always answer);
  legacy openlines* wire types removed - upgrade the whole mesh together
- rename misnamed openConnections -> externalEndpoints and
  denyConnection{Query,Broadcast} -> denyExternalEndpoint{Query,Broadcast};
  legacy config keys normalized on load because gson-2.1 lacks
  @SerializedName(alternate=...)
- remove TCP-based KLALBRemoteManagement, superseded by the HTTP API
- dashboard settings follow the renamed keys; update AGENTS.md
2026-08-26 00:23:21 +08:00
SerinaNya 9cea74943c fix(web): avoid gson-2.1 members-wrapped JSON responses; update AGENTS.md
- sendJsonResponse now serializes JsonElement via toString(), since gson-2.1's
  JSON_ELEMENT factory misses JsonObject/JsonArray subclasses and falls back to
  reflection (leaking the internal "members" field into every REST response)
- Document javac full-path requirement, pre-existing compile warnings,
  frontend page data flow, and gson quirks in AGENTS.md
2026-08-24 23:59:14 +08:00
SerinaNya 35c430114e update AGENTS.md 2026-08-24 00:13:33 +08:00
SerinaNya 6c7017bc75 feat(web): integrate embedded web server, REST/SSE APIs and modernize configuration
- Add embedded KLALBWebServer with REST API, SSE streaming (200ms) and SPA hosting
- Introduce enableTUN configuration flag with fallback and non-admin execution support
- Refactor LineTable and ConnectLineTable to openConnections and autoConnections
- Rename denyLineTableQuery/Broadcast to denyConnectionQuery/Broadcast with backwards compatibility
- Support runtime config hot-reloading and automatic persistence to klalb-config.json
- Update default Web API port to 4665 and update dashboard submodule reference
2026-08-24 00:01:24 +08:00
SerinaNya 041cc31c5f 🍱 add dashboard as submodule 2026-08-23 17:42:49 +08:00
SerinaNya cc811eddfe remove: ui ux pro max skill 2026-08-23 01:08:48 +08:00
SerinaNya 984031c6e2 add shadcn skill 2026-08-23 00:04:32 +08:00
SerinaNya 75ff49a9d1 add ui-ux-pro-max skill 2026-08-22 23:50:31 +08:00
SerinaNya d6ac0da75d update vscode java runtime settings 2026-08-22 23:50:12 +08:00
SerinaNya 455139dfe9 feat: 新增设备信息与额外路由设置,支持设备名广播及TUN显式禁用
- 设置页新增设备名称(单行)、设备描述(多行)、额外路由(CIDR列表)设置项
- RouterInfo 广播携带 deviceName,并在拓扑图节点与节点概览中展示;deviceDescription 保持本地概览显示
- 拓扑图节点标签支持多行文本绘制
- 节点概览面板新增 IP 地址及设备详情展示
- 支持将 TUNName 设为 null/"null"/空字符串时完全跳过 Wintun 虚拟网卡创建
- .classpath 容器改为标准 JavaSE-25 执行环境以同时兼容 JDK 25 与 26
- 新增 AGENTS.md 与 VS Code 调试及运行配置

BREAKING CHANGE: RouterInfo 在序列化末尾追加了 deviceName 字段(writeUTF),新旧版本节点混连时路由协议解析异常,需同步升级
2026-08-21 23:34:41 +08:00
KNEMC f0efa6d041 ntp和udp URI的GUI现在可以不写协议 2026-07-14 16:38:47 +08:00
KNEMC 314f90b5f3 隧道链接改成标准URI格式 2026-07-11 22:28:50 +08:00
KNEMC 053450429c Changes 2026-07-07 01:24:34 +08:00
KNEMC 8dcdb08755 Merge branch 'master' of https://gitee.com/knemc/klablb-load-balancing-system
# Conflicts:
#	src/org/kne/cloud/network/DatagramServerSocket.java
#	src/org/kne/cloud/network/congestion/DCTCP2CongestionAlgorithm.java
#	src/org/kne/cloud/network/congestion/DCTCPCongestionAlgorithm.java
#	src/org/kne/cloud/network/ipv6/AbstractIPv6NetworkLink.java
#	src/org/kne/cloud/network/ipv6/IPv6Packet.java
#	src/org/kne/cloud/network/ipv6/IPv6TUNLoopbackNetworkLink.java
#	src/org/kne/cloud/network/ipv6/LoopbackIPv6NetworkLink.java
#	src/org/kne/cloud/network/ipv6/RouteItem.java
#	src/org/kne/cloud/network/klalb/BindableConsumer.java
#	src/org/kne/cloud/network/klalb/KLALBController.java
#	src/org/kne/cloud/network/klalb/KLALBRemoteLink.java
#	src/org/kne/cloud/network/klalb/KLALBRemoteManagement.java
#	src/org/kne/cloud/network/klalb/KLALBVirtualDatagramSocketImpl.java
#	src/org/kne/cloud/network/klalb/KLALBVirtualRawSocketImpl.java
#	src/org/kne/cloud/network/klalb/KLALBVirtualSocketImpl.java
#	src/org/kne/cloud/network/klalb/ui/DialPanel.java
#	src/org/kne/cloud/network/klalb/ui/KLALBStateGUI3.java
#	src/org/kne/cloud/network/klalb/ui/LineMonitorGUI.java
#	src/org/kne/cloud/network/klalb/ui/TPanel2.java
#	src/org/kne/cloud/network/klalb/ui/UIEnv.java
#	src/org/kne/cloud/network/perf/Kperf.java
#	src/org/kne/cloud/network/srv6/IPv6PacketConsumer.java
#	src/org/kne/cloud/network/srv6/KLALBRoutingProtocol.java
#	src/org/kne/cloud/network/srv6/KLALBRoutingProtocolAPIClient.java
#	src/org/kne/cloud/network/srv6/SRv6Router.java
#	src/org/kne/cloud/network/tcp/TCPPacket.java
#	src/org/kne/cloud/network/te/DWRRLoadingBalanceAlgorithm.java
2026-07-07 00:29:03 +08:00
KNEMC ba61d3b5ae Changes 2026-07-07 00:26:38 +08:00
KNEMC 74e4360d71 Changes 2026-07-07 00:22:32 +08:00
116 changed files with 5887 additions and 2030 deletions
+277
View File
@@ -0,0 +1,277 @@
---
name: shadcn
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
user-invocable: false
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
---
# shadcn/ui
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
## Current Project Context
```json
!`npx shadcn@latest info --json`
```
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
## Principles
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
## Critical Rules
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
### Styling & Tailwind → [styling.md](./rules/styling.md)
- **`className` for layout, not styling.** Never override component colors or typography.
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
### Forms & Inputs → [forms.md](./rules/forms.md)
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
- **Option sets (27 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
### Component Structure → [composition.md](./rules/composition.md)
- **Items always inside their Group.** `SelectItem``SelectGroup`. `DropdownMenuItem``DropdownMenuGroup`. `CommandItem``CommandGroup`.
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
- **Callouts use `Alert`.** Don't build custom styled divs.
- **Empty states use `Empty`.** Don't build custom empty state markup.
- **Toast follows the project base.** Use `toast` from the `toast` component for
Base UI projects. Use `toast()` from `sonner` for Radix and React Aria
projects.
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
- **Use `Badge`** instead of custom styled spans.
### Icons → [icons.md](./rules/icons.md)
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
### Chat & Messaging → [chat.md](./rules/chat.md)
- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.
- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.
- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.
### CLI
- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.
- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
## Key Patterns
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
```tsx
// Form layout: FieldGroup + Field, not div + Label.
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" />
</Field>
</FieldGroup>
// Validation: data-invalid on Field, aria-invalid on the control.
<Field data-invalid>
<FieldLabel>Email</FieldLabel>
<Input aria-invalid />
<FieldDescription>Invalid email.</FieldDescription>
</Field>
// Icons in buttons: data-icon, no sizing classes.
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
// Spacing: gap-*, not space-y-*.
<div className="flex flex-col gap-4"> // correct
<div className="space-y-4"> // wrong
// Equal dimensions: size-*, not w-* h-*.
<Avatar className="size-10"> // correct
<Avatar className="w-10 h-10"> // wrong
// Status colors: Badge variants or semantic tokens, not raw colors.
<Badge variant="secondary">+20.1%</Badge> // correct
<span className="text-emerald-600">+20.1%</span> // wrong
```
## Component Selection
| Need | Use |
| -------------------------- | --------------------------------------------------------------------------------------------------- |
| Button/action | `Button` with appropriate variant |
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
| Toggle between 25 options | `ToggleGroup` + `ToggleGroupItem` |
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |
| Command palette | `Command` inside `Dialog` |
| Charts | `Chart` (wraps Recharts) |
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
| Empty states | `Empty` |
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |
## Key Fields
The injected project context contains these key fields:
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.
See [cli.md — `info` command](./cli.md) for the full field reference.
## Component Docs, Examples, and Usage
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
```bash
npx shadcn@latest docs button dialog select
```
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
## Workflow
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
3. **Find components**`npx shadcn@latest search`.
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
5. **Install or update**`npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
- **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.
- **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
- **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.
- **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
## Updating Components
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
3. Decide per file based on the diff:
- No local changes → safe to overwrite.
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
- User says "just update everything" → use `--overwrite`, but confirm first.
4. **Never use `--overwrite` without the user's explicit approval.**
## Quick Reference
```bash
# Create a new project.
npx shadcn@latest init --name my-app --preset base-nova
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
# Create a monorepo project.
npx shadcn@latest init --name my-app --preset base-nova --monorepo
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
# Initialize existing project.
npx shadcn@latest init --preset base-nova
npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
# Apply a preset to an existing project.
npx shadcn@latest apply a2r6bw
npx shadcn@latest apply a2r6bw --only theme
npx shadcn@latest apply a2r6bw --only font
npx shadcn@latest apply a2r6bw --only theme,font
# Inspect preset codes and project preset state.
npx shadcn@latest preset decode a2r6bw
npx shadcn@latest preset url a2r6bw
npx shadcn@latest preset open a2r6bw
npx shadcn@latest preset resolve
npx shadcn@latest preset resolve --json
# Add components.
npx shadcn@latest add button card dialog
npx shadcn@latest add @magicui/shimmer-button
npx shadcn@latest add owner/repo/item
npx shadcn@latest add --all
# Preview changes before adding/updating.
npx shadcn@latest add button --dry-run
npx shadcn@latest add button --diff button.tsx
npx shadcn@latest add @acme/form --view button.tsx
npx shadcn@latest add owner/repo/item --dry-run
# Search registries.
npx shadcn@latest search @shadcn -q "sidebar"
npx shadcn@latest search @tailark -q "stats"
npx shadcn@latest search owner/repo -q "login"
npx shadcn@latest search # all configured registries
npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type
# Get component docs and example URLs.
npx shadcn@latest docs button dialog select
# View registry item details (for items not yet installed).
npx shadcn@latest view @shadcn/button
npx shadcn@latest view owner/repo/item
```
**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).
## Detailed References
- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
- [cli.md](./cli.md) — Commands, flags, presets, templates
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
- [customization.md](./customization.md) — Theming, CSS variables, extending components
+5
View File
@@ -0,0 +1,5 @@
interface:
display_name: "shadcn/ui"
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
icon_small: "./assets/shadcn-small.png"
icon_large: "./assets/shadcn.png"
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+290
View File
@@ -0,0 +1,290 @@
# shadcn CLI Reference
Configuration is read from `components.json`.
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
## Contents
- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
- Templates: next, vite, start, react-router, astro
- Presets: named, code, URL formats and fields
- Switching presets
---
## Commands
### `init` — Initialize or create a project
```bash
npx shadcn@latest init [components...] [options]
```
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
| Flag | Short | Description | Default |
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
| `--yes` | `-y` | Skip confirmation prompt | `true` |
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
| `--force` | `-f` | Force overwrite existing configuration | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--name <name>` | `-n` | Name for new project | — |
| `--silent` | `-s` | Mute output | `false` |
| `--rtl` | | Enable RTL support | — |
| `--reinstall` | | Re-install existing UI components | `false` |
| `--monorepo` | | Scaffold a monorepo project | — |
| `--no-monorepo` | | Skip the monorepo prompt | — |
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
### `apply` — Apply a preset to an existing project
```bash
npx shadcn@latest apply [preset] [options]
```
Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------ | ------- |
| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
| `--yes` | `-y` | Skip confirmation prompt | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--silent` | `-s` | Mute output | `false` |
`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
### `add` — Add components
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
```bash
npx shadcn@latest add [components...] [options]
```
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`),
GitHub item addresses (`owner/repo/item`), URLs, or local paths.
| Flag | Short | Description | Default |
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
| `--yes` | `-y` | Skip confirmation prompt | `false` |
| `--overwrite` | `-o` | Overwrite existing files | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--all` | `-a` | Add all available components | `false` |
| `--path <path>` | `-p` | Target path for the component | — |
| `--silent` | `-s` | Mute output | `false` |
| `--dry-run` | | Preview all changes without writing files | `false` |
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
#### Dry-Run Mode
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
```bash
# Preview all changes.
npx shadcn@latest add button --dry-run
# Show diffs for all files (top 5).
npx shadcn@latest add button --diff
# Show the diff for a specific file.
npx shadcn@latest add button --diff button.tsx
# Show contents for all files (top 5).
npx shadcn@latest add button --view
# Show the full content of a specific file.
npx shadcn@latest add button --view button.tsx
# Works with URLs too.
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
# Works with public GitHub registries too.
npx shadcn@latest add owner/repo/item --dry-run
# CSS diffs.
npx shadcn@latest add button --diff globals.css
```
**When to use dry-run:**
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
- Before overwriting existing components — use `--diff` to preview the changes first.
- When the user wants to inspect component source code without installing — use `--view`.
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
#### Smart Merge from Upstream
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
### `search` — Search registries
```bash
npx shadcn@latest search [registries...] [options]
```
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`.
Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`),
and registry catalog URLs. Without `-q`, lists all items. When no registries are
passed, searches every registry configured in `components.json`.
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------------- | ------- |
| `--query <query>` | `-q` | Search query | — |
| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |
| `--limit <number>` | `-l` | Max items to display | `100` |
| `--offset <number>` | `-o` | Items to skip | `0` |
| `--json` | | Output as JSON | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
### `view` — View item details
```bash
npx shadcn@latest view <items...> [options]
```
Displays item info including file contents. Examples:
`npx shadcn@latest view @shadcn/button`,
`npx shadcn@latest view owner/repo/item`.
### `docs` — Get component documentation URLs
```bash
npx shadcn@latest docs <components...> [options]
```
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
Example output for `npx shadcn@latest docs input button`:
```
base radix
input
docs https://ui.shadcn.com/docs/components/radix/input
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
button
docs https://ui.shadcn.com/docs/components/radix/button
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
```
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
### `diff` — Check for updates
Do not use this command. Use `npx shadcn@latest add --diff` instead.
### `info` — Project information
```bash
npx shadcn@latest info [options]
```
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
| Flag | Short | Description | Default |
| ------------- | ----- | ----------------- | ------- |
| `--cwd <cwd>` | `-c` | Working directory | current |
**Project Info fields:**
| Field | Type | Meaning |
| -------------------- | --------- | ------------------------------------------------------------------ |
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
| `isRSC` | `boolean` | Whether React Server Components are enabled |
| `isTsx` | `boolean` | Whether the project uses TypeScript |
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
| `tailwindCssFile` | `string` | Path to the global CSS file |
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
**Components.json fields:**
| Field | Type | Meaning |
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
| `rsc` | `boolean` | RSC flag from config |
| `tsx` | `boolean` | TypeScript flag |
| `tailwind.config` | `string` | Tailwind config path |
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
| `registries` | `object` | Configured custom registries |
**Links fields:**
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
### `build` — Build a custom registry
```bash
npx shadcn@latest build [registry] [options]
```
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
For authoring rules, `include`, item definitions, `registryDependencies`, and
GitHub registry behavior, see [registry.md](./registry.md).
| Flag | Short | Description | Default |
| ----------------- | ----- | ----------------- | ------------ |
| `--output <path>` | `-o` | Output directory | `./public/r` |
| `--cwd <cwd>` | `-c` | Working directory | current |
---
## Templates
| Value | Framework | Monorepo support |
| -------------- | -------------- | ---------------- |
| `next` | Next.js | Yes |
| `vite` | Vite | Yes |
| `start` | TanStack Start | Yes |
| `react-router` | React Router | Yes |
| `astro` | Astro | Yes |
| `laravel` | Laravel | No |
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
---
## Presets
Three ways to specify a preset via `--preset`:
1. **Named:** `--preset nova` or `--preset lyra`
2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
> Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
## Switching Presets
Ask the user first: **overwrite**, **merge**, or **skip** existing components?
- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
+209
View File
@@ -0,0 +1,209 @@
# Customization & Theming
Components reference semantic CSS variable tokens. Change the variables to change every component.
## Contents
- How it works (CSS variables → Tailwind utilities → components)
- Color variables and OKLCH format
- Dark mode setup
- Changing the theme (presets, CSS variables)
- Adding custom colors (Tailwind v3 and v4)
- Border radius
- Customizing components (variants, className, wrappers)
- Checking for updates
---
## How It Works
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
3. Components use these utilities — changing a variable changes all components that reference it.
---
## Color Variables
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
| Variable | Purpose |
| -------------------------------------------- | -------------------------------- |
| `--background` / `--foreground` | Page background and default text |
| `--card` / `--card-foreground` | Card surfaces |
| `--primary` / `--primary-foreground` | Primary buttons and actions |
| `--secondary` / `--secondary-foreground` | Secondary actions |
| `--muted` / `--muted-foreground` | Muted/disabled states |
| `--accent` / `--accent-foreground` | Hover and accent states |
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
| `--border` | Default border color |
| `--input` | Form input borders |
| `--ring` | Focus ring color |
| `--chart-1` through `--chart-5` | Chart/data visualization |
| `--sidebar-*` | Sidebar-specific colors |
| `--surface` / `--surface-foreground` | Secondary surface |
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (01), chroma (0 = gray), and hue (0360).
---
## Dark Mode
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
```tsx
import { ThemeProvider } from "next-themes"
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
```
---
## Changing the Theme
```bash
# Apply a preset code from ui.shadcn.com.
npx shadcn@latest apply --preset a2r6bw
# Positional shorthand also works.
npx shadcn@latest apply a2r6bw
# Switch to a named preset and overwrite existing components.
npx shadcn@latest apply --preset nova
# Preserve existing components instead.
npx shadcn@latest init --preset nova --force --no-reinstall
# Use a custom theme URL.
npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
```
Or edit CSS variables directly in `globals.css`.
---
## Adding Custom Colors
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
```css
/* 1. Define in the global CSS file. */
:root {
--warning: oklch(0.84 0.16 84);
--warning-foreground: oklch(0.28 0.07 46);
}
.dark {
--warning: oklch(0.41 0.11 46);
--warning-foreground: oklch(0.99 0.02 95);
}
```
```css
/* 2a. Register with Tailwind v4 (@theme inline). */
@theme inline {
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}
```
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
```js
// 2b. Register with Tailwind v3 (tailwind.config.js).
module.exports = {
theme: {
extend: {
colors: {
warning: "oklch(var(--warning) / <alpha-value>)",
"warning-foreground":
"oklch(var(--warning-foreground) / <alpha-value>)",
},
},
},
}
```
```tsx
// 3. Use in components.
<div className="bg-warning text-warning-foreground">Warning</div>
```
---
## Border Radius
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
---
## Customizing Components
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
Prefer these approaches in order:
### 1. Built-in variants
```tsx
<Button variant="outline" size="sm">
Click
</Button>
```
### 2. Tailwind classes via `className`
```tsx
<Card className="mx-auto max-w-md">...</Card>
```
### 3. Add a new variant
Edit the component source to add a variant via `cva`:
```tsx
// components/ui/button.tsx
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
```
### 4. Wrapper components
Compose shadcn/ui primitives into higher-level components:
```tsx
export function ConfirmDialog({ title, description, onConfirm, children }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
---
## Checking for Updates
```bash
npx shadcn@latest add button --diff
```
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
```bash
npx shadcn@latest add button --dry-run # see all affected files
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
```
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
+77
View File
@@ -0,0 +1,77 @@
{
"skill_name": "shadcn",
"evals": [
{
"id": 1,
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
"files": [],
"expectations": [
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
"No manual dark: color overrides"
]
},
{
"id": 2,
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
"files": [],
"expectations": [
"Includes DialogTitle for accessibility (visible or with sr-only class)",
"Avatar component includes AvatarFallback",
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
"Uses asChild for custom triggers (radix preset)"
]
},
{
"id": 3,
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
"files": [],
"expectations": [
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
"Uses Badge component for percentage change instead of custom styled spans",
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
"Uses gap-* instead of space-y-* or space-x-* for spacing",
"Uses size-* when width and height are equal instead of separate w-* h-*"
]
},
{
"id": 4,
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.",
"expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.",
"files": [],
"expectations": [
"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea",
"Wraps each row in MessageScrollerItem inside MessageScrollerContent",
"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs",
"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary",
"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card",
"Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label",
"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*",
"Includes \"use client\" when the component uses state or event handlers (isRSC)"
]
},
{
"id": 5,
"prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.",
"expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.",
"files": [],
"expectations": [
"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring",
"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state",
"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation",
"Wraps each message row in MessageScrollerItem inside MessageScrollerContent",
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
]
}
]
}
+105
View File
@@ -0,0 +1,105 @@
# shadcn MCP Server
The CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.
---
## Setup
```bash
shadcn mcp # start the MCP server (stdio)
shadcn mcp init # write config for your editor
```
Editor config files:
| Editor | Config file |
| ----------- | ------------------------------- |
| Claude Code | `.mcp.json` |
| Cursor | `.cursor/mcp.json` |
| VS Code | `.vscode/mcp.json` |
| OpenCode | `opencode.json` |
| Codex | `~/.codex/config.toml` (manual) |
---
## Tools
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
### `shadcn:get_project_registries`
Returns registry names from `components.json`. Errors if no `components.json` exists.
**Input:** none
### `shadcn:list_items_in_registries`
Lists all items from one or more registries. Registries can be configured
namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or
registry catalog URLs. Omit `registries` to list from every registry configured
in `components.json`.
**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
### `shadcn:search_items_in_registries`
Fuzzy search across registries. Registries can be configured namespaces, public
GitHub sources, or registry catalog URLs. Omit `registries` to search every
registry configured in `components.json` — e.g. "find me a hero" across all
configured registries.
**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
### `shadcn:view_items_in_registries`
View item details including full file contents.
**Input:** `items` (string[]) — e.g.
`["@shadcn/button", "@shadcn/card", "owner/repo/item"]`
### `shadcn:get_item_examples_from_registries`
Find usage examples and demos with source code. Omit `registries` to search
every registry configured in `components.json`.
**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
### `shadcn:get_add_command_for_items`
Returns the CLI install command.
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
### `shadcn:get_audit_checklist`
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
**Input:** none
---
## Configuring Registries
Namespaced and authenticated registries are set in `components.json`. The
`@shadcn` registry is always built-in. Public GitHub registries can also be used
directly as `owner/repo` registry sources when the repository has a root
`registry.json`; they do not need `components.json` configuration.
```json
{
"registries": {
"@acme": "https://acme.com/r/{name}.json",
"@private": {
"url": "https://private.com/r/{name}.json",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
```
- Names must start with `@`.
- URLs must contain `{name}`.
- `${VAR}` references are resolved from environment variables.
Community registry index: `https://ui.shadcn.com/r/registries.json`
+277
View File
@@ -0,0 +1,277 @@
# Registry Authoring and Addresses
Use this reference when the user wants to create, fix, publish, or reason about
a shadcn registry.
## Mental Model
A registry has two forms:
- **Source registry**: an authored `registry.json` in a project or repository.
It may use `include` and file paths that point at source files.
- **Built registry**: generated JSON files served to CLI consumers, usually
from `public/r`. Use `npx shadcn@latest build` to create this form.
The CLI installer consumes registry item payloads. A source registry is a way to
author those payloads from real files.
Registry items are not limited to React components. They can distribute
components, hooks, utilities, design tokens, pages, config files, docs, rules,
workflows, templates, MCP files, and other project files.
## Root `registry.json`
The root registry file should define registry metadata and either `items` or
`include`.
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme",
"homepage": "https://acme.com",
"items": [
{
"name": "absolute-url",
"type": "registry:lib",
"title": "Absolute URL",
"description": "A utility to turn any path into an absolute URL.",
"files": [
{
"path": "lib/absolute-url.ts",
"type": "registry:lib"
}
]
}
]
}
```
Root registry rules:
- Root `registry.json` must include `name` and `homepage`.
- `items` is an array of registry item definitions.
- `include` may be used to split the source registry into multiple files.
- Included registry files may omit `name` and `homepage`.
## Include
Use `include` to keep large registries modular.
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme",
"homepage": "https://acme.com",
"include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
}
```
Include rules:
- Include paths are relative to the `registry.json` that declares them.
- Include paths must explicitly point to a `registry.json` file.
- Do not use remote URLs, absolute paths, or parent traversal (`..`).
- Item file paths are relative to the registry file that declares the item.
- Duplicate item names fail across the resolved registry.
Example included file:
```json
{
"items": [
{
"name": "button",
"type": "registry:ui",
"files": [
{
"path": "button.tsx",
"type": "registry:ui"
}
]
}
]
}
```
If this file is at `registry/ui/registry.json`, then `button.tsx` is read from
`registry/ui/button.tsx`, and the built item path is emitted relative to the
root registry.
## Item Definitions
Common item fields:
```json
{
"name": "login-form",
"type": "registry:block",
"title": "Login Form",
"description": "A login form with email and password fields.",
"dependencies": ["zod"],
"registryDependencies": ["button", "input", "label"],
"files": [
{
"path": "blocks/login-form.tsx",
"type": "registry:block"
}
],
"cssVars": {
"light": {
"brand": "oklch(0.62 0.18 250)"
},
"dark": {
"brand": "oklch(0.72 0.16 250)"
}
}
}
```
Important fields:
- `name`: the installable item name. It is not necessarily a file path.
- `type`: one of the registry item types, such as `registry:ui`,
`registry:block`, `registry:lib`, `registry:hook`, `registry:file`,
`registry:page`, `registry:theme`, `registry:style`, `registry:font`, or
`registry:item`.
- `files`: source files copied or generated by the item.
- `dependencies`: npm runtime dependencies.
- `devDependencies`: npm development dependencies.
- `registryDependencies`: other registry items required by this item.
- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time
additions.
File rules:
- File paths are relative to the declaring `registry.json`.
- `registry:file` and `registry:page` files require a `target`.
- Do not use remote file URLs in source registry file paths.
- Keep source files copy-pasteable: no hidden app-only imports.
## Registry Dependencies
`registryDependencies` entries are item addresses, not file paths.
```json
{
"name": "login-form",
"type": "registry:block",
"registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
"files": [
{
"path": "blocks/login-form.tsx",
"type": "registry:block"
}
]
}
```
Dependency rules:
- Bare names such as `"button"` mean official shadcn items.
- Bare names never mean same-registry or same-repository items.
- Namespaced dependencies use `@namespace/item-name`.
- GitHub dependencies use `owner/repo/item-name`.
- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.
- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same
repo at `v2`, write `owner/repo/bar#v2`.
- Do not use relative dependencies such as `"./bar"`.
## Address Schemes
When reasoning about a registry item string, classify it first.
| Address | Scheme | Meaning |
| ----------------------------------- | --------- | ------------------------------------------------------------ |
| `button` | shadcn | Official shadcn item named `button`. |
| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |
| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |
| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |
| `./button.json` | file | Built registry item JSON on disk. |
| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |
| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |
For namespace and GitHub addresses, slashful item names are allowed and are item
names, not file paths. Addresses ending in `.json` keep file-address
precedence, so `acme/ui/data/schema.json` is treated as a file path, not a
GitHub item address.
## GitHub Registries
A public GitHub repository can act as a source registry when it has a root
`registry.json`.
```txt
owner/repo/item-name[#ref]
```
Rules:
- The first two path segments are GitHub owner and repo.
- All remaining path segments are the registry item name.
- The source entrypoint is always root `registry.json`.
- GitHub registries are source registries consumed directly by the CLI. They do
not require `shadcn build` or generated item JSON files.
- `include` follows the same source-registry rules as local registries.
- Currently, GitHub addresses support public `github.com` repositories only.
- Private repos and GitHub Enterprise require explicit product decisions.
When implementing GitHub registry fetching, resolve refs to a commit SHA before
reading source files. Do not read moving refs directly from
`raw.githubusercontent.com`, because branch-like refs can be cached for several
minutes.
Preferred flow:
```txt
owner/repo[#ref]
-> resolve ref with git ls-remote
-> commit SHA
-> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
-> read includes and item files from the same SHA
```
This keeps a command on one consistent repository snapshot.
Full 40-character commit SHAs are already stable and can be used directly.
Branches, tags, and short refs require Git so the CLI can resolve them to a
commit SHA first.
## Build and Verify
Use the CLI to build source registries:
```bash
npx shadcn@latest build
npx shadcn@latest build registry.json --output public/r
```
Use CLI commands to inspect the result:
```bash
npx shadcn@latest list @acme
npx shadcn@latest search @acme -q "login"
npx shadcn@latest view @acme/login-form
npx shadcn@latest add @acme/login-form --dry-run
npx shadcn@latest registry validate ./registry.json
```
Use GitHub addresses directly for public GitHub registries:
```bash
npx shadcn@latest list owner/repo
npx shadcn@latest search owner/repo -q "login"
npx shadcn@latest view owner/repo/item
npx shadcn@latest add owner/repo/item --dry-run
npx shadcn@latest registry validate owner/repo
```
When working on registry implementation in the shadcn/ui codebase:
- Keep address parsing pure and testable.
- Do not add side effects to validators.
- Preserve existing behavior for official shadcn, namespace, URL, and file
schemes.
- Add tests for address parsing, source loading, dependency resolution, list,
search, view, and add paths.
- Prefer small source-reader abstractions over a plugin system until there are
multiple real providers.
@@ -0,0 +1,306 @@
# Base vs Radix
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
## Contents
- Composition: asChild vs render
- Button / trigger as non-button element
- Select (items prop, placeholder, positioning, multiple, object values)
- ToggleGroup (type vs multiple)
- Slider (scalar vs array)
- Accordion (type and defaultValue)
---
## Composition: asChild (radix) vs render (base)
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
**Incorrect:**
```tsx
<DialogTrigger>
<div>
<Button>Open</Button>
</div>
</DialogTrigger>
```
**Correct (radix):**
```tsx
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
```
**Correct (base):**
```tsx
<DialogTrigger render={<Button />}>Open</DialogTrigger>
```
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
---
## Button / trigger as non-button element (base only)
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
**Incorrect (base):** missing `nativeButton={false}`.
```tsx
<Button render={<a href="/docs" />}>Read the docs</Button>
```
**Correct (base):**
```tsx
<Button render={<a href="/docs" />} nativeButton={false}>
Read the docs
</Button>
```
**Correct (radix):**
```tsx
<Button asChild>
<a href="/docs">Read the docs</a>
</Button>
```
Same for triggers whose `render` is not a `Button`:
```tsx
// base.
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
Pick date
</PopoverTrigger>
```
---
## Select
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
**Incorrect (base):**
```tsx
<Select>
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
</Select>
```
**Correct (base):**
```tsx
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
]
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
```
**Correct (radix):**
```tsx
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
```
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
```tsx
// base.
<SelectContent alignItemWithTrigger={false} side="bottom">
// radix.
<SelectContent position="popper">
```
---
## Select — multiple selection and object values (base only)
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
**Correct (base — multiple selection):**
```tsx
<Select items={items} multiple defaultValue={[]}>
<SelectTrigger>
<SelectValue>
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
</SelectValue>
</SelectTrigger>
...
</Select>
```
**Correct (base — object values):**
```tsx
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
<SelectTrigger>
<SelectValue>{(value) => value.name}</SelectValue>
</SelectTrigger>
...
</Select>
```
---
## ToggleGroup
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
**Incorrect (base):**
```tsx
<ToggleGroup type="single" defaultValue="daily">
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
</ToggleGroup>
```
**Correct (base):**
```tsx
// Single (no prop needed), defaultValue is always an array.
<ToggleGroup defaultValue={["daily"]} spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// Multi-selection.
<ToggleGroup multiple>
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**Correct (radix):**
```tsx
// Single, defaultValue is a string.
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// Multi-selection.
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**Controlled single value:**
```tsx
// base — wrap/unwrap arrays.
const [value, setValue] = React.useState("normal")
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
// radix — plain string.
const [value, setValue] = React.useState("normal")
<ToggleGroup type="single" value={value} onValueChange={setValue}>
```
---
## Slider
Base accepts a plain number for a single thumb. Radix always requires an array.
**Incorrect (base):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
**Correct (base):**
```tsx
<Slider defaultValue={50} max={100} step={1} />
```
**Correct (radix):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
```tsx
// base.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
// radix.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={setValue} />
```
---
## Accordion
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
**Incorrect (base):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
**Correct (base):**
```tsx
<Accordion defaultValue={["item-1"]}>
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
// Multi-select.
<Accordion multiple defaultValue={["item-1", "item-2"]}>
<AccordionItem value="item-1">...</AccordionItem>
<AccordionItem value="item-2">...</AccordionItem>
</Accordion>
```
**Correct (radix):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
+224
View File
@@ -0,0 +1,224 @@
# Chat & Messaging
Components for conversation and chat UI. Compose these instead of hand-rolling
bubbles, scroll containers, dividers, or attachment cards.
Install: `npx shadcn@latest add message-scroller message bubble attachment marker`
The same component names and props ship for both `base` and `radix`; only
composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).
## Contents
- Scrollable threads use MessageScroller
- Message rows use Message
- Message surfaces use Bubble
- Attachments use Attachment
- System notes and dividers use Marker
- Streaming, anchoring, and jump-to-latest are built in
- Escape hatch: the scroller hooks
---
## Scrollable threads use MessageScroller
A conversation that scrolls, follows new messages, restores position, or jumps
to a message uses `MessageScroller`. Don't build a raw overflow container with
manual scroll wiring, and don't reach for `ScrollArea`.
The parts nest in a fixed order. Every direct child of the content is wrapped in
a `MessageScrollerItem` so the scroller can measure, anchor, preserve position,
track visibility, and jump to it. `MessageScrollerButton` sits inside
`MessageScroller`, after the viewport.
**Incorrect:**
```tsx
// Hand-rolled scroll container with manual stick-to-bottom logic.
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto">
<div className="flex flex-col gap-6 p-4">
{messages.map((m) => (
<ChatMessage key={m.id} message={m} />
))}
</div>
</div>
```
**Correct:**
```tsx
<MessageScrollerProvider autoScroll>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
{messages.map((message) => (
<MessageScrollerItem
key={message.id}
messageId={message.id}
scrollAnchor={message.role === "user"}
>
<Message align={message.role === "user" ? "end" : "start"}>
{/* ...message content... */}
</Message>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
```
---
## Message rows use Message
`Message` lays out a single row: avatar, header, content, footer, with
alignment. Group consecutive rows from one sender with `MessageGroup`. Don't
rebuild the row from flex divs.
`align="end"` is the current user's side; `align="start"` is everyone else.
```tsx
<Message align="start">
<MessageAvatar>
<Avatar>
<AvatarImage src={sender.avatar} alt={sender.name} />
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<MessageHeader>{sender.name}</MessageHeader>
<Bubble>
<BubbleContent>{text}</BubbleContent>
</Bubble>
<MessageFooter>{time}</MessageFooter>
</MessageContent>
</Message>
```
---
## Message surfaces use Bubble
The colored message surface is `Bubble` + `BubbleContent`, never a styled `div`
with `bg-muted` / `bg-primary` and hand-managed corners.
- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.
- `align`: `start` or `end` (matches the `Message` side).
`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and
`align` (`start` | `end`) position it against the bubble. Don't lay reactions out
with absolutely-positioned `Badge`s.
**Incorrect:**
```tsx
<div className="w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground">
{text}
</div>
```
**Correct:**
```tsx
<Bubble variant="default" align="end">
<BubbleContent>{text}</BubbleContent>
<BubbleReactions side="bottom" align="end">
<Badge variant="secondary">👍 2</Badge>
</BubbleReactions>
</Bubble>
```
---
## Attachments use Attachment
File and image attachments use `Attachment`, not `Item` or a custom card. It
carries upload state, so wire `state` to the real status rather than rendering a
separate spinner.
- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and
`processing` apply the `shimmer` animation to the title automatically.
- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.
- Use `AttachmentGroup` to lay out several attachments in a scrolling row.
```tsx
<Attachment state="done">
<AttachmentMedia variant="icon">
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction>
<DownloadIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>
```
For an image, use `<AttachmentMedia variant="image">` with an `img` child.
---
## System notes and dividers use Marker
Status lines ("Sarah joined the conversation"), date dividers ("Today"), and
labeled separators are `Marker`, not a `Separator` plus a centered span.
- `variant`: `default` (plain row), `separator` (centered label with rules on
each side), `border` (bottom-bordered row).
- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.
**Incorrect:**
```tsx
<div className="flex items-center gap-3 py-2">
<Separator className="flex-1" />
<span className="text-xs text-muted-foreground">Today</span>
<Separator className="flex-1" />
</div>
```
**Correct:**
```tsx
<Marker variant="separator">
<MarkerContent>Today</MarkerContent>
</Marker>
```
---
## Streaming, anchoring, and jump-to-latest are built in
`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't
write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.
- **Follow the live edge while streaming.** `MessageScrollerProvider` with
`autoScroll` keeps the view pinned to new content and yields the moment the
user scrolls up. Streaming token updates that grow the last message are
followed automatically.
- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to
hold in view (typically the user's message that started the turn).
- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away
and scrolls back on click. `direction="end"` (default) or `direction="start"`.
It is a self-managing control, so don't gate it behind your own scroll-position
state.
For a "thinking…" indicator while the model generates, apply the `shimmer`
utility to text. Don't author a custom keyframe animation. See
[styling.md](./styling.md).
---
## Escape hatch: the scroller hooks
For behavior the parts don't expose, read state from the hooks rather than
re-implementing the scroller: `useMessageScroller`,
`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come
from the auto-installed `@shadcn/react` dependency, so there's nothing extra to
install. Reach for them only when composition can't express what you need.
+213
View File
@@ -0,0 +1,213 @@
# Component Composition
## Contents
- Items always inside their Group component
- Callouts use Alert
- Empty states use Empty component
- Toast notifications follow the project base
- Choosing between overlay components
- Dialog, Sheet, and Drawer always need a Title
- Card structure
- Button has no isPending or isLoading prop
- TabsTrigger must be inside TabsList
- Avatar always needs AvatarFallback
- Use Separator instead of raw hr or border divs
- Use Skeleton for loading placeholders
- Use Badge instead of custom styled spans
---
## Items always inside their Group component
Never render items directly inside the content container.
**Incorrect:**
```tsx
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
```
**Correct:**
```tsx
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
```
This applies to all group-based components:
| Item | Group |
|------|-------|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
| `MenubarItem` | `MenubarGroup` |
| `ContextMenuItem` | `ContextMenuGroup` |
| `CommandItem` | `CommandGroup` |
| `MessageScrollerItem` | `MessageScrollerContent` |
| `Message` (consecutive, same sender) | `MessageGroup` |
| `Bubble` (stacked) | `BubbleGroup` |
| `Attachment` (in a row) | `AttachmentGroup` |
Chat components nest in a fixed order (`MessageScrollerProvider``MessageScroller``MessageScrollerViewport``MessageScrollerContent``MessageScrollerItem`). See [chat.md](./chat.md).
---
## Callouts use Alert
```tsx
<Alert>
<AlertTitle>Warning</AlertTitle>
<AlertDescription>Something needs attention.</AlertDescription>
</Alert>
```
---
## Empty states use Empty component
```tsx
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create Project</Button>
</EmptyContent>
</Empty>
```
---
## Toast notifications follow the project base
For Base UI projects, use the `toast` component:
```tsx
import { toast } from "@/components/ui/toast"
toast.add({
title: "Changes saved.",
})
```
For Radix and React Aria projects, use Sonner:
```tsx
import { toast } from "sonner"
toast.success("Changes saved.")
toast.error("Something went wrong.")
toast("File deleted.", {
action: { label: "Undo", onClick: () => undoDelete() },
})
```
---
## Choosing between overlay components
| Use case | Component |
|----------|-----------|
| Focused task that requires input | `Dialog` |
| Destructive action confirmation | `AlertDialog` |
| Side panel with details or filters | `Sheet` |
| Mobile-first bottom panel | `Drawer` |
| Quick info on hover | `HoverCard` |
| Small contextual content on click | `Popover` |
---
## Dialog, Sheet, and Drawer always need a Title
`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
```tsx
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
<DialogDescription>Update your profile.</DialogDescription>
</DialogHeader>
...
</DialogContent>
```
---
## Card structure
Use full composition — don't dump everything into `CardContent`:
```tsx
<Card>
<CardHeader>
<CardTitle>Team Members</CardTitle>
<CardDescription>Manage your team.</CardDescription>
</CardHeader>
<CardContent>...</CardContent>
<CardFooter>
<Button>Invite</Button>
</CardFooter>
</Card>
```
---
## Button has no isPending or isLoading prop
Compose with `Spinner` + `data-icon` + `disabled`:
```tsx
<Button disabled>
<Spinner data-icon="inline-start" />
Saving...
</Button>
```
---
## TabsTrigger must be inside TabsList
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
```tsx
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">...</TabsContent>
</Tabs>
```
---
## Avatar always needs AvatarFallback
Always include `AvatarFallback` for when the image fails to load:
```tsx
<Avatar>
<AvatarImage src="/avatar.png" alt="User" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
```
---
## Use existing components instead of custom markup
| Instead of | Use |
|---|---|
| `<hr>` or `<div className="border-t">` | `<Separator />` |
| `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
+192
View File
@@ -0,0 +1,192 @@
# Forms & Inputs
## Contents
- Forms use FieldGroup + Field
- InputGroup requires InputGroupInput/InputGroupTextarea
- Buttons inside inputs use InputGroup + InputGroupAddon
- Option sets (27 choices) use ToggleGroup
- FieldSet + FieldLegend for grouping related fields
- Field validation and disabled states
---
## Forms use FieldGroup + Field
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
```tsx
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" />
</Field>
<Field>
<FieldLabel htmlFor="password">Password</FieldLabel>
<Input id="password" type="password" />
</Field>
</FieldGroup>
```
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
**Choosing form controls:**
- Simple text input → `Input`
- Dropdown with predefined options → `Select`
- Searchable dropdown → `Combobox`
- Native HTML select (no JS) → `native-select`
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
- Single choice from few options → `RadioGroup`
- Toggle between 25 options → `ToggleGroup` + `ToggleGroupItem`
- OTP/verification code → `InputOTP`
- Multi-line text → `Textarea`
---
## InputGroup requires InputGroupInput/InputGroupTextarea
Never use raw `Input` or `Textarea` inside an `InputGroup`.
**Incorrect:**
```tsx
<InputGroup>
<Input placeholder="Search..." />
</InputGroup>
```
**Correct:**
```tsx
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="Search..." />
</InputGroup>
```
---
## Buttons inside inputs use InputGroup + InputGroupAddon
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
**Incorrect:**
```tsx
<div className="relative">
<Input placeholder="Search..." className="pr-10" />
<Button className="absolute right-0 top-0" size="icon">
<SearchIcon />
</Button>
</div>
```
**Correct:**
```tsx
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Button size="icon">
<SearchIcon data-icon="inline-start" />
</Button>
</InputGroupAddon>
</InputGroup>
```
---
## Option sets (27 choices) use ToggleGroup
Don't manually loop `Button` components with active state.
**Incorrect:**
```tsx
const [selected, setSelected] = useState("daily")
<div className="flex gap-2">
{["daily", "weekly", "monthly"].map((option) => (
<Button
key={option}
variant={selected === option ? "default" : "outline"}
onClick={() => setSelected(option)}
>
{option}
</Button>
))}
</div>
```
**Correct:**
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
<ToggleGroup spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
</ToggleGroup>
```
Combine with `Field` for labelled toggle groups:
```tsx
<Field orientation="horizontal">
<FieldTitle id="theme-label">Theme</FieldTitle>
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
<ToggleGroupItem value="light">Light</ToggleGroupItem>
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
<ToggleGroupItem value="system">System</ToggleGroupItem>
</ToggleGroup>
</Field>
```
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
---
## FieldSet + FieldLegend for grouping related fields
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
```tsx
<FieldSet>
<FieldLegend variant="label">Preferences</FieldLegend>
<FieldDescription>Select all that apply.</FieldDescription>
<FieldGroup className="gap-3">
<Field orientation="horizontal">
<Checkbox id="dark" />
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
```
---
## Field validation and disabled states
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
```tsx
// Invalid.
<Field data-invalid>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" aria-invalid />
<FieldDescription>Invalid email address.</FieldDescription>
</Field>
// Disabled.
<Field data-disabled>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" disabled />
</Field>
```
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
+101
View File
@@ -0,0 +1,101 @@
# Icons
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide``lucide-react`, `tabler``@tabler/icons-react`, etc. Never assume `lucide-react`.
---
## Icons in Button use data-icon attribute
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
**Incorrect:**
```tsx
<Button>
<SearchIcon className="mr-2 size-4" />
Search
</Button>
```
**Correct:**
```tsx
<Button>
<SearchIcon data-icon="inline-start"/>
Search
</Button>
<Button>
Next
<ArrowRightIcon data-icon="inline-end"/>
</Button>
```
---
## No sizing classes on icons inside components
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
**Incorrect:**
```tsx
<Button>
<SearchIcon className="size-4" data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon className="mr-2 size-4" />
Settings
</DropdownMenuItem>
```
**Correct:**
```tsx
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon />
Settings
</DropdownMenuItem>
```
---
## Pass icons as component objects, not string keys
Use `icon={CheckIcon}`, not a string key to a lookup map.
**Incorrect:**
```tsx
const iconMap = {
check: CheckIcon,
alert: AlertIcon,
}
function StatusBadge({ icon }: { icon: string }) {
const Icon = iconMap[icon]
return <Icon />
}
<StatusBadge icon="check" />
```
**Correct:**
```tsx
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
import { CheckIcon } from "lucide-react"
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
return <Icon />
}
<StatusBadge icon={CheckIcon} />
```
+185
View File
@@ -0,0 +1,185 @@
# Styling & Customization
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
## Contents
- Semantic colors
- Built-in variants first
- className for layout only
- No space-x-* / space-y-*
- Prefer size-* over w-* h-* when equal
- Prefer truncate shorthand
- No manual dark: color overrides
- Use cn() for conditional classes
- No manual z-index on overlay components
- Use shimmer / scroll-fade utilities, not custom animations
---
## Semantic colors
**Incorrect:**
```tsx
<div className="bg-blue-500 text-white">
<p className="text-gray-600">Secondary text</p>
</div>
```
**Correct:**
```tsx
<div className="bg-primary text-primary-foreground">
<p className="text-muted-foreground">Secondary text</p>
</div>
```
---
## No raw color values for status/state indicators
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
**Incorrect:**
```tsx
<span className="text-emerald-600">+20.1%</span>
<span className="text-green-500">Active</span>
<span className="text-red-600">-3.2%</span>
```
**Correct:**
```tsx
<Badge variant="secondary">+20.1%</Badge>
<Badge>Active</Badge>
<span className="text-destructive">-3.2%</span>
```
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
---
## Built-in variants first
**Incorrect:**
```tsx
<Button className="border border-input bg-transparent hover:bg-accent">
Click me
</Button>
```
**Correct:**
```tsx
<Button variant="outline">Click me</Button>
```
---
## className for layout only
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
**Incorrect:**
```tsx
<Card className="bg-blue-100 text-blue-900 font-bold">
<CardContent>Dashboard</CardContent>
</Card>
```
**Correct:**
```tsx
<Card className="max-w-md mx-auto">
<CardContent>Dashboard</CardContent>
</Card>
```
To customize a component's appearance, prefer these approaches in order:
1. **Built-in variants**`variant="outline"`, `variant="destructive"`, etc.
2. **Semantic color tokens**`bg-primary`, `text-muted-foreground`.
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
---
## No space-x-* / space-y-*
Use `gap-*` instead. `space-y-4``flex flex-col gap-4`. `space-x-2``flex gap-2`.
```tsx
<div className="flex flex-col gap-4">
<Input />
<Input />
<Button>Submit</Button>
</div>
```
---
## Prefer size-* over w-* h-* when equal
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
---
## Prefer truncate shorthand
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
---
## No manual dark: color overrides
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
---
## Use cn() for conditional classes
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
**Incorrect:**
```tsx
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
```
**Correct:**
```tsx
import { cn } from "@/lib/utils"
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
```
---
## No manual z-index on overlay components
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
---
## Use shimmer / scroll-fade utilities, not custom animations
For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.
For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.
**Incorrect:**
```tsx
<span className="animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]">
Thinking
</span>
```
**Correct:**
```tsx
<span className="shimmer">Thinking</span>
```
+4 -2
View File
@@ -4,7 +4,6 @@
<classpathentry kind="lib" path="lib/gson-2.1.jar"/> <classpathentry kind="lib" path="lib/gson-2.1.jar"/>
<classpathentry kind="lib" path="lib/javassist.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1686963856070/source/javassist-3.29.2-GA-sources.jar"/> <classpathentry kind="lib" path="lib/javassist.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1686963856070/source/javassist-3.29.2-GA-sources.jar"/>
<classpathentry kind="lib" path="lib/ini4j.jar"/> <classpathentry kind="lib" path="lib/ini4j.jar"/>
<classpathentry kind="lib" path="lib/JFreeChart1.5.2.jar"/>
<classpathentry kind="lib" path="lib/toml4j-0.7.1.jar"/> <classpathentry kind="lib" path="lib/toml4j-0.7.1.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-core-1.8.2.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1725520274095/source/pcap4j-core-1.8.2-sources.jar"/> <classpathentry kind="lib" path="lib/pcap4j-core-1.8.2.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1725520274095/source/pcap4j-core-1.8.2-sources.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar"/> <classpathentry kind="lib" path="lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar"/>
@@ -24,10 +23,13 @@
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/> <classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/> <classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/> <classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-26.0.1"> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25">
<attributes> <attributes>
<attribute name="module" value="true"/> <attribute name="module" value="true"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3-sources.jar"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>
+13
View File
@@ -3,3 +3,16 @@
/klalbs4.json /klalbs4.json
/klalbs.json /klalbs.json
/klalbs2.json /klalbs2.json
# Dashboard / Frontend
dashboard/node_modules/
dashboard/dist/
dashboard/.pnpm-store/
.pnpm-debug.log*
dashboard/.env.local
dashboard/.env.*.local
/klalb-config.json
/klalbconfig-old.json
/.slim/
+3
View File
@@ -0,0 +1,3 @@
[submodule "dashboard"]
path = dashboard
url = https://git.code.cq.cn/SerinaNya/KLALB-dashboard.git
+38
View File
@@ -0,0 +1,38 @@
<component name="libraryTable">
<library name="jfreechart-1.5.3">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/gson-2.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javassist.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNEOptimize.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-core-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-static-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packettest-1.8.2-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-sample-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1-sources.jar!/" />
</SOURCES>
</library>
</component>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2"> <component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="jdk-26.0.1" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" /> <output url="file://$PROJECT_DIR$/classes" />
</component> </component>
</project> </project>
+11
View File
@@ -14,4 +14,15 @@
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
</natures> </natures>
<filteredResources>
<filter>
<id>1787317094049</id>
<name></name>
<type>30</type>
<matcher>
<id>org.eclipse.core.resources.regexFilterMatcher</id>
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
</matcher>
</filter>
</filteredResources>
</projectDescription> </projectDescription>
+27
View File
@@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "KLALBMain (VS Code 自动构建)",
"request": "launch",
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
"projectName": "KLALB",
"cwd": "${workspaceFolder}",
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
},
{
"type": "java",
"name": "KLALBMain (直接用 bin 目录)",
"request": "launch",
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
"cwd": "${workspaceFolder}",
"classPaths": [
"${workspaceFolder}/bin",
"${workspaceFolder}/src",
"${workspaceFolder}/lib/*"
],
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
}
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"java.configuration.runtimes": [
{
"name": "JavaSE-25",
"path": "C:\\Program Files\\Zulu\\zulu-25",
"default": true
}
]
}
+101
View File
@@ -0,0 +1,101 @@
# AGENTS.md
KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system that merges multiple WAN links into one virtual IPv6/SRv6 network. Version constant lives in `src/org/kne/cloud/network/klalb/CONST.java`. Protocol specs and manuals are the Chinese `.docx` files in the repo root.
## Build & run
No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`.
Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text):
```powershell
& "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
```
Warnings about `ThreadTool` varargs / deprecated `finalize` are pre-existing and expected — success = exit code 0. After recompiling, restart the running app (IDE-debugged JVMs keep old classes).
Run from the repo root — CWD matters:
- reads `klalb-config.json` from CWD
- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support)
- classpath must include `src` as well as `bin`: i18n bundles (`/klalb_*.properties`) and images (`/assets/*`) are classpath resources that Eclipse copies to `bin` but manual `javac` does not
```powershell
java --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
```
IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JDK 25. `.classpath` now references the standard container `JavaSE-25` — an execution-environment spec that any JDK ≥25 satisfies, so it works unchanged on JDK 26 machines too (the original named `jdk-26.0.1` VM broke VS Code import on machines without it). `.vscode/settings.json` maps `JavaSE-25` to the locally installed Adoptium JDK; register every installed JDK there when adding another one. Keep compiler compliance ≤25 (`.settings` pins 19) so both JDKs stay usable.
Runtime gotchas (all verified):
- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs).
- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work.
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"enableTUN": false` in `klalb-config.json` or toggle off "启用 TUN 虚拟网卡" in GUI/Web settings.
- Routing broadcast (`RouterInfo`) transmits `deviceName`, which topology and node overview panels display. `deviceDescription` is NOT broadcast — it only leaves the node in full node-info query responses (see srv6 API below); `ExtraRoutes` remain local controller configs.
## Verification
No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `main()` harnesses requiring real network peers. Practical check = compile succeeds + app launches.
## Architecture
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
- API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routing-table`, `/api/nodes` (topology graph), `/api/node-info?address=<ipv6>` (on-demand full node info), `/api/config`.
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config.
- `...network.kltp` — custom reliable transport protocol (packets/streams).
- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation.
- **Node-info query API** (`...network.srv6`, JSON datagrams on `KLALBRoutingProtocol.DEFAULT_PORT=1001`): `KLALBRoutingProtocolAPIServer/Client` speak two request types —
- `nodeinfotinyreq/resp` → device name ONLY; never gated by any flag (name is public via broadcast anyway).
- `nodeinfofullreq/resp` → externalEndpoints + deviceName + deviceDescription. `denyExternalEndpointQuery=true` hides ONLY the endpoint list (`data=null`); name/description still answer.
- GUI rule: opening `NodeInformationPanel` = Full query; use `requestNodeInfoTiny` for lightweight/background lookups. Legacy `openlines*` message types were removed — mixed-version meshes get silence, so upgrade the whole network together.
- `JsonDataPacket` stores its UTF-8 payload length in a 2-byte header field: keep every JSON message under 64 KiB.
- `...network.frpc` — frp client integration.
- `...klalb.ui` — all Swing UI code.
## Frontend (Dashboard)
Located in `dashboard/`:
- **Git layout**: `dashboard/` is a separate git repo wired in as a submodule (own origin on `git.code.cq.cn`). Commit frontend changes inside `dashboard/` first, then bump the submodule pointer in the parent repo — parent-repo commits alone do not capture them.
- **Stack**: Vite + React 19 + TypeScript + Tailwind CSS v4 + `@base-ui/react` (style: `base-nova`, icons: `lucide-react`, toasts: `@base-ui/react/toast`).
- **Routing**: Hash-based routing (`#/overview`, `#/connections`, `#/routing-table`, `#/topology`, `#/settings`) for seamless SPA hosting under Java `KLALBWebServer`.
- **Package Manager**: `pnpm` (run all commands from `dashboard/` directory).
- **Component installation**: **Must** use CLI via `pnpm dlx shadcn@latest add <component>` (e.g. `pnpm dlx shadcn@latest add alert card badge toast`). Never create or fake shadcn components manually. Non-shadcn libs (`@xyflow/react`, `d3-force`) are installed via plain `pnpm add`.
- **Commands**:
- `pnpm dev` — Start Vite dev server (proxies `/api` to backend `http://127.0.0.1:4665`).
- `pnpm build` — Typecheck and build SPA to `dashboard/dist` (which Java `KLALBWebServer` serves directly).
- `pnpm lint` / `pnpm typecheck` — Verification.
- **Pages & data flow**:
- Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links).
- Settings loads/saves `/api/config` (`use-klalb-config.ts`); save payload must keep legacy field aliases alongside new names for compatibility.
- Routing table polls `/api/routing-table` every 1s (`use-routing-table.ts`); `cost` is delay-derived and displayed in milliseconds.
- Topology polls `/api/nodes` every 1s (`use-topology.ts`) — SSE does NOT carry topology.
- Selecting a topology node queries `/api/node-info`; remote node descriptions require a full SRv6 node-info request and can time out after 3s.
- Topology layout: `d3-force` headless simulation (recomputed only when node/edge structure changes) rendered by `@xyflow/react` with custom `device-node` / `link-edge` components in `src/components/topology/`.
- React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern).
## Config
`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`).
Key controller config fields:
- `externalEndpoints` / `autoConnections`: published vs auto-connect endpoint lists (renamed from `openConnections`, which itself replaced legacy `LineTable`; the old name was a developer naming mistake — these addresses are this node's externally published endpoints, not "connections").
- `ntpServers`: time server list (replaces `ntpServerTable`).
- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`).
- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name).
- `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input.
Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names.
Gson quirks:
- **gson-2.1 (vendored) is ancient**: its `JSON_ELEMENT` adapter factory only matches exact `JsonElement.class`, NOT subclasses. Calling `gson.toJson(Object)` with a runtime `JsonObject`/`JsonArray` reflectively serializes the internal field as `{"members": {...}}`. `KLALBWebServer.sendJsonResponse` guards against this by using `JsonElement.toString()` for JsonElement instances — keep that guard when adding new response paths. SSE avoids the issue entirely via `JsonObject.toString()`.
- `/api/config` GET/POST is parsed field-by-field in `KLALBWebServer.handleConfig` (NOT whole-object Gson reflection) because polymorphic fields (`List<InetAddress>`, `List<MultiProtocolSocketAddress>`) break reflective mapping. Keep new config fields in sync there, accepting both legacy and new JSON key names.
- `InetAddress`, `MultiProtocolSocketAddress`, and `KLALBConfigItem` custom adapters are registered on the shared Gson in `KLALBProxySystem`; the web server reuses that instance via `proxySystem.getGson()`.
- Saving via web API persists through `KLALBProxySystem.saveConfigToFile()` (GUI save consumer takes precedence when present).
## Conventions
- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese.
- UI strings go through `UIEnv.getRsb().getString(...)`; add keys to **both** `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
- `client.cfg`, `server.cfg`, `linetable.txt` at the root are example line-table/port-rule files loaded via the GUI file picker — not hardwired paths.
+1
View File
@@ -249,5 +249,6 @@
</library> </library>
</orderEntry> </orderEntry>
<orderEntry type="jdk" jdkName="jdk-26.0.1" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="jdk-26.0.1" jdkType="JavaSDK" />
<orderEntry type="library" name="jfreechart-1.5.3" level="project" />
</component> </component>
</module> </module>
-20
View File
@@ -1,20 +0,0 @@
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
Submodule
+1
Submodule dashboard added at 89474b9e91
BIN
View File
Binary file not shown.
-123
View File
@@ -1,123 +0,0 @@
[
{
"language": "ZH_CN",
"nogui": false,
"VirtualAddress": "2486:1:0:0:0:0:0:8888",
"VirtualASN": 2142606939373348329,
"DNS": [
"2486:1:0:0:0:0:0:8888"
],
"TCPListen": "0.0.0.0:4565",
"UDPListen": "{UDP}0.0.0.0:4572",
"VirtualSocketName": "KLALB_Stream",
"LineTable": [
"07f4acdef99b.ofalias.net:4565"
],
"ConnectLineTable": [
"home-ipv6.sliveridc1.cn:4565",
"home-ltipv6.sliveridc1.cn:4565",
"cn-gd-gz.sliveridc1.cn:4565"
],
"ntpServerTable": [
"{UDP}ntp1.aliyun.com:123",
"{UDP}ntp2.aliyun.com:123",
"{UDP}ntp3.aliyun.com:123",
"{UDP}ntp4.aliyun.com:123",
"{UDP}ntp5.aliyun.com:123",
"{UDP}ntp6.aliyun.com:123",
"{UDP}ntp7.aliyun.com:123",
"{UDP}ntp1.tencent.com:123",
"{UDP}ntp2.tencent.com:123",
"{UDP}ntp3.tencent.com:123",
"{UDP}ntp4.tencent.com:123",
"{UDP}ntp5.tencent.com:123",
"{UDP}time.google.com:123",
"{UDP}time.apple.com:123",
"{UDP}pool.ntp.org:123",
"{UDP}ntp.ntsc.ac.cn:123",
"{UDP}us.ntp.org.cn:123"
],
"denyLineTableQuery": true,
"denyLineTableBroadcast": true,
"congestionAlgorithm": "BBR",
"burstLimit": 1.5,
"delayUpperBound": 1.3,
"delayLowerBound": 1.3,
"nagleDelayTime": 1000000,
"linkNagleDelayTime": 0,
"linkConnectionsCount": 1,
"TUNName": "KLALB_SRv6",
"NetworkInterfaceExcepts": [],
"Type": "KLALBController"
},
{
"Listen": "0.0.0.0:4572",
"Bridge": {
"KLALB": "SocketBridge",
"RDP": "SocketBridge",
"HTTP": "SocketBridge",
"HTTPS": "SocketBridge",
"DEFAULT": "MinecraftSocketBridge23332"
},
"Connect": {
"KLALB": "127.0.0.1:4565",
"RDP": "127.0.0.1:3389",
"HTTP": "127.0.0.1:5212",
"HTTPS": "192.168.1.235:443",
"DEFAULT": "127.0.0.1:36555"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23332",
"Bridge": {
"DEFAULT": "MinecraftSocketBridge23332"
},
"Connect": {
"DEFAULT": "127.0.0.1:36555"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23333",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "127.0.0.1:5212"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:25565",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "127.0.0.1:25566"
},
"Type": "SocketBridge"
},
{
"Listen": "0.0.0.0:25561",
"Bridge": {
"KLALB": "SocketBridge",
"DEFAULT": "MinecraftSocketBridge23330"
},
"Connect": {
"KLALB": "127.0.0.1:4565",
"DEFAULT": "127.0.0.1:25562"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23330",
"Bridge": {
"DEFAULT": "MinecraftSocketBridge23330"
},
"Connect": {
"DEFAULT": "127.0.0.1:25562"
},
"Type": "SocketBridge"
}
]
View File
-79
View File
@@ -1,79 +0,0 @@
[
{"Type":"KLALBController" ,
"TCPListen" : "0.0.0.0:4565",
"UDPListen" : "{UDP}0.0.0.0:4572",
"VirtualSocketName" : "KLALB_Stream",
"LineTable":["43.248.189.107:65529",
"cn-bj-bgp-3.openfrp.top:65529",
"180.76.147.250:65529",
"cn-ah-dx-1.natfrp.cloud:65529",
"cn-nn-dx-1.natfrp.cloud:65529",
"cn-wh-dx-1.natfrp.cloud:65529",
"cn-zz-bgp-10.natfrp.cloud:23330",
"cn-zz-bgp-7.natfrp.cloud:33336",
"43.143.109.64:49965",
"frp.104300.xyz:49965",
"us.afrps.cn:49966",
"hk.afrps.cn:49966",
"la.afrps.cn:49966",
"frp.freefrp.net:49965",
"frp1.freefrp.net:49965",
"frp2.freefrp.net:49965",
"frp4.freefrp.net:49965",
"cn-he-plc-2.openfrp.top:4572",
"{UDP}cn-he-plc-2.openfrp.top:4572",
"frp-mom.top:22226",
"frp-man.top:36555",
"us-sjc-bgp-2.openfrp.top:36555",
"us-sjc-bgp-1.openfrp.top:36555",
"[240e:340:d856:b700:6d02:6a6a:f9fc:865b]:4572"]
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:4572",
"Bridge":{
"KLALB" : "SocketBridge",
"RDP":"SocketBridge",
"HTTP":"SocketBridge",
"HTTPS":"SocketBridge",
"DEFAULT":"MinecraftSocketBridge23332"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"RDP":"127.0.0.1:3389",
"HTTP":"127.0.0.1:5212",
"HTTPS":"192.168.1.235:443",
"DEFAULT":"127.0.0.1:36555"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23332",
"Bridge":"MinecraftSocketBridge23332",
"Connect" : "127.0.0.1:36555"
},
{ "Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23333",
"Bridge":"SocketBridge",
"Connect" : "127.0.0.1:5212"
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:25561",
"Bridge":{
"KLALB" : "SocketBridge",
"DEFAULT":"MinecraftSocketBridge23330"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"DEFAULT":"127.0.0.1:25562"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23330",
"Bridge":"MinecraftSocketBridge23330",
"Connect" : "127.0.0.1:25562"
}
]
-3
View File
@@ -1,3 +0,0 @@
virtualip=cefe:49b8:f837:4b00:b9cd:dbdd:c299:349
bind=0.0.0.0:4569
local=127.0.0.1:5212
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-29
View File
@@ -1,29 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
-18
View File
@@ -1,18 +0,0 @@
43.248.189.107:65529
cn-bj-bgp-3.openfrp.top:65529
180.76.147.250:65529
cn-ah-dx-1.natfrp.cloud:65529
cn-nn-dx-1.natfrp.cloud:65529
cn-wh-dx-1.natfrp.cloud:65529
cn-zz-bgp-10.natfrp.cloud:23330
cn-zz-bgp-7.natfrp.cloud:33336
43.143.109.64:49965
frp.104300.xyz:49965
us.afrps.cn:49966
hk.afrps.cn:49966
la.afrps.cn:49966
frp.freefrp.net:49965
frp1.freefrp.net:49965
frp2.freefrp.net:49965
frp4.freefrp.net:49965
cn-he-plc-2.openfrp.top:4569
View File
-8
View File
@@ -1,8 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
+11
View File
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"shadcn": {
"source": "shadcn/ui",
"sourceType": "github",
"skillPath": "skills/shadcn/SKILL.md",
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
}
}
}
+12
View File
@@ -3,7 +3,10 @@ networkgraph=Network graph
language=Language language=Language
basicsettings=Basic settings basicsettings=Basic settings
ipv6addr=IPv6 address ipv6addr=IPv6 address
devicename=Device name
devicedescription=Device description
dnsserver=DNS server dnsserver=DNS server
extraroutes=Extra routes
asnumber=AS number asnumber=AS number
tcplistening=TCP listening tcplistening=TCP listening
udplistening=UDP listening udplistening=UDP listening
@@ -34,6 +37,7 @@ saveconfigsuccess=Save config success
warning=Warning warning=Warning
invaildipv6addr=IPv6 address:Invaild Input invaildipv6addr=IPv6 address:Invaild Input
invailddnsserver=DNS server:Invaild Input invailddnsserver=DNS server:Invaild Input
invaildextraroutes=Extra routes:Invaild Input
invaildasnumber=AS number:Invaild Input invaildasnumber=AS number:Invaild Input
invaildtcplisten=TCP listen:Invaild Input invaildtcplisten=TCP listen:Invaild Input
invaildudplisten=UDP listen:Invaild Input invaildudplisten=UDP listen:Invaild Input
@@ -105,3 +109,11 @@ uploadbasedelay=Upload Base
burstlimit=Burst limit burstlimit=Burst limit
performancesettings=Performance settings performancesettings=Performance settings
performancestrategy=Performance strategy performancestrategy=Performance strategy
singlecore=Single-Core - Cache Affinity First (Best energy/performance ratio, for low-power & cloud)
multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose physical servers)
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
webapisettings=Web API settings
enablewebapi=Enable Web API
weblistenaddr=Web API listen address
invaildweblistenaddr=Invalid Web API listen address
enabletun=Enable TUN adapter
+12
View File
@@ -3,7 +3,10 @@ networkgraph=网络拓扑图
language=语言 language=语言
basicsettings=基本设置 basicsettings=基本设置
ipv6addr=IPv6地址 ipv6addr=IPv6地址
devicename=设备名称
devicedescription=设备描述
dnsserver=DNS服务器 dnsserver=DNS服务器
extraroutes=额外路由
asnumber=AS号码 asnumber=AS号码
tcplistening=TCP监听端口 tcplistening=TCP监听端口
udplistening=UDP监听端口 udplistening=UDP监听端口
@@ -34,6 +37,7 @@ saveconfigsuccess=保存配置成功
warning=警告 warning=警告
invaildipv6addr=IPv6地址:非法输入 invaildipv6addr=IPv6地址:非法输入
invailddnsserver=DNS服务器:非法输入 invailddnsserver=DNS服务器:非法输入
invaildextraroutes=额外路由:非法输入
invaildasnumber=AS号码:非法输入 invaildasnumber=AS号码:非法输入
invaildtcplisten=TCP监听端口:非法输入 invaildtcplisten=TCP监听端口:非法输入
invaildudplisten=UDP监听端口:非法输入 invaildudplisten=UDP监听端口:非法输入
@@ -105,3 +109,11 @@ uploadbasedelay=上传延迟基线
burstlimit=突发限制 burstlimit=突发限制
performancesettings=性能设置 performancesettings=性能设置
performancestrategy=性能策略 performancestrategy=性能策略
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API 设置
enablewebapi=启用 Web API
weblistenaddr=Web API 监听地址:端口
invaildweblistenaddr=无效的 Web API 监听地址:端口
enabletun=启用TUN虚拟网卡
@@ -3,14 +3,9 @@ package org.kne.cloud.network;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.net.ServerSocketFactory;
public class DatagramSocketListener implements Closeable,AutoCloseable{ public class DatagramSocketListener implements Closeable,AutoCloseable{
private DatagramServerSocket serverSocket; private DatagramServerSocket serverSocket;
public DatagramServerSocket getDatagramServerSocket() { public DatagramServerSocket getDatagramServerSocket() {
@@ -36,10 +31,10 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
} }
} }
}; };
private MultipurposeSocketAddress multipurposeSocketAddress; private MultiProtocolSocketAddress multiProtocolSocketAddress;
public DatagramSocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException { public DatagramSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multipurposeSocketAddress=multipurposeSocketAddress; this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open(); open();
} }
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException { public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
@@ -49,7 +44,7 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
private void open() throws IOException { private void open() throws IOException {
if(serverSocket==null) if(serverSocket==null)
serverSocket=multipurposeSocketAddress.listenDatagramServerSocket(); serverSocket= multiProtocolSocketAddress.listenDatagramServerSocket();
//new Thread(r).start(); //new Thread(r).start();
ThreadTool.makePThreadIfSupport("端口监听线程",r).start(); ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
} }
@@ -63,8 +58,8 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() { public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress; return multiProtocolSocketAddress;
} }
public Consumer<DatagramSocket> getCon() { public Consumer<DatagramSocket> getCon() {
+3 -4
View File
@@ -1,9 +1,8 @@
package org.kne.cloud.network; package org.kne.cloud.network;
import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress> { public class HostPortMap extends LinkedHashMap<String, MultiProtocolSocketAddress> {
/** /**
* *
@@ -11,9 +10,9 @@ public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
public MultipurposeSocketAddress putN$H(String str) { public MultiProtocolSocketAddress putN$H(String str) {
String[] strx=str.split("\\$"); String[] strx=str.split("\\$");
return super.put(strx[0], new MultipurposeSocketAddress( strx[1])); return super.put(strx[0], new MultiProtocolSocketAddress( strx[1]));
} }
} }
@@ -7,16 +7,8 @@ import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@@ -29,12 +21,11 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
private NetworkInterface ninterface; private NetworkInterface ninterface;
private volatile boolean closed = false; private volatile boolean closed = false;
private InetSocketAddress group; private InetSocketAddress group;
private InetSocketAddress bind;
private int type; private int type;
private List<MultipurposeSocketAddress> msas; private List<MultiProtocolSocketAddress> msas;
private UUID selfUUID; private UUID selfUUID;
private volatile Consumer<MultipurposeSocketAddress> con; private volatile Consumer<MultiProtocolSocketAddress> con;
private long timeInterval; private long timeInterval;
public long getTimeInterval() { public long getTimeInterval() {
@@ -45,7 +36,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
this.timeInterval = timeInterval; this.timeInterval = timeInterval;
} }
public NetworkInterface getNinterface() { public NetworkInterface getInterface() {
return ninterface; return ninterface;
} }
@@ -54,22 +45,24 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
} }
public InetSocketAddress getBind() { public InetSocketAddress getBind() {
return bind; return (InetSocketAddress) soc.getLocalSocketAddress();
} }
public Consumer<MultipurposeSocketAddress> getCon() { public Consumer<MultiProtocolSocketAddress> getCon() {
return con; return con;
} }
public void setCon(Consumer<MultipurposeSocketAddress> con) { public void setCon(Consumer<MultiProtocolSocketAddress> con) {
this.con = con; this.con = con;
} }
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface, public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException { List<MultiProtocolSocketAddress> msas, UUID node, long timeInterval) throws IOException {
soc = new MulticastSocket(bind); soc = new MulticastSocket(null);
soc.setReuseAddress(true);
soc.bind(bind);
soc.setNetworkInterface(ninterface);
soc.joinGroup(group, ninterface); soc.joinGroup(group, ninterface);
this.bind = bind;
this.group = group; this.group = group;
this.ninterface = ninterface; this.ninterface = ninterface;
this.msas = msas; this.msas = msas;
@@ -82,46 +75,22 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> { ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
try { try {
while (!closed) { while (!closed) {
HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>(); /*MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
synchronized (msas) { multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());*/
for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) { if(msas!=null) {
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator synchronized (msas) {
.next(); for (Iterator<MultiProtocolSocketAddress> iterator = msas.iterator(); iterator.hasNext(); ) {
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator
.next();
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress( try {
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort()); send(multiProtocolSocketAddress);
try { } catch (NoRouteToHostException | UnknownHostException e) {
if (s.add(mpsa)) {
boolean bf = true;
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
if (inetAddress.equals(multipurposeSocketAddress.getInetAddress())) {
bf = false;
break;
} }
}
if (bf) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dops = new DataOutputStream(baos);
dops.writeInt(multipurposeSocketAddress.getPort());
dops.writeUTF(multipurposeSocketAddress.getType());
dops.writeLong(selfUUID.getMostSignificantBits());
dops.writeLong(selfUUID.getLeastSignificantBits());
dops.close();
byte[] b = baos.toByteArray();
DatagramPacket dp = new DatagramPacket(b, b.length, group);
soc.send(dp);
if(debug)
System.out.println("发送广播:"+multipurposeSocketAddress+" "+group+" "+selfUUID);
} }
} }
} catch (NoRouteToHostException|UnknownHostException e) {
}
} }
}
Thread.sleep(timeInterval); Thread.sleep(timeInterval);
} }
} catch (IOException e) { } catch (IOException e) {
@@ -143,28 +112,9 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
try { try {
loop:while (!closed) { loop:while (!closed) {
try { try {
byte[] b = new byte[65535]; MultiProtocolSocketAddress mpsa = receive();
DatagramPacket dp = new DatagramPacket(b, b.length); if (mpsa == null) continue;
soc.receive(dp); InetAddress mpsai=mpsa.getInetAddress();
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
DataInputStream dis = new DataInputStream(bis);
int port = dis.readInt();
String type = dis.readUTF();
long h=dis.readLong();
long l=dis.readLong();
UUID uid=new UUID(h, l);
dis.close();
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(type, dp.getAddress().getHostAddress(),
port);
if(debug) {
if(uid.equals(selfUUID)) {
System.out.println("丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
continue;
}else {
System.out.println("接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
}
}
InetAddress mpsai=mpsa.getInetAddress();
Enumeration<InetAddress> ei = ninterface.getInetAddresses(); Enumeration<InetAddress> ei = ninterface.getInetAddresses();
while (ei.hasMoreElements()) { while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement(); InetAddress inetAddress = (InetAddress) ei.nextElement();
@@ -186,7 +136,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
if(!isClosed()) if(!isClosed())
e.printStackTrace(); e.printStackTrace();
}catch (IOException e) { }catch (IOException e) {
System.out.println(bind + " " + group + " " + ninterface); System.out.println(getBind() + " " + group + " " + ninterface);
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
try { try {
@@ -196,7 +146,48 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
e.printStackTrace(); e.printStackTrace();
} }
} }
}); }).start();
}
private MultiProtocolSocketAddress receive() throws IOException {
byte[] b = new byte[65535];
DatagramPacket dp = new DatagramPacket(b, b.length);
soc.receive(dp);
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
DataInputStream dis = new DataInputStream(bis);
String val = dis.readUTF();
long h=dis.readLong();
long l=dis.readLong();
UUID uid=new UUID(h, l);
dis.close();
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(val);
if(mpsa.getInetAddress().isAnyLocalAddress()){
mpsa=new MultiProtocolSocketAddress(mpsa.getProtocol(),dp.getAddress().getHostAddress(),mpsa.getPort());
}
if(debug) {
if(uid.equals(selfUUID)) {
System.out.println(ninterface.getDisplayName()+" 丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
return null;
}else {
System.out.println(ninterface.getDisplayName()+ " 接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
}
}
return mpsa;
}
private void send(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dops = new DataOutputStream(baos);
dops.writeUTF(multiProtocolSocketAddress.toString());
dops.writeLong(selfUUID.getMostSignificantBits());
dops.writeLong(selfUUID.getLeastSignificantBits());
dops.close();
byte[] b = baos.toByteArray();
DatagramPacket dp = new DatagramPacket(b, b.length, group);
soc.send(dp);
if(debug)
System.out.println(ninterface.getDisplayName()+" 发送广播:"+ multiProtocolSocketAddress +" "+group+" "+selfUUID);
} }
@Override @Override
@@ -3,14 +3,7 @@ package org.kne.cloud.network;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.DatagramSocket; import java.net.*;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.ServerSocketChannel; import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.*; import java.util.*;
@@ -18,7 +11,6 @@ import java.util.*;
import javax.net.ServerSocketFactory; import javax.net.ServerSocketFactory;
import javax.net.SocketFactory; import javax.net.SocketFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
@@ -27,17 +19,18 @@ import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import org.kne.cloud.network.ntp.NTPSocketType;
public class MultipurposeSocketAddress implements Serializable{ public class MultiProtocolSocketAddress implements Serializable{
/** /**
* *
*/ */
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>(); private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
static { static {
socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory())); socketTypeRegister.put("tcp", TCPSocketType.getInstance());
socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory())); socketTypeRegister.put("udp", UDPSocketType.getInstance());
socketTypeRegister.put("ntp", NTPSocketType.getInstance());
} }
@@ -46,46 +39,59 @@ public class MultipurposeSocketAddress implements Serializable{
return socketTypeRegister; return socketTypeRegister;
} }
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private String type; private String protocol;
private String host; private String host;
private ProtocolStack ps=new ProtocolStack(); private ProtocolStack ps=new ProtocolStack();
private int port; private int port;
public MultipurposeSocketAddress(String hostport) { public MultiProtocolSocketAddress(String hostport) {
this(hostport,"TCP"); this(hostport,"tcp");
} }
public MultipurposeSocketAddress(String hostport,String defaulttype) { public MultiProtocolSocketAddress(String hostport, String defaultprotocol) {
hostport=hostport.trim(); try {
if (hostport.startsWith("{")) { if (!hostport.contains("://")) {
int i=hostport.indexOf("}"); hostport = defaultprotocol.toLowerCase() + "://" + hostport;
if(i==-1) {
throw new IllegalArgumentException("missing }");
} }
type=hostport.substring(1, i); URI uri = new URI(hostport);
hostport=hostport.substring(i+1);
}else { this.protocol = uri.getScheme().toLowerCase();
type=defaulttype; this.host = uri.getHost();
if (this.host == null) {
throw new IllegalArgumentException("无效的主机地址: " + hostport);
}
this.port = resolvePort(uri, this.protocol);
// 可选处理查询参数
String query = uri.getQuery();
if (query != null && !query.isEmpty()) {
// 解析查询参数作为扩展信息
}
} catch (URISyntaxException e) {
throw new IllegalArgumentException("无效的地址格式: " + hostport, e);
} }
int index =hostport.lastIndexOf(":"); }
host=hostport.substring(0,index);
host=host.replace("[", ""); private int resolvePort(URI uri, String protocol) {
host=host.replace("]", ""); int port = uri.getPort();
String tps=hostport.substring(index+1); if (port != -1) {
int v=tps.indexOf("("); return port;
if(v!=-1) {
ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
tps=tps.substring(0, v);
} }
port=Integer.parseInt(tps); SocketType socketType = socketTypeRegister.get(protocol);
if(port<0) { if (socketType != null) {
throw new IllegalArgumentException("port < 0"); int defaultPort = socketType.getDefaultPort();
if (defaultPort != -1) {
return defaultPort;
}
} }
throw new IllegalArgumentException(
"未指定端口号,且协议 " + protocol + " 没有默认端口: " + uri
);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(host, port, ps, type); return Objects.hash(host, port, ps, protocol);
} }
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
@@ -95,9 +101,9 @@ public class MultipurposeSocketAddress implements Serializable{
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj; MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps) return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(type, other.type); && Objects.equals(protocol, other.protocol);
} }
public boolean equals2(Object obj) { public boolean equals2(Object obj) {
@@ -107,7 +113,7 @@ public class MultipurposeSocketAddress implements Serializable{
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj; MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
InetAddress addra=null; InetAddress addra=null;
try { try {
addra = InetAddress.getByName(host); addra = InetAddress.getByName(host);
@@ -121,25 +127,25 @@ public class MultipurposeSocketAddress implements Serializable{
boolean hostequals=Objects.equals(addra, addrb); boolean hostequals=Objects.equals(addra, addrb);
return hostequals && port == other.port && Objects.equals(ps, other.ps) return hostequals && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(type, other.type); && Objects.equals(protocol, other.protocol);
} }
public MultipurposeSocketAddress(String host2, int port2) { public MultiProtocolSocketAddress(String host2, int port2) {
this("TCP", host2, port2); this("tcp", host2, port2);
} }
public MultipurposeSocketAddress(String type,String host2, int port2) { public MultiProtocolSocketAddress(String protocol, String host2, int port2) {
host=host2; host=host2;
port=port2; port=port2;
this.type=type; this.protocol = protocol;
} }
public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) { public MultiProtocolSocketAddress(InetSocketAddress remoteSocketAddress) {
this("TCP",remoteSocketAddress); this("tcp",remoteSocketAddress);
} }
public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) { public MultiProtocolSocketAddress(String protocol, InetSocketAddress remoteSocketAddress) {
host=remoteSocketAddress.getAddress().getHostAddress(); host=remoteSocketAddress.getAddress().getHostAddress();
port=remoteSocketAddress.getPort(); port=remoteSocketAddress.getPort();
this.type=type; this.protocol = protocol;
} }
@@ -149,8 +155,8 @@ public class MultipurposeSocketAddress implements Serializable{
public int getPort() { public int getPort() {
return port; return port;
} }
public String getType() { public String getProtocol() {
return type; return protocol;
} }
public InetSocketAddress getSocketAddress() { public InetSocketAddress getSocketAddress() {
@@ -159,27 +165,25 @@ public class MultipurposeSocketAddress implements Serializable{
@Override @Override
public String toString() { public String toString() {
StringBuilder sb=new StringBuilder(); try {
if(!"TCP".equals(type)) { // 获取协议的默认端口
sb.append('{').append(type).append('}'); SocketType socketType = socketTypeRegister.get(protocol);
int defaultPort = (socketType != null) ? socketType.getDefaultPort() : -1;
// 如果当前端口等于默认端口则隐去端口号
int portToUse = (defaultPort != -1 && this.port == defaultPort) ? -1 : this.port;
URI uri = new URI(protocol, null, host, portToUse, null, null, null);
return uri.toString();
} catch (URISyntaxException e) {
return protocol + "://" + host + ":" + port;
} }
if(host.contains(":")) {
sb.append('[').append( host).append( "]:").append( port);
}else {
sb.append( host).append( ':').append( port);
}
if(!ps.isEmpty()) {
sb.append('(').append(ps.pop().getName()).append(')');
}
return sb.toString();
} }
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException { public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory(); SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -194,7 +198,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException { public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory(); SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -209,7 +213,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public Socket connectSocket() throws UnknownHostException, IOException { public Socket connectSocket() throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory(); SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -223,7 +227,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public Socket connectSocket(int timeout) throws UnknownHostException, IOException { public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory(); SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -244,7 +248,7 @@ public class MultipurposeSocketAddress implements Serializable{
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException { public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory(); SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -265,7 +269,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException { public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory(); SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -280,7 +284,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException { public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory(); SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -294,7 +298,7 @@ public class MultipurposeSocketAddress implements Serializable{
return s; return s;
} }
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException { public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory(); SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) { if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported"); throw new UnsupportedOperationException("Socket Unsupported");
} }
@@ -319,7 +323,7 @@ public class MultipurposeSocketAddress implements Serializable{
public ServerSocket listenServerSocket() throws UnknownHostException, IOException { public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory(); ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported"); throw new UnsupportedOperationException("ServerSocket Unsupported");
} }
@@ -327,7 +331,7 @@ public class MultipurposeSocketAddress implements Serializable{
return sk; return sk;
} }
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException { public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory(); ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported"); throw new UnsupportedOperationException("ServerSocket Unsupported");
} }
@@ -337,7 +341,7 @@ public class MultipurposeSocketAddress implements Serializable{
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException { public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory(); ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported"); throw new UnsupportedOperationException("ServerSocket Unsupported");
} }
@@ -345,7 +349,7 @@ public class MultipurposeSocketAddress implements Serializable{
return sk; return sk;
} }
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException { public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory(); ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported"); throw new UnsupportedOperationException("ServerSocket Unsupported");
} }
@@ -355,13 +359,13 @@ public class MultipurposeSocketAddress implements Serializable{
public boolean isStream() { public boolean isStream() {
return checkIsStream(type); return checkIsStream(protocol);
} }
public static boolean checkIsStream(String type2) { public static boolean checkIsStream(String type2) {
return socketTypeRegister.get(type2).isStream(); return socketTypeRegister.get(type2).isStream();
} }
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException { public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory(); DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) { if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported"); throw new UnsupportedOperationException("DatagramSocket Unsupported");
} }
@@ -376,7 +380,7 @@ public class MultipurposeSocketAddress implements Serializable{
return dgd; return dgd;
} }
public DatagramSocket connectDatagramSocket() throws IOException { public DatagramSocket connectDatagramSocket() throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory(); DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) { if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported"); throw new UnsupportedOperationException("DatagramSocket Unsupported");
} }
@@ -390,7 +394,7 @@ public class MultipurposeSocketAddress implements Serializable{
return dgd; return dgd;
} }
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException { public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory(); DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) { if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported"); throw new UnsupportedOperationException("DatagramSocket Unsupported");
} }
@@ -405,7 +409,7 @@ public class MultipurposeSocketAddress implements Serializable{
} }
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException { public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory(); DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported"); throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
} }
@@ -413,7 +417,7 @@ public class MultipurposeSocketAddress implements Serializable{
return sk; return sk;
} }
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException { public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory(); DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
if(srf==null) { if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported"); throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
} }
@@ -427,7 +431,7 @@ public class MultipurposeSocketAddress implements Serializable{
return InetAddress.getAllByName(host); return InetAddress.getAllByName(host);
} }
public boolean supportNIO() { public boolean supportNIO() {
SocketType stp= socketTypeRegister.get(getType()); SocketType stp= socketTypeRegister.get(getProtocol());
if(stp==null) if(stp==null)
return false; return false;
if(stp.getSocketChannelFactory()==null) { if(stp.getSocketChannelFactory()==null) {
@@ -437,35 +441,37 @@ public class MultipurposeSocketAddress implements Serializable{
} }
} }
public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){ public static JsonDeserializer<MultiProtocolSocketAddress>getDefaultJsonDeserializer(){
return new JsonDeserializer<MultipurposeSocketAddress>() { return new JsonDeserializer<MultiProtocolSocketAddress>() {
@Override @Override
public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2) public MultiProtocolSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException { throws JsonParseException {
if(arg0.isJsonPrimitive()) { if(arg0.isJsonPrimitive()) {
JsonPrimitive jp=(JsonPrimitive) arg0; JsonPrimitive jp=(JsonPrimitive) arg0;
if(jp.isString()) { if(jp.isString()) {
return new MultipurposeSocketAddress(jp.getAsString()); MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(jp.getAsString());
// System.out.println(msa);
return msa;
} }
} }
throw new JsonParseException("not a string:"+arg0); throw new JsonParseException("not a string:"+arg0);
} }
}; };
} }
public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){ public static JsonSerializer<MultiProtocolSocketAddress>getDefaultJsonSerializer(){
return new JsonSerializer<MultipurposeSocketAddress>() { return new JsonSerializer<MultiProtocolSocketAddress>() {
@Override @Override
public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) { public JsonElement serialize(MultiProtocolSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
return new JsonPrimitive(arg0.toString()); return new JsonPrimitive(arg0.toString());
} }
}; };
} }
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) { public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer()); gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonDeserializer());
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer()); gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonSerializer());
} }
} }
@@ -1,9 +1,9 @@
package org.kne.cloud.network; package org.kne.cloud.network;
public interface NetworkService { public interface NetworkService {
public void listen(MultipurposeSocketAddress msa); public void listen(MultiProtocolSocketAddress msa);
public void unlisten(MultipurposeSocketAddress msc); public void unlisten(MultiProtocolSocketAddress msc);
public void connect(MultipurposeSocketAddress msa); public void connect(MultiProtocolSocketAddress msa);
public void unconnect(MultipurposeSocketAddress msc); public void unconnect(MultiProtocolSocketAddress msc);
} }
+3 -9
View File
@@ -1,13 +1,7 @@
package org.kne.cloud.network; package org.kne.cloud.network;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.AbstractInterruptibleChannel;
import java.util.*;
public class PortRelay { public class PortRelay {
private int port; private int port;
@@ -16,8 +10,8 @@ public class PortRelay {
public PortRelay(int port, HostPortMap services) throws IOException { public PortRelay(int port, HostPortMap services) throws IOException {
this.services = services; this.services = services;
this.port = port; this.port = port;
MultipurposeSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) ); MultiProtocolSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
ssc=new SocketListener(new MultipurposeSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port)); ssc=new SocketListener(new MultiProtocolSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
} }
public void start() throws IOException { public void start() throws IOException {
ssc.setCon((s)->{ ssc.setCon((s)->{
@@ -30,7 +24,7 @@ public class PortRelay {
}else { }else {
nx=pds.getProtocolStack().pop().getName(); nx=pds.getProtocolStack().pop().getName();
} }
MultipurposeSocketAddress hp=services.get(nx); MultiProtocolSocketAddress hp=services.get(nx);
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress(); InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
System.out.println(sa.getAddress().getHostAddress() + ":" + port System.out.println(sa.getAddress().getHostAddress() + ":" + port
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort()); + "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
@@ -1,23 +1,19 @@
package org.kne.cloud.network; package org.kne.cloud.network;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class ServiceElement { public class ServiceElement {
public String name; public String name;
public String protocol; public String protocol;
public MultipurposeSocketAddress ipport; public MultiProtocolSocketAddress ipport;
public ServiceElement(String ini) throws UnknownHostException { public ServiceElement(String ini) throws UnknownHostException {
String[]t=ini.split("\\$"); String[]t=ini.split("\\$");
if(t.length==1) { if(t.length==1) {
protocol=""; protocol="";
ipport=new MultipurposeSocketAddress(t[0]); ipport=new MultiProtocolSocketAddress(t[0]);
}else { }else {
protocol=t[0]; protocol=t[0];
ipport=new MultipurposeSocketAddress(t[1]); ipport=new MultiProtocolSocketAddress(t[1]);
} }
} }
public ServiceElement() { public ServiceElement() {
@@ -1,21 +1,20 @@
package org.kne.cloud.network; package org.kne.cloud.network;
import java.io.IOException; import java.io.IOException;
import java.util.function.Consumer;
public class ServiceToSocketProxy extends Proxy{ public class ServiceToSocketProxy extends Proxy{
public NetworkService getSrc() { public NetworkService getSrc() {
return src; return src;
} }
public MultipurposeSocketAddress getDes() { public MultiProtocolSocketAddress getDes() {
return des; return des;
} }
public ServiceToSocketProxy(String l, String r) { public ServiceToSocketProxy(String l, String r) {
src=getRegister().get(l.substring(1, l.length()-1)); src=getRegister().get(l.substring(1, l.length()-1));
des=new MultipurposeSocketAddress(r); des=new MultiProtocolSocketAddress(r);
} }
private NetworkService src; private NetworkService src;
private MultipurposeSocketAddress des; private MultiProtocolSocketAddress des;
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// TODO 自动生成的方法存根 // TODO 自动生成的方法存根
@@ -50,10 +50,10 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
} }
} }
}; };
private MultipurposeSocketAddress multipurposeSocketAddress; private MultiProtocolSocketAddress multiProtocolSocketAddress;
public SocketChannelListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException { public SocketChannelListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multipurposeSocketAddress=multipurposeSocketAddress; this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open(); open();
} }
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException { public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
@@ -62,9 +62,9 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
} }
protected void open() throws UnknownHostException, IOException { protected void open() throws UnknownHostException, IOException {
if(serverSocketChannel==null) if(serverSocketChannel==null)
serverSocketChannel=multipurposeSocketAddress.listenServerSocketChannel(); serverSocketChannel= multiProtocolSocketAddress.listenServerSocketChannel();
if(serverSocketChannel==null) { if(serverSocketChannel==null) {
throw new IOException(multipurposeSocketAddress.getType()+" unsupport SocketChannel"); throw new IOException(multiProtocolSocketAddress.getProtocol()+" unsupport SocketChannel");
} }
ThreadTool.makeVThread("端口监听线程", r).start(); ThreadTool.makeVThread("端口监听线程", r).start();
} }
@@ -82,8 +82,8 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() { public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress; return multiProtocolSocketAddress;
} }
public Consumer<SocketChannel> getCon() { public Consumer<SocketChannel> getCon() {
@@ -2,15 +2,12 @@ package org.kne.cloud.network;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.function.Consumer; import java.util.function.Consumer;
import javax.net.ServerSocketFactory;
public class SocketListener implements Closeable,AutoCloseable{ public class SocketListener implements Closeable,AutoCloseable{
protected ServerSocket serverSocket; protected ServerSocket serverSocket;
public ServerSocket getServerSocket() { public ServerSocket getServerSocket() {
@@ -53,10 +50,10 @@ public class SocketListener implements Closeable,AutoCloseable{
} }
} }
}; };
private MultipurposeSocketAddress multipurposeSocketAddress; private MultiProtocolSocketAddress multiProtocolSocketAddress;
public SocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException { public SocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multipurposeSocketAddress=multipurposeSocketAddress; this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open(); open();
} }
public SocketListener(ServerSocket tserverSocket) throws IOException { public SocketListener(ServerSocket tserverSocket) throws IOException {
@@ -65,7 +62,7 @@ public class SocketListener implements Closeable,AutoCloseable{
} }
protected void open() throws UnknownHostException, IOException { protected void open() throws UnknownHostException, IOException {
if(serverSocket==null) if(serverSocket==null)
serverSocket=multipurposeSocketAddress.listenServerSocket(); serverSocket= multiProtocolSocketAddress.listenServerSocket();
ThreadTool.makeVThread("端口监听线程", r).start(); ThreadTool.makeVThread("端口监听线程", r).start();
} }
public void close() { public void close() {
@@ -82,8 +79,8 @@ public class SocketListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() { public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress; return multiProtocolSocketAddress;
} }
public Consumer<Socket> getCon() { public Consumer<Socket> getCon() {
@@ -1,21 +1,19 @@
package org.kne.cloud.network; package org.kne.cloud.network;
import java.io.IOException; import java.io.IOException;
import java.net.Socket;
import java.util.function.Consumer;
public class SocketToServiceProxy extends Proxy { public class SocketToServiceProxy extends Proxy {
public SocketToServiceProxy(String l, String r) { public SocketToServiceProxy(String l, String r) {
src=new MultipurposeSocketAddress(l); src=new MultiProtocolSocketAddress(l);
des=getRegister().get(r.substring(1, r.length()-1)) ; des=getRegister().get(r.substring(1, r.length()-1)) ;
} }
public MultipurposeSocketAddress getSrc() { public MultiProtocolSocketAddress getSrc() {
return src; return src;
} }
public NetworkService getDes() { public NetworkService getDes() {
return des; return des;
} }
private MultipurposeSocketAddress src; private MultiProtocolSocketAddress src;
private NetworkService des; private NetworkService des;
@Override @Override
public void close() throws IOException { public void close() throws IOException {
@@ -4,10 +4,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.net.StandardSocketOptions; import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.util.Map; import java.util.Map;
@@ -15,8 +12,8 @@ public class SocketToSocketProxy extends Proxy {
private SocketListener sl; private SocketListener sl;
private SocketChannelListener scl; private SocketChannelListener scl;
private DatagramSocketListener dsl; private DatagramSocketListener dsl;
private MultipurposeSocketAddress listen, cbind, defaultConnect; private MultiProtocolSocketAddress listen, cbind, defaultConnect;
private Map<String, MultipurposeSocketAddress> detectedConnect; private Map<String, MultiProtocolSocketAddress> detectedConnect;
private SocketBridgeFactory defaultFactory; private SocketBridgeFactory defaultFactory;
private SocketChannelBridgeFactory defaultChannelFactory; private SocketChannelBridgeFactory defaultChannelFactory;
private Map<String, SocketBridgeFactory> detectedFactory; private Map<String, SocketBridgeFactory> detectedFactory;
@@ -30,28 +27,28 @@ public class SocketToSocketProxy extends Proxy {
this.defaultSoTimeout = defaultSoTimeout; this.defaultSoTimeout = defaultSoTimeout;
} }
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) throws IOException { public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress connect) throws IOException {
this(listen, new MultipurposeSocketAddress(new InetSocketAddress(0)), connect); this(listen, new MultiProtocolSocketAddress(new InetSocketAddress(0)), connect);
} }
public SocketToSocketProxy(String l, String r) throws IOException { public SocketToSocketProxy(String l, String r) throws IOException {
this(new MultipurposeSocketAddress(l), new MultipurposeSocketAddress(r)); this(new MultiProtocolSocketAddress(l), new MultiProtocolSocketAddress(r));
} }
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect) MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect)
throws IOException { throws IOException {
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null); this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
} }
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory) MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory)
throws IOException { throws IOException {
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory()); this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
} }
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory,SocketChannelBridgeFactory defaultChannelFactory) MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory, SocketChannelBridgeFactory defaultChannelFactory)
throws IOException { throws IOException {
this.listen = listen; this.listen = listen;
this.cbind = cbind; this.cbind = cbind;
@@ -66,8 +63,8 @@ public class SocketToSocketProxy extends Proxy {
open(); open();
} }
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind, public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultipurposeSocketAddress defaultConnect) throws IOException { MultiProtocolSocketAddress defaultConnect) throws IOException {
this(listen, cbind, defaultConnect,null); this(listen, cbind, defaultConnect,null);
} }
@@ -112,7 +109,7 @@ public class SocketToSocketProxy extends Proxy {
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack(); ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
if (!ps.isEmpty()) { if (!ps.isEmpty()) {
String pname=ps.pop().getName(); String pname=ps.pop().getName();
MultipurposeSocketAddress pmsa = detectedConnect.get(pname); MultiProtocolSocketAddress pmsa = detectedConnect.get(pname);
if(pmsa!=null) { if(pmsa!=null) {
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort()); sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
}else { }else {
@@ -178,19 +175,19 @@ public class SocketToSocketProxy extends Proxy {
sb.run(); sb.run();
} }
public MultipurposeSocketAddress getListen() { public MultiProtocolSocketAddress getListen() {
return listen; return listen;
} }
public MultipurposeSocketAddress getCbind() { public MultiProtocolSocketAddress getCbind() {
return cbind; return cbind;
} }
public MultipurposeSocketAddress getDefaultConnect() { public MultiProtocolSocketAddress getDefaultConnect() {
return defaultConnect; return defaultConnect;
} }
public Map<String, MultipurposeSocketAddress> getDetectedConnect() { public Map<String, MultiProtocolSocketAddress> getDetectedConnect() {
return detectedConnect; return detectedConnect;
} }
+4 -1
View File
@@ -15,6 +15,7 @@ public class SocketType {
private DatagramSocketFactory datagramSocketFactory; private DatagramSocketFactory datagramSocketFactory;
private DatagramServerSocketFactory datagramServerSocketFactory; private DatagramServerSocketFactory datagramServerSocketFactory;
private boolean stream; private boolean stream;
private int defaultPort=-1;
public SocketFactory Channel() { public SocketFactory Channel() {
return socketFactory; return socketFactory;
@@ -74,5 +75,7 @@ public class SocketType {
return stream; return stream;
} }
public int getDefaultPort(){
return defaultPort;
}
} }
@@ -0,0 +1,23 @@
package org.kne.cloud.network;
public class TCPSocketType extends SocketType {
private static final TCPSocketType INSTANCE = new TCPSocketType();
private TCPSocketType() {
super(
new DefaultSocketFactory(),
new DefaultServerSocketFactory(),
new DefaultSocketChannelFactory(),
new DefaultServerSocketChannelFactory()
);
}
public static TCPSocketType getInstance() {
return INSTANCE;
}
@Override
public boolean isStream() {
return true;
}
}
@@ -0,0 +1,21 @@
package org.kne.cloud.network;
public class UDPSocketType extends SocketType {
private static final UDPSocketType INSTANCE = new UDPSocketType();
protected UDPSocketType() {
super(
new DefaultDatagramSocketFactory(),
new DefaultDatagramServerSocketFactory()
);
}
public static UDPSocketType getInstance() {
return INSTANCE;
}
@Override
public boolean isStream() {
return false;
}
}
@@ -4,16 +4,13 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.SocketImpl; import java.net.SocketImpl;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import org.ini4j.Profile.Section; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.MultipurposeSocketAddress;
public class FrpcServerSocket extends ServerSocket { public class FrpcServerSocket extends ServerSocket {
private FrpcProcess frpc; private FrpcProcess frpc;
@@ -76,10 +73,10 @@ public class FrpcServerSocket extends ServerSocket {
return frpcini.getRemotePort(tunnel); return frpcini.getRemotePort(tunnel);
} }
public MultipurposeSocketAddress getTunnelSocketAddress() { public MultiProtocolSocketAddress getTunnelSocketAddress() {
if (!isBound()) if (!isBound())
return null; return null;
return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort()); return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
} }
@Override @Override
@@ -260,18 +260,18 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
List<RouteItem> rlist = new ArrayList<>(); List<RouteItem> rlist = new ArrayList<>();
for (IPv6AddressGroup grp : getAddressGroups()) { for (IPv6AddressGroup grp : getAddressGroups()) {
rlist.add(new RouteItem(new IPv6AddressGroup(grp.getAddress(), 128), grp.getAddress(), this, "Direct", 0, 1, rlist.add(new RouteItem(new IPv6AddressGroup(grp.getAddress(), 128), grp.getAddress(), this, RouteItem.DIRECT, 0, 1,
null, "D", true)); null, "D"));
} }
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) { for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next(); Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128), RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D", false); addresses.getAddress().getAddress(), this, RouteItem.SRv6_ENDXSID, 0, 128, null, "D");
rlist.add(ri); rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this, RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
"KLALB SRv6", 13, 128, null, "D", false); RouteItem.SRv6_ENDSID, 13, 128, null, "D");
rlist.add(ris); rlist.add(ris);
} }
@@ -98,19 +98,18 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
List<RouteItem> rlist = new ArrayList<>(); List<RouteItem> rlist = new ArrayList<>();
for(IPv6AddressGroup group:addressGroups) { for(IPv6AddressGroup group:addressGroups) {
rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128), rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128),
group.getAddress(), this, "Direct", 0, 0, null, "D", true)); group.getAddress(), this, RouteItem.DIRECT, 0, 0, null, "D"));
} }
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) { for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next(); Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128), RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D", addresses.getAddress().getAddress(), this,RouteItem.SRv6_ENDXSID, 0, 128, null, "D");
false);
rlist.add(ri); rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(), RouteItem ris = new RouteItem(addresses.getLocator(),
addresses.getLocator().getAddress(), this, "KLALB SRv6", 13, 128, addresses.getLocator().getAddress(), this, RouteItem.SRv6_ENDSID, 13, 128,
null, "D", false); null, "D");
rlist.add(ris); rlist.add(ris);
} }
@@ -4,6 +4,10 @@ import java.util.Objects;
import java.util.function.Supplier; import java.util.function.Supplier;
public class RouteItem implements Comparable<RouteItem>,Cloneable{ public class RouteItem implements Comparable<RouteItem>,Cloneable{
public static final String DIRECT="Direct";
public static final String SRv6_ENDSID="SRv6 ENDSID";
public static final String SRv6_ENDXSID="SRv6 ENDXSID";
private IPv6AddressGroup destination; private IPv6AddressGroup destination;
private IPv6Address nexthop; private IPv6Address nexthop;
private IPv6NetworkLink destlink; private IPv6NetworkLink destlink;
@@ -12,7 +16,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
private long cost; private long cost;
private String flag; private String flag;
private Supplier<Long> costSupplier; private Supplier<Long> costSupplier;
private boolean isLoopback;
public String getFlag() { public String getFlag() {
return flag; return flag;
} }
@@ -34,7 +37,7 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
&& pre == other.pre && Objects.equals(proto, other.proto); && pre == other.pre && Objects.equals(proto, other.proto);
} }
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre, public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,String flag,boolean isLoopback) { long cost,String flag) {
super(); super();
this.destination = destination; this.destination = destination;
this.nexthop = nexthop; this.nexthop = nexthop;
@@ -43,10 +46,9 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
this.pre = pre; this.pre = pre;
this.cost = cost; this.cost = cost;
this.flag=flag; this.flag=flag;
this.isLoopback=isLoopback;
} }
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre, public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,Supplier<Long> costSupplier,String flag,boolean isLoopback) { long cost,Supplier<Long> costSupplier,String flag) {
super(); super();
this.destination = destination; this.destination = destination;
this.nexthop = nexthop; this.nexthop = nexthop;
@@ -56,7 +58,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
this.cost = cost; this.cost = cost;
this.costSupplier=costSupplier; this.costSupplier=costSupplier;
this.flag=flag; this.flag=flag;
this.isLoopback=isLoopback;
} }
public IPv6AddressGroup getDestination() { public IPv6AddressGroup getDestination() {
return destination; return destination;
@@ -73,9 +74,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
public int getPre() { public int getPre() {
return pre; return pre;
} }
public boolean isLoopback() {
return isLoopback;
}
@Override @Override
public String toString() { public String toString() {
return getDestination()+"\t"+getProto()+"\t"+getPre()+"\t"+getCost()+"\t"+getFlag()+"\t"+getNexthop()+"\t"+getDestlink().getName(); return getDestination()+"\t"+getProto()+"\t"+getPre()+"\t"+getCost()+"\t"+getFlag()+"\t"+getNexthop()+"\t"+getDestlink().getName();
+1 -1
View File
@@ -12,7 +12,7 @@ public class CONST {
public static final String KLALB_S_RV6 = "KLALB_SRv6"; public static final String KLALB_S_RV6 = "KLALB_SRv6";
public static final String KLTP_VIRTUAL_SOCKET_NAME="kltp";
public static final int itemwidth = 1000; public static final int itemwidth = 1000;
public static final int linepanelheight = 41; public static final int linepanelheight = 41;
@@ -116,9 +116,7 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
}*/ }*/
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@@ -128,15 +126,14 @@ import java.net.SocketException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.kne.cloud.network.DatagramServerSocket; import org.kne.cloud.network.DatagramServerSocket;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket; import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.SpeedLimiter;
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink { public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
@Override @Override
public String toString() { public String toString() {
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+""+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress()); return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+""+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
} }
private DatagramSocket ds; private DatagramSocket ds;
@@ -1,39 +1,31 @@
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import javax.annotation.processing.Filer; import org.kne.cloud.network.MultiProtocolSocketAddress;
import javax.lang.model.element.Element;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileManager.Location;
import org.kne.cloud.network.MultipurposeSocketAddress;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException; import com.google.gson.JsonIOException;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
public class KLALBConfig extends ArrayList<KLALBConfigItem>{ public class KLALBConfig extends ArrayList<KLALBConfigItem>{
public KLALBConfig(){
}
public static KLALBConfig getDefault(){
KLALBConfig config=new KLALBConfig();
config.add(new KLALBControllerConfigItem());
return config;
}
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException { public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
GsonBuilder gb=new GsonBuilder().setPrettyPrinting(); GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
MultipurposeSocketAddress.registerToGsonBuilder(gb); MultiProtocolSocketAddress.registerToGsonBuilder(gb);
KLALBConfigItem.registerToGsonBuilder(gb); KLALBConfigItem.registerToGsonBuilder(gb);
Gson gson=gb.create(); Gson gson=gb.create();
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class); KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
@@ -1,15 +1,8 @@
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import org.kne.cloud.network.MultipurposeSocketAddress;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
@@ -69,6 +62,7 @@ public class KLALBConfigItem {
String s=jobj.get("Type").getAsString(); String s=jobj.get("Type").getAsString();
switch(s) { switch(s) {
case "KLALBController": case "KLALBController":
normalizeLegacyControllerKeys(jobj);
return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType()); return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType());
case "SocketBridge": case "SocketBridge":
return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType()); return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType());
@@ -78,6 +72,39 @@ public class KLALBConfigItem {
} }
throw new JsonParseException("not a object:"+arg0); throw new JsonParseException("not a object:"+arg0);
} }
/**
* 将历史命名错误的配置键归一化为当前键名(仅在当前键名不存在时生效):
* openConnections/lineTable → externalEndpoints
* denyConnectionQuery/denyLineTableQuery → denyExternalEndpointQuery
* denyConnectionBroadcast/denyLineTableBroadcast → denyExternalEndpointBroadcast。
*/
private void normalizeLegacyControllerKeys(JsonObject jobj) {
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast");
if (!jobj.has("webListen") && jobj.has("webPort") && !jobj.get("webPort").isJsonNull()) {
int port = jobj.get("webPort").getAsInt();
jobj.add("webListen", new JsonPrimitive("http://0.0.0.0:" + port));
}
jobj.remove("webPort");
}
private void renameLegacyKey(JsonObject jobj, String newKey, String... legacyKeys) {
if (jobj.has(newKey)) {
for (String legacyKey : legacyKeys) {
jobj.remove(legacyKey);
}
return;
}
for (String legacyKey : legacyKeys) {
if (jobj.has(legacyKey)) {
jobj.add(newKey, jobj.get(legacyKey));
jobj.remove(legacyKey);
return;
}
}
}
}; };
} }
public static JsonSerializer<KLALBConfigItem>getDefaultJsonSerializer(){ public static JsonSerializer<KLALBConfigItem>getDefaultJsonSerializer(){
@@ -33,7 +33,7 @@ import java.util.function.Consumer;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.network.IPMulticastDiscovery; import org.kne.cloud.network.IPMulticastDiscovery;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.PortPair; import org.kne.cloud.network.PortPair;
import org.kne.cloud.network.SocketType; import org.kne.cloud.network.SocketType;
import org.kne.cloud.network.ThreadTool; import org.kne.cloud.network.ThreadTool;
@@ -67,7 +67,7 @@ public class KLALBController {
public HighAccuracyClock getClock() { public HighAccuracyClock getClock() {
return clock; return clock;
} }
private final long TIME_WINDOW=5000000000L; private final long TIME_WINDOW=2000000000L;
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl( private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ), new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW)); new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
@@ -76,8 +76,13 @@ public class KLALBController {
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW), new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW)); new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
private List<MultipurposeSocketAddress> selflineTable = new ArrayList<>(); private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
public List<MultiProtocolSocketAddress> getListenSocketAddress() {
return listensSocketAddress;
}
private static final boolean debug=false; private static final boolean debug=false;
private static final boolean showpacket = false; private static final boolean showpacket = false;
@@ -86,10 +91,12 @@ public class KLALBController {
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>(); private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>(); private NetworkInterfaceManager networkInterfaceManager=new NetworkInterfaceManager();
public NetworkInterfaceManager getNetworkInterfaceManager() {
return networkInterfaceManager;
}
public List<NetworkInterface> getNetworkInterfaceExcept() { public List<NetworkInterface> getNetworkInterfaceExcept() {
return networkInterfaceExcept; return networkInterfaceManager.getNetworkInterfaceExcept();
} }
private List<InetAddress> dnsAddresses = new ArrayList<>(); private List<InetAddress> dnsAddresses = new ArrayList<>();
@@ -111,55 +118,28 @@ public class KLALBController {
private Timer twk = new Timer("网卡检测扫描计时器", true); private Timer twk = new Timer("网卡检测扫描计时器", true);
private List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
List<InetAddress> addresses = new ArrayList<InetAddress>();
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
while (eu.hasMoreElements()) {
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
if (networkInterface.getDisplayName()
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
continue;
}
if (networkInterfaceExcept.contains(networkInterface)) {
continue;
}
if (networkInterface.isUp()) {
// System.out.println(networkInterface+" "+networkInterface.isUp());
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
if (checkIsSelfLocator(inetAddress)) {
continue;
}
addresses.add(inetAddress);
}
}
}
return addresses;
}
private TimerTask tsk1 = new TimerTask() { private TimerTask tsk1 = new TimerTask() {
@Override @Override
public void run() { public void run() {
try { try {
List<InetAddress> localaddress = getAllNetworkInterfaceAddress(); List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
for (InetAddress inetAddress : localaddress) { for (InetAddress inetAddress : localaddress) {
if (!inetAddress.isLoopbackAddress()) for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
for (Iterator<MultipurposeSocketAddress> iterator = listens.iterator(); iterator.hasNext();) { MultiProtocolSocketAddress tcpl = (MultiProtocolSocketAddress) iterator.next();
MultipurposeSocketAddress tcpl = (MultipurposeSocketAddress) iterator.next();
try { try {
if (tcpl.getInetAddress().isAnyLocalAddress() if (tcpl.getInetAddress().isAnyLocalAddress()
|| tcpl.getInetAddress().equals(inetAddress)) { || tcpl.getInetAddress().equals(inetAddress)) {
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(tcpl.getType(), MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
inetAddress.getHostAddress(), tcpl.getPort()); inetAddress.getHostAddress(), tcpl.getPort());
// System.out.println(bind); // System.out.println(bind);
synchronized (selflineTable) { synchronized (externalEndpoints) {
if (!selflineTable.contains(bind)) { if (!externalEndpoints.contains(bind)) {
selflineTable.add(bind); externalEndpoints.add(bind);
} }
} }
} }
@@ -175,46 +155,45 @@ public class KLALBController {
try { try {
Set<MultipurposeSocketAddress> st = new HashSet<>(); Set<MultiProtocolSocketAddress> st = new HashSet<>();
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) { for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
IPv6NetworkLink link = iterator.next(); if (link instanceof KLALBRemoteLink) {
if (link instanceof KLALBRemoteLink) { KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link; if (multipurposeSocketAddress.getSocketAddress() != null)
if (multipurposeSocketAddress.getSocketAddress() != null) st.add(multipurposeSocketAddress.getSocketAddress());
st.add(multipurposeSocketAddress.getSocketAddress()); }
} }
}
for (Iterator<MultipurposeSocketAddress> iterator = st.iterator(); iterator.hasNext();) { for (MultiProtocolSocketAddress target : st) {
MultipurposeSocketAddress target = (MultipurposeSocketAddress) iterator.next(); addRemoteLines(target);
addRemoteLines(target); }
}
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next();
if (link instanceof KLALBRemoteLink) {
KLALBRemoteLink reml = (KLALBRemoteLink) link;
if (reml.getSocketAddress() != null) {
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
try {
MultipurposeSocketAddress iadr=reml.getBindAddress();
if(iadr!=null) {
if (!localaddress.contains(iadr.getInetAddress())) {
reml.close();
srv6Router.getLinkTabel().remove(reml);
}
}else {
reml.close(); /*
} for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
} catch (UnknownHostException e) { if (link instanceof KLALBRemoteLink) {
reml.close(); KLALBRemoteLink reml = (KLALBRemoteLink) link;
srv6Router.getLinkTabel().remove(reml); if (reml.getSocketAddress() != null) {
e.printStackTrace(); if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
} try {
} MultipurposeSocketAddress iadr = reml.getBindAddress();
} if (iadr != null) {
} if (!localaddress.contains(iadr.getInetAddress())) {
reml.close();
srv6Router.getLinkTabel().remove(reml);
}
} else {
reml.close();
}
} catch (UnknownHostException e) {
reml.close();
srv6Router.getLinkTabel().remove(reml);
e.printStackTrace();
}
}
}
}
*/
} finally { } finally {
lineslock.writeLock().unlock(); lineslock.writeLock().unlock();
} }
@@ -228,112 +207,99 @@ public class KLALBController {
} }
private void updateBroadcastNetworkInterface() throws SocketException { private void updateBroadcastNetworkInterface() throws SocketException {
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) { for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext(); ) {
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next(); IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getNinterface().isUp()) if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getInterface().isUp())
|| (configItem != null && configItem.isDenyLineTableBroadcast())) { || (configItem != null && configItem.isDenyExternalEndpointBroadcast())) {
iterator.remove(); iterator.remove();
try { try {
ipMulticastDiscovery.close(); ipMulticastDiscovery.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
if(debug) if (debug)
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getNinterface()); System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getInterface());
} }
} }
if (configItem != null && configItem.isDenyLineTableBroadcast()) { if (configItem != null && configItem.isDenyExternalEndpointBroadcast()) {
} else { } else {
Enumeration<NetworkInterface> eu2 = NetworkInterface.getNetworkInterfaces(); List<NetworkInterface> interfaceList = networkInterfaceManager.getAllAvaliableNetworkInterface();
while (eu2.hasMoreElements()) { for(NetworkInterface networkInterface:interfaceList){
NetworkInterface networkInterface = (NetworkInterface) eu2.nextElement(); List<InetAddress> addressList= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
if (networkInterface.getDisplayName() loop:for (InetAddress bidr:addressList) {
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
continue;
}
if (networkInterfaceExcept.contains(networkInterface)) {
continue;
}
if (networkInterface.isUp()) {
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
loop: while (ei.hasMoreElements()) {
InetAddress bidr = (InetAddress) ei.nextElement();
if (checkIsSelfLocator(bidr)) {
continue;
}
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
if (networkInterface.equals(ipMulticastDiscovery.getNinterface())
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
continue loop;
}
}
try {
// InetAddress bidr=InetAddress.getByName("::0");
if (bidr instanceof Inet6Address) {
IPMulticastDiscovery ipd = new IPMulticastDiscovery(
new InetSocketAddress(bidr, DISCOVERY_PORT),
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
DISCOVERY_PORT),
networkInterface, selflineTable, nodeuuid,10000L);
ipd.setCon((mpa) -> {
// System.out.println("添加本地IPv6链路:"+mpa);
try {
if (!checkIsSelf(mpa))
addRemoteLines(mpa);
} catch (UnknownHostException e) {
}
});
ipd.start();
ipmd.add(ipd);
} else if (bidr instanceof Inet4Address) {
// bidr=InetAddress.getByName("0.0.0.0");
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(
new InetSocketAddress(bidr, DISCOVERY_PORT),
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
DISCOVERY_PORT),
networkInterface, selflineTable,nodeuuid, 10000L);
ipd2.setCon((mpa) -> {
// System.out.println("添加本地IPv4链路:"+mpa);
try {
if (!checkIsSelf(mpa))
addRemoteLines(mpa);
} catch (UnknownHostException e) {
}
});
ipd2.start();
ipmd.add(ipd2);
}
if(debug)
System.out.println("开启网卡地址广播:" + networkInterface);
} catch (BindException e) {
// e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
for (IPMulticastDiscovery ipMulticastDiscovery : ipmd) {
if (networkInterface.equals(ipMulticastDiscovery.getInterface())
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
continue loop;
} }
// }
} }
}
try {
// InetAddress bidr=InetAddress.getByName("::0");
if (bidr instanceof Inet6Address) {
IPMulticastDiscovery ipd = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
DISCOVERY_PORT),
networkInterface, listensSocketAddress, nodeuuid, 10000L);
ipd.setCon((mpa) -> {
if (debug)
System.out.println("添加本地IPv6链路:" + mpa);
try {
if (!checkIsSelf(mpa))
addRemoteLines(mpa);
} catch (UnknownHostException e) {
}
});
ipd.start();
ipmd.add(ipd);
} else if (bidr instanceof Inet4Address) {
// bidr=InetAddress.getByName("0.0.0.0");
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
DISCOVERY_PORT),
networkInterface, listensSocketAddress, nodeuuid, 10000L);
ipd2.setCon((mpa) -> {
if (debug)
System.out.println("添加本地IPv4链路:" + mpa);
try {
if (!checkIsSelf(mpa))
addRemoteLines(mpa);
} catch (UnknownHostException e) {
}
});
ipd2.start();
ipmd.add(ipd2);
}
if (debug)
System.out.println("开启网卡地址广播:" + networkInterface);
} catch (BindException e) {
// e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// }
}
}
} }
} }
}; };
private boolean checkIsSelf(MultipurposeSocketAddress inetAddress) throws UnknownHostException { private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress() return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|| selflineTable.contains(inetAddress); || externalEndpoints.contains(inetAddress);
} }
private boolean checkIsSelfLocator(InetAddress inetAddress) { private boolean checkIsSelfLocator(InetAddress inetAddress) {
@@ -345,16 +311,16 @@ public class KLALBController {
@Override @Override
public void run() { public void run() {
List<MultipurposeSocketAddress> nt = ntptable; List<MultiProtocolSocketAddress> nt = ntptable;
// System.out.println("srv6 ip"+nb); // System.out.println("srv6 ip"+nb);
if (nvc2 != null) { if (nvc2 != null) {
Set<NTPPeer> sp = nvc2.getPeers(); Set<NTPPeer> sp = nvc2.getPeers();
for (MultipurposeSocketAddress server : nt) { for (MultiProtocolSocketAddress server : nt) {
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT)); sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
} }
sp.removeIf((p) -> { sp.removeIf((p) -> {
for (MultipurposeSocketAddress server : nt) { for (MultiProtocolSocketAddress server : nt) {
if (server.equals(p.getAddress())) { if (server.equals(p.getAddress())) {
return false; return false;
} }
@@ -365,33 +331,30 @@ public class KLALBController {
} }
}; };
private TimerTask tsk3 = new TimerTask() { public void tryConnectMore(){
if (srv6Router != null && routingProtocol != null) {
@Override Set<IPv6AddressGroup> nb = srv6Router.getLocators();
public void run() { for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
if (srv6Router != null && routingProtocol != null) { IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next();
Set<IPv6AddressGroup> nb = srv6Router.getLocators(); try {
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) { InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next(); KLALBRoutingProtocol.DEFAULT_PORT);
try { apiClient.requestNodeInfoFull(iaddr, (v) -> {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(), ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
KLALBRoutingProtocol.DEFAULT_PORT); for (MultiProtocolSocketAddress msa : v.getOpenLines()) {
apiClient.requestOpenLines(iaddr, (v) -> { // System.out.print(msa);
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> { addRemoteLines(msa);
for (MultipurposeSocketAddress msa : v) { }
// System.out.print(msa); }).start();
addRemoteLines(msa); });
} } catch (IOException e) {
}).start(); e.printStackTrace();
});
} catch (IOException e) {
e.printStackTrace();
}
} }
} }
} }
}; }
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() { public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
return linkMonitor; return linkMonitor;
@@ -403,6 +366,8 @@ public class KLALBController {
private SocketType streamSocketType = new KLALBStreamSocketType(); private SocketType streamSocketType = new KLALBStreamSocketType();
public class KLALBStreamSocketType extends SocketType { public class KLALBStreamSocketType extends SocketType {
public KLALBStreamSocketType() { public KLALBStreamSocketType() {
@@ -436,8 +401,8 @@ public class KLALBController {
} }
public List<MultipurposeSocketAddress> getSelflineTable() { public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return selflineTable; return externalEndpoints;
} }
@@ -472,13 +437,13 @@ public class KLALBController {
private KLALBControllerConfigItem configItem; private KLALBControllerConfigItem configItem;
public void addRemoteLines(List<MultipurposeSocketAddress> select) { public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
for (MultipurposeSocketAddress target : select) { for (MultiProtocolSocketAddress target : select) {
addRemoteLines(target); addRemoteLines(target);
} }
} }
public List<KLALBRemoteLink> addRemoteLines(MultipurposeSocketAddress target) { public List<KLALBRemoteLink> addRemoteLines(MultiProtocolSocketAddress target) {
lineslock.writeLock().lock(); lineslock.writeLock().lock();
try { try {
List<KLALBRemoteLink> added = new ArrayList<>(); List<KLALBRemoteLink> added = new ArrayList<>();
@@ -492,7 +457,7 @@ public class KLALBController {
while (ei.hasMoreElements()) { while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement(); InetAddress inetAddress = (InetAddress) ei.nextElement();
try { try {
MultipurposeSocketAddress bind = new MultipurposeSocketAddress( MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(
inetAddress.getHostAddress(), 0); inetAddress.getHostAddress(), 0);
try { try {
if (target.getInetAddress().isLoopbackAddress() if (target.getInetAddress().isLoopbackAddress()
@@ -553,7 +518,7 @@ public class KLALBController {
} }
} }
public List<KLALBRemoteLink> removeRemoteLines(MultipurposeSocketAddress mpsa) { public List<KLALBRemoteLink> removeRemoteLines(MultiProtocolSocketAddress mpsa) {
List<KLALBRemoteLink> rmved = new ArrayList<>(); List<KLALBRemoteLink> rmved = new ArrayList<>();
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) { for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
@@ -569,7 +534,7 @@ public class KLALBController {
return rmved; return rmved;
} }
public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException { public Inet6Address getRemoteVaddrBySocketAddress(MultiProtocolSocketAddress target) throws SocketTimeoutException {
KLALBRemoteLink kr = null; KLALBRemoteLink kr = null;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) { for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next(); IPv6NetworkLink link = iterator.next();
@@ -593,7 +558,7 @@ public class KLALBController {
return kr.getRemoteVaddr().getAddress().toInet6Address(); return kr.getRemoteVaddr().getAddress().toInet6Address();
} }
private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target, MultipurposeSocketAddress bind) { private boolean checkContainsTargetAndBind(MultiProtocolSocketAddress target, MultiProtocolSocketAddress bind) {
boolean b = false; boolean b = false;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) { for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next(); IPv6NetworkLink link = iterator.next();
@@ -609,7 +574,7 @@ public class KLALBController {
return b; return b;
} }
private boolean checkContainsTarget(MultipurposeSocketAddress target) { private boolean checkContainsTarget(MultiProtocolSocketAddress target) {
boolean b = false; boolean b = false;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) { for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next(); IPv6NetworkLink link = iterator.next();
@@ -628,19 +593,19 @@ public class KLALBController {
lineslock.writeLock().lock(); lineslock.writeLock().lock();
try { try {
krs.startIO(); krs.startIO();
String selflineTable = generateSelfLineTable(); String externalEndpointsText = generateExternalEndpointsString();
if (selflineTable != null && !selflineTable.equals("")) if (externalEndpointsText != null && !externalEndpointsText.equals(""))
krs.sendPacket(new ADDLINESPacket(selflineTable)); krs.sendPacket(new ADDLINESPacket(externalEndpointsText));
srv6Router.getLinkTabel().add(krs); srv6Router.getLinkTabel().add(krs);
} finally { } finally {
lineslock.writeLock().unlock(); lineslock.writeLock().unlock();
} }
} }
private String generateSelfLineTable() { private String generateExternalEndpointsString() {
StringBuilder sbd = new StringBuilder(); StringBuilder sbd = new StringBuilder();
for (Iterator<MultipurposeSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) { for (Iterator<MultiProtocolSocketAddress> iterator = externalEndpoints.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next(); MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
sbd.append(klalbRemoteLine.toString()); sbd.append(klalbRemoteLine.toString());
sbd.append('\n'); sbd.append('\n');
} }
@@ -668,7 +633,12 @@ public class KLALBController {
} }
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) { private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
networkInterfaceManager.getInetAddressesExcept().add(selfx.getAddress().toInet6Address());
srv6Router = new SRv6Router(selfx, clock); srv6Router = new SRv6Router(selfx, clock);
if(configItem!=null) {
srv6Router.setPerformanceStrategy(PerformanceStrategy.fromDescription(configItem.getPerformanceStrategy()));
srv6Router.setDeviceName(configItem.getDeviceName());
}
srv6Router.runKLALBRouteProtocol(); srv6Router.runKLALBRouteProtocol();
routingProtocol = srv6Router.getKlalbRouteProtol(); routingProtocol = srv6Router.getKlalbRouteProtol();
routingProtocol.addReceiver((addr, packet) -> { routingProtocol.addReceiver((addr, packet) -> {
@@ -677,11 +647,13 @@ public class KLALBController {
rawPortBinder= new PortBinder(this.getSelf().getAddress()); rawPortBinder= new PortBinder(this.getSelf().getAddress());
System.out.println(" Loaded: SRv6 Stack"); System.out.println(" Loaded: SRv6 Stack");
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6; String name = (configItem != null && configItem.getTUNName() != null) ? configItem.getTUNName() : CONST.KLALB_S_RV6;
if (enableVirtualAdapter&&(name!=null)) { boolean isEnabled = configItem == null || configItem.isEnableTUN();
boolean enableTUN = enableVirtualAdapter && isEnabled && (name != null) && !name.trim().isEmpty() && !name.trim().equalsIgnoreCase("null");
if (enableTUN) {
Thread t=new Thread(()->{ Thread t=new Thread(()->{
try { try {
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name, IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name.trim(),
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses); new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
tunlink.setMonitor(datatMonitor); tunlink.setMonitor(datatMonitor);
// srv6Router.getLinkTabel().add(tunlink); // srv6Router.getLinkTabel().add(tunlink);
@@ -695,7 +667,7 @@ public class KLALBController {
}); });
t.start(); t.start();
}else { }else {
System.out.println(" Tun Adapter Disbaled"); System.out.println(" Tun Adapter Disabled");
} }
udpr=new UDPProtocolRegister(this); udpr=new UDPProtocolRegister(this);
@@ -715,7 +687,7 @@ public class KLALBController {
// System.out.println(context); // System.out.println(context);
try { try {
NTPv4Protocol nvc = new NTPv4Protocol(context, NTPv4Protocol nvc = new NTPv4Protocol(context,
new MultipurposeSocketAddress("{" + iproxyname + "_Datagram}0.0.0.0:123")); new MultiProtocolSocketAddress(iproxyname + "datagram","0.0.0.0",123));
srv6Router.addSRv6RouterListener(new SRv6RouterListener() { srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
@Override @Override
@@ -726,7 +698,7 @@ public class KLALBController {
for (Neighbor neighbor : nb) { for (Neighbor neighbor : nb) {
if (neighbor.getLocator() != null) if (neighbor.getLocator() != null)
sp.add(new NTPPeer( sp.add(new NTPPeer(
new MultipurposeSocketAddress(iproxyname + "_Datagram", new MultiProtocolSocketAddress(iproxyname + "datagram",
neighbor.getLocator().getAddress().toString(), 123), neighbor.getLocator().getAddress().toString(), 123),
NTPv4Packet.NTP_SYMMETRIC_ACTIVE)); NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
@@ -747,7 +719,7 @@ public class KLALBController {
}); });
} }
}); });
nvc2 = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199 nvc2 = new NTPv4Protocol(context, new MultiProtocolSocketAddress("udp","0.0.0.0",0));
System.out.println(" Loaded: NTP Module"); System.out.println(" Loaded: NTP Module");
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@@ -764,7 +736,6 @@ public class KLALBController {
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol); this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
twk.schedule(tsk1, 5000, 5000); twk.schedule(tsk1, 5000, 5000);
twk.schedule(tsk2, 5000, 5000); twk.schedule(tsk2, 5000, 5000);
twk.schedule(tsk3, 5000, 5000);
} }
public KLALBController() { public KLALBController() {
@@ -830,18 +801,18 @@ public class KLALBController {
getIpv6Router().setASN(vasn); getIpv6Router().setASN(vasn);
} }
List<MultipurposeSocketAddress> linele = configItem.getLineTable(); List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
if (linele != null) { if (linele != null) {
getSelflineTable().addAll(linele); getExternalEndpoints().addAll(linele);
} }
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable(); List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
if (linetoc != null) { if (linetoc != null) {
linetoc.forEach((aline) -> { linetoc.forEach((aline) -> {
addRemoteLines(aline); addRemoteLines(aline);
}); });
} }
List<MultipurposeSocketAddress> ntps = configItem.getNtpServerTable(); List<MultiProtocolSocketAddress> ntps = configItem.getNtpServers();
if (ntps != null) { if (ntps != null) {
getNTPTable().addAll(ntps); getNTPTable().addAll(ntps);
} }
@@ -1004,24 +975,19 @@ public class KLALBController {
private List<MultipurposeSocketAddress> listens = new CopyOnWriteArrayList<>();
public void registerToProxyTypeAs(String proxyname) { public void registerToProxyTypeAs(String proxyname) {
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType); MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "stream", streamSocketType);
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType); MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "datagram", datagramSocketType);
// ProxyProfileEntry.getRegister().put(proxyname, this); // ProxyProfileEntry.getRegister().put(proxyname, this);
} }
public List<MultipurposeSocketAddress> getListenSocketAddress() { private List<MultiProtocolSocketAddress> ntptable = new CopyOnWriteArrayList();
return listens;
}
private List<MultipurposeSocketAddress> ntptable = new CopyOnWriteArrayList();
private KLALBRoutingProtocol routingProtocol; private KLALBRoutingProtocol routingProtocol;
public List<MultipurposeSocketAddress> getNTPTable() { public List<MultiProtocolSocketAddress> getNTPTable() {
return ntptable; return ntptable;
} }
@@ -3,31 +3,87 @@ package org.kne.cloud.network.klalb;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
public class KLALBControllerConfigItem extends KLALBConfigItem { public class KLALBControllerConfigItem extends KLALBConfigItem {
private String language; private String language;
private boolean nogui; private boolean nogui;
private String VirtualAddress; private String VirtualAddress=KLALBUtils.randomKLALBIPv6Address().toString();
private Long VirtualASN; private Long VirtualASN;
private List<InetAddress> DNS; private List<InetAddress> DNS;
private MultipurposeSocketAddress TCPListen; private MultiProtocolSocketAddress TCPListen=new MultiProtocolSocketAddress("0.0.0.0",4565);
private MultipurposeSocketAddress UDPListen; private MultiProtocolSocketAddress UDPListen=new MultiProtocolSocketAddress("udp","0.0.0.0",4565);
private String VirtualSocketName; private String VirtualSocketName;
private List<MultipurposeSocketAddress>LineTable=new ArrayList<>(); private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultipurposeSocketAddress>ConnectLineTable=new ArrayList<>(); private List<MultiProtocolSocketAddress> autoConnections = new ArrayList<>();
private List<MultipurposeSocketAddress>ntpServerTable=new ArrayList<>(); private List<MultiProtocolSocketAddress> ntpServers = new ArrayList<>();
private boolean denyLineTableQuery=false; private List<String> ExtraRoutes = new ArrayList<>();
private boolean denyLineTableBroadcast=false; private boolean denyExternalEndpointQuery = false;
private boolean denyExternalEndpointBroadcast = false;
private String congestionAlgorithm="BBR"; private String congestionAlgorithm="BBR";
private double burstLimit=1.20; private double burstLimit=1.50;
private double delayUpperBound=1.20; private double delayUpperBound=1.20;
private double delayLowerBound=1.15; private double delayLowerBound=1.15;
private long nagleDelayTime=1000000L; private long nagleDelayTime=1000000L;
private long linkNagleDelayTime=1000000L; private long linkNagleDelayTime=1000000L;
private int linkConnectionsCount=1; private int linkConnectionsCount=1;
private boolean enableTUN = true;
private String TUNName=CONST.KLALB_S_RV6; private String TUNName=CONST.KLALB_S_RV6;
private String performanceStrategy="multifill";
private String DeviceName;
private String DeviceDescription;
private boolean webUI = false;
private MultiProtocolSocketAddress webListen = new MultiProtocolSocketAddress("http", "0.0.0.0", 4665);
public boolean isEnableTUN() {
return enableTUN;
}
public void setEnableTUN(boolean enableTUN) {
this.enableTUN = enableTUN;
}
public boolean isWebUI() {
return webUI;
}
public void setWebUI(boolean webUI) {
this.webUI = webUI;
}
public MultiProtocolSocketAddress getWebListen() {
return webListen;
}
public void setWebListen(MultiProtocolSocketAddress webListen) {
this.webListen = webListen;
}
public String getPerformanceStrategy() {
return performanceStrategy;
}
public void setPerformanceStrategy(String performanceStrategy) {
this.performanceStrategy = performanceStrategy;
}
public String getDeviceName() {
return DeviceName;
}
public void setDeviceName(String deviceName) {
DeviceName = deviceName;
}
public String getDeviceDescription() {
return DeviceDescription;
}
public void setDeviceDescription(String deviceDescription) {
DeviceDescription = deviceDescription;
}
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) { public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
NetworkInterfaceExcepts = networkInterfaceExcepts; NetworkInterfaceExcepts = networkInterfaceExcepts;
@@ -52,8 +108,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
public KLALBControllerConfigItem(String type) { public KLALBControllerConfigItem() {
super(type); super("KLALBController");
} }
@@ -124,63 +180,76 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
public MultipurposeSocketAddress getTCPListen() { public MultiProtocolSocketAddress getTCPListen() {
return TCPListen; return TCPListen;
} }
public void setTCPListen(MultipurposeSocketAddress tCPListen) { public void setTCPListen(MultiProtocolSocketAddress tCPListen) {
TCPListen = tCPListen; TCPListen = tCPListen;
} }
public MultipurposeSocketAddress getUDPListen() { public MultiProtocolSocketAddress getUDPListen() {
return UDPListen; return UDPListen;
} }
public void setUDPListen(MultipurposeSocketAddress uDPListen) { public void setUDPListen(MultiProtocolSocketAddress uDPListen) {
UDPListen = uDPListen; UDPListen = uDPListen;
} }
public List<MultipurposeSocketAddress> getLineTable() { public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return LineTable; return externalEndpoints;
} }
public void setExternalEndpoints(List<MultiProtocolSocketAddress> externalEndpoints) {
this.externalEndpoints = externalEndpoints;
public void setLineTable(List<MultipurposeSocketAddress> lineTable) {
LineTable = lineTable;
} }
public List<MultiProtocolSocketAddress> getAutoConnections() {
return autoConnections;
public List<MultipurposeSocketAddress> getConnectLineTable() {
return ConnectLineTable;
} }
public void setAutoConnections(List<MultiProtocolSocketAddress> autoConnections) {
this.autoConnections = autoConnections;
public void setConnectLineTable(List<MultipurposeSocketAddress> connectLineTable) {
ConnectLineTable = connectLineTable;
} }
public List<MultiProtocolSocketAddress> getConnectLineTable() {
return autoConnections;
public List<MultipurposeSocketAddress> getNtpServerTable() {
return ntpServerTable;
} }
public void setConnectLineTable(List<MultiProtocolSocketAddress> connectLineTable) {
this.autoConnections = connectLineTable;
}
public List<MultiProtocolSocketAddress> getNtpServers() {
return ntpServers;
}
public void setNtpServers(List<MultiProtocolSocketAddress> ntpServers) {
this.ntpServers = ntpServers;
}
public void setNtpServerTable(List<MultipurposeSocketAddress> ntpServerTable) { public List<MultiProtocolSocketAddress> getNtpServerTable() {
this.ntpServerTable = ntpServerTable; return ntpServers;
}
public void setNtpServerTable(List<MultiProtocolSocketAddress> ntpServerTable) {
this.ntpServers = ntpServerTable;
}
public List<String> getExtraRoutes() {
return ExtraRoutes;
}
public void setExtraRoutes(List<String> extraRoutes) {
ExtraRoutes = extraRoutes;
} }
@@ -243,23 +312,20 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
public boolean isDenyLineTableQuery() { public boolean isDenyExternalEndpointQuery() {
return denyLineTableQuery; return denyExternalEndpointQuery;
} }
public void setDenyExternalEndpointQuery(boolean denyExternalEndpointQuery) {
public void setDenyLineTableQuery(boolean denyLineTableQuery) { this.denyExternalEndpointQuery = denyExternalEndpointQuery;
this.denyLineTableQuery = denyLineTableQuery;
} }
public boolean isDenyExternalEndpointBroadcast() {
public boolean isDenyLineTableBroadcast() { return denyExternalEndpointBroadcast;
return denyLineTableBroadcast;
} }
public void setDenyExternalEndpointBroadcast(boolean denyExternalEndpointBroadcast) {
public void setDenyLineTableBroadcast(boolean denyLineTableBroadcast) { this.denyExternalEndpointBroadcast = denyExternalEndpointBroadcast;
this.denyLineTableBroadcast = denyLineTableBroadcast;
} }
@@ -306,146 +372,49 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
@Override @Override
public String toString() { public String toString() {
return "KLALBControllerConfigItem [language=" + language + ", nogui=" + nogui + ", VirtualAddress=" return "KLALBControllerConfigItem{" +
+ VirtualAddress + ", VirtualASN=" + VirtualASN + ", DNS=" + DNS + ", TCPListen=" + TCPListen "language='" + language + '\'' +
+ ", UDPListen=" + UDPListen + ", VirtualSocketName=" + VirtualSocketName + ", LineTable=" + LineTable ", nogui=" + nogui +
+ ", ConnectLineTable=" + ConnectLineTable + ", ntpServerTable=" + ntpServerTable ", VirtualAddress='" + VirtualAddress + '\'' +
+ ", denyLineTableQuery=" + denyLineTableQuery + ", denyLineTableBroadcast=" + denyLineTableBroadcast ", VirtualASN=" + VirtualASN +
+ ", congestionAlgorithm=" + congestionAlgorithm + ", delayUpperBound=" + delayUpperBound ", DNS=" + DNS +
+ ", delayLowerBound=" + delayLowerBound + ", nagleDelayTime=" + nagleDelayTime ", TCPListen=" + TCPListen +
+ ", linkNagleDelayTime=" + linkNagleDelayTime + ", linkConnectionsCount=" + linkConnectionsCount ", UDPListen=" + UDPListen +
+ ", TUNName=" + TUNName + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + "]"; ", VirtualSocketName='" + VirtualSocketName + '\'' +
", externalEndpoints=" + externalEndpoints +
", autoConnections=" + autoConnections +
", ntpServers=" + ntpServers +
", ExtraRoutes=" + ExtraRoutes +
", denyExternalEndpointQuery=" + denyExternalEndpointQuery +
", denyExternalEndpointBroadcast=" + denyExternalEndpointBroadcast +
", congestionAlgorithm='" + congestionAlgorithm + '\'' +
", burstLimit=" + burstLimit +
", delayUpperBound=" + delayUpperBound +
", delayLowerBound=" + delayLowerBound +
", nagleDelayTime=" + nagleDelayTime +
", linkNagleDelayTime=" + linkNagleDelayTime +
", linkConnectionsCount=" + linkConnectionsCount +
", enableTUN=" + enableTUN +
", TUNName='" + TUNName + '\'' +
", performanceStrategy='" + performanceStrategy + '\'' +
", DeviceName='" + DeviceName + '\'' +
", DeviceDescription='" + DeviceDescription + '\'' +
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
", webUI=" + webUI +
", webListen=" + webListen +
'}';
} }
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
KLALBControllerConfigItem that = (KLALBControllerConfigItem) o;
return nogui == that.nogui && enableTUN == that.enableTUN && webUI == that.webUI && denyExternalEndpointQuery == that.denyExternalEndpointQuery && denyExternalEndpointBroadcast == that.denyExternalEndpointBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && Objects.equals(webListen, that.webListen) && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(externalEndpoints, that.externalEndpoints) && Objects.equals(autoConnections, that.autoConnections) && Objects.equals(ntpServers, that.ntpServers) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts);
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, externalEndpoints, autoConnections, ntpServers, ExtraRoutes, denyExternalEndpointQuery, denyExternalEndpointBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, enableTUN, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts, webUI, webListen);
int result = super.hashCode();
result = prime * result + ((ConnectLineTable == null) ? 0 : ConnectLineTable.hashCode());
result = prime * result + ((DNS == null) ? 0 : DNS.hashCode());
result = prime * result + ((LineTable == null) ? 0 : LineTable.hashCode());
result = prime * result + ((NetworkInterfaceExcepts == null) ? 0 : NetworkInterfaceExcepts.hashCode());
result = prime * result + ((TCPListen == null) ? 0 : TCPListen.hashCode());
result = prime * result + ((TUNName == null) ? 0 : TUNName.hashCode());
result = prime * result + ((UDPListen == null) ? 0 : UDPListen.hashCode());
result = prime * result + ((VirtualASN == null) ? 0 : VirtualASN.hashCode());
result = prime * result + ((VirtualAddress == null) ? 0 : VirtualAddress.hashCode());
result = prime * result + ((VirtualSocketName == null) ? 0 : VirtualSocketName.hashCode());
result = prime * result + ((congestionAlgorithm == null) ? 0 : congestionAlgorithm.hashCode());
long temp;
temp = Double.doubleToLongBits(delayLowerBound);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(delayUpperBound);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + (denyLineTableBroadcast ? 1231 : 1237);
result = prime * result + (denyLineTableQuery ? 1231 : 1237);
result = prime * result + ((language == null) ? 0 : language.hashCode());
result = prime * result + linkConnectionsCount;
result = prime * result + (int) (linkNagleDelayTime ^ (linkNagleDelayTime >>> 32));
result = prime * result + (int) (nagleDelayTime ^ (nagleDelayTime >>> 32));
result = prime * result + (nogui ? 1231 : 1237);
result = prime * result + ((ntpServerTable == null) ? 0 : ntpServerTable.hashCode());
return result;
} }
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
KLALBControllerConfigItem other = (KLALBControllerConfigItem) obj;
if (ConnectLineTable == null) {
if (other.ConnectLineTable != null)
return false;
} else if (!ConnectLineTable.equals(other.ConnectLineTable))
return false;
if (DNS == null) {
if (other.DNS != null)
return false;
} else if (!DNS.equals(other.DNS))
return false;
if (LineTable == null) {
if (other.LineTable != null)
return false;
} else if (!LineTable.equals(other.LineTable))
return false;
if (NetworkInterfaceExcepts == null) {
if (other.NetworkInterfaceExcepts != null)
return false;
} else if (!NetworkInterfaceExcepts.equals(other.NetworkInterfaceExcepts))
return false;
if (TCPListen == null) {
if (other.TCPListen != null)
return false;
} else if (!TCPListen.equals(other.TCPListen))
return false;
if (TUNName == null) {
if (other.TUNName != null)
return false;
} else if (!TUNName.equals(other.TUNName))
return false;
if (UDPListen == null) {
if (other.UDPListen != null)
return false;
} else if (!UDPListen.equals(other.UDPListen))
return false;
if (VirtualASN == null) {
if (other.VirtualASN != null)
return false;
} else if (!VirtualASN.equals(other.VirtualASN))
return false;
if (VirtualAddress == null) {
if (other.VirtualAddress != null)
return false;
} else if (!VirtualAddress.equals(other.VirtualAddress))
return false;
if (VirtualSocketName == null) {
if (other.VirtualSocketName != null)
return false;
} else if (!VirtualSocketName.equals(other.VirtualSocketName))
return false;
if (congestionAlgorithm == null) {
if (other.congestionAlgorithm != null)
return false;
} else if (!congestionAlgorithm.equals(other.congestionAlgorithm))
return false;
if (Double.doubleToLongBits(delayLowerBound) != Double.doubleToLongBits(other.delayLowerBound))
return false;
if (Double.doubleToLongBits(delayUpperBound) != Double.doubleToLongBits(other.delayUpperBound))
return false;
if (denyLineTableBroadcast != other.denyLineTableBroadcast)
return false;
if (denyLineTableQuery != other.denyLineTableQuery)
return false;
if (language == null) {
if (other.language != null)
return false;
} else if (!language.equals(other.language))
return false;
if (linkConnectionsCount != other.linkConnectionsCount)
return false;
if (linkNagleDelayTime != other.linkNagleDelayTime)
return false;
if (nagleDelayTime != other.nagleDelayTime)
return false;
if (nogui != other.nogui)
return false;
if (ntpServerTable == null) {
if (other.ntpServerTable != null)
return false;
} else if (!ntpServerTable.equals(other.ntpServerTable))
return false;
return true;
}
} }
+64 -15
View File
@@ -2,7 +2,9 @@ package org.kne.cloud.network.klalb;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.nio.channels.ServerSocketChannel; import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@@ -11,7 +13,7 @@ import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Scanner; import java.util.Scanner;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.SocketChannelListener; import org.kne.cloud.network.SocketChannelListener;
import org.kne.cloud.network.ipv6.IPv6NetworkLink; import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.RouteItem; import org.kne.cloud.network.ipv6.RouteItem;
@@ -20,8 +22,7 @@ import org.kne.cloud.network.klalb.ui.UIEnv;
import org.kne.cloud.network.perf.Kperf; import org.kne.cloud.network.perf.Kperf;
import org.kne.cloud.network.perf.NodeBenchmark; import org.kne.cloud.network.perf.NodeBenchmark;
import org.kne.debug.TimeDebugger; import org.kne.debug.TimeDebugger;
import org.kne.membandboost.MembandBenchmark; import org.kne.membandboost.benchmark.*;;
import org.kne.membandboost.ObjectPoolBandwidthTest;
public class KLALBMain { public class KLALBMain {
public static KLALBStateGUI3 ksg; public static KLALBStateGUI3 ksg;
@@ -50,13 +51,15 @@ public class KLALBMain {
}catch(Throwable e) { }catch(Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
try {
if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().isWebUI()) {
kpcje.enableWebServer();
}
} catch(Throwable e) {
System.err.println("Failed to start web server: " + e.getMessage());
}
dtb.putTime("UI"); dtb.putTime("UI");
//dtb.print(); //dtb.print();
/*MultipurposeSocketAddress mpa=new MultipurposeSocketAddress("127.9.9.9", 49573);
kpcje.enableRemoteManagement(mpa);
System.out.println("远程管理端口已在"+mpa+"端口上开启");*/
/*if(true)
return;*/
ServerSocketChannel kpsvr=KLALBVirtualServerSocketChannel.open(kpcje.getKlalbController()); ServerSocketChannel kpsvr=KLALBVirtualServerSocketChannel.open(kpcje.getKlalbController());
kpsvr.bind(new InetSocketAddress("::0", 4564)); kpsvr.bind(new InetSocketAddress("::0", 4564));
SocketChannelListener stlr=new SocketChannelListener(kpsvr); SocketChannelListener stlr=new SocketChannelListener(kpsvr);
@@ -82,11 +85,13 @@ public class KLALBMain {
case "help": case "help":
System.out.println(" help / ?: see help"); System.out.println(" help / ?: see help");
System.out.println(" monitor: show monitor GUI"); System.out.println(" monitor: show monitor GUI");
System.out.println(" web [start|stop|status <port>]: manage web dashboard");
System.out.println(" links-state: query link states"); System.out.println(" links-state: query link states");
System.out.println(" links-add <addr:port>: add link"); System.out.println(" links-add <addr:port>: add link");
System.out.println(" links-remove <addr:port>: remove link"); System.out.println(" links-remove <addr:port>: remove link");
System.out.println(" links-reconnect: reconnect all link"); System.out.println(" links-reconnect: reconnect all link");
System.out.println(" kltp-state: query kltp states"); System.out.println(" kltp-state: query kltp states");
System.out.println(" interfaces: show all network interfaces");
System.out.println(" route: display internal route table"); System.out.println(" route: display internal route table");
System.out.println(" kperf <addr:port>: performance benchmark"); System.out.println(" kperf <addr:port>: performance benchmark");
System.out.println(" exit: Exit"); System.out.println(" exit: Exit");
@@ -99,6 +104,42 @@ public class KLALBMain {
e.printStackTrace(); e.printStackTrace();
} }
break; break;
case "web":
if(sc.length >= 2) {
String action = sc[1].toLowerCase();
if("start".equals(action)) {
int p = 4665;
if(sc.length >= 3) {
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebListen() != null) {
p = kpcje.getControllerConfig().getWebListen().getPort();
}
try {
kpcje.enableWebServer(p);
System.out.println("Web dashboard started on http://localhost:" + p);
} catch(Exception e) {
System.out.println("Failed to start web server: " + e.getMessage());
}
} else if("stop".equals(action)) {
kpcje.disableWebServer();
System.out.println("Web dashboard stopped.");
} else if("status".equals(action)) {
if(kpcje.isWebServerEnabled()) {
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
} else {
System.out.println("Web dashboard is stopped.");
}
} else {
System.out.println("Usage: web [start|stop|status <port>]");
}
} else {
if(kpcje.isWebServerEnabled()) {
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
} else {
System.out.println("Web dashboard is not running. Use 'web start [port]' to start.");
}
}
break;
case "links-state": case "links-state":
System.out.println("links state"); System.out.println("links state");
//System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动"); //System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动");
@@ -115,7 +156,7 @@ public class KLALBMain {
break; break;
case "links-add": case "links-add":
if(sc.length>=2) { if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]); MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa); List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
if(addl.isEmpty()) { if(addl.isEmpty()) {
//System.out.println("添加失败,线路已存在!"); //System.out.println("添加失败,线路已存在!");
@@ -135,7 +176,7 @@ public class KLALBMain {
break; break;
case "links-remove": case "links-remove":
if(sc.length>=2) { if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]); MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa); List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
if(rmvl.isEmpty()) { if(rmvl.isEmpty()) {
System.out.println("No matched link has been found."); System.out.println("No matched link has been found.");
@@ -161,6 +202,18 @@ public class KLALBMain {
case "kltp-state": case "kltp-state":
System.out.println(kpcje.getKlalbController().getKLTPregister().toString()); System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
break; break;
case "interfaces":
NetworkInterfaceManager networkInterfaceManager= kpcje.getKlalbController().getNetworkInterfaceManager();
List<NetworkInterface> interfaceList=networkInterfaceManager.getAllAvaliableNetworkInterface();
System.out.println("Network interface detected:");
for(NetworkInterface networkInterface:interfaceList){
System.out.println(networkInterface.getName()+"("+networkInterface.getDisplayName()+")");
List<InetAddress> lst= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
for(InetAddress address :lst){
System.out.println(" "+address.getHostAddress());
}
}
break;
case "route": case "route":
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel()); List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
Collections.sort(lri); Collections.sort(lri);
@@ -174,7 +227,7 @@ public class KLALBMain {
break; break;
case "kperf": case "kperf":
if(sc.length>=2) { if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]); MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
Kperf kp=new Kperf(mpsa); Kperf kp=new Kperf(mpsa);
kp.startPerfing(); kp.startPerfing();
}else { }else {
@@ -185,10 +238,6 @@ public class KLALBMain {
case "membandbenchmark": case "membandbenchmark":
MembandBenchmark.main(args); MembandBenchmark.main(args);
break; break;
case "memqueuebenchmark":
ObjectPoolBandwidthTest.main(args);
break;
//case "$$SYSTEM:": //case "$$SYSTEM:":
//System.out.println(); //System.out.println();
//break; //break;
@@ -2,12 +2,6 @@ package org.kne.cloud.network.klalb;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
import org.kne.cloud.network.MultipurposeSocketAddress;
public interface KLALBPacketLink { public interface KLALBPacketLink {
public void writeKLALBPacket(KLALBPacket kp) throws IOException; public void writeKLALBPacket(KLALBPacket kp) throws IOException;
@@ -7,7 +7,7 @@ import java.net.UnknownHostException;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.kne.cloud.network.KLALBDetectorItem; import org.kne.cloud.network.KLALBDetectorItem;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ProtocolDetector; import org.kne.cloud.network.ProtocolDetector;
import org.kne.cloud.network.ProtocolDetectorServerSocket; import org.kne.cloud.network.ProtocolDetectorServerSocket;
import org.kne.cloud.network.ProtocolDetectorSocket; import org.kne.cloud.network.ProtocolDetectorSocket;
@@ -30,8 +30,8 @@ public class KLALBProtocolDetectSocketListener extends SocketListener {
super.open(); super.open();
} }
public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException { public KLALBProtocolDetectSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress, KLALBController klalbc) throws IOException {
super(multipurposeSocketAddress); super(multiProtocolSocketAddress);
this.klalbController=klalbc; this.klalbController=klalbc;
} }
@@ -5,6 +5,8 @@ import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.HashSet; import java.util.HashSet;
@@ -12,27 +14,54 @@ import org.kne.cloud.network.*;
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3; import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
import org.kne.cloud.network.klalb.ui.Language; import org.kne.cloud.network.klalb.ui.Language;
import org.kne.cloud.network.klalb.ui.UIEnv; import org.kne.cloud.network.klalb.ui.UIEnv;
import org.kne.cloud.network.klalb.web.KLALBWebServer;
import java.util.Set; import java.util.Set;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
public class KLALBProxySystem { public class KLALBProxySystem {
private Set<Proxy> proxys=new HashSet<>(); private Set<Proxy> proxys=new HashSet<>();
private KLALBController klalbController; private KLALBController klalbController;
private KLALBRemoteManagement krm; private KLALBWebServer webServer;
private KLALBConfig config; private KLALBConfig config;
private Gson gson; private Gson gson;
private File jsonFile; private File jsonFile;
{ {
GsonBuilder gb=new GsonBuilder().setPrettyPrinting(); GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
MultipurposeSocketAddress.registerToGsonBuilder(gb); MultiProtocolSocketAddress.registerToGsonBuilder(gb);
KLALBConfigItem.registerToGsonBuilder(gb); KLALBConfigItem.registerToGsonBuilder(gb);
gb.registerTypeAdapter(InetAddress.class, new JsonSerializer<InetAddress>() {
@Override
public JsonElement serialize(InetAddress src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.getHostAddress());
}
});
gb.registerTypeAdapter(InetAddress.class, new JsonDeserializer<InetAddress>() {
@Override
public InetAddress deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
try {
return InetAddress.getByName(json.getAsString());
} catch (Exception e) {
throw new JsonParseException(e);
}
}
});
gson=gb.create(); gson=gb.create();
} }
public Gson getGson() {
return gson;
}
public Set<Proxy> getProxys() { public Set<Proxy> getProxys() {
return proxys; return proxys;
} }
@@ -55,42 +84,54 @@ public class KLALBProxySystem {
public KLALBProxySystem() { public KLALBProxySystem() {
} }
public void enableRemoteManagement() throws IOException { public void enableWebServer() throws IOException {
if(krm==null) { KLALBControllerConfigItem cci = getControllerConfig();
krm=new KLALBRemoteManagement(this); MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
}else { ? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
throw new IllegalStateException("Remote Management already enabled!"); enableWebServer(listen);
}
public void enableWebServer(int port) throws IOException {
enableWebServer(new MultiProtocolSocketAddress("http", "0.0.0.0", port));
}
public void enableWebServer(MultiProtocolSocketAddress listen) throws IOException {
if (webServer == null) {
webServer = new KLALBWebServer(this, listen);
webServer.start();
} else {
throw new IllegalStateException("Web server already enabled!");
} }
} }
public void enableRemoteManagement(MultipurposeSocketAddress bind) throws IOException { public boolean isWebServerEnabled() {
if(krm==null) { return webServer != null && webServer.isRunning();
krm=new KLALBRemoteManagement(this,bind);
}else {
throw new IllegalStateException("Remote Management already enabled!");
}
} }
public boolean isRemoteManagementEnabled() { public KLALBWebServer getWebServer() {
return krm!=null; return webServer;
} }
public void disableRemoteManagement() { public void disableWebServer() {
if(krm!=null) { if (webServer != null) {
krm.close(); webServer.stop();
krm=null; webServer = null;
} }
} }
public void loadConfigJson(File jsonFile) throws IOException { public void loadConfigJson(File jsonFile) throws IOException {
this.jsonFile=jsonFile; this.jsonFile=jsonFile;
FileReader fr = null; if(jsonFile.exists()) {
try { FileReader fr = null;
fr=new FileReader(jsonFile); try {
loadConfigJson(fr); fr = new FileReader(jsonFile);
}finally { loadConfigJson(fr);
if(fr!=null) } finally {
fr.close(); if (fr != null)
fr.close();
}
}else{
loadConfig(KLALBConfig.getDefault());
} }
} }
@@ -127,10 +168,12 @@ public class KLALBProxySystem {
String vsne=kcci.getVirtualSocketName(); String vsne=kcci.getVirtualSocketName();
//if(vsne!=null) { if(vsne!=null) {
MultipurposeSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType()); MultiProtocolSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
//} }else{
MultipurposeSocketAddress tcple=kcci.getTCPListen(); MultiProtocolSocketAddress.getSocketTypeRegister().put(CONST.KLTP_VIRTUAL_SOCKET_NAME, klalbController.getStreamSocketType());
}
MultiProtocolSocketAddress tcple=kcci.getTCPListen();
if(tcple!=null) { if(tcple!=null) {
SocketChannelListener tcpl = null; SocketChannelListener tcpl = null;
@@ -148,13 +191,13 @@ public class KLALBProxySystem {
} }
}); });
MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(tcple.getType(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort()); MultiProtocolSocketAddress mpsa2=new MultiProtocolSocketAddress(tcple.getProtocol(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
klalbController.getListenSocketAddress().add(mpsa2); klalbController.getListenSocketAddress().add(mpsa2);
} catch (IOException e1) { } catch (IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
} }
} }
MultipurposeSocketAddress udple=kcci.getUDPListen(); MultiProtocolSocketAddress udple=kcci.getUDPListen();
if(udple!=null) { if(udple!=null) {
DatagramSocketListener udpl = null; DatagramSocketListener udpl = null;
try { try {
@@ -171,7 +214,7 @@ public class KLALBProxySystem {
} }
}); });
MultipurposeSocketAddress mpsau2=new MultipurposeSocketAddress(udple.getType(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort()); MultiProtocolSocketAddress mpsau2=new MultiProtocolSocketAddress(udple.getProtocol(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
//klalbController.getListenSocketAddress().add(mpsau2); //klalbController.getListenSocketAddress().add(mpsau2);
} catch (IOException e1) { } catch (IOException e1) {
e1.printStackTrace(); e1.printStackTrace();
@@ -191,30 +234,24 @@ public class KLALBProxySystem {
} }
public void saveConfigToFile() {
if (jsonFile != null && config != null) {
String json = gson.toJson(config);
try (FileWriter fw = new FileWriter(jsonFile)) {
fw.write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private KLALBStateGUI3 kgui; private KLALBStateGUI3 kgui;
public KLALBStateGUI3 getKLALBGUI() { public KLALBStateGUI3 getKLALBGUI() {
if(kgui==null) { if(kgui==null) {
kgui=new KLALBStateGUI3(klalbController); kgui=new KLALBStateGUI3(klalbController);
kgui.loadConfig(config); kgui.loadConfig(config);
kgui.setSaveComsumer((cfg)->{ kgui.setSaveComsumer((cfg)->{
String json=gson.toJson(cfg); saveConfigToFile();
if(jsonFile!=null) {
FileWriter fw = null;
try {
fw=new FileWriter(jsonFile);
fw.write(json);
}catch(IOException e) {
e.printStackTrace();
}finally {
if(fw!=null)
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}); });
} }
return kgui; return kgui;
@@ -2,6 +2,9 @@ package org.kne.cloud.network.klalb;
import java.io.IOException; import java.io.IOException;
import java.io.StreamCorruptedException; import java.io.StreamCorruptedException;
import java.net.BindException;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.nio.channels.UnresolvedAddressException; import java.nio.channels.UnresolvedAddressException;
import java.security.SecureRandom; import java.security.SecureRandom;
@@ -21,7 +24,7 @@ import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.clock.NTPTimestamps; import org.kne.cloud.clock.NTPTimestamps;
import org.kne.cloud.clock.ReliabilityBackoffTimeClock; import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
import org.kne.cloud.clock.WatchDogTimer; import org.kne.cloud.clock.WatchDogTimer;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ThreadTool; import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm; import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
import org.kne.cloud.network.congestion.CongestionAlgorithm; import org.kne.cloud.network.congestion.CongestionAlgorithm;
@@ -100,8 +103,8 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
private LinkStatus status=new LinkStatus(); private LinkStatus status=new LinkStatus();
private SpeedAndTrafficAndDelayMonitorDataImpl monitor; private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
private volatile KLALBPacketLink kplink; private volatile KLALBPacketLink kplink;
private MultipurposeSocketAddress bindAddress; private MultiProtocolSocketAddress bindAddress;
private MultipurposeSocketAddress socketAddress; private MultiProtocolSocketAddress socketAddress;
public void setKplink(KLALBPacketLink kplink) { public void setKplink(KLALBPacketLink kplink) {
@@ -112,11 +115,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
return kplink; return kplink;
} }
public MultipurposeSocketAddress getSocketAddress() { public MultiProtocolSocketAddress getSocketAddress() {
return socketAddress; return socketAddress;
} }
public MultipurposeSocketAddress getBindAddress() { public MultiProtocolSocketAddress getBindAddress() {
return bindAddress; return bindAddress;
} }
@@ -134,7 +137,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
return peerAddress; return peerAddress;
} }
public KLALBRemoteLink(KLALBController controller, MultipurposeSocketAddress mpa, IPv6AddressGroup address) { public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
this(controller,mpa, null,address); this(controller,mpa, null,address);
} }
@@ -147,7 +150,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
name=kpl.toString(); name=kpl.toString();
} }
private KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr, IPv6AddressGroup address) { private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
this.klalbController=controller; this.klalbController=controller;
sysclk = klalbController.getClock(); sysclk = klalbController.getClock();
this.socketAddress = mpa; this.socketAddress = mpa;
@@ -162,11 +165,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
} }
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bind) { public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
this(controller,mpa, bind, null); this(controller,mpa, bind, null);
} }
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa) { public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
this(controller,mpa, (IPv6AddressGroup) null); this(controller,mpa, (IPv6AddressGroup) null);
} }
@@ -283,7 +286,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
sendLock.unlock(); sendLock.unlock();
} }
} }
tlock.parkNanos(1000000L); tlock.parkNanos(10000000L);
} }
} catch (StreamCorruptedException sce) { } catch (StreamCorruptedException sce) {
@@ -464,16 +467,18 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
break; break;
case KLALBPacket.VADDR: case KLALBPacket.VADDR:
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr(); IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
remoteVaddr = vdr; close();
sendPacket(new VADDRACKPacket()); }else {
if (fst.compareAndSet(true, false)) { remoteVaddr = vdr;
// coll.resetCoolingTime(); sendPacket(new VADDRACKPacket());
status.setState(LinkStatus.UP); if (fst.compareAndSet(true, false)) {
KLALBRemoteLink.this.onOnlineStateUpdate(); // coll.resetCoolingTime();
status.setState(LinkStatus.UP);
KLALBRemoteLink.this.onOnlineStateUpdate();
}
KLALBRemoteLink.this.onLocatorUpdate();
} }
KLALBRemoteLink.this.onLocatorUpdate();
break; break;
case KLALBPacket.VADDRACK: case KLALBPacket.VADDRACK:
break; break;
@@ -567,7 +572,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
public void run() { public void run() {
while (!closed) { while (!closed) {
status.updateReliability(); status.updateReliability();
monitor.update(); if(status.getState()!=LinkStatus.DOWN) {
monitor.update();
}
try { try {
Thread.sleep(1); Thread.sleep(1);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -672,7 +679,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
} }
} catch (StreamCorruptedException sce) { } catch (StreamCorruptedException sce) {
sce.printStackTrace(); sce.printStackTrace();
} catch (IOException | UnresolvedAddressException e) { }catch(BindException e) {
close();
if (debug)
e.printStackTrace();
}catch(NoRouteToHostException e){
close();
if (debug)
e.printStackTrace();
} catch(SocketException e) {
String massage = e.getMessage();
if (massage.toLowerCase().contains("unreachable")) {
close();
} else {
if (debug)
e.printStackTrace();
}
}catch (IOException | UnresolvedAddressException e) {
if (debug) if (debug)
e.printStackTrace(); e.printStackTrace();
} catch (Exception e) { } catch (Exception e) {
@@ -927,23 +950,25 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
public List<RouteItem> getRouteItems() { public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>(); List<RouteItem> rlist = new ArrayList<>();
if(isUp()) {
IPv6AddressGroup adg = addressGroup; IPv6AddressGroup adg = addressGroup;
/*if (adg != null) /*if (adg != null)
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0, rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
null, "D", true));*/ null, "D", true));*/
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) { for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
Neighbor addresses = (Neighbor) iteratorx.next(); Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128), RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false); addresses.getAddress().getAddress(), this, RouteItem.SRv6_ENDXSID, 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D");
rlist.add(ri); rlist.add(ri);
if (addresses.getLocator() != null) { if (addresses.getLocator() != null) {
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this, RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
"KLALB SRv6", 13, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false); RouteItem.SRv6_ENDSID, 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D");
rlist.add(ris); rlist.add(ris);
} }
} }
}
return rlist; return rlist;
} }
@@ -1,174 +0,0 @@
package org.kne.cloud.network.klalb;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.SocketListener;
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.monitor.LinkStatus;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
public class KLALBRemoteManagement {
SocketListener slr;
private KLALBProxySystem klalbProxySystem;
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem) throws IOException {
this(klalbProxySystem,new MultipurposeSocketAddress("127.9.9.9", 49573));
}
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem,MultipurposeSocketAddress listen) throws IOException {
this.klalbProxySystem=klalbProxySystem;
slr=new SocketListener(listen);
slr.setCon((srcv)->{
try {
srcv.setSoTimeout(10000);
byte[]input= srcv.getInputStream().readAllBytes();
srcv.shutdownInput();
String req=new String(input,Charset.forName("UTF-8"));
System.out.println("远程管理请求:"+req);
String rsp=processSignal(req);
System.out.println("远程管理响应:"+rsp);
srcv.getOutputStream().write(rsp.getBytes(Charset.forName("UTF-8")));
srcv.shutdownOutput();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
srcv.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
public KLALBProxySystem getKlalbProxySystem() {
return klalbProxySystem;
}
private String processSignal(String req) {
JsonObject jreq= (JsonObject) new JsonParser().parse(req);
JsonObject jrsp=new JsonObject();
String reqt=jreq.getAsJsonPrimitive("REQ").getAsString();
jrsp.addProperty("RSP", reqt);
switch (reqt) {
case "GETLINES":
JsonArray lines=new JsonArray();
List<IPv6NetworkLink>lineslist= klalbProxySystem.getKlalbController().getLines();
synchronized (lineslist) {
for (Iterator<IPv6NetworkLink> iterator = lineslist.iterator(); iterator.hasNext();) {
IPv6NetworkLink link=iterator.next();
if(!(link instanceof KLALBRemoteLink)) {
continue;
}
KLALBRemoteLink klalbRemoteLine = (KLALBRemoteLink) link;
if(klalbRemoteLine.getSocketAddress()==null)
continue;
JsonObject jklbrl=new JsonObject();
jklbrl.addProperty("ipport", klalbRemoteLine.getSocketAddress().toString());
jklbrl.addProperty("state",LinkStatus.stateToString( klalbRemoteLine.getState()));
jklbrl.addProperty("Vaddr", klalbRemoteLine.getRemoteVaddr().getAddress().toString());
jklbrl.addProperty("uploadspeed", klalbRemoteLine.getMonitor().getOutSpeed());
jklbrl.addProperty("downloadspeed", klalbRemoteLine.getMonitor().getInSpeed());
jklbrl.addProperty("uploadtraffic", klalbRemoteLine.getMonitor().getOutTraffic());
jklbrl.addProperty("downloadtraffic", klalbRemoteLine.getMonitor().getInTraffic());
jklbrl.addProperty("uploaddelay",klalbRemoteLine.getMonitor().getOutDelay() );
jklbrl.addProperty("downloaddelay", klalbRemoteLine.getMonitor().getInDelay());
jklbrl.addProperty("uploaddelaymin",klalbRemoteLine.getMonitor().getOutDelayMin() );
jklbrl.addProperty("downloaddelaymin", klalbRemoteLine.getMonitor().getInDelayMin());
lines.add(jklbrl);
}
}
jrsp.add("table", lines);
break;
case "ADDLINE":
String mip=jreq.getAsJsonPrimitive("ipport").getAsString();
try {
jrsp.addProperty ("Vaddr",klalbProxySystem.getKlalbController().getRemoteVaddrBySocketAddress(new MultipurposeSocketAddress(mip)).getHostAddress());
} catch (SocketTimeoutException e) {
e.printStackTrace();
}
break;
case "GETSELFLINES":
JsonArray lines2=new JsonArray();
List<MultipurposeSocketAddress>selflineslist=klalbProxySystem.getKlalbController().getSelflineTable();
synchronized (selflineslist) {
for (Iterator<MultipurposeSocketAddress> iterator = selflineslist.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator.next();
lines2.add(new JsonPrimitive(multipurposeSocketAddress.toString()));
}
}
jrsp.add("table", lines2);
break;
case "ADDSELFLINE":
String mips=jreq.getAsJsonPrimitive("ipport").getAsString();
List<MultipurposeSocketAddress>selflineslist2=klalbProxySystem.getKlalbController().getSelflineTable();
synchronized (selflineslist2) {
selflineslist2.add(new MultipurposeSocketAddress(mips));
}
break;
case "REMOVESELFLINE":
String mipsr=jreq.getAsJsonPrimitive("ipport").getAsString();
List<MultipurposeSocketAddress>selflineslist21=klalbProxySystem.getKlalbController().getSelflineTable();
synchronized (selflineslist21) {
selflineslist21.add(new MultipurposeSocketAddress(mipsr));
}
break;
case "GETLINKMONITOR":
jrsp.addProperty("uploadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getOutSpeed());
jrsp.addProperty("downloadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getInSpeed());
jrsp.addProperty("uploadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getOutTraffic());
jrsp.addProperty("downloadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getInTraffic());
break;
case "OPENMONITORUI":
klalbProxySystem.getKLALBGUI().setVisible(true);
break;
/*case "GETSOCKETBRIDGE":
JsonArray bridges=new JsonArray();
Set<Proxy> pxy=klalbProxySystem.getProxys();
synchronized (pxy) {
for (Iterator<Proxy> iterator = pxy.iterator(); iterator.hasNext();) {
Proxy proxy = (Proxy) iterator.next();
bridges.add(klalbProxySystem.createJsonObjectByProxy(proxy));
}
}
jrsp.add("table", bridges);
break;
case "ADDSOCKETBRIDGE":
JsonObject jpxy= jreq.getAsJsonObject("socketbridge");
Set<Proxy> pxy2=klalbProxySystem.getProxys();
synchronized (pxy2) {
try {
pxy2.add(klalbProxySystem.createProxyByJson(jpxy));
} catch (IOException e) {
e.printStackTrace();
}
}
break;*/
default:
System.out.println("未知请求类型:"+reqt);
break;
}
return jrsp.toString();
}
public void close() {
slr.close();
}
}
@@ -15,7 +15,7 @@ import java.util.regex.Pattern;
import org.jctools.counters.CountersFactory; import org.jctools.counters.CountersFactory;
import org.jctools.counters.FixedSizeStripedLongCounter; import org.jctools.counters.FixedSizeStripedLongCounter;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
public class KLALBUtils { public class KLALBUtils {
@@ -51,6 +51,27 @@ public class KLALBUtils {
v[3] = 0x01; v[3] = 0x01;
return IPv6Address.valueOf(v); return IPv6Address.valueOf(v);
} }
public static String parseCidr(String cidr) {
int idx = cidr.indexOf('/');
if (idx <= 0 || idx == cidr.length() - 1)
throw new IllegalArgumentException("not a cidr: " + cidr);
int prefix;
try {
prefix = Integer.parseInt(cidr.substring(idx + 1));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("bad prefix: " + cidr);
}
try {
InetAddress addr = InetAddress.getByName(cidr.substring(0, idx));
int max = (addr instanceof Inet6Address) ? 128 : 32;
if (prefix < 0 || prefix > max)
throw new IllegalArgumentException("prefix out of range: " + cidr);
return addr.getHostAddress() + "/" + prefix;
} catch (UnknownHostException e) {
throw new IllegalArgumentException("bad address: " + cidr);
}
}
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(uuidToIP(new UUID(-1,-1))); System.out.println(uuidToIP(new UUID(-1,-1)));
} }
@@ -208,7 +229,7 @@ public class KLALBUtils {
return col; return col;
} }
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, MultipurposeSocketAddress targetAddress,boolean buffered) public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress, MultiProtocolSocketAddress targetAddress, boolean buffered)
throws IOException { throws IOException {
if (targetAddress.isStream()) { if (targetAddress.isStream()) {
if (targetAddress.supportNIO()) { if (targetAddress.supportNIO()) {
@@ -235,8 +256,8 @@ public class KLALBUtils {
} }
} }
} }
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress,
MultipurposeSocketAddress targetAddress,boolean buffered, int timeout) throws UnknownHostException, IOException { MultiProtocolSocketAddress targetAddress, boolean buffered, int timeout) throws UnknownHostException, IOException {
if (targetAddress.isStream()) { if (targetAddress.isStream()) {
if (targetAddress.supportNIO()) { if (targetAddress.supportNIO()) {
if (bindAddress != null) { if (bindAddress != null) {
@@ -0,0 +1,71 @@
package org.kne.cloud.network.klalb;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class NetworkInterfaceManager {
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
private List<InetAddress> inetAddressesExcept = new ArrayList<>();
public List<NetworkInterface> getNetworkInterfaceExcept() {
return networkInterfaceExcept;
}
public List<InetAddress> getInetAddressesExcept() {
return inetAddressesExcept;
}
public List<NetworkInterface> getAllAvaliableNetworkInterface() throws SocketException {
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
while (eu.hasMoreElements()) {
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
if (networkInterface.getDisplayName()
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
continue;
}
if (networkInterfaceExcept.contains(networkInterface)) {
continue;
}
if (networkInterface.isUp()) {
interfaces.add(networkInterface);
}
}
return interfaces;
}
public List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
List<NetworkInterface> interfaces=getAllAvaliableNetworkInterface();
List<InetAddress> addresses = new ArrayList<InetAddress>();
for (NetworkInterface nif:interfaces){
addresses.addAll( getNetworkInterfaceAddress(nif));
}
return addresses;
}
public List<InetAddress> getNetworkInterfaceAddress(NetworkInterface networkInterface) throws SocketException {
List<InetAddress> addresses = new ArrayList<InetAddress>();
// System.out.println(networkInterface+" "+networkInterface.isUp());
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
if (inetAddress.isLoopbackAddress()){
continue;
}
if (inetAddressesExcept.contains( inetAddress)) {
continue;
}
addresses.add(inetAddress);
}
return addresses;
}
}
@@ -0,0 +1,35 @@
package org.kne.cloud.network.klalb;
import org.kne.cloud.network.klalb.ui.UIEnv;
public enum PerformanceStrategy {
SINGLE_CORE("singlecore"),
MULTI_FILL("multifill"),
MULTI_SCATTER("multiscatter");
private String description;
private PerformanceStrategy(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
/**
* 根据 description 查找对应的枚举对象
*
* @param description 描述文本资源包key
* @return 对应的枚举对象
*/
public static PerformanceStrategy fromDescription(String description) {
for (PerformanceStrategy strategy : values()) {
if (strategy.description.equals(description)) {
return strategy;
}
}
return null;
}
}
@@ -1,40 +1,23 @@
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.channels.MulticastChannel;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory; import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
import org.kne.cloud.network.DefaultSocketBridgeFactory; import org.kne.cloud.network.DefaultSocketBridgeFactory;
import org.kne.cloud.network.HostPortMap; import org.kne.cloud.network.HostPortMap;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.Proxy; import org.kne.cloud.network.Proxy;
import org.kne.cloud.network.SocketBridgeFactory; import org.kne.cloud.network.SocketBridgeFactory;
import org.kne.cloud.network.SocketToSocketProxy; import org.kne.cloud.network.SocketToSocketProxy;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
public class SocketBridgeConfigItem extends KLALBConfigItem { public class SocketBridgeConfigItem extends KLALBConfigItem {
private MultipurposeSocketAddress Listen; private MultiProtocolSocketAddress Listen;
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>(); private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
private HostPortMap Connect=new HostPortMap(); private HostPortMap Connect=new HostPortMap();
public SocketBridgeConfigItem( MultipurposeSocketAddress listen, public SocketBridgeConfigItem( MultiProtocolSocketAddress listen,
LinkedHashMap<String, String> bridge, HostPortMap connect) { LinkedHashMap<String, String> bridge, HostPortMap connect) {
super("SocketBridge"); super("SocketBridge");
Listen = listen; Listen = listen;
@@ -85,12 +68,12 @@ public class SocketBridgeConfigItem extends KLALBConfigItem {
public Proxy createProxy(KLALBController controller) throws IOException { public Proxy createProxy(KLALBController controller) throws IOException {
HostPortMap mapp=new HostPortMap(); HostPortMap mapp=new HostPortMap();
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>(); LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
MultipurposeSocketAddress l=Listen; MultiProtocolSocketAddress l=Listen;
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller); SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
MultipurposeSocketAddress r=getDefaultConnect(Connect,mapp); MultiProtocolSocketAddress r=getDefaultConnect(Connect,mapp);
return new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb); return new SocketToSocketProxy(l ,new MultiProtocolSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
} }
private MultipurposeSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultipurposeSocketAddress> mapp) { private MultiProtocolSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultiProtocolSocketAddress> mapp) {
connect2.entrySet().forEach((en)->{ connect2.entrySet().forEach((en)->{
mapp.put(en.getKey(), en.getValue()); mapp.put(en.getKey(), en.getValue());
}); });
@@ -116,29 +116,20 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
}*/ }*/
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketException; import java.net.SocketException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.util.concurrent.atomic.AtomicLong;
import org.kne.cloud.network.DatagramServerSocket;
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket; import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket; import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.PacketRebuilder; import org.kne.cloud.network.PacketRebuilder;
import org.kne.cloud.network.PacketSpliter; import org.kne.cloud.network.PacketSpliter;
import org.kne.cloud.network.SpeedLimiter;
import org.kne.debug.TimeDebugger;
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink { public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
private PacketSpliter pslr; private PacketSpliter pslr;
@@ -146,7 +137,7 @@ public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink impl
@Override @Override
public String toString() { public String toString() {
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+""+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress()); return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+""+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
} }
private DatagramSocket ds; private DatagramSocket ds;
@@ -13,7 +13,7 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import org.kne.cloud.network.BufferedChannel; import org.kne.cloud.network.BufferedChannel;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket; import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ThreadTool; import org.kne.cloud.network.ThreadTool;
import org.kne.io.KNEChannels; import org.kne.io.KNEChannels;
@@ -45,7 +45,7 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
@Override @Override
public String toString() { public String toString() {
try { try {
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalAddress())+""+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteAddress()); return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalAddress())+""+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteAddress());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
return "?←?"; return "?←?";
@@ -1,21 +1,18 @@
package org.kne.cloud.network.klalb; package org.kne.cloud.network.klalb;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.nio.Buffer;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink { public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
@Override @Override
public String toString() { public String toString() {
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalSocketAddress())+""+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteSocketAddress()); return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalSocketAddress())+""+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteSocketAddress());
} }
private Socket connectSocket; private Socket connectSocket;
@@ -346,7 +346,16 @@ public class GraphPanel extends JPanel {
g.setFont(UIEnv.getFont().deriveFont(10.0f)); g.setFont(UIEnv.getFont().deriveFont(10.0f));
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3)); //g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
g.drawString(text, (int)(x-image.getWidth(null)/2-10), (int)(y+image.getHeight(null)/2+10)); int imgw=(image!=null)?image.getWidth(null):(int)nodesize;
int imgh=(image!=null)?image.getHeight(null):(int)nodesize;
java.awt.FontMetrics fm=g.getFontMetrics();
int lineHeight=fm.getHeight()+2;
int textx=(int)(x-imgw/2-10);
int texty=(int)(y+imgh/2+10)+fm.getAscent();
String[] lines=text.split("\n");
for (int i = 0; i < lines.length; i++) {
g.drawString(lines[i], textx, texty+i*lineHeight);
}
} }
public Vector2 getPositionVec2() { public Vector2 getPositionVec2() {
@@ -20,7 +20,7 @@ import org.jfree.chart.plot.dial.*;
import org.jfree.data.general.DefaultValueDataset; import org.jfree.data.general.DefaultValueDataset;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.klalb.uitool.*; import org.kne.cloud.klalb.uitool.*;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6NetworkLink; import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.klalb.*; import org.kne.cloud.network.klalb.*;
@@ -75,12 +75,18 @@ public class KLALBStateGUI3 extends XFrame {
private JTextField addressField; // 地址显示框 private JTextField addressField; // 地址显示框
private JTextField addressFieldSet; // 地址设置框 private JTextField addressFieldSet; // 地址设置框
private JTextField asnFieldSet; // ASN设置框 private JTextField asnFieldSet; // ASN设置框
private JCheckBox enableTUN; // 虚拟网卡启用禁用
private JTextField tunDeviceName; // 虚拟网卡名称设置框 private JTextField tunDeviceName; // 虚拟网卡名称设置框
private JTextField webListenSet; // Web API 监听地址设置框
private NetworkGraphPanel graph; // 网络图面板 private NetworkGraphPanel graph; // 网络图面板
private JTextField textFieldLocate; // 定位地址输入框 private JTextField textFieldLocate; // 定位地址输入框
private JTextField asnField2; // ASN显示框 private JTextField asnField2; // ASN显示框
private JTextArea dnsAreaSet; // DNS服务器设置区域 private JTextArea dnsAreaSet; // DNS服务器设置区域
private JComboBox comboPerformance; // 性能策略选择框 private JTextArea extraRoutesSet; // 额外路由设置区域
private JTextField deviceNameSet; // 设备名称设置框
private JTextArea deviceDescriptionSet; // 设备描述设置区域
private KLALBController kcontroller; // 关联的控制器
private JComboBox<PerformanceStrategyItem> comboPerformance; // 性能策略选择框
private JComboBox comboCongestion; // 拥塞控制算法选择框 private JComboBox comboCongestion; // 拥塞控制算法选择框
private JTextField tcpListeningSet; // TCP监听设置 private JTextField tcpListeningSet; // TCP监听设置
private JTextField udpListeningSet; // UDP监听设置 private JTextField udpListeningSet; // UDP监听设置
@@ -92,6 +98,7 @@ public class KLALBStateGUI3 extends XFrame {
private JTextArea ntpServerSet; // NTP服务器设置区域 private JTextArea ntpServerSet; // NTP服务器设置区域
private JCheckBox denyBroadcast; private JCheckBox denyBroadcast;
private JCheckBox denyQuery; private JCheckBox denyQuery;
private JCheckBox webApiEnabled; // 启用 Web API 开关
private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板 private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板
private JCheckBox nogui; private JCheckBox nogui;
@@ -138,6 +145,7 @@ public class KLALBStateGUI3 extends XFrame {
* 初始化UI界面 * 初始化UI界面
*/ */
private void initUI(KLALBController kc, String title) { private void initUI(KLALBController kc, String title) {
kcontroller = kc;
// 设置窗口基本属性 // 设置窗口基本属性
setIconImage(UIEnv.getIcon()); setIconImage(UIEnv.getIcon());
setTitleColor(UIEnv.getDefaultTitleColor()); setTitleColor(UIEnv.getDefaultTitleColor());
@@ -597,7 +605,11 @@ public class KLALBStateGUI3 extends XFrame {
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline")); JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
btnNewButton.addActionListener(e -> { btnNewButton.addActionListener(e -> {
try { try {
kc.addRemoteLines(new MultipurposeSocketAddress(textField.getText())); MultiProtocolSocketAddress mpsa= new MultiProtocolSocketAddress(textField.getText());
Thread t=new Thread(()->{
kc.addRemoteLines(mpsa);
});
t.start();
} catch (RuntimeException ex) { } catch (RuntimeException ex) {
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error", JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
JOptionPane.ERROR_MESSAGE); JOptionPane.ERROR_MESSAGE);
@@ -781,18 +793,42 @@ public class KLALBStateGUI3 extends XFrame {
addressFieldSet = addr6.getTextField(); addressFieldSet = addr6.getTextField();
settings.getView().add(addr6); settings.getView().add(addr6);
// 设备名称设置
TextSettingItem deviceName = new TextSettingItem(UIEnv.getRsb().getString("devicename"),
CONST.itemwidth, CONST.settingheight);
deviceNameSet = deviceName.getTextField();
settings.getView().add(deviceName);
// 设备描述设置
TextAreaSettingItem deviceDescription = new TextAreaSettingItem(UIEnv.getRsb().getString("devicedescription"),
CONST.itemwidth, CONST.settingheight * 5);
deviceDescriptionSet = deviceDescription.getTextArea();
settings.getView().add(deviceDescription);
// DNS服务器设置 // DNS服务器设置
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"), TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
CONST.itemwidth, CONST.settingheight * 5); CONST.itemwidth, CONST.settingheight * 5);
dnsAreaSet = dns.getTextArea(); dnsAreaSet = dns.getTextArea();
settings.getView().add(dns); settings.getView().add(dns);
// 额外路由设置
TextAreaSettingItem extraRoutes = new TextAreaSettingItem(UIEnv.getRsb().getString("extraroutes"),
CONST.itemwidth, CONST.settingheight * 5);
extraRoutesSet = extraRoutes.getTextArea();
settings.getView().add(extraRoutes);
// ASN设置 // ASN设置
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"), TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
CONST.itemwidth, CONST.settingheight); CONST.itemwidth, CONST.settingheight);
asnFieldSet = asn.getTextField(); asnFieldSet = asn.getTextField();
settings.getView().add(asn); settings.getView().add(asn);
//虚拟网卡
CheckBoxSettingItem enableTUNs = new CheckBoxSettingItem(UIEnv.getRsb().getString("enabletun"),
CONST.itemwidth, CONST.settingheight);
enableTUN=enableTUNs.getCheckBox();
settings.getView().add(enableTUNs);
TextSettingItem tunName = new TextSettingItem(UIEnv.getRsb().getString("tunname"), TextSettingItem tunName = new TextSettingItem(UIEnv.getRsb().getString("tunname"),
CONST.itemwidth, CONST.settingheight); CONST.itemwidth, CONST.settingheight);
tunDeviceName = tunName.getTextField(); tunDeviceName = tunName.getTextField();
@@ -878,6 +914,21 @@ public class KLALBStateGUI3 extends XFrame {
denyBroadcast=denyBroadcastc.getCheckBox(); denyBroadcast=denyBroadcastc.getCheckBox();
settings.getView().add(denyBroadcastc); settings.getView().add(denyBroadcastc);
// Web服务设置标题
SettingItem wsi = new SettingItem(UIEnv.getRsb().getString("webapisettings"),
UIEnv.getFont().deriveFont(20.0f).deriveFont(Font.BOLD), CONST.itemwidth, CONST.settingheight);
settings.getView().add(wsi);
CheckBoxSettingItem webApic = new CheckBoxSettingItem(UIEnv.getRsb().getString("enablewebapi"),
CONST.itemwidth, CONST.settingheight);
webApiEnabled=webApic.getCheckBox();
settings.getView().add(webApic);
TextSettingItem webListen = new TextSettingItem(UIEnv.getRsb().getString("weblistenaddr"),
CONST.itemwidth, CONST.settingheight);
webListenSet = webListen.getTextField();
settings.getView().add(webListen);
//性能设置标题 //性能设置标题
SettingItem pshi = new SettingItem(UIEnv.getRsb().getString("performancesettings"), SettingItem pshi = new SettingItem(UIEnv.getRsb().getString("performancesettings"),
@@ -888,8 +939,10 @@ public class KLALBStateGUI3 extends XFrame {
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"), performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
CONST.itemwidth, CONST.settingheight); CONST.itemwidth, CONST.settingheight);
comboPerformance = performanceStrategy.getComboBox(); comboPerformance = performanceStrategy.getComboBox();
comboPerformance.addItem("BBR"); comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.SINGLE_CORE));
comboPerformance.addItem("Vegas2"); comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_FILL));
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_SCATTER));
comboPerformance.setSelectedIndex(1);
settings.getView().add(performanceStrategy); settings.getView().add(performanceStrategy);
@@ -1004,7 +1057,7 @@ public class KLALBStateGUI3 extends XFrame {
private void saveConfig() { private void saveConfig() {
if (config == null) { if (config == null) {
config = new KLALBConfig(); config = new KLALBConfig();
config.add(new KLALBControllerConfigItem("KLALBController")); config.add(new KLALBControllerConfigItem());
} }
for (KLALBConfigItem item : config) { for (KLALBConfigItem item : config) {
@@ -1016,6 +1069,26 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNogui(nogui.isSelected()); kck.setNogui(nogui.isSelected());
// 保存设备名称
String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) {
kck.setDeviceName(null);
} else {
kck.setDeviceName(dnametext);
}
if (kcontroller != null && kcontroller.getIpv6Router() != null) {
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName());
}
// 保存设备描述
String ddesctext = deviceDescriptionSet.getText().trim();
if (ddesctext.equals("")) {
kck.setDeviceDescription(null);
} else {
kck.setDeviceDescription(ddesctext);
}
// 保存IPv6地址 // 保存IPv6地址
String ttext = addressFieldSet.getText().trim(); String ttext = addressFieldSet.getText().trim();
if (ttext.equals("")) { if (ttext.equals("")) {
@@ -1052,6 +1125,24 @@ public class KLALBStateGUI3 extends XFrame {
} }
kck.setDNS(iaddr); kck.setDNS(iaddr);
// 保存额外路由列表
String[] spltEr = extraRoutesSet.getText().split("\n");
List<String> eroutes = new ArrayList<>();
for (String str : spltEr) {
str = str.trim();
if (!str.isEmpty()) {
try {
eroutes.add(KLALBUtils.parseCidr(str));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildextraroutes"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return;
}
}
}
kck.setExtraRoutes(eroutes);
// 保存ASN // 保存ASN
String asntext = asnFieldSet.getText(); String asntext = asnFieldSet.getText();
if (asntext.equals("")) { if (asntext.equals("")) {
@@ -1067,20 +1158,38 @@ public class KLALBStateGUI3 extends XFrame {
} }
} }
String tunNameText=tunDeviceName.getText(); kck.setEnableTUN( enableTUN.isSelected());
if(tunNameText.equals("")) {
String tunNameText=tunDeviceName.getText().trim();
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
kck.setTUNName(null); kck.setTUNName(null);
}else { }else {
kck.setTUNName(tunNameText); kck.setTUNName(tunNameText);
} }
// 保存Web API设置
kck.setWebUI(webApiEnabled.isSelected());
String webListenText = webListenSet.getText().trim();
if (webListenText.equals("")) {
kck.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", 4665));
} else {
try {
kck.setWebListen(new MultiProtocolSocketAddress(webListenText));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildweblistenaddr"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return;
}
}
// 保存TCP监听设置 // 保存TCP监听设置
String tcptext = tcpListeningSet.getText(); String tcptext = tcpListeningSet.getText();
if (tcptext.equals("")) { if (tcptext.equals("")) {
kck.setTCPListen(null); kck.setTCPListen(null);
} else { } else {
try { try {
kck.setTCPListen(new MultipurposeSocketAddress(tcptext)); kck.setTCPListen(new MultiProtocolSocketAddress(tcptext));
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"), JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
@@ -1095,7 +1204,7 @@ public class KLALBStateGUI3 extends XFrame {
kck.setUDPListen(null); kck.setUDPListen(null);
} else { } else {
try { try {
kck.setUDPListen(new MultipurposeSocketAddress(udptext)); kck.setUDPListen(new MultiProtocolSocketAddress(udptext,"udp"));
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"), JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
@@ -1106,13 +1215,13 @@ public class KLALBStateGUI3 extends XFrame {
// 保存开放线路表 // 保存开放线路表
String[] splt1 = openLineTabelSet.getText().split("\n"); String[] splt1 = openLineTabelSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr1 = new ArrayList<>(); List<MultiProtocolSocketAddress> iaddr1 = new ArrayList<>();
boolean show1 = true; boolean show1 = true;
for (String str : splt1) { for (String str : splt1) {
try { try {
str = str.trim(); str = str.trim();
if (!str.isEmpty()) if (!str.isEmpty())
iaddr1.add(new MultipurposeSocketAddress(str)); iaddr1.add(new MultiProtocolSocketAddress(str));
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
if (show1) { if (show1) {
@@ -1123,17 +1232,17 @@ public class KLALBStateGUI3 extends XFrame {
} }
} }
} }
kck.setLineTable(iaddr1); kck.setExternalEndpoints(iaddr1);
// 保存自动连接线路表 // 保存自动连接线路表
String[] splt11 = connectLineTabelSet.getText().split("\n"); String[] splt11 = connectLineTabelSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr11 = new ArrayList<>(); List<MultiProtocolSocketAddress> iaddr11 = new ArrayList<>();
boolean show11 = true; boolean show11 = true;
for (String str : splt11) { for (String str : splt11) {
try { try {
str = str.trim(); str = str.trim();
if (!str.isEmpty()) if (!str.isEmpty())
iaddr11.add(new MultipurposeSocketAddress(str)); iaddr11.add(new MultiProtocolSocketAddress(str));
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
if (show11) { if (show11) {
@@ -1148,13 +1257,13 @@ public class KLALBStateGUI3 extends XFrame {
// 保存NTP服务器表 // 保存NTP服务器表
String[] splt111 = ntpServerSet.getText().split("\n"); String[] splt111 = ntpServerSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr111 = new ArrayList<>(); List<MultiProtocolSocketAddress> iaddr111 = new ArrayList<>();
boolean show111 = true; boolean show111 = true;
for (String str : splt111) { for (String str : splt111) {
try { try {
str = str.trim(); str = str.trim();
if (!str.isEmpty()) if (!str.isEmpty())
iaddr111.add(new MultipurposeSocketAddress(str)); iaddr111.add(new MultiProtocolSocketAddress(str,"ntp"));
} catch (RuntimeException e) { } catch (RuntimeException e) {
e.printStackTrace(); e.printStackTrace();
if (show111) { if (show111) {
@@ -1176,9 +1285,14 @@ public class KLALBStateGUI3 extends XFrame {
kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue()); kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue());
kck.setDenyLineTableQuery(denyQuery.isSelected()); kck.setDenyExternalEndpointQuery(denyQuery.isSelected());
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected()); kck.setDenyExternalEndpointBroadcast(denyBroadcast.isSelected());
PerformanceStrategyItem psi=((PerformanceStrategyItem)comboPerformance.getSelectedItem());
if(psi!=null) {
kck.setPerformanceStrategy(psi.getStrategy().toString());
}
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem()); kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
@@ -1213,58 +1327,60 @@ public class KLALBStateGUI3 extends XFrame {
tsk = new TimerTask() { tsk = new TimerTask() {
@Override @Override
public void run() { public void run() {
Object[] kll = kc.getLines().toArray(); if (isVisible()) {
Object[] kll = kc.getLines().toArray();
// 遍历所有线路更新或添加TPanel2 // 遍历所有线路更新或添加TPanel2
for (Object obj : kll) { for (Object obj : kll) {
IPv6NetworkLink link = (IPv6NetworkLink) obj; IPv6NetworkLink link = (IPv6NetworkLink) obj;
if (!(link instanceof KLALBRemoteLink)) { if (!(link instanceof KLALBRemoteLink)) {
continue; continue;
}
KLALBRemoteLink ent = (KLALBRemoteLink) link;
Component[] count = ysp.getView().getComponents();
boolean found = false;
// 查找是否已有对应线路的面板
for (Component tp : count) {
if (tp instanceof TPanel2) {
if (((TPanel2) tp).getTunnel().equals(ent)) {
tp.setVisible(showoffline.isSelected() || (
ent.getState() != LinkStatus.DOWN));
found = true;
break;
}
}
}
// 如果没有找到创建新面板
if (!found) {
TPanel2 tp = new TPanel2(ent, kc);
tp.setVisible(showoffline.isSelected() || (
ent.getState() != LinkStatus.DOWN));
ysp.getView().add(tp);
}
} }
KLALBRemoteLink ent = (KLALBRemoteLink) link;
// 更新所有面板并移除不存在的线路
Component[] count = ysp.getView().getComponents(); Component[] count = ysp.getView().getComponents();
boolean found = false;
// 查找是否已有对应线路的面板
for (Component tp : count) { for (Component tp : count) {
if (tp instanceof TPanel2) { if (tp instanceof TPanel2) {
if (((TPanel2) tp).getTunnel().equals(ent)) { if (kc.getLines().contains(((TPanel2) tp).getTunnel())) {
tp.setVisible(showoffline.isSelected() || ( try {
ent.getState() != LinkStatus.DOWN)); if (tp.isVisible())
found = true; ((TPanel2) tp).updateTraffic();
break; } catch (RuntimeException e) {
e.printStackTrace();
}
} else {
ysp.getView().remove(tp);
} }
} }
} }
// 如果没有找到创建新面板 ysp.updateScrool();
if (!found) {
TPanel2 tp = new TPanel2(ent, kc);
tp.setVisible(showoffline.isSelected() || (
ent.getState()!= LinkStatus.DOWN));
ysp.getView().add(tp);
}
} }
// 更新所有面板并移除不存在的线路
Component[] count = ysp.getView().getComponents();
for (Component tp : count) {
if (tp instanceof TPanel2) {
if (kc.getLines().contains(((TPanel2) tp).getTunnel())) {
try {
if (tp.isVisible())
((TPanel2) tp).updateTraffic();
} catch (RuntimeException e) {
e.printStackTrace();
}
} else {
ysp.getView().remove(tp);
}
}
}
ysp.updateScrool();
} }
}; };
t.scheduleAtFixedRate(tsk, 1000, 1000); t.scheduleAtFixedRate(tsk, 1000, 1000);
@@ -1440,6 +1556,14 @@ public class KLALBStateGUI3 extends XFrame {
nogui.setSelected( kck.isNogui()); nogui.setSelected( kck.isNogui());
// 加载设备名称
String dname = kck.getDeviceName();
deviceNameSet.setText(dname != null ? dname : "");
// 加载设备描述
String ddesc = kck.getDeviceDescription();
deviceDescriptionSet.setText(ddesc != null ? ddesc : "");
// 加载IPv6地址 // 加载IPv6地址
String vaddr = kck.getVirtualAddress(); String vaddr = kck.getVirtualAddress();
addressFieldSet.setText(vaddr != null ? vaddr : ""); addressFieldSet.setText(vaddr != null ? vaddr : "");
@@ -1448,31 +1572,45 @@ public class KLALBStateGUI3 extends XFrame {
List<InetAddress> dns = kck.getDNS(); List<InetAddress> dns = kck.getDNS();
dnsAreaSet.setText(listToStr(dns)); dnsAreaSet.setText(listToStr(dns));
// 加载额外路由
List<String> ers = kck.getExtraRoutes();
extraRoutesSet.setText(listToStr2(ers));
// 加载ASN // 加载ASN
Long vasn = kck.getVirtualASN(); Long vasn = kck.getVirtualASN();
asnFieldSet.setText(vasn != null ? vasn.toString() : ""); asnFieldSet.setText(vasn != null ? vasn.toString() : "");
enableTUN.setSelected(kck.isEnableTUN());
String tunname=kck.getTUNName(); String tunname=kck.getTUNName();
tunDeviceName.setText(tunname!=null?tunname:""); tunDeviceName.setText(tunname!=null?tunname:"");
// 加载Web API设置
webApiEnabled.setSelected(kck.isWebUI());
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
: "http://0.0.0.0:4665");
// 加载TCP监听 // 加载TCP监听
MultipurposeSocketAddress mpat = kck.getTCPListen(); MultiProtocolSocketAddress mpat = kck.getTCPListen();
tcpListeningSet.setText(mpat != null ? mpat.toString() : ""); tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
// 加载UDP监听 // 加载UDP监听
MultipurposeSocketAddress mpau = kck.getUDPListen(); MultiProtocolSocketAddress mpau = kck.getUDPListen();
udpListeningSet.setText(mpau != null ? mpau.toString() : ""); udpListeningSet.setText(mpau != null ? mpau.toString() : "");
// 加载开放线路表 // 加载开放线路表
List<MultipurposeSocketAddress> linet = kck.getLineTable(); List<MultiProtocolSocketAddress> linet = kck.getExternalEndpoints();
openLineTabelSet.setText(listToStr2(linet)); openLineTabelSet.setText(listToStr2(linet));
// 加载自动连接线路表 // 加载自动连接线路表
List<MultipurposeSocketAddress> clinet = kck.getConnectLineTable(); List<MultiProtocolSocketAddress> clinet = kck.getConnectLineTable();
connectLineTabelSet.setText(listToStr2(clinet)); connectLineTabelSet.setText(listToStr2(clinet));
// 加载NTP服务器表 // 加载NTP服务器表
List<MultipurposeSocketAddress> ntps = kck.getNtpServerTable(); List<MultiProtocolSocketAddress> ntps = kck.getNtpServerTable();
ntpServerSet.setText(listToStr2(ntps)); ntpServerSet.setText(listToStr2(ntps));
// 加载网络接口排除列表 // 加载网络接口排除列表
@@ -1493,9 +1631,20 @@ public class KLALBStateGUI3 extends XFrame {
} }
linkConnectionsCount.getSlider().setValue(conc); linkConnectionsCount.getSlider().setValue(conc);
denyQuery.setSelected( kck.isDenyLineTableQuery()); denyQuery.setSelected( kck.isDenyExternalEndpointQuery());
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast()); denyBroadcast.setSelected( kck.isDenyExternalEndpointBroadcast());
String stategy= kck.getPerformanceStrategy();
PerformanceStrategy pfs=PerformanceStrategy.fromDescription(stategy);
if(pfs!=null) {
for (int i=0;i<comboPerformance.getItemCount();i++){
if(((PerformanceStrategyItem)comboPerformance.getModel().getElementAt(i)).getStrategy().equals(pfs)) {
comboPerformance.setSelectedIndex(i);
break;
}
}
}
String con=kck.getCongestionAlgorithm(); String con=kck.getCongestionAlgorithm();
if(con==null) { if(con==null) {
@@ -8,7 +8,7 @@ import javax.swing.border.LineBorder;
import org.jfree.chart.plot.dial.DialTextAnnotation; import org.jfree.chart.plot.dial.DialTextAnnotation;
import org.jfree.data.general.DefaultValueDataset; import org.jfree.data.general.DefaultValueDataset;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.klalb.CONST; import org.kne.cloud.network.klalb.CONST;
import org.kne.cloud.network.klalb.KLALBUtils; import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl; import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
@@ -143,9 +143,9 @@ public class KperfGUI extends XFrame{
if(kperf==null) { if(kperf==null) {
try { try {
String tar=targetAddressField.getText(); String tar=targetAddressField.getText();
MultipurposeSocketAddress msa=new MultipurposeSocketAddress(tar); MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(tar);
String sour=sourceAddressField.getText(); String sour=sourceAddressField.getText();
MultipurposeSocketAddress msas=new MultipurposeSocketAddress(sour); MultiProtocolSocketAddress msas=new MultiProtocolSocketAddress(sour);
kperf=new Kperf(msa,msas); kperf=new Kperf(msa,msas);
kperf.setReports(reports); kperf.setReports(reports);
kperf.startPerfing(); kperf.startPerfing();
@@ -650,10 +650,10 @@ public class KperfGUI extends XFrame{
startButton.setText(UIEnv.getRsb().getString("stoptest")); startButton.setText(UIEnv.getRsb().getString("stoptest"));
} }
} }
public void setTarget(MultipurposeSocketAddress target) { public void setTarget(MultiProtocolSocketAddress target) {
targetAddressField.setText(target.toString()); targetAddressField.setText(target.toString());
} }
public void setTarget(MultipurposeSocketAddress target,MultipurposeSocketAddress source) { public void setTarget(MultiProtocolSocketAddress target, MultiProtocolSocketAddress source) {
targetAddressField.setText(target.toString()); targetAddressField.setText(target.toString());
sourceAddressField.setText(source.toString()); sourceAddressField.setText(source.toString());
} }
@@ -1,48 +1,25 @@
package org.kne.cloud.network.klalb.ui; package org.kne.cloud.network.klalb.ui;
import java.awt.BasicStroke;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image; import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.geom.Dimension2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Random;
import java.util.Set; import java.util.Set;
import javax.swing.JButton;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
import javax.swing.border.LineBorder;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
public class NetworkGraphPanel extends GraphPanel { public class NetworkGraphPanel extends GraphPanel {
@@ -70,7 +47,7 @@ public class NetworkGraphPanel extends GraphPanel {
} }
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) { public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
super(getText(address), color, x, y, ismarked); super(getNodeText(address), color, x, y, ismarked);
this.address=address; this.address=address;
initign(); initign();
} }
@@ -105,8 +82,8 @@ public class NetworkGraphPanel extends GraphPanel {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
KperfGUI kpfg=new KperfGUI(); KperfGUI kpfg=new KperfGUI();
kpfg.setVisible(true); kpfg.setVisible(true);
MultipurposeSocketAddress source=new MultipurposeSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0); MultiProtocolSocketAddress source=new MultiProtocolSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
MultipurposeSocketAddress target=new MultipurposeSocketAddress("KLALB_Stream", address.toString(), 4564); MultiProtocolSocketAddress target=new MultiProtocolSocketAddress("KLALB_Stream", address.toString(), 4564);
kpfg.setTarget(target,source); kpfg.setTarget(target,source);
} }
}); });
@@ -160,18 +137,34 @@ public class NetworkGraphPanel extends GraphPanel {
return address2.toCompressedString(); return address2.toCompressedString();
} }
public void updateLabel() {
setText(getNodeText(address));
}
public IPv6Address getAddress() { public IPv6Address getAddress() {
return address; return address;
} }
public void setAddress(IPv6Address address) { public void setAddress(IPv6Address address) {
this.address = address; this.address = address;
super.setText(getText(address)); super.setText(getNodeText(address));
} }
} }
private double nsPerPixel=1000L; /**
* 节点标签显示广播得知的设备名称若有+IP地址
*/
private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder();
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
if(dname!=null)
sb.append(dname).append('\n');
sb.append(InetGraphNode.getText(address));
return sb.toString();
}
private double nsPerPixel=5000L;
private class InetGraphEdgeGroup extends GraphEdgeGroup{ private class InetGraphEdgeGroup extends GraphEdgeGroup{
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) { public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
super(nodeA, nodeB); super(nodeA, nodeB);
@@ -201,6 +194,8 @@ public class NetworkGraphPanel extends GraphPanel {
IPv6Address inet6Address = (IPv6Address) iterator.next(); IPv6Address inet6Address = (IPv6Address) iterator.next();
if(!addr.containsKey(inet6Address)) { if(!addr.containsKey(inet6Address)) {
iterator.remove(); iterator.remove();
}else {
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
} }
} }
//System.out.println("------------------------------------"); //System.out.println("------------------------------------");
@@ -5,13 +5,12 @@ import java.awt.Image;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.List; import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.ImageIcon; import javax.swing.ImageIcon;
import javax.swing.BorderFactory;
import javax.swing.JButton; import javax.swing.JButton;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.JPopupMenu; import javax.swing.JPopupMenu;
@@ -22,7 +21,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener; import javax.swing.event.ListSelectionListener;
import org.kne.cloud.klalb.uitool.XDefaultListModel; import org.kne.cloud.klalb.uitool.XDefaultListModel;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address; import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
@@ -30,7 +29,6 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import javax.swing.JTabbedPane; import javax.swing.JTabbedPane;
import javax.swing.JList; import javax.swing.JList;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
@@ -43,7 +41,7 @@ public class NodeInformationPanel extends JPanel {
private KLALBController controller; private KLALBController controller;
private Image image; private Image image;
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>(); private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
public KLALBController getController() { public KLALBController getController() {
return controller; return controller;
} }
@@ -69,11 +67,28 @@ public class NodeInformationPanel extends JPanel {
panel.setLayout(new BorderLayout(0, 0)); panel.setLayout(new BorderLayout(0, 0));
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null); tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
JTextArea overviewArea = new JTextArea();
overviewArea.setEditable(false);
overviewArea.setLineWrap(true);
overviewArea.setWrapStyleWord(true);
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
String ddesc=null;
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
ddesc=controller.getConfigItem().getDeviceDescription();
if(ddesc!=null&&ddesc.isEmpty()) {
ddesc=null;
}
}
overviewArea.setText(buildOverviewText(dname, ddesc));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
JPanel panel_1 = new JPanel(); JPanel panel_1 = new JPanel();
panel_1.setLayout(new BorderLayout(0, 0)); panel_1.setLayout(new BorderLayout(0, 0));
panel_1.add(scrollPane); panel_1.add(scrollPane);
JList<MultipurposeSocketAddress> list = new JList<MultipurposeSocketAddress>(listModel); JList<MultiProtocolSocketAddress> list = new JList<MultiProtocolSocketAddress>(listModel);
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
scrollPane.setViewportView(list); scrollPane.setViewportView(list);
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null); tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
@@ -81,7 +96,7 @@ public class NodeInformationPanel extends JPanel {
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline")); JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
btnNewButton.addActionListener(new ActionListener() { btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
List<MultipurposeSocketAddress> select=list.getSelectedValuesList(); List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
controller.addRemoteLines(select); controller.addRemoteLines(select);
} }
}); });
@@ -93,7 +108,7 @@ public class NodeInformationPanel extends JPanel {
@Override @Override
public void valueChanged(ListSelectionEvent e) { public void valueChanged(ListSelectionEvent e) {
List<MultipurposeSocketAddress> select=list.getSelectedValuesList(); List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
btnNewButton.setEnabled( !select.isEmpty()) ; btnNewButton.setEnabled( !select.isEmpty()) ;
copy.setEnabled(!select.isEmpty()); copy.setEnabled(!select.isEmpty());
} }
@@ -103,9 +118,9 @@ public class NodeInformationPanel extends JPanel {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
StringBuilder sb=new StringBuilder(); StringBuilder sb=new StringBuilder();
List<MultipurposeSocketAddress> select=list.getSelectedValuesList(); List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
for (MultipurposeSocketAddress multipurposeSocketAddress : select) { for (MultiProtocolSocketAddress multiProtocolSocketAddress : select) {
sb.append(multipurposeSocketAddress); sb.append(multiProtocolSocketAddress);
sb.append('\n'); sb.append('\n');
} }
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
@@ -150,16 +165,38 @@ public class NodeInformationPanel extends JPanel {
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol()); client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
try { try {
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{ client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
listModel.clear(); listModel.clear();
for (MultipurposeSocketAddress multipurposeSocketAddress : result) { for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) {
listModel.addElement(multipurposeSocketAddress); listModel.addElement(multiProtocolSocketAddress);
} }
// 用对端返回的设备名称/描述更新概览旧版本节点无该字段时保留原显示
String dn=info.getDeviceName();
if(dn==null||dn.isEmpty()) {
dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
}
String dd=info.getDeviceDescription();
if(dd!=null&&dd.isEmpty()) {
dd=null;
}
overviewArea.setText(buildOverviewText(dn, dd));
}); });
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
private String buildOverviewText(String dname,String ddesc) {
StringBuilder sb=new StringBuilder();
sb.append(UIEnv.getRsb().getString("ipv6addr")).append(": ").append(address.toCompressedString()).append('\n');
if(dname!=null&&!dname.isEmpty()) {
sb.append('\n').append(UIEnv.getRsb().getString("devicename")).append(": ").append(dname).append('\n');
}
if(ddesc!=null&&!ddesc.isEmpty()) {
sb.append('\n').append(UIEnv.getRsb().getString("devicedescription")).append(":\n").append(ddesc).append('\n');
}
return sb.toString();
}
public Icon getIcon() { public Icon getIcon() {
return new ImageIcon(image); return new ImageIcon(image);
} }
@@ -0,0 +1,20 @@
package org.kne.cloud.network.klalb.ui;
import org.kne.cloud.network.klalb.PerformanceStrategy;
public class PerformanceStrategyItem {
private PerformanceStrategy strategy;
public PerformanceStrategyItem(PerformanceStrategy strategy) {
this.strategy = strategy;
}
public PerformanceStrategy getStrategy() {
return strategy;
}
@Override
public String toString() {
return UIEnv.getRsb().getString(strategy.toString());
}
}
@@ -9,7 +9,7 @@ import java.awt.Dimension;
import javax.swing.SwingConstants; import javax.swing.SwingConstants;
import javax.swing.border.LineBorder; import javax.swing.border.LineBorder;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6AddressGroup; import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import org.kne.cloud.network.klalb.CONST; import org.kne.cloud.network.klalb.CONST;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
@@ -219,7 +219,7 @@ public class TPanel2 extends JPanel {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
MultipurposeSocketAddress mtar=tunnel.getSocketAddress(); MultiProtocolSocketAddress mtar=tunnel.getSocketAddress();
if(mtar!=null) { if(mtar!=null) {
kc.removeRemoteLines(mtar); kc.removeRemoteLines(mtar);
}else { }else {
@@ -0,0 +1,986 @@
package org.kne.cloud.network.klalb.web;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import com.sun.net.httpserver.*;
import com.google.gson.*;
import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.RouteItem;
import org.kne.cloud.network.klalb.*;
import org.kne.cloud.network.monitor.LinkStatus;
import org.kne.cloud.network.srv6.KLALBNodeInformation;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NeighborInfo;
import org.kne.cloud.network.srv6.RouterInfo;
import org.kne.cloud.network.srv6.SRv6Router;
/**
* KLALB Web Server providing REST API, SSE live event stream,
* and static dashboard hosting.
*/
public class KLALBWebServer {
private final KLALBProxySystem proxySystem;
private final MultiProtocolSocketAddress listen;
private HttpServer server;
private final Gson gson;
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "KLALB-Web-SSE");
t.setDaemon(true);
return t;
});
private final Set<HttpExchange> sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>());
private final AtomicBoolean running = new AtomicBoolean(false);
public KLALBWebServer(KLALBProxySystem proxySystem, int port) {
this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port));
}
public KLALBWebServer(KLALBProxySystem proxySystem, MultiProtocolSocketAddress listen) {
this.proxySystem = proxySystem;
this.listen = listen;
this.gson = proxySystem.getGson();
}
public synchronized void start() throws IOException {
if (running.get()) return;
InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost())
? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress();
server = HttpServer.create(socketAddress, 0);
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
// API Contexts
server.createContext("/api/status", this::handleStatus);
server.createContext("/api/events", this::handleEvents);
server.createContext("/api/links", this::handleLinks);
server.createContext("/api/links/action", this::handleLinkAction);
server.createContext("/api/links/reconnect", this::handleReconnectAll);
server.createContext("/api/routing-table", this::handleRoutingTable);
server.createContext("/api/nodes", this::handleNodes);
server.createContext("/api/node-info", this::handleNodeInfo);
server.createContext("/api/config", this::handleConfig);
// Static Files / SPA Fallback Handler
server.createContext("/", this::handleStatic);
server.start();
running.set(true);
startSseBroadcaster();
System.out.println("KLALB Web Dashboard started at " + listen);
}
public synchronized void stop() {
if (!running.get()) return;
running.set(false);
sseExecutor.shutdownNow();
for (HttpExchange client : sseClients) {
try {
client.close();
} catch (Exception ignored) {}
}
sseClients.clear();
if (server != null) {
server.stop(1);
server = null;
}
System.out.println("KLALB Web Dashboard stopped.");
}
public boolean isRunning() {
return running.get();
}
public int getPort() {
return listen.getPort();
}
private void setCorsHeaders(HttpExchange exchange) {
Headers headers = exchange.getResponseHeaders();
headers.set("Access-Control-Allow-Origin", "*");
headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
}
private boolean handleCorsPreflight(HttpExchange exchange) throws IOException {
setCorsHeaders(exchange);
if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
exchange.sendResponseHeaders(204, -1);
exchange.close();
return true;
}
return false;
}
private void sendJsonResponse(HttpExchange exchange, int statusCode, Object data) throws IOException {
setCorsHeaders(exchange);
String json;
if (data instanceof JsonElement) {
// gson-2.1 toJson(Object) 会按运行时类型 JsonObject 反射序列化出内部的 members 字段
// 必须走 JsonElement 重载 toString直接输出 JSON
json = ((JsonElement) data).toString();
} else {
json = gson.toJson(data);
}
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
exchange.sendResponseHeaders(statusCode, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
private void sendError(HttpExchange exchange, int statusCode, String message) throws IOException {
JsonObject error = new JsonObject();
error.addProperty("error", message);
sendJsonResponse(exchange, statusCode, error);
}
private String readRequestBody(HttpExchange exchange) throws IOException {
try (InputStream is = exchange.getRequestBody()) {
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
// -------------------------------------------------------------
// API Handlers
// -------------------------------------------------------------
private JsonObject getStatusJson() {
JsonObject root = new JsonObject();
KLALBController kc = proxySystem.getKlalbController();
if (kc == null) {
root.addProperty("status", "not_ready");
return root;
}
root.addProperty("version", CONST.klalbver);
root.addProperty("title", CONST.klalb);
IPv6AddressGroup self = kc.getSelf();
root.addProperty("address", self != null ? self.getAddress().toString() : "");
SRv6Router router = kc.getIpv6Router();
if (router != null && router.getKlalbRouteProtol() != null) {
root.addProperty("onlineDevices", router.getKlalbRouteProtol().getDevicesFound());
root.addProperty("deviceName", router.getDeviceName() != null ? router.getDeviceName() : "");
} else {
root.addProperty("onlineDevices", 0);
root.addProperty("deviceName", "");
}
KLALBControllerConfigItem configItem = proxySystem.getControllerConfig();
if (configItem != null) {
root.addProperty("deviceDescription", configItem.getDeviceDescription() != null ? configItem.getDeviceDescription() : "");
root.addProperty("enableTUN", configItem.isEnableTUN());
root.addProperty("tunName", configItem.isEnableTUN() && configItem.getTUNName() != null ? configItem.getTUNName() : "");
root.addProperty("congestionAlgorithm", configItem.getCongestionAlgorithm() != null ? configItem.getCongestionAlgorithm() : "");
root.addProperty("performanceStrategy", configItem.getPerformanceStrategy() != null ? configItem.getPerformanceStrategy() : "");
}
// Clock
HighAccuracyClock hac = kc.getClock();
if (hac != null) {
long delta = hac.getFrequency() - 1000000000;
double ppm = delta / 1000.0;
root.addProperty("timeStr", hac.toString2());
root.addProperty("timePpm", ppm);
}
// Metrics
JsonObject metrics = new JsonObject();
if (kc.getLinkMonitor() != null) {
metrics.addProperty("upSpeed", kc.getLinkMonitor().getOutSpeed());
metrics.addProperty("downSpeed", kc.getLinkMonitor().getInSpeed());
metrics.addProperty("upSpeedMax", kc.getLinkMonitor().getOutSpeedMax());
metrics.addProperty("downSpeedMax", kc.getLinkMonitor().getInSpeedMax());
metrics.addProperty("upPPS", kc.getLinkMonitor().getOutPPS());
metrics.addProperty("downPPS", kc.getLinkMonitor().getInPPS());
metrics.addProperty("upPPSMax", kc.getLinkMonitor().getOutPPSMax());
metrics.addProperty("downPPSMax", kc.getLinkMonitor().getInPPSMax());
metrics.addProperty("upTraffic", kc.getLinkMonitor().getOutTraffic());
metrics.addProperty("downTraffic", kc.getLinkMonitor().getInTraffic());
}
if (kc.getDatatMonitor() != null) {
metrics.addProperty("dataUpSpeed", kc.getDatatMonitor().getOutSpeed());
metrics.addProperty("dataDownSpeed", kc.getDatatMonitor().getInSpeed());
metrics.addProperty("dataUpPPS", kc.getDatatMonitor().getOutPPS());
metrics.addProperty("dataDownPPS", kc.getDatatMonitor().getInPPS());
metrics.addProperty("dataUpTraffic", kc.getDatatMonitor().getOutTraffic());
metrics.addProperty("dataDownTraffic", kc.getDatatMonitor().getInTraffic());
}
if (router != null) {
metrics.addProperty("backplaneDelay", router.getBackplaneTime());
metrics.addProperty("backplanePPS", router.getBackplanePPS());
metrics.addProperty("backplanePPSMax", router.getBackplanePPSMax());
metrics.addProperty("backplaneECNRate", router.getECNRate());
metrics.addProperty("backplaneLossRate", router.getLossRate());
}
root.add("metrics", metrics);
return root;
}
private void handleStatus(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
sendJsonResponse(exchange, 200, getStatusJson());
}
private JsonArray getLinksJson() {
JsonArray array = new JsonArray();
KLALBController kc = proxySystem.getKlalbController();
if (kc == null) return array;
List<IPv6NetworkLink> linesList = kc.getLines();
synchronized (linesList) {
for (IPv6NetworkLink link : linesList) {
if (!(link instanceof KLALBRemoteLink)) continue;
KLALBRemoteLink remoteLink = (KLALBRemoteLink) link;
JsonObject obj = new JsonObject();
obj.addProperty("name", remoteLink.getName());
obj.addProperty("state", LinkStatus.stateToString(remoteLink.getState()));
obj.addProperty("stateCode", remoteLink.getState());
MultiProtocolSocketAddress mpsa = remoteLink.getSocketAddress();
obj.addProperty("socketAddress", mpsa != null ? mpsa.toString() : "");
IPv6AddressGroup vaddr = remoteLink.getRemoteVaddr();
obj.addProperty("vaddr", vaddr != null ? vaddr.getAddress().toString() : "");
if (remoteLink.getMonitor() != null) {
obj.addProperty("upSpeed", remoteLink.getMonitor().getOutSpeed());
obj.addProperty("downSpeed", remoteLink.getMonitor().getInSpeed());
obj.addProperty("upPPS", remoteLink.getMonitor().getOutPPS());
obj.addProperty("downPPS", remoteLink.getMonitor().getInPPS());
obj.addProperty("upTraffic", remoteLink.getMonitor().getOutTraffic());
obj.addProperty("downTraffic", remoteLink.getMonitor().getInTraffic());
obj.addProperty("upDelay", remoteLink.getMonitor().getOutDelay());
obj.addProperty("downDelay", remoteLink.getMonitor().getInDelay());
obj.addProperty("upDelayMin", remoteLink.getMonitor().getOutDelayMin());
obj.addProperty("downDelayMin", remoteLink.getMonitor().getInDelayMin());
obj.addProperty("upJitter", remoteLink.getMonitor().getOutJitter());
obj.addProperty("downJitter", remoteLink.getMonitor().getInJitter());
}
array.add(obj);
}
}
return array;
}
private void handleLinks(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
String method = exchange.getRequestMethod();
KLALBController kc = proxySystem.getKlalbController();
if ("GET".equalsIgnoreCase(method)) {
sendJsonResponse(exchange, 200, getLinksJson());
} else if ("POST".equalsIgnoreCase(method)) {
String body = readRequestBody(exchange);
try {
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
String address = req.get("address").getAsString().trim();
if (address.isEmpty()) {
sendError(exchange, 400, "Address is required");
return;
}
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(address);
List<KLALBRemoteLink> added = kc.addRemoteLines(mpsa);
JsonObject resp = new JsonObject();
resp.addProperty("success", !added.isEmpty());
resp.addProperty("count", added.size());
sendJsonResponse(exchange, 200, resp);
} catch (Exception e) {
sendError(exchange, 400, "Failed to add link: " + e.getMessage());
}
} else if ("DELETE".equalsIgnoreCase(method)) {
String query = exchange.getRequestURI().getQuery();
String address = null;
if (query != null && query.startsWith("address=")) {
address = URLDecoder.decode(query.substring(8), StandardCharsets.UTF_8);
} else {
String body = readRequestBody(exchange);
if (!body.isEmpty()) {
try {
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
if (req.has("address")) {
address = req.get("address").getAsString();
}
} catch (Exception ignored) {}
}
}
if (address == null || address.trim().isEmpty()) {
sendError(exchange, 400, "Address parameter required");
return;
}
try {
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(address.trim());
List<KLALBRemoteLink> removed = kc.removeRemoteLines(mpsa);
JsonObject resp = new JsonObject();
resp.addProperty("success", !removed.isEmpty());
resp.addProperty("count", removed.size());
sendJsonResponse(exchange, 200, resp);
} catch (Exception e) {
sendError(exchange, 400, "Failed to remove link: " + e.getMessage());
}
} else {
sendError(exchange, 405, "Method not allowed");
}
}
private void handleLinkAction(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
String body = readRequestBody(exchange);
try {
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
String action = req.get("action").getAsString();
String address = req.has("address") ? req.get("address").getAsString() : null;
KLALBController kc = proxySystem.getKlalbController();
KLALBRemoteLink target = null;
if (address != null) {
for (IPv6NetworkLink link : kc.getLines()) {
if (link instanceof KLALBRemoteLink rl) {
if (rl.getName().equalsIgnoreCase(address) ||
(rl.getSocketAddress() != null && rl.getSocketAddress().toString().equalsIgnoreCase(address))) {
target = rl;
break;
}
}
}
}
JsonObject resp = new JsonObject();
if (target != null) {
switch (action) {
case "reconnect" -> {
target.reconnectImmediately();
resp.addProperty("success", true);
}
case "disconnect" -> {
target.dislink();
resp.addProperty("success", true);
}
case "remove" -> {
MultiProtocolSocketAddress mtar = target.getSocketAddress();
if (mtar != null) {
kc.removeRemoteLines(mtar);
} else {
target.close();
}
resp.addProperty("success", true);
}
default -> {
sendError(exchange, 400, "Unknown action: " + action);
return;
}
}
} else {
sendError(exchange, 404, "Target link not found: " + address);
return;
}
sendJsonResponse(exchange, 200, resp);
} catch (Exception e) {
sendError(exchange, 400, "Failed to execute link action: " + e.getMessage());
}
}
private void handleReconnectAll(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
KLALBController kc = proxySystem.getKlalbController();
if (kc != null) {
kc.reconnectImmediately();
JsonObject resp = new JsonObject();
resp.addProperty("success", true);
sendJsonResponse(exchange, 200, resp);
} else {
sendError(exchange, 500, "Controller not ready");
}
}
private void handleRoutingTable(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
KLALBController kc = proxySystem.getKlalbController();
JsonArray routesArray = new JsonArray();
if (kc != null && kc.getIpv6Router() != null) {
List<RouteItem> list = new ArrayList<>(kc.getIpv6Router().getCurrentRouteTabel());
Collections.sort(list);
for (RouteItem item : list) {
JsonObject obj = new JsonObject();
obj.addProperty("destination", item.getDestination() != null ? item.getDestination().toString() : "");
obj.addProperty("protocol", item.getProto());
obj.addProperty("preference", item.getPre());
obj.addProperty("cost", item.getCost());
obj.addProperty("flag", item.getFlag());
obj.addProperty("nexthop", item.getNexthop() != null ? item.getNexthop().toString() : "");
obj.addProperty("interface", item.getDestlink() != null ? item.getDestlink().getName() : "");
routesArray.add(obj);
}
}
sendJsonResponse(exchange, 200, routesArray);
}
private KLALBRoutingProtocolAPIClient nodeInfoClient;
private synchronized KLALBRoutingProtocolAPIClient getNodeInfoClient(KLALBController kc) {
if (nodeInfoClient == null) {
nodeInfoClient = new KLALBRoutingProtocolAPIClient(kc.getIpv6Router().getKlalbRouteProtol());
}
return nodeInfoClient;
}
private void handleNodeInfo(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
Map<String, String> params = parseQuery(exchange.getRequestURI().getRawQuery());
String addressStr = params.get("address");
if (addressStr == null || addressStr.trim().isEmpty()) {
sendError(exchange, 400, "address parameter is required");
return;
}
KLALBController kc = proxySystem.getKlalbController();
if (kc == null || kc.getIpv6Router() == null || kc.getIpv6Router().getKlalbRouteProtol() == null) {
sendError(exchange, 503, "Controller not ready");
return;
}
IPv6Address target;
try {
target = new IPv6Address((Inet6Address) InetAddress.getByName(addressStr.trim()));
} catch (Exception e) {
sendError(exchange, 400, "Invalid IPv6 address: " + addressStr);
return;
}
SRv6Router router = kc.getIpv6Router();
boolean isSelf = router.getLocator().getAddress().equals(target);
JsonObject resp = new JsonObject();
resp.addProperty("address", target.toString());
if (isSelf) {
// 本机描述直接取本地控制器配置
KLALBControllerConfigItem cfg = proxySystem.getControllerConfig();
resp.addProperty("isSelf", true);
String dname = router.getDeviceName() != null ? router.getDeviceName()
: (cfg != null && cfg.getDeviceName() != null ? cfg.getDeviceName() : "");
resp.addProperty("deviceName", dname);
resp.addProperty("deviceDescription",
cfg != null && cfg.getDeviceDescription() != null ? cfg.getDeviceDescription() : "");
JsonArray lines = new JsonArray();
for (MultiProtocolSocketAddress mpsa : kc.getExternalEndpoints()) {
lines.add(new JsonPrimitive(mpsa.toString()));
}
resp.add("openLines", lines);
sendJsonResponse(exchange, 200, resp);
return;
}
// 远端节点 SRv6 虚拟网络发送完整节点信息查询异步回调限时等待
resp.addProperty("isSelf", false);
CompletableFuture<KLALBNodeInformation> future = new CompletableFuture<>();
try {
getNodeInfoClient(kc).requestNodeInfoFull(
new InetSocketAddress(target.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
info -> future.complete(info));
KLALBNodeInformation info;
try {
info = future.get(3, TimeUnit.SECONDS);
} catch (TimeoutException te) {
info = null;
}
String dname = info != null && info.getDeviceName() != null && !info.getDeviceName().isEmpty()
? info.getDeviceName()
: router.getKlalbRouteProtol().getDeviceName(target);
resp.addProperty("deviceName", dname != null ? dname : "");
resp.addProperty("deviceDescription",
info != null && info.getDeviceDescription() != null ? info.getDeviceDescription() : "");
resp.addProperty("reachable", info != null);
JsonArray lines = new JsonArray();
if (info != null && info.getOpenLines() != null) {
for (MultiProtocolSocketAddress mpsa : info.getOpenLines()) {
lines.add(new JsonPrimitive(mpsa.toString()));
}
}
resp.add("openLines", lines);
} catch (Exception e) {
resp.addProperty("deviceName", "");
resp.addProperty("deviceDescription", "");
resp.addProperty("reachable", false);
resp.add("openLines", new JsonArray());
}
sendJsonResponse(exchange, 200, resp);
}
private Map<String, String> parseQuery(String rawQuery) {
Map<String, String> params = new HashMap<>();
if (rawQuery == null || rawQuery.isEmpty()) return params;
for (String pair : rawQuery.split("&")) {
int idx = pair.indexOf('=');
if (idx <= 0) continue;
try {
params.put(URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8),
URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8));
} catch (Exception ignored) {}
}
return params;
}
private void handleNodes(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
KLALBController kc = proxySystem.getKlalbController();
JsonObject result = new JsonObject();
JsonArray nodesArray = new JsonArray();
JsonArray edgesArray = new JsonArray();
if (kc != null && kc.getIpv6Router() != null && kc.getIpv6Router().getKlalbRouteProtol() != null) {
KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol();
Map<IPv6Address, Long> addrs = rproto.getAddresses();
IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress();
if (addrs != null) {
for (IPv6Address addr : addrs.keySet()) {
JsonObject nodeObj = new JsonObject();
nodeObj.addProperty("id", addr.toString());
nodeObj.addProperty("address", addr.toString());
nodeObj.addProperty("compressedAddress", addr.toCompressedString());
nodeObj.addProperty("isSelf", addr.equals(selfAddr));
String dname = rproto.getDeviceName(addr);
nodeObj.addProperty("deviceName", dname != null ? dname : "");
nodesArray.add(nodeObj);
}
}
Map<IPv6Address, RouterInfo> netmap = rproto.getNetmap();
if (netmap != null) {
Set<String> seenEdges = new HashSet<>();
for (Map.Entry<IPv6Address, RouterInfo> entry : netmap.entrySet()) {
IPv6Address from = entry.getKey();
RouterInfo info = entry.getValue();
if (info != null && info.getNeighborAddresses() != null) {
for (NeighborInfo nb : info.getNeighborAddresses()) {
if (nb != null && nb.getLocator() != null) {
String to = nb.getLocator().getAddress().toString();
String edgeKey = from.toString().compareTo(to) < 0
? from.toString() + "->" + to
: to + "->" + from.toString();
if (!seenEdges.contains(edgeKey)) {
seenEdges.add(edgeKey);
JsonObject edgeObj = new JsonObject();
edgeObj.addProperty("source", from.toString());
edgeObj.addProperty("target", to);
edgeObj.addProperty("delay", nb.getUploadDelay());
edgeObj.addProperty("cost", nb.getUploadDelayMin());
edgesArray.add(edgeObj);
}
}
}
}
}
}
}
result.add("nodes", nodesArray);
result.add("edges", edgesArray);
sendJsonResponse(exchange, 200, result);
}
private void handleConfig(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
String method = exchange.getRequestMethod();
if ("GET".equalsIgnoreCase(method)) {
KLALBControllerConfigItem config = proxySystem.getControllerConfig();
if (config != null) {
sendJsonResponse(exchange, 200, config);
} else {
sendError(exchange, 404, "Configuration not found");
}
} else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
String body = readRequestBody(exchange);
try {
JsonObject json = new JsonParser().parse(body).getAsJsonObject();
KLALBControllerConfigItem current = proxySystem.getControllerConfig();
if (current != null) {
if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) {
current.setDeviceName(json.get("deviceName").getAsString());
} else if (json.has("DeviceName") && !json.get("DeviceName").isJsonNull()) {
current.setDeviceName(json.get("DeviceName").getAsString());
}
if (json.has("deviceDescription") && !json.get("deviceDescription").isJsonNull()) {
current.setDeviceDescription(json.get("deviceDescription").getAsString());
} else if (json.has("DeviceDescription") && !json.get("DeviceDescription").isJsonNull()) {
current.setDeviceDescription(json.get("DeviceDescription").getAsString());
}
if (json.has("language") && !json.get("language").isJsonNull()) {
current.setLanguage(json.get("language").getAsString());
}
if (json.has("virtualAddress") && !json.get("virtualAddress").isJsonNull()) {
current.setVirtualAddress(json.get("virtualAddress").getAsString());
} else if (json.has("VirtualAddress") && !json.get("VirtualAddress").isJsonNull()) {
current.setVirtualAddress(json.get("VirtualAddress").getAsString());
}
if (json.has("virtualASN") && !json.get("virtualASN").isJsonNull()) {
current.setVirtualASN(json.get("virtualASN").getAsLong());
} else if (json.has("VirtualASN") && !json.get("VirtualASN").isJsonNull()) {
current.setVirtualASN(json.get("VirtualASN").getAsLong());
}
if (json.has("virtualSocketName") && !json.get("virtualSocketName").isJsonNull()) {
current.setVirtualSocketName(json.get("virtualSocketName").getAsString());
} else if (json.has("VirtualSocketName") && !json.get("VirtualSocketName").isJsonNull()) {
current.setVirtualSocketName(json.get("VirtualSocketName").getAsString());
}
if (json.has("enableTUN")) {
current.setEnableTUN(json.get("enableTUN").getAsBoolean());
} else if (json.has("EnableTUN")) {
current.setEnableTUN(json.get("EnableTUN").getAsBoolean());
}
if (json.has("tunName") && !json.get("tunName").isJsonNull()) {
current.setTUNName(json.get("tunName").getAsString());
} else if (json.has("TUNName") && !json.get("TUNName").isJsonNull()) {
current.setTUNName(json.get("TUNName").getAsString());
}
if (json.has("webUI")) {
current.setWebUI(json.get("webUI").getAsBoolean());
}
if (json.has("webListen") && !json.get("webListen").isJsonNull()) {
current.setWebListen(new MultiProtocolSocketAddress(json.get("webListen").getAsString()));
} else if (json.has("webPort")) {
current.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", json.get("webPort").getAsInt()));
}
if (json.has("nogui")) {
current.setNogui(json.get("nogui").getAsBoolean());
}
if (json.has("tcpListen") && !json.get("tcpListen").isJsonNull()) {
current.setTCPListen(new MultiProtocolSocketAddress(json.get("tcpListen").getAsString()));
} else if (json.has("TCPListen") && !json.get("TCPListen").isJsonNull()) {
current.setTCPListen(new MultiProtocolSocketAddress(json.get("TCPListen").getAsString()));
}
if (json.has("udpListen") && !json.get("udpListen").isJsonNull()) {
current.setUDPListen(new MultiProtocolSocketAddress(json.get("udpListen").getAsString()));
} else if (json.has("UDPListen") && !json.get("UDPListen").isJsonNull()) {
current.setUDPListen(new MultiProtocolSocketAddress(json.get("UDPListen").getAsString()));
}
if (json.has("externalEndpoints") || json.has("ExternalEndpoints") || json.has("openConnections") || json.has("OpenConnections") || json.has("lineTable") || json.has("LineTable")) {
JsonArray arr = json.has("externalEndpoints") ? json.getAsJsonArray("externalEndpoints")
: json.has("ExternalEndpoints") ? json.getAsJsonArray("ExternalEndpoints")
: json.has("openConnections") ? json.getAsJsonArray("openConnections")
: json.has("OpenConnections") ? json.getAsJsonArray("OpenConnections")
: json.has("lineTable") ? json.getAsJsonArray("lineTable") : json.getAsJsonArray("LineTable");
List<MultiProtocolSocketAddress> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
list.add(new MultiProtocolSocketAddress(el.getAsString()));
}
}
current.setExternalEndpoints(list);
}
if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) {
JsonArray arr = json.has("autoConnections") ? json.getAsJsonArray("autoConnections")
: json.has("AutoConnections") ? json.getAsJsonArray("AutoConnections")
: json.has("connectLineTable") ? json.getAsJsonArray("connectLineTable") : json.getAsJsonArray("ConnectLineTable");
List<MultiProtocolSocketAddress> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
list.add(new MultiProtocolSocketAddress(el.getAsString()));
}
}
current.setAutoConnections(list);
}
if (json.has("ntpServers") || json.has("NtpServers") || json.has("ntpServerTable")) {
JsonArray arr = json.has("ntpServers") ? json.getAsJsonArray("ntpServers")
: json.has("NtpServers") ? json.getAsJsonArray("NtpServers")
: json.getAsJsonArray("ntpServerTable");
List<MultiProtocolSocketAddress> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
list.add(new MultiProtocolSocketAddress(el.getAsString()));
}
}
current.setNtpServers(list);
}
if (json.has("dns") || json.has("DNS")) {
JsonArray arr = json.has("dns") ? json.getAsJsonArray("dns") : json.getAsJsonArray("DNS");
List<InetAddress> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
try {
list.add(InetAddress.getByName(el.getAsString()));
} catch (Exception ignored) {}
}
}
current.setDNS(list);
}
if (json.has("extraRoutes") || json.has("ExtraRoutes")) {
JsonArray arr = json.has("extraRoutes") ? json.getAsJsonArray("extraRoutes") : json.getAsJsonArray("ExtraRoutes");
List<String> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
list.add(el.getAsString());
}
}
current.setExtraRoutes(list);
}
if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) {
JsonArray arr = json.has("networkInterfaceExcepts") ? json.getAsJsonArray("networkInterfaceExcepts") : json.getAsJsonArray("NetworkInterfaceExcepts");
List<String> list = new ArrayList<>();
for (JsonElement el : arr) {
if (el.isJsonPrimitive()) {
list.add(el.getAsString());
}
}
current.setNetworkInterfaceExcepts(list);
}
if (json.has("congestionAlgorithm") && !json.get("congestionAlgorithm").isJsonNull()) {
current.setCongestionAlgorithm(json.get("congestionAlgorithm").getAsString());
}
if (json.has("performanceStrategy") && !json.get("performanceStrategy").isJsonNull()) {
current.setPerformanceStrategy(json.get("performanceStrategy").getAsString());
}
if (json.has("linkConnectionsCount")) {
current.setLinkConnectionsCount(json.get("linkConnectionsCount").getAsInt());
}
if (json.has("burstLimit")) {
current.setBurstLimit(json.get("burstLimit").getAsDouble());
}
if (json.has("delayUpperBound")) {
current.setDelayUpperBound(json.get("delayUpperBound").getAsDouble());
}
if (json.has("delayLowerBound")) {
current.setDelayLowerBound(json.get("delayLowerBound").getAsDouble());
}
if (json.has("nagleDelayTime")) {
current.setNagleDelayTime(json.get("nagleDelayTime").getAsLong());
}
if (json.has("linkNagleDelayTime")) {
current.setLinkNagleDelayTime(json.get("linkNagleDelayTime").getAsLong());
}
if (json.has("denyExternalEndpointQuery")) {
current.setDenyExternalEndpointQuery(json.get("denyExternalEndpointQuery").getAsBoolean());
} else if (json.has("denyConnectionQuery")) {
current.setDenyExternalEndpointQuery(json.get("denyConnectionQuery").getAsBoolean());
} else if (json.has("denyLineTableQuery")) {
current.setDenyExternalEndpointQuery(json.get("denyLineTableQuery").getAsBoolean());
}
if (json.has("denyExternalEndpointBroadcast")) {
current.setDenyExternalEndpointBroadcast(json.get("denyExternalEndpointBroadcast").getAsBoolean());
} else if (json.has("denyConnectionBroadcast")) {
current.setDenyExternalEndpointBroadcast(json.get("denyConnectionBroadcast").getAsBoolean());
} else if (json.has("denyLineTableBroadcast")) {
current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean());
}
// Trigger GUI save consumer or save directly
if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) {
proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig());
} else {
proxySystem.saveConfigToFile();
}
JsonObject resp = new JsonObject();
resp.addProperty("success", true);
resp.addProperty("message", "Configuration updated successfully");
sendJsonResponse(exchange, 200, resp);
} else {
sendError(exchange, 500, "Current configuration is null");
}
} catch (Exception e) {
sendError(exchange, 400, "Failed to update configuration: " + e.getMessage());
}
} else {
sendError(exchange, 405, "Method not allowed");
}
}
// -------------------------------------------------------------
// SSE Handler & Broadcaster
// -------------------------------------------------------------
private void handleEvents(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
sendError(exchange, 405, "Method not allowed");
return;
}
Headers headers = exchange.getResponseHeaders();
headers.set("Content-Type", "text/event-stream; charset=utf-8");
headers.set("Cache-Control", "no-cache, no-transform");
headers.set("Connection", "keep-alive");
headers.set("Access-Control-Allow-Origin", "*");
exchange.sendResponseHeaders(200, 0);
sseClients.add(exchange);
}
private void startSseBroadcaster() {
sseExecutor.scheduleAtFixedRate(() -> {
if (sseClients.isEmpty()) return;
JsonObject eventData = new JsonObject();
eventData.add("status", getStatusJson());
eventData.add("links", getLinksJson());
String eventStr = "data: " + eventData.toString() + "\n\n";
byte[] bytes = eventStr.getBytes(StandardCharsets.UTF_8);
Iterator<HttpExchange> it = sseClients.iterator();
while (it.hasNext()) {
HttpExchange ex = it.next();
try {
OutputStream os = ex.getResponseBody();
os.write(bytes);
os.flush();
} catch (Exception e) {
try {
ex.close();
} catch (Exception ignored) {}
it.remove();
}
}
}, 200, 200, TimeUnit.MILLISECONDS);
}
// -------------------------------------------------------------
// Static Resource / SPA Handler
// -------------------------------------------------------------
private void handleStatic(HttpExchange exchange) throws IOException {
if (handleCorsPreflight(exchange)) return;
String path = exchange.getRequestURI().getPath();
if (path.startsWith("/api/")) {
sendError(exchange, 404, "API endpoint not found");
return;
}
// Look for dashboard/dist
File distDir = new File("dashboard/dist");
File targetFile = null;
if (distDir.exists() && distDir.isDirectory()) {
String relPath = path.equals("/") ? "index.html" : path.substring(1);
targetFile = new File(distDir, relPath);
if (!targetFile.exists() || targetFile.isDirectory()) {
targetFile = new File(distDir, "index.html");
}
}
if (targetFile != null && targetFile.exists() && targetFile.isFile()) {
String mime = getMimeType(targetFile.getName());
byte[] content = Files.readAllBytes(targetFile.toPath());
setCorsHeaders(exchange);
exchange.getResponseHeaders().set("Content-Type", mime);
exchange.sendResponseHeaders(200, content.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(content);
}
} else {
// Fallback welcome message if frontend not built yet
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>KLALB Web Dashboard</title>" +
"<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#09090b;color:#fafafa}" +
".card{background:#18181b;padding:2rem;border-radius:0.75rem;border:1px solid #27272a;max-width:480px;text-align:center}" +
"h1{margin:0 0 0.5rem;font-size:1.5rem}p{color:#a1a1aa;margin:0 0 1rem;font-size:0.875rem}code{background:#27272a;padding:0.2rem 0.4rem;border-radius:0.25rem}</style></head>" +
"<body><div class='card'><h1>KLALB Web Dashboard API Active</h1>" +
"<p>The backend API is ready. Start the frontend dev server or build the dashboard: <br><code>cd dashboard && pnpm build</code></p>" +
"<p><a href='/api/status' style='color:#38bdf8'>View /api/status</a></p></div></body></html>";
byte[] bytes = html.getBytes(StandardCharsets.UTF_8);
setCorsHeaders(exchange);
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
exchange.sendResponseHeaders(200, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
}
}
private String getMimeType(String filename) {
String lower = filename.toLowerCase();
if (lower.endsWith(".html")) return "text/html; charset=utf-8";
if (lower.endsWith(".js") || lower.endsWith(".mjs")) return "application/javascript; charset=utf-8";
if (lower.endsWith(".css")) return "text/css; charset=utf-8";
if (lower.endsWith(".json")) return "application/json; charset=utf-8";
if (lower.endsWith(".svg")) return "image/svg+xml";
if (lower.endsWith(".png")) return "image/png";
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
if (lower.endsWith(".ico")) return "image/x-icon";
if (lower.endsWith(".woff2")) return "font/woff2";
if (lower.endsWith(".woff")) return "font/woff";
if (lower.endsWith(".ttf")) return "font/ttf";
return "application/octet-stream";
}
}
@@ -258,7 +258,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
case KLTPPacket.KLTP_TYPE_DATA: case KLTPPacket.KLTP_TYPE_DATA:
//ackSequenceBatcher.putMessage(kseq); //ackSequenceBatcher.putMessage(kseq);
recvMap.put(kltp.getSequence(), kltp); recvMap.put(kltp.getSequence(), kltp);
controller.getIpv6Router().runPacketSendTask(()->{ controller.getIpv6Router().enqueuePacketSendTask(()->{
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0); KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
pack.setCE(u.isCE()); pack.setCE(u.isCE());
return controller.createPacketToAddress(remoteaddr,0,pack); return controller.createPacketToAddress(remoteaddr,0,pack);
@@ -267,7 +267,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
case KLTPPacket.KLTP_TYPE_DATAFIN: case KLTPPacket.KLTP_TYPE_DATAFIN:
//ackSequenceBatcher.putMessage(kseq2); //ackSequenceBatcher.putMessage(kseq2);
recvMap.put(kltp.getSequence(), kltp); recvMap.put(kltp.getSequence(), kltp);
controller.getIpv6Router().runPacketSendTask(()->{ controller.getIpv6Router().enqueuePacketSendTask(()->{
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0); KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
pack.setCE(u.isCE()); pack.setCE(u.isCE());
return controller.createPacketToAddress(remoteaddr,0,pack); return controller.createPacketToAddress(remoteaddr,0,pack);
@@ -1,23 +1,22 @@
package org.kne.cloud.network.minecraft; package org.kne.cloud.network.minecraft;
import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.scanner.InetAddressRange; import org.kne.cloud.network.scanner.InetAddressRange;
import org.kne.cloud.network.scanner.PortRange; import org.kne.cloud.network.scanner.PortRange;
import org.kne.cloud.network.scanner.ScanRange; import org.kne.cloud.network.scanner.ScanRange;
import org.kne.cloud.network.scanner.TCPNetworkScanner; import org.kne.cloud.network.scanner.TCPNetworkScanner;
public class MinecraftScanner extends TCPNetworkScanner{ public class MinecraftScanner extends TCPNetworkScanner{
private BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger>minecraftConsumer; private BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger>minecraftConsumer;
public BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> getMinecraftConsumer() { public BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
return minecraftConsumer; return minecraftConsumer;
} }
public void setMinecraftConsumer(BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> minecraftConsumer) { public void setMinecraftConsumer(BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> minecraftConsumer) {
this.minecraftConsumer = minecraftConsumer; this.minecraftConsumer = minecraftConsumer;
} }
{ {
@@ -6,9 +6,9 @@ import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext; import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext; import javax.naming.directory.InitialDirContext;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
public class MinecraftServerAddress extends MultipurposeSocketAddress public class MinecraftServerAddress extends MultiProtocolSocketAddress
{ {
private String originAddr; private String originAddr;
@@ -3,14 +3,8 @@ package org.kne.cloud.network.minecraft;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.io.StringReader;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.Socket; import java.net.Socket;
@@ -19,13 +13,11 @@ import java.util.Base64;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
public class MinecraftServerPinger implements Serializable{ public class MinecraftServerPinger implements Serializable{
/** /**
@@ -34,7 +26,7 @@ public class MinecraftServerPinger implements Serializable{
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private transient Socket socket; private transient Socket socket;
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext(); private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
private MultipurposeSocketAddress targetaddr; private MultiProtocolSocketAddress targetaddr;
@@ -53,11 +45,11 @@ public class MinecraftServerPinger implements Serializable{
private String icon; private String icon;
private transient BufferedImage image; private transient BufferedImage image;
public MinecraftServerPinger(MultipurposeSocketAddress target) throws UnknownHostException, IOException { public MinecraftServerPinger(MultiProtocolSocketAddress target) throws UnknownHostException, IOException {
this(target,target.connectSocket()); this(target,target.connectSocket());
} }
public MinecraftServerPinger(MultipurposeSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{ public MinecraftServerPinger(MultiProtocolSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
targetaddr=target; targetaddr=target;
socket=Asocket; socket=Asocket;
socket.setTcpNoDelay(true); socket.setTcpNoDelay(true);
@@ -302,7 +294,7 @@ public class MinecraftServerPinger implements Serializable{
return json; return json;
} }
public MultipurposeSocketAddress getTargetaddr() { public MultiProtocolSocketAddress getTargetaddr() {
return targetaddr; return targetaddr;
} }
@@ -2,7 +2,7 @@ package org.kne.cloud.network.minecraft;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.scanner.InetAddressRange; import org.kne.cloud.network.scanner.InetAddressRange;
import org.kne.cloud.network.scanner.PortRange; import org.kne.cloud.network.scanner.PortRange;
import org.kne.cloud.network.scanner.ScanRange; import org.kne.cloud.network.scanner.ScanRange;
@@ -13,7 +13,7 @@ public class MinecraftServerScanRange extends ScanRange {
super(addressRange, portRange); super(addressRange, portRange);
} }
public MinecraftServerScanRange(MultipurposeSocketAddress begin, MultipurposeSocketAddress end) public MinecraftServerScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end)
throws UnknownHostException { throws UnknownHostException {
super(begin, end); super(begin, end);
} }
@@ -25,8 +25,8 @@ public class MinecraftServerScanRange extends ScanRange {
@Override @Override
protected void setMsaString(String range) throws UnknownHostException { protected void setMsaString(String range) throws UnknownHostException {
String[]sp=range.split("~"); String[]sp=range.split("~");
MultipurposeSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]); MultiProtocolSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
MultipurposeSocketAddress end; MultiProtocolSocketAddress end;
if(sp.length>1) { if(sp.length>1) {
end=MinecraftServerAddress.findAddress (sp[1]); end=MinecraftServerAddress.findAddress (sp[1]);
}else { }else {
@@ -4,6 +4,7 @@ import java.util.UUID;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.network.klalb.KLALBUtils; import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.concurrent.LazyEvaluator;
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData { public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
@@ -16,11 +17,12 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
private final long TIME_WINDOW=2000000000L;
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) { public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
super(); super();
this.clock=clock; this.clock=clock;
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, 5000000000L); uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, TIME_WINDOW);
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, 5000000000L); downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, TIME_WINDOW);
} }
@@ -86,12 +88,13 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
private LazyEvaluator<Long> outSpeedEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculateBandwidth(timewindow);});
public long getOutSpeed() { public long getOutSpeed() {
return uploadBandwidth.calculateBandwidth(timewindow); return outSpeedEvaluator.get();
} }
private LazyEvaluator<Long> inSpeedEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculateBandwidth(timewindow);});
public long getInSpeed() { public long getInSpeed() {
return downloadBandwidth.calculateBandwidth(timewindow); return inSpeedEvaluator.get();
} }
@@ -117,13 +120,15 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
private LazyEvaluator<Long> outPPSEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculatePacketRate(timewindow);});
public long getOutPPS() { public long getOutPPS() {
return uploadBandwidth.calculatePacketRate(timewindow); return outPPSEvaluator.get();
} }
private LazyEvaluator<Long> inPPSEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculatePacketRate(timewindow);});
public long getInPPS() { public long getInPPS() {
return downloadBandwidth.calculatePacketRate(timewindow); return inPPSEvaluator.get();
} }
private long inSpeed; private long inSpeed;
private long outSpeed; private long outSpeed;
@@ -159,9 +164,18 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
public void update() { public void update() {
upSampler.update(); upSampler.update();
downSampler.update(); downSampler.update();
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)upSampler.getPacketCountSinceLastSnapshot() ,(int)upSampler.getByteCountSinceLastSnapshot()); long uppacketcount=upSampler.getPacketCountSinceLastSnapshot();
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)downSampler.getPacketCountSinceLastSnapshot() ,(int) downSampler.getByteCountSinceLastSnapshot()); if(uppacketcount>0) {
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) uppacketcount, (int) upSampler.getByteCountSinceLastSnapshot());
outSpeedEvaluator.markDirty();
outPPSEvaluator.markDirty();
}
long downpacketcount=downSampler.getPacketCountSinceLastSnapshot();
if(downpacketcount>0) {
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) downpacketcount, (int) downSampler.getByteCountSinceLastSnapshot());
inSpeedEvaluator.markDirty();
inPPSEvaluator.markDirty();
}
long time=System.nanoTime(); long time=System.nanoTime();
if(time-stime>timewindowmax) { if(time-stime>timewindowmax) {
stime=time; stime=time;
+10 -10
View File
@@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.clock.NTPTimestamps; import org.kne.cloud.clock.NTPTimestamps;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer; import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
import org.kne.math.Long128; import org.kne.math.Long128;
@@ -110,13 +110,13 @@ public class NTPContext implements Closeable, AutoCloseable {
return clock; return clock;
} }
private ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>>(); private ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>>();
protected void clearPackets() { protected void clearPackets() {
recvmap.clear(); recvmap.clear();
} }
protected void putPacket(NTPv4Packet nv4, MultipurposeSocketAddress inetSocketAddress) { protected void putPacket(NTPv4Packet nv4, MultiProtocolSocketAddress inetSocketAddress) {
checkIP(); checkIP();
List<NTPv4Packet> newv = new Vector<NTPv4Packet>(); List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv); List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
@@ -133,10 +133,10 @@ public class NTPContext implements Closeable, AutoCloseable {
} }
private void checkIP() { private void checkIP() {
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet(); Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
.hasNext();) { .hasNext();) {
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
.next(); .next();
AtomicBoolean ab = new AtomicBoolean(false); AtomicBoolean ab = new AtomicBoolean(false);
ios.forEach((x) -> { ios.forEach((x) -> {
@@ -153,7 +153,7 @@ public class NTPContext implements Closeable, AutoCloseable {
} }
private class PeerInfo implements Comparable<PeerInfo> { private class PeerInfo implements Comparable<PeerInfo> {
private MultipurposeSocketAddress address; private MultiProtocolSocketAddress address;
private int leapIndicator = 3; private int leapIndicator = 3;
private int stratum = 16; private int stratum = 16;
private int referenceIdentifier; private int referenceIdentifier;
@@ -265,10 +265,10 @@ public class NTPContext implements Closeable, AutoCloseable {
private List<PeerInfo> mergeResponses() { private List<PeerInfo> mergeResponses() {
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>(); List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet(); Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
.hasNext();) { .hasNext();) {
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
.next(); .next();
List<NTPv4Packet> newv = entry.getValue(); List<NTPv4Packet> newv = entry.getValue();
@@ -0,0 +1,20 @@
package org.kne.cloud.network.ntp;
import org.kne.cloud.network.UDPSocketType;
public class NTPSocketType extends UDPSocketType {
private static final NTPSocketType INSTANCE = new NTPSocketType();
private NTPSocketType() {
super();
}
public static NTPSocketType getInstance() {
return INSTANCE;
}
@Override
public int getDefaultPort() {
return 123;
}
}
@@ -20,7 +20,8 @@ import java.util.concurrent.locks.ReentrantLock;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.clock.NTPTimestamps; import org.kne.cloud.clock.NTPTimestamps;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ThreadTool;
import org.kne.io.KNEChannels; import org.kne.io.KNEChannels;
import org.kne.math.Long128; import org.kne.math.Long128;
@@ -32,7 +33,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
private static final long EPHEMERAL_TIMEOUT = 60000000000L; private static final long EPHEMERAL_TIMEOUT = 60000000000L;
public static class NTPPeer { public static class NTPPeer {
private MultipurposeSocketAddress address; private MultiProtocolSocketAddress address;
private boolean isEphemeral;// ephemeral private boolean isEphemeral;// ephemeral
private volatile long ephemeralUpdateTime = System.nanoTime(); private volatile long ephemeralUpdateTime = System.nanoTime();
private volatile long pollInterval = DEFAULT_POLL_INTERVAL; private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
@@ -70,11 +71,11 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
this.pollInterval = pollInterval; this.pollInterval = pollInterval;
} }
public NTPPeer(MultipurposeSocketAddress address, int requestMode) { public NTPPeer(MultiProtocolSocketAddress address, int requestMode) {
this(address, requestMode, false); this(address, requestMode, false);
} }
protected NTPPeer(MultipurposeSocketAddress address, int requestMode, boolean isEphemeral) { protected NTPPeer(MultiProtocolSocketAddress address, int requestMode, boolean isEphemeral) {
super(); super();
checkRequestMode(requestMode); checkRequestMode(requestMode);
this.address = address; this.address = address;
@@ -90,10 +91,10 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
} }
public NTPPeer(String hostport, int requestMode) { public NTPPeer(String hostport, int requestMode) {
this(new MultipurposeSocketAddress(hostport), requestMode); this(new MultiProtocolSocketAddress(hostport), requestMode);
} }
public MultipurposeSocketAddress getAddress() { public MultiProtocolSocketAddress getAddress() {
return address; return address;
} }
@@ -153,17 +154,28 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
private NTPContext context; private NTPContext context;
private DatagramSocket dgs; private DatagramSocket dgs;
private MultipurposeSocketAddress bind; private MultiProtocolSocketAddress bind;
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException { public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
this(context, new MultipurposeSocketAddress("UDP", "::0", NTP_DEFAULT_PORT)); this(context, new MultiProtocolSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
} }
public NTPv4Protocol(NTPContext context, MultipurposeSocketAddress bind) throws UnknownHostException, IOException { public NTPv4Protocol(NTPContext context, MultiProtocolSocketAddress bind) throws UnknownHostException, IOException {
this.bind = bind; this.bind = bind;
this.context = context; this.context = context;
dgs = bind.listenDatagramSocket(); dgs = bind.listenDatagramSocket();
context.registerIO(this); context.registerIO(this);
Thread t4= ThreadTool.makeVThread("NTPv4 Packet Cleaner",()->{
while(isClosed()){
removeTimeoutTimestamp();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t4.start();
Thread tr = new Thread(recv); Thread tr = new Thread(recv);
tr.setName("NTPv4 Receive Thread"); tr.setName("NTPv4 Receive Thread");
tr.start(); tr.start();
@@ -219,7 +231,6 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) { private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
checkLock.lock(); checkLock.lock();
try { try {
removeTimeoutTimestamp();
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer)); checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
} finally { } finally {
checkLock.unlock(); checkLock.unlock();
@@ -227,13 +238,18 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
} }
private void removeTimeoutTimestamp() { private void removeTimeoutTimestamp() {
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext();) { checkLock.lock();
CheckListItem bigInteger = (CheckListItem) iterator.next(); try {
if (bigInteger.checkTimeout()) { for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext(); ) {
if (showPacket) CheckListItem bigInteger = (CheckListItem) iterator.next();
System.out.println("timeout:" + bigInteger.timeStamp); if (bigInteger.checkTimeout()) {
iterator.remove(); if (showPacket)
System.out.println("timeout:" + bigInteger.timeStamp);
iterator.remove();
}
} }
}finally{
checkLock.unlock();
} }
} }
@@ -266,7 +282,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
return null; return null;
} }
NTPPeer findPeer(MultipurposeSocketAddress addr) { NTPPeer findPeer(MultiProtocolSocketAddress addr) {
Object[] objs = peers.toArray(); Object[] objs = peers.toArray();
for (int i = 0; i < objs.length; i++) { for (int i = 0; i < objs.length; i++) {
NTPPeer np = (NTPPeer) objs[i]; NTPPeer np = (NTPPeer) objs[i];
@@ -291,7 +307,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
while (!dgs.isClosed()) { while (!dgs.isClosed()) {
AtomicReference<InetSocketAddress> isa = new AtomicReference<>(); AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
NTPv4Packet nv4 = receiveNTPPacket(isa); NTPv4Packet nv4 = receiveNTPPacket(isa);
MultipurposeSocketAddress mpsafrom = new MultipurposeSocketAddress(bind.getType(), isa.get()); MultiProtocolSocketAddress mpsafrom = new MultiProtocolSocketAddress(bind.getProtocol(), isa.get());
switch (nv4.getMode()) { switch (nv4.getMode()) {
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE: case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
NTPv4Packet nv4r = new NTPv4Packet(context.getClock()); NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
@@ -393,7 +409,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
HighAccuracyClock hac = new HighAccuracyClock(); HighAccuracyClock hac = new HighAccuracyClock();
NTPContext context = new NTPContext(hac); NTPContext context = new NTPContext(hac);
System.out.println(context); System.out.println(context);
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199 NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT)); nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT)); nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE)); nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
+2 -3
View File
@@ -4,8 +4,7 @@ import java.io.IOException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.kne.cloud.clock.HighAccuracyClock; import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
public class TestNTP1 { public class TestNTP1 {
public static void main(String[] args) throws UnknownHostException, IOException { public static void main(String[] args) throws UnknownHostException, IOException {
@@ -13,7 +12,7 @@ public class TestNTP1 {
NTPContext context = new NTPContext(hac); NTPContext context = new NTPContext(hac);
context.syncToSystem(); context.syncToSystem();
System.out.println(context); System.out.println(context);
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199 NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT)); //nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
} }
} }

Some files were not shown because too many files have changed in this diff Show More