29 Commits
Author SHA1 Message Date
SerinaNya dde54d0929 Revert "fix(config): sync Swing settings after Web updates"
This reverts commit b6acc6d509.
2026-08-31 13:24:21 +08:00
SerinaNya 3dae7dd876 feat(srv6)!: split node information queries
Replace the combined node-info protocol with independent profile,
external-endpoint, and extra-route requests. Aggregate consumers use
NodeInfoQueryCoordinator and validate response source addresses.

BREAKING CHANGE: nodeinfotinyreq/resp and nodeinfofullreq/resp are
removed. Peers must use the profile, external-endpoint, and extra-route
request pairs.
2026-08-31 01:13:49 +08:00
SerinaNya b6acc6d509 fix(config): sync Swing settings after Web updates
Add revisioned configuration events and detached commits so Web changes
refresh clean Swing forms without overwriting unsaved local edits. Preserve
configuration formatting and document the synchronized update workflow.
2026-08-29 00:24:20 +08:00
SerinaNya be82f55277 feat(srv6)!: add node-info update invalidation
Publish node metadata through Tiny and Full queries, synchronize Swing and
web topology details, and document the updated protocol workflow.

BREAKING CHANGE: RouterInfo no longer carries device names; peers must use
Tiny node-info queries.
2026-08-28 23:14:04 +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
102 changed files with 7355 additions and 2229 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>
```
+1 -1
View File
@@ -24,7 +24,7 @@
<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>
+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
+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
}
]
}
+39
View File
@@ -0,0 +1,39 @@
# KLALB Repository Guide
KLALB is a Java SRv6/load-balancing system. Start at `org.kne.cloud.network.klalb.KLALBMain`; runtime configuration is root-relative `klalb-config.json`.
## Build And Run
- This is an Eclipse Java project, not Maven or Gradle: `src/` contains sources, `lib/` vendored dependencies, and `bin/` compiled output. `.classpath` targets Java 25.
- Source files contain Chinese text, so compile with UTF-8 from the repository root:
```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)
```
- Run from the repository root. `src` must stay on the classpath because manual compilation does not copy resource bundles:
```powershell
& "C:\Program Files\Zulu\zulu-25\bin\java.exe" --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
```
- Native libraries and `klalb-config.json` are resolved from the current directory; restart the JVM after recompiling. TUN creation requires elevation, so use `"enableTUN": false` for non-admin checks.
## Dashboard
- `dashboard/` is a Git submodule. Commit dashboard changes in that repository, then update the parent submodule pointer.
- Run frontend commands from `dashboard/`: `pnpm install --frozen-lockfile`, `pnpm lint`, `pnpm typecheck`, and `pnpm build`. The build is `tsc -b && vite build` and produces `dashboard/dist`, which the Java server hosts.
- Vite proxies `/api` to `http://127.0.0.1:4665`; use `pnpm dev` only with the Java API running there.
## Verification
- No repository test runner or CI workflow is configured. For Java changes, compile and launch the app; for dashboard changes, run `pnpm typecheck` and `pnpm build`.
## Important Boundaries
- `KLALBConfigItem` is a polymorphic JSON array keyed by case-sensitive `Type`. New types need serializer and deserializer support; preserve unknown items' raw JSON.
- `/api/config` is field-by-field parsing, not whole-object Gson mapping. Keep legacy key aliases in sync with new fields.
- Vendored Gson is `2.1`: responses that are `JsonElement` instances must be serialized with `JsonElement.toString()`, not reflective `gson.toJson(Object)`.
- UI strings use `UIEnv.getRsb()`; add keys to both `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
- `KLALBController.PublishedNodeInfo` is the thread-safe node-info source. Publish profile, effective external endpoints, and extra routes through the controller so snapshots and update flags remain coherent. Peer queries are separate profile, endpoint, and extra-route requests; use `NodeInfoQueryCoordinator` when a consumer needs an aggregate detail result.
- `RouterInfo` retains an empty legacy UTF slot. `RouterInfoPacket` appends optional update flags behind a marker; treat binary codec changes as mesh-compatibility work and preserve old-reader behavior.
Submodule
+1
Submodule dashboard added at 8c13af680a
-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
-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
+11
View File
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"shadcn": {
"source": "shadcn/ui",
"sourceType": "github",
"skillPath": "skills/shadcn/SKILL.md",
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
}
}
}
+13
View File
@@ -3,7 +3,11 @@ 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
noextraroutes=No extra routes
asnumber=AS number asnumber=AS number
tcplistening=TCP listening tcplistening=TCP listening
udplistening=UDP listening udplistening=UDP listening
@@ -34,6 +38,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 +110,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
+13
View File
@@ -3,7 +3,11 @@ networkgraph=网络拓扑图
language=语言 language=语言
basicsettings=基本设置 basicsettings=基本设置
ipv6addr=IPv6地址 ipv6addr=IPv6地址
devicename=设备名称
devicedescription=设备描述
dnsserver=DNS服务器 dnsserver=DNS服务器
extraroutes=额外路由
noextraroutes=暂无额外路由
asnumber=AS号码 asnumber=AS号码
tcplistening=TCP监听端口 tcplistening=TCP监听端口
udplistening=UDP监听端口 udplistening=UDP监听端口
@@ -34,6 +38,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 +110,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
@@ -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(){
@@ -11,11 +11,13 @@ import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashSet; import java.util.HashSet;
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.Objects;
import java.util.Set; import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
@@ -33,7 +35,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;
@@ -54,6 +56,7 @@ import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIServer;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.SRv6Router; import org.kne.cloud.network.srv6.SRv6Router;
import org.kne.cloud.network.srv6.SRv6RouterListener; import org.kne.cloud.network.srv6.SRv6RouterListener;
import org.kne.cloud.network.tcp.UDPPacket; import org.kne.cloud.network.tcp.UDPPacket;
@@ -67,7 +70,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 +79,56 @@ 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> configuredExternalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> discoveredExternalEndpoints = new ArrayList<>();
public static final class PublishedNodeInfo {
private final String deviceName;
private final String deviceDescription;
private final List<MultiProtocolSocketAddress> externalEndpoints;
private final List<String> extraRoutes;
private final long fullRevision;
private PublishedNodeInfo(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> externalEndpoints, List<String> extraRoutes, long fullRevision) {
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
this.externalEndpoints = Collections.unmodifiableList(
new ArrayList<MultiProtocolSocketAddress>(externalEndpoints));
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(extraRoutes));
this.fullRevision = fullRevision;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public long getFullRevision() {
return fullRevision;
}
}
private volatile PublishedNodeInfo publishedNodeInfo = new PublishedNodeInfo(null, null,
Collections.<MultiProtocolSocketAddress>emptyList(), Collections.<String>emptyList(), 0L);
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 +137,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,56 +164,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();
List<MultiProtocolSocketAddress> currentDiscoveredExternalEndpoints = new ArrayList<>();
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) { if (!currentDiscoveredExternalEndpoints.contains(bind)) {
if (!selflineTable.contains(bind)) { currentDiscoveredExternalEndpoints.add(bind);
selflineTable.add(bind);
}
} }
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
@@ -170,51 +195,56 @@ public class KLALBController {
} }
} }
synchronized (externalEndpoints) {
if (!discoveredExternalEndpoints.equals(currentDiscoveredExternalEndpoints)) {
discoveredExternalEndpoints = currentDiscoveredExternalEndpoints;
publishDiscoveredExternalEndpointLocked();
}
}
lineslock.writeLock().lock(); lineslock.writeLock().lock();
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 +258,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); || publishedNodeInfo.getExternalEndpoints().contains(inetAddress);
} }
private boolean checkIsSelfLocator(InetAddress inetAddress) { private boolean checkIsSelfLocator(InetAddress inetAddress) {
@@ -345,16 +362,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 +382,32 @@ 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.requestExternalEndpoints(iaddr, (v) -> {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(), if (v == null || v.getStatus() != NodeInfoQueryStatus.OK) return;
KLALBRoutingProtocol.DEFAULT_PORT); ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
apiClient.requestOpenLines(iaddr, (v) -> { if (v.getExternalEndpoints() == null) return;
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> { for (MultiProtocolSocketAddress msa : v.getExternalEndpoints()) {
for (MultipurposeSocketAddress msa : v) { // System.out.print(msa);
// System.out.print(msa); addRemoteLines(msa);
addRemoteLines(msa); }
} }).start();
}).start(); });
}); } catch (IOException e) {
} catch (IOException e) { e.printStackTrace();
e.printStackTrace();
}
} }
} }
} }
}; }
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() { public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
return linkMonitor; return linkMonitor;
@@ -403,6 +419,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 +454,103 @@ public class KLALBController {
} }
public List<MultipurposeSocketAddress> getSelflineTable() { public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return selflineTable; return publishedNodeInfo.getExternalEndpoints();
}
public List<MultiProtocolSocketAddress> getExternalEndpointsSnapshot() {
return new ArrayList<MultiProtocolSocketAddress>(publishedNodeInfo.getExternalEndpoints());
}
public PublishedNodeInfo getPublishedNodeInfo() {
return publishedNodeInfo;
}
private List<MultiProtocolSocketAddress> createEffectiveExternalEndpointsLocked() {
List<MultiProtocolSocketAddress> effective = new ArrayList<MultiProtocolSocketAddress>();
for (MultiProtocolSocketAddress endpoint : configuredExternalEndpoints) {
if (!effective.contains(endpoint)) {
effective.add(endpoint);
}
}
for (MultiProtocolSocketAddress endpoint : discoveredExternalEndpoints) {
if (!effective.contains(endpoint)) {
effective.add(endpoint);
}
}
return effective;
}
private void publishNodeInfoLocked(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
configuredExternalEndpoints = configEndpoints == null
? new ArrayList<MultiProtocolSocketAddress>()
: new ArrayList<MultiProtocolSocketAddress>(configEndpoints);
List<String> publishedExtraRoutes = extraRoutes == null
? new ArrayList<String>() : new ArrayList<String>(extraRoutes);
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
PublishedNodeInfo previous = publishedNodeInfo;
boolean nameChanged = !Objects.equals(previous.getDeviceName(), deviceName);
boolean fullChanged = !Objects.equals(previous.getDeviceDescription(), deviceDescription)
|| !previous.getExternalEndpoints().equals(effective)
|| !previous.getExtraRoutes().equals(publishedExtraRoutes);
externalEndpoints.clear();
externalEndpoints.addAll(effective);
if (srv6Router != null) {
srv6Router.setDeviceName(deviceName);
}
long fullRevision = previous.getFullRevision() + (fullChanged ? 1L : 0L);
publishedNodeInfo = new PublishedNodeInfo(deviceName, deviceDescription, effective, publishedExtraRoutes,
fullRevision);
if (routingProtocol != null) {
if (nameChanged) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED);
}
if (fullChanged) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
}
}
}
public void publishNodeInfo(String deviceName, String deviceDescription,
List<MultiProtocolSocketAddress> configEndpoints, List<String> extraRoutes) {
synchronized (externalEndpoints) {
if (configItem != null) {
configItem.setDeviceName(deviceName);
configItem.setDeviceDescription(deviceDescription);
configItem.setExternalEndpoints(new ArrayList<MultiProtocolSocketAddress>(configEndpoints == null
? Collections.<MultiProtocolSocketAddress>emptyList() : configEndpoints));
configItem.setExtraRoutes(new ArrayList<String>(extraRoutes == null
? Collections.<String>emptyList() : extraRoutes));
}
publishNodeInfoLocked(deviceName, deviceDescription, configEndpoints, extraRoutes);
}
}
private void publishDiscoveredExternalEndpointLocked() {
List<MultiProtocolSocketAddress> effective = createEffectiveExternalEndpointsLocked();
if (new HashSet<MultiProtocolSocketAddress>(effective)
.equals(new HashSet<MultiProtocolSocketAddress>(externalEndpoints))) {
return;
}
externalEndpoints.clear();
externalEndpoints.addAll(effective);
PublishedNodeInfo previous = publishedNodeInfo;
publishedNodeInfo = new PublishedNodeInfo(previous.getDeviceName(), previous.getDeviceDescription(), effective,
previous.getExtraRoutes(), previous.getFullRevision() + 1L);
if (routingProtocol != null) {
routingProtocol.announceNodeInfoUpdate(
org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
}
}
public void replaceExternalEndpoints(List<MultiProtocolSocketAddress> endpoints) {
PublishedNodeInfo published = publishedNodeInfo;
publishNodeInfo(published.getDeviceName(), published.getDeviceDescription(), endpoints,
published.getExtraRoutes());
} }
@@ -472,13 +585,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 +605,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 +666,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 +682,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 +706,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 +722,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 +741,18 @@ 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 (MultiProtocolSocketAddress klalbRemoteLine : publishedNodeInfo.getExternalEndpoints()) {
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next();
sbd.append(klalbRemoteLine.toString()); sbd.append(klalbRemoteLine.toString());
sbd.append('\n'); sbd.append('\n');
} }
@@ -668,7 +780,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 +794,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 +814,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 +834,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 +845,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 +866,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 +883,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() {
@@ -794,6 +912,9 @@ public class KLALBController {
} }
}).start(); }).start();
loadSRv6ProtocolStack(selfg, enableVirtualAdapter); loadSRv6ProtocolStack(selfg, enableVirtualAdapter);
if (configItem == null) {
publishNodeInfo(srv6Router.getDeviceName(), null, null, null);
}
loadController(); loadController();
} }
@@ -830,18 +951,17 @@ public class KLALBController {
getIpv6Router().setASN(vasn); getIpv6Router().setASN(vasn);
} }
List<MultipurposeSocketAddress> linele = configItem.getLineTable(); List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
if (linele != null) { publishNodeInfo(configItem.getDeviceName(), configItem.getDeviceDescription(), linele,
getSelflineTable().addAll(linele); configItem.getExtraRoutes());
} List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable();
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 +1124,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;
}
} }
+63 -9
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;
@@ -50,13 +52,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 +86,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 +105,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 +157,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 +177,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 +203,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 +228,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 {
@@ -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());
} }
} }
@@ -128,9 +169,9 @@ 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());
//} //}
MultipurposeSocketAddress tcple=kcci.getTCPListen(); MultiProtocolSocketAddress tcple=kcci.getTCPListen();
if(tcple!=null) { if(tcple!=null) {
SocketChannelListener tcpl = null; SocketChannelListener tcpl = null;
@@ -148,13 +189,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 +212,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 +232,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, "Direct", 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
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); "KLALB SRv6", 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
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,211 @@
package org.kne.cloud.network.klalb;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.srv6.ExternalEndpointsResult;
import org.kne.cloud.network.srv6.ExtraRoutesResult;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.NodeProfile;
/** Coordinates the three independent sections of a remote node-info query. */
public final class NodeInfoQueryCoordinator {
private static final long DEADLINE_MILLIS = 3000L;
private static final ExecutorService REQUEST_EXECUTOR = Executors.newVirtualThreadPerTaskExecutor();
private static final ScheduledExecutorService DEADLINE_EXECUTOR = Executors
.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r, "KLALB-node-info-deadline");
thread.setDaemon(true);
return thread;
});
private final KLALBRoutingProtocolAPIClient client;
public NodeInfoQueryCoordinator(KLALBRoutingProtocolAPIClient client) {
this.client = Objects.requireNonNull(client, "client");
}
/** Starts all section requests and completes at the first all-sections response or the shared deadline. */
public CompletableFuture<Result> query(SocketAddress address) {
Objects.requireNonNull(address, "address");
Pending pending = new Pending();
pending.result.whenComplete((ignored, error) -> {
if (pending.result.isCancelled()) pending.cancel();
});
pending.deadline = DEADLINE_EXECUTOR.schedule(pending::timeout, DEADLINE_MILLIS, TimeUnit.MILLISECONDS);
issue(pending, () -> client.requestNodeProfile(address, pending::profile), pending::profileFailed);
issue(pending, () -> client.requestExternalEndpoints(address, pending::externalEndpoints),
pending::externalEndpointsFailed);
issue(pending, () -> client.requestExtraRoutes(address, pending::extraRoutes), pending::extraRoutesFailed);
return pending.result;
}
private void issue(Pending pending, ThrowingRequest request, Runnable failure) {
REQUEST_EXECUTOR.execute(() -> pending.issue(request, failure));
}
@FunctionalInterface
private interface ThrowingRequest {
void run() throws Exception;
}
public static final class Section<T> {
private final boolean received;
private final Optional<T> value;
private final Optional<NodeInfoQueryStatus> status;
private Section(boolean received, T value, NodeInfoQueryStatus status) {
this.received = received;
this.value = Optional.ofNullable(value);
this.status = Optional.ofNullable(status);
}
public boolean isReceived() {
return received;
}
public Optional<T> getValue() {
return value;
}
public Optional<NodeInfoQueryStatus> getStatus() {
return status;
}
private static <T> Section<T> missing() {
return new Section<T>(false, null, null);
}
private static <T> Section<T> received(T value, NodeInfoQueryStatus status) {
return new Section<T>(true, value, status);
}
}
public static final class Result {
private final Section<NodeProfile> profile;
private final Section<List<MultiProtocolSocketAddress>> externalEndpoints;
private final Section<List<String>> extraRoutes;
private Result(Section<NodeProfile> profile,
Section<List<MultiProtocolSocketAddress>> externalEndpoints,
Section<List<String>> extraRoutes) {
this.profile = profile;
this.externalEndpoints = externalEndpoints;
this.extraRoutes = extraRoutes;
}
public Section<NodeProfile> getProfile() {
return profile;
}
public Section<List<MultiProtocolSocketAddress>> getExternalEndpoints() {
return externalEndpoints;
}
public Section<List<String>> getExtraRoutes() {
return extraRoutes;
}
}
private final class Pending {
private final CompletableFuture<Result> result = new CompletableFuture<>();
private final AtomicBoolean finished = new AtomicBoolean();
private int remaining = 3;
private Section<NodeProfile> profile = Section.missing();
private Section<List<MultiProtocolSocketAddress>> externalEndpoints = Section.missing();
private Section<List<String>> extraRoutes = Section.missing();
private java.util.concurrent.ScheduledFuture<?> deadline;
private synchronized void profile(NodeProfile value) {
if (profile.isReceived()) return;
profile = Section.received(value, NodeInfoQueryStatus.OK);
completeSection();
}
private synchronized void profileFailed() {
if (profile.isReceived()) return;
profile = Section.missing();
completeSection();
}
private synchronized void externalEndpoints(ExternalEndpointsResult value) {
if (externalEndpoints.isReceived()) return;
if (value == null) {
externalEndpoints = Section.missing();
} else {
List<MultiProtocolSocketAddress> endpoints = value.getExternalEndpoints();
externalEndpoints = Section.received(endpoints == null
? Collections.<MultiProtocolSocketAddress>emptyList()
: Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(endpoints)),
value.getStatus());
}
completeSection();
}
private synchronized void externalEndpointsFailed() {
if (externalEndpoints.isReceived()) return;
externalEndpoints = Section.missing();
completeSection();
}
private synchronized void extraRoutes(ExtraRoutesResult value) {
if (extraRoutes.isReceived()) return;
if (value == null) {
extraRoutes = Section.missing();
} else {
List<String> routes = value.getExtraRoutes();
extraRoutes = Section.received(routes == null
? Collections.<String>emptyList()
: Collections.unmodifiableList(new ArrayList<String>(routes)), value.getStatus());
}
completeSection();
}
private synchronized void extraRoutesFailed() {
if (extraRoutes.isReceived()) return;
extraRoutes = Section.missing();
completeSection();
}
private synchronized void issue(ThrowingRequest request, Runnable failure) {
if (finished.get() || result.isDone() || result.isCancelled()) return;
try {
request.run();
} catch (Exception e) {
failure.run();
}
}
private synchronized void cancel() {
finish();
}
private void completeSection() {
remaining--;
if (remaining == 0) finish();
}
private synchronized void timeout() {
finish();
}
private void finish() {
if (!finished.compareAndSet(false, true)) return;
if (deadline != null) deadline.cancel(false);
result.complete(new Result(profile, externalEndpoints, extraRoutes));
}
}
}
@@ -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() {
@@ -7,6 +7,7 @@ import java.io.IOException;
import java.net.*; import java.net.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -20,7 +21,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 +76,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 +99,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 +146,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 +606,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 +794,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 +915,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 +940,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,18 +1058,45 @@ 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) {
if (item instanceof KLALBControllerConfigItem) { if (item instanceof KLALBControllerConfigItem) {
KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item; KLALBControllerConfigItem kck = (KLALBControllerConfigItem) item;
String oldDeviceName = kck.getDeviceName();
String oldDeviceDescription = kck.getDeviceDescription();
List<MultiProtocolSocketAddress> oldExternalEndpoints = kck.getExternalEndpoints() == null
? null
: new ArrayList<MultiProtocolSocketAddress>(kck.getExternalEndpoints());
List<String> oldExtraRoutes = kck.getExtraRoutes() == null
? null : new ArrayList<String>(kck.getExtraRoutes());
String newDeviceName;
String newDeviceDescription;
List<String> newExtraRoutes;
List<MultiProtocolSocketAddress> newExternalEndpoints;
// 保存语言设置 // 保存语言设置
kck.setLanguage(((Language) comboLang.getSelectedItem()).name()); kck.setLanguage(((Language) comboLang.getSelectedItem()).name());
kck.setNogui(nogui.isSelected()); kck.setNogui(nogui.isSelected());
// 保存设备名称
String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) {
newDeviceName = null;
} else {
newDeviceName = dnametext;
}
// 保存设备描述
String ddesctext = deviceDescriptionSet.getText().trim();
if (ddesctext.equals("")) {
newDeviceDescription = null;
} else {
newDeviceDescription = ddesctext;
}
// 保存IPv6地址 // 保存IPv6地址
String ttext = addressFieldSet.getText().trim(); String ttext = addressFieldSet.getText().trim();
if (ttext.equals("")) { if (ttext.equals("")) {
@@ -1052,6 +1133,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;
}
}
}
newExtraRoutes = eroutes;
// 保存ASN // 保存ASN
String asntext = asnFieldSet.getText(); String asntext = asnFieldSet.getText();
if (asntext.equals("")) { if (asntext.equals("")) {
@@ -1067,20 +1166,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 +1212,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 +1223,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 +1240,17 @@ public class KLALBStateGUI3 extends XFrame {
} }
} }
} }
kck.setLineTable(iaddr1); newExternalEndpoints = 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 +1265,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 +1293,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());
@@ -1191,6 +1313,22 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L); kck.setNagleDelayTime(nagleDelayTime.getSlider().getValue()*100000L);
kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L); kck.setLinkNagleDelayTime(linkNagleDelayTime.getSlider().getValue()*100000L);
boolean deviceNameChanged = !Objects.equals(oldDeviceName, newDeviceName);
boolean deviceDescriptionChanged = !Objects.equals(oldDeviceDescription, newDeviceDescription);
boolean externalEndpointsChanged = !Objects.equals(oldExternalEndpoints, newExternalEndpoints);
boolean extraRoutesChanged = !Objects.equals(oldExtraRoutes, newExtraRoutes);
if (deviceNameChanged || deviceDescriptionChanged || externalEndpointsChanged || extraRoutesChanged) {
if (kcontroller != null) {
kcontroller.publishNodeInfo(newDeviceName, newDeviceDescription, newExternalEndpoints,
newExtraRoutes);
} else {
kck.setDeviceName(newDeviceName);
kck.setDeviceDescription(newDeviceDescription);
kck.setExternalEndpoints(newExternalEndpoints);
kck.setExtraRoutes(newExtraRoutes);
}
}
} }
} }
@@ -1213,58 +1351,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);
@@ -1290,17 +1430,19 @@ public class KLALBStateGUI3 extends XFrame {
tsk5 = new TimerTask() { tsk5 = new TimerTask() {
@Override @Override
public void run() { public void run() {
if (isVisible()) { SwingUtilities.invokeLater(() -> {
graph.loadNodes(); if (isVisible()) {
for (int i = 0; i < 100; i++) { graph.loadNodes();
graph.runPhy(); for (int i = 0; i < 100; i++) {
graph.runPhy();
}
graph.repaint();
graph.revalidate();
} }
graph.repaint(); });
graph.revalidate();
}
} }
}; };
t.scheduleAtFixedRate(tsk5, 1000, 1000); t.scheduleAtFixedRate(tsk5, 0, 1000);
} }
/** /**
@@ -1440,6 +1582,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 +1598,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 +1657,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) {
@@ -1613,6 +1788,15 @@ public class KLALBStateGUI3 extends XFrame {
* 关闭窗口并清理资源 * 关闭窗口并清理资源
*/ */
public void close() { public void close() {
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
Component component = tabbedPane.getComponentAt(i);
if (component instanceof NodeInformationPanel) {
((NodeInformationPanel) component).close();
}
}
if (graph != null) {
graph.close();
}
setVisible(false); setVisible(false);
if (tsk != null) { if (tsk != null) {
tsk.cancel(); tsk.cancel();
@@ -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,58 +1,65 @@
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.io.IOException;
import java.awt.geom.GeneralPath; import java.net.InetSocketAddress;
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.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 java.util.function.BiConsumer;
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 javax.swing.SwingUtilities;
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;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection; import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
import org.kne.cloud.network.srv6.NodeProfile;
import org.kne.cloud.network.srv6.RouterInfoPacket;
public class NetworkGraphPanel extends GraphPanel { public class NetworkGraphPanel extends GraphPanel {
private KLALBController controller; private KLALBController controller;
private KLALBStateGUI3 klbgui; private KLALBStateGUI3 klbgui;
private final KLALBRoutingProtocolAPIClient nodeInfoClient;
private final KLALBRoutingProtocol nodeInfoRoutingProtocol;
private final BiConsumer<IPv6Address, Integer> nodeInfoUpdateListener;
private final Object nodeInfoLock = new Object();
private final Map<IPv6Address, String> nodeNameCache = new HashMap<IPv6Address, String>();
private final Map<IPv6Address, Long> nodeNameCacheTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestTimes = new HashMap<IPv6Address, Long>();
private final Map<IPv6Address, Long> nodeInfoRequestGenerations = new HashMap<IPv6Address, Long>();
private static final long NODE_INFO_REQUEST_TIMEOUT = 3000L;
private static final long NODE_INFO_CACHE_TTL = 60000L;
private long nextNodeInfoRequestGeneration;
private volatile boolean nodeInfoClosed;
public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) { public NetworkGraphPanel(KLALBController controller,KLALBStateGUI3 klbgui) {
super(); super();
this.controller = controller; this.controller = controller;
this.klbgui=klbgui; this.klbgui=klbgui;
this.nodeInfoRoutingProtocol = controller.getIpv6Router().getKlalbRouteProtol();
this.nodeInfoClient = new KLALBRoutingProtocolAPIClient(nodeInfoRoutingProtocol);
this.nodeInfoUpdateListener = (address, flags) -> {
if ((flags & RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED) != 0) {
SwingUtilities.invokeLater(() -> invalidateTinyNodeInfo(address));
}
};
nodeInfoRoutingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
cacheLocalNodeName();
} }
public NetworkGraphPanel(KLALBController kc) { public NetworkGraphPanel(KLALBController kc) {
@@ -70,7 +77,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 +112,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 +167,162 @@ 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; /**
* 节点标签显示 Tiny API 查询的设备名称若有+IP地址
*/
private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder();
String dname;
synchronized (nodeInfoLock) {
dname = nodeNameCache.get(address);
}
if(dname!=null&&!dname.isEmpty())
sb.append(dname).append('\n');
sb.append(InetGraphNode.getText(address));
return sb.toString();
}
private void cacheLocalNodeName() {
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
synchronized (nodeInfoLock) {
nodeNameCache.put(localAddress, controller.getIpv6Router().getDeviceName());
nodeNameCacheTimes.put(localAddress, System.currentTimeMillis());
}
}
private void requestNodeProfile(final IPv6Address address) {
final long requestTime = System.currentTimeMillis();
final long requestGeneration;
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
Long cachedAt = nodeNameCacheTimes.get(address);
if (nodeNameCache.containsKey(address) && cachedAt != null
&& requestTime - cachedAt < NODE_INFO_CACHE_TTL)
return;
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
Long previousRequestTime = nodeInfoRequestTimes.get(address);
if (previousRequestTime != null && requestTime - previousRequestTime < NODE_INFO_REQUEST_TIMEOUT)
return;
nodeInfoRequestTimes.put(address, requestTime);
requestGeneration = ++nextNodeInfoRequestGeneration;
nodeInfoRequestGenerations.put(address, requestGeneration);
}
try {
nodeInfoClient.requestNodeProfile(
new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT),
(profile) -> SwingUtilities.invokeLater(() -> handleNodeProfile(address, requestGeneration, profile)));
} catch (IOException e) {
synchronized (nodeInfoLock) {
if (nodeInfoRequestGenerations.get(address) != null
&& nodeInfoRequestGenerations.get(address).longValue() == requestGeneration) {
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
}
}
}
private void handleNodeProfile(IPv6Address address, long requestGeneration, NodeProfile profile) {
if (nodeInfoClosed)
return;
boolean currentRequest;
boolean nodeExists = getNodes().containsKey(address);
String deviceName = profile == null ? "" : profile.getDeviceName();
if (deviceName == null || deviceName.isEmpty())
deviceName = "";
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
Long activeRequestGeneration = nodeInfoRequestGenerations.get(address);
currentRequest = activeRequestGeneration != null
&& activeRequestGeneration.longValue() == requestGeneration;
if (currentRequest) {
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
if (nodeExists)
nodeNameCache.put(address, deviceName);
else
nodeNameCache.remove(address);
if (nodeExists)
nodeNameCacheTimes.put(address, System.currentTimeMillis());
else
nodeNameCacheTimes.remove(address);
}
}
if (currentRequest && nodeExists) {
((InetGraphNode) getNodes().get(address)).updateLabel();
repaint();
}
}
private void removeNodeInfoState(IPv6Address address) {
synchronized (nodeInfoLock) {
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
}
private void invalidateTinyNodeInfo(IPv6Address address) {
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
nodeNameCache.remove(address);
nodeNameCacheTimes.remove(address);
nodeInfoRequestTimes.remove(address);
nodeInfoRequestGenerations.remove(address);
}
InetGraphNode node = (InetGraphNode) getNodes().get(address);
if (node != null) {
node.updateLabel();
}
repaint();
requestNodeProfile(address);
}
public void close() {
synchronized (nodeInfoLock) {
if (nodeInfoClosed)
return;
nodeInfoClosed = true;
nodeNameCache.clear();
nodeNameCacheTimes.clear();
nodeInfoRequestTimes.clear();
nodeInfoRequestGenerations.clear();
}
nodeInfoRoutingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
nodeInfoClient.close();
}
@Override
public void removeNotify() {
super.removeNotify();
SwingUtilities.invokeLater(() -> {
if (!isDisplayable() && getParent() == null) {
close();
}
});
}
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);
@@ -188,6 +339,8 @@ public class NetworkGraphPanel extends GraphPanel {
} }
protected void loadNodes() { protected void loadNodes() {
Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses(); Map<IPv6Address, Long> addr= controller.getIpv6Router().getKlalbRouteProtol().getAddresses();
IPv6Address localAddress = controller.getIpv6Router().getLocator().getAddress();
cacheLocalNodeName();
Set<IPv6Address> ks=addr.keySet(); Set<IPv6Address> ks=addr.keySet();
for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) { for (Iterator<IPv6Address> iterator = ks.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next(); IPv6Address inet6Address = (IPv6Address) iterator.next();
@@ -195,12 +348,17 @@ public class NetworkGraphPanel extends GraphPanel {
Vector2 v2pos=super.getRandomPos(); Vector2 v2pos=super.getRandomPos();
getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress()))); getNodes().put(inet6Address,new InetGraphNode(inet6Address,Color.BLACK,v2pos.x,v2pos.y,inet6Address.equals(controller.getIpv6Router().getLocator().getAddress())));
} }
if(!inet6Address.equals(localAddress))
requestNodeProfile(inet6Address);
} }
Set<IPv6Address> kns=getNodes().keySet(); Set<IPv6Address> kns=getNodes().keySet();
for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) { for (Iterator<IPv6Address> iterator = kns.iterator(); iterator.hasNext();) {
IPv6Address inet6Address = (IPv6Address) iterator.next(); IPv6Address inet6Address = (IPv6Address) iterator.next();
if(!addr.containsKey(inet6Address)) { if(!addr.containsKey(inet6Address)) {
iterator.remove(); iterator.remove();
removeNodeInfoState(inet6Address);
}else {
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
} }
} }
//System.out.println("------------------------------------"); //System.out.println("------------------------------------");
@@ -4,14 +4,13 @@ import java.awt.BorderLayout;
import java.awt.Image; 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.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collections;
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,28 +21,36 @@ 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.klalb.NodeInfoQueryCoordinator;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol; import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient; import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import org.kne.cloud.network.srv6.NodeInfoQueryStatus;
import org.kne.cloud.network.srv6.NodeProfile;
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;
import java.awt.event.MouseListener; import java.awt.event.MouseListener;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.concurrent.atomic.AtomicLong;
public class NodeInformationPanel extends JPanel { public class NodeInformationPanel extends JPanel {
private IPv6Address address; private IPv6Address address;
private KLALBRoutingProtocolAPIClient client; private KLALBRoutingProtocolAPIClient client;
private NodeInfoQueryCoordinator queryCoordinator;
private KLALBController controller; private KLALBController controller;
private Image image; private Image image;
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>(); private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
private XDefaultListModel<String> extraRoutesModel=new XDefaultListModel<>();
private volatile boolean active=true;
private final AtomicLong fullInfoGeneration=new AtomicLong();
private final java.util.function.BiConsumer<IPv6Address,Integer> nodeInfoUpdateListener;
public KLALBController getController() { public KLALBController getController() {
return controller; return controller;
} }
@@ -69,11 +76,32 @@ 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));
overviewArea.setText(buildOverviewText(null, null));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
JPanel extraRoutesPanel = new JPanel(new BorderLayout());
JList<String> extraRoutesList = new JList<String>(extraRoutesModel);
extraRoutesList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
extraRoutesList.setFont(UIEnv.getFont().deriveFont(14.0f));
extraRoutesPanel.add(new JScrollPane(extraRoutesList), BorderLayout.CENTER);
javax.swing.JLabel extraRoutesEmptyLabel=new javax.swing.JLabel(UIEnv.getRsb().getString("noextraroutes"));
extraRoutesEmptyLabel.setText("...");
extraRoutesEmptyLabel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
extraRoutesEmptyLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
extraRoutesPanel.add(extraRoutesEmptyLabel, BorderLayout.SOUTH);
tabbedPane.addTab(UIEnv.getRsb().getString("extraroutes"), null, extraRoutesPanel, null);
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 +109,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 +121,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 +131,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);
@@ -148,17 +176,121 @@ public class NodeInformationPanel extends JPanel {
panel_1.add(btnNewButton, BorderLayout.SOUTH); panel_1.add(btnNewButton, BorderLayout.SOUTH);
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol()); KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
try { client=new KLALBRoutingProtocolAPIClient(routingProtocol);
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{ queryCoordinator=new NodeInfoQueryCoordinator(client);
listModel.clear(); nodeInfoUpdateListener=(updatedAddress, flags) -> {
for (MultipurposeSocketAddress multipurposeSocketAddress : result) { if(active && address.equals(updatedAddress)
listModel.addElement(multipurposeSocketAddress); && (flags & org.kne.cloud.network.srv6.RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED) != 0) {
} requestFullInfo(overviewArea, extraRoutesEmptyLabel);
}); }
} catch (IOException e) { };
e.printStackTrace(); routingProtocol.addNodeInfoUpdateListener(nodeInfoUpdateListener);
requestFullInfo(overviewArea, extraRoutesEmptyLabel);
}
private void requestFullInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel) {
final long generation=fullInfoGeneration.incrementAndGet();
if (controller.getIpv6Router().getLocator().getAddress().equals(address)) {
KLALBController.PublishedNodeInfo published=controller.getPublishedNodeInfo();
applyNodeInfo(overviewArea, extraRoutesEmptyLabel, published.getDeviceName(),
published.getDeviceDescription(), published.getExternalEndpoints(), published.getExtraRoutes());
return;
} }
try {
queryCoordinator.query(new InetSocketAddress(address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT)).whenComplete((info, error)->{
if(!active || generation!=fullInfoGeneration.get()) return;
javax.swing.SwingUtilities.invokeLater(() -> {
if(!active || generation!=fullInfoGeneration.get()) return;
if (error != null || info == null) {
return;
}
applyRemoteNodeInfo(overviewArea, extraRoutesEmptyLabel, info);
});
});
} catch (RuntimeException e) {
if(active) e.printStackTrace();
}
}
private void applyNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
String deviceName, String deviceDescription, List<MultiProtocolSocketAddress> endpoints,
List<String> routes) {
applyProfile(overviewArea, deviceName, deviceDescription);
applyEndpoints(endpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : endpoints);
applyRoutes(extraRoutesEmptyLabel, routes == null ? Collections.<String>emptyList() : routes);
}
private void applyRemoteNodeInfo(JTextArea overviewArea, javax.swing.JLabel extraRoutesEmptyLabel,
NodeInfoQueryCoordinator.Result info) {
if (info.getProfile().isReceived()) {
NodeProfile profile=info.getProfile().getValue().orElse(null);
applyProfile(overviewArea, profile == null ? null : profile.getDeviceName(),
profile == null ? null : profile.getDeviceDescription());
}
if (info.getExternalEndpoints().isReceived()) {
List<MultiProtocolSocketAddress> endpoints=info.getExternalEndpoints().getStatus()
.filter(NodeInfoQueryStatus.OK::equals).isPresent()
? info.getExternalEndpoints().getValue().orElse(Collections.<MultiProtocolSocketAddress>emptyList())
: Collections.<MultiProtocolSocketAddress>emptyList();
applyEndpoints(endpoints);
}
if (info.getExtraRoutes().isReceived()) {
List<String> routes=info.getExtraRoutes().getStatus().filter(NodeInfoQueryStatus.OK::equals).isPresent()
? info.getExtraRoutes().getValue().orElse(Collections.<String>emptyList())
: Collections.<String>emptyList();
applyRoutes(extraRoutesEmptyLabel, routes);
}
}
private void applyProfile(JTextArea overviewArea, String deviceName, String deviceDescription) {
overviewArea.setText(buildOverviewText(deviceName,
deviceDescription == null || deviceDescription.isEmpty() ? null : deviceDescription));
}
private void applyEndpoints(List<MultiProtocolSocketAddress> endpoints) {
listModel.clear();
if (endpoints != null) for (MultiProtocolSocketAddress item : endpoints) listModel.addElement(item);
}
private void applyRoutes(javax.swing.JLabel extraRoutesEmptyLabel, List<String> routes) {
extraRoutesModel.clear();
if (routes != null) for (String route : routes) extraRoutesModel.addElement(route);
extraRoutesEmptyLabel.setText(UIEnv.getRsb().getString("noextraroutes"));
extraRoutesEmptyLabel.setVisible(extraRoutesModel.isEmpty());
}
public synchronized void close() {
if (!active)
return;
active=false;
fullInfoGeneration.incrementAndGet();
KLALBRoutingProtocol routingProtocol=controller.getIpv6Router().getKlalbRouteProtol();
routingProtocol.removeNodeInfoUpdateListener(nodeInfoUpdateListener);
if(client!=null) {
client.close();
client=null;
}
}
@Override
public void removeNotify() {
close();
super.removeNotify();
}
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 {
File diff suppressed because it is too large Load Diff
@@ -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));
} }
} }
+2 -2
View File
@@ -4,7 +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; import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
public class TestNTP2 { public class TestNTP2 {
@@ -13,7 +13,7 @@ public class TestNTP2 {
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:0"));// 106.55.184.199 NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
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));
} }
+6 -6
View File
@@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import org.kne.cloud.clock.AdjustedNanoClock; import org.kne.cloud.clock.AdjustedNanoClock;
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.SpeedLimiter; import org.kne.cloud.network.SpeedLimiter;
import org.kne.cloud.network.ThreadTool; import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.klalb.KLALBPacket; import org.kne.cloud.network.klalb.KLALBPacket;
@@ -32,8 +32,8 @@ public class Kperf implements Runnable{
private static final boolean showpacket = false; private static final boolean showpacket = false;
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock(); private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
private MultipurposeSocketAddress targetAddress; private MultiProtocolSocketAddress targetAddress;
private MultipurposeSocketAddress bindAddress; private MultiProtocolSocketAddress bindAddress;
private volatile boolean connected=false; private volatile boolean connected=false;
private volatile KLALBPacketLink link; private volatile KLALBPacketLink link;
@@ -43,13 +43,13 @@ public class Kperf implements Runnable{
private volatile boolean closed = false; private volatile boolean closed = false;
private volatile ThreadParker tlock=new ThreadParker(); private volatile ThreadParker tlock=new ThreadParker();
public Kperf(MultipurposeSocketAddress targetAddress,MultipurposeSocketAddress bindAddress) { public Kperf(MultiProtocolSocketAddress targetAddress, MultiProtocolSocketAddress bindAddress) {
Objects.requireNonNull(targetAddress); Objects.requireNonNull(targetAddress);
this.targetAddress=targetAddress; this.targetAddress=targetAddress;
this.bindAddress=bindAddress; this.bindAddress=bindAddress;
} }
public Kperf(MultipurposeSocketAddress targetAddress) { public Kperf(MultiProtocolSocketAddress targetAddress) {
this(targetAddress,new MultipurposeSocketAddress("[::0]:0")); this(targetAddress,new MultiProtocolSocketAddress("[::0]:0"));
} }
public Kperf(KLALBPacketLink link) { public Kperf(KLALBPacketLink link) {
Objects.requireNonNull(link); Objects.requireNonNull(link);
@@ -7,7 +7,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
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.ThreadTool; import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
@@ -58,7 +58,7 @@ for (int i = 0; i < nodes; i++) {
for (int j = 0; j < 1; j++) { for (int j = 0; j < 1; j++) {
int ind=r.nextInt(size); int ind=r.nextInt(size);
NodeEntry ne=nodeList.get(ind); NodeEntry ne=nodeList.get(ind);
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress()); MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
kct.addRemoteLines(mpsa); kct.addRemoteLines(mpsa);
System.out.println("connect:" +ne.getKc().getSelf().getAddress()); System.out.println("connect:" +ne.getKc().getSelf().getAddress());
@@ -75,7 +75,7 @@ for (int i = 0; i < nodes; i++) {
} }
try { try {
NodeEntry ne=nodeList.get(0); NodeEntry ne=nodeList.get(0);
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress()); MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
klalbController.addRemoteLines(mpsa); klalbController.addRemoteLines(mpsa);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@@ -4,7 +4,7 @@ import java.io.Serializable;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Objects; import java.util.Objects;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
public class ScanRange implements Serializable { public class ScanRange implements Serializable {
public ScanRange(String range) throws UnknownHostException { public ScanRange(String range) throws UnknownHostException {
@@ -12,21 +12,21 @@ public class ScanRange implements Serializable {
} }
protected void setMsaString(String range) throws UnknownHostException { protected void setMsaString(String range) throws UnknownHostException {
String[]sp=range.split("~"); String[]sp=range.split("~");
MultipurposeSocketAddress begin=new MultipurposeSocketAddress(sp[0]); MultiProtocolSocketAddress begin=new MultiProtocolSocketAddress(sp[0]);
MultipurposeSocketAddress end; MultiProtocolSocketAddress end;
if(sp.length>1) { if(sp.length>1) {
end=new MultipurposeSocketAddress(sp[1]); end=new MultiProtocolSocketAddress(sp[1]);
}else { }else {
end=new MultipurposeSocketAddress(sp[0]); end=new MultiProtocolSocketAddress(sp[0]);
} }
setMsa(begin, end); setMsa(begin, end);
} }
public ScanRange(MultipurposeSocketAddress begin,MultipurposeSocketAddress end) throws UnknownHostException { public ScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
setMsa(begin,end); setMsa(begin,end);
} }
protected void setMsa(MultipurposeSocketAddress begin, MultipurposeSocketAddress end) throws UnknownHostException { protected void setMsa(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost()); this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
this.portRange=new PortRange(begin.getPort(), end.getPort()); this.portRange=new PortRange(begin.getPort(), end.getPort());
} }
@@ -47,7 +47,7 @@ public class ScanRange implements Serializable {
} }
public String toString() { public String toString() {
return new MultipurposeSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultipurposeSocketAddress(addressRange.getEndHost(), portRange.getEnd()); return new MultiProtocolSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultiProtocolSocketAddress(addressRange.getEndHost(), portRange.getEnd());
} }
@Override @Override
public int hashCode() { public int hashCode() {
@@ -10,7 +10,6 @@ import java.net.UnknownHostException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
@@ -20,11 +19,11 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ThreadTool; import org.kne.cloud.network.ThreadTool;
public class TCPNetworkScanner implements Runnable { public class TCPNetworkScanner implements Runnable {
private BiConsumer<MultipurposeSocketAddress,Socket> consumer; private BiConsumer<MultiProtocolSocketAddress,Socket> consumer;
private Consumer<Float>processConsumer; private Consumer<Float>processConsumer;
public Consumer<Float> getProcessConsumer() { public Consumer<Float> getProcessConsumer() {
@@ -44,11 +43,11 @@ public class TCPNetworkScanner implements Runnable {
return ranges; return ranges;
} }
public BiConsumer<MultipurposeSocketAddress,Socket> getConsumer() { public BiConsumer<MultiProtocolSocketAddress,Socket> getConsumer() {
return consumer; return consumer;
} }
public void setConsumer(BiConsumer<MultipurposeSocketAddress,Socket> consumer) { public void setConsumer(BiConsumer<MultiProtocolSocketAddress,Socket> consumer) {
this.consumer = consumer; this.consumer = consumer;
} }
@@ -128,13 +127,13 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
exc.execute(()->{ exc.execute(()->{
MultipurposeSocketAddress mpsa; MultiProtocolSocketAddress mpsa;
if (currAddress.equals(inetAddressRange.getBegin())) { if (currAddress.equals(inetAddressRange.getBegin())) {
mpsa = new MultipurposeSocketAddress(inetAddressRange.getBeginHost(), curport0); mpsa = new MultiProtocolSocketAddress(inetAddressRange.getBeginHost(), curport0);
}else if (currAddress.equals(inetAddressRange.getEnd())) { }else if (currAddress.equals(inetAddressRange.getEnd())) {
mpsa = new MultipurposeSocketAddress(inetAddressRange.getEndHost(), curport0); mpsa = new MultiProtocolSocketAddress(inetAddressRange.getEndHost(), curport0);
} else { } else {
mpsa = new MultipurposeSocketAddress(currAddress.getHostAddress(), mpsa = new MultiProtocolSocketAddress(currAddress.getHostAddress(),
curport0); curport0);
} }
Socket s = null; Socket s = null;
@@ -228,7 +227,7 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ; System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
} }
protected void acceptMultipurposeSocketAddress(MultipurposeSocketAddress scanned, Socket s) { protected void acceptMultipurposeSocketAddress(MultiProtocolSocketAddress scanned, Socket s) {
consumer.accept(scanned,s); consumer.accept(scanned,s);
} }
@@ -0,0 +1,49 @@
package org.kne.cloud.network.srv6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.kne.cloud.network.MultiProtocolSocketAddress;
public final class ExternalEndpointsResult {
private final List<MultiProtocolSocketAddress> externalEndpoints;
private final NodeInfoQueryStatus status;
public ExternalEndpointsResult(List<MultiProtocolSocketAddress> externalEndpoints, NodeInfoQueryStatus status) {
this.externalEndpoints = Collections.unmodifiableList(new ArrayList<MultiProtocolSocketAddress>(
externalEndpoints == null ? Collections.<MultiProtocolSocketAddress>emptyList() : externalEndpoints));
this.status = status;
}
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public NodeInfoQueryStatus getStatus() {
return status;
}
@Override
public int hashCode() {
return Objects.hash(externalEndpoints, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExternalEndpointsResult)) {
return false;
}
ExternalEndpointsResult other = (ExternalEndpointsResult) obj;
return Objects.equals(externalEndpoints, other.externalEndpoints) && status == other.status;
}
@Override
public String toString() {
return "ExternalEndpointsResult [externalEndpoints=" + externalEndpoints + ", status=" + status + "]";
}
}
@@ -0,0 +1,47 @@
package org.kne.cloud.network.srv6;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
public final class ExtraRoutesResult {
private final List<String> extraRoutes;
private final NodeInfoQueryStatus status;
public ExtraRoutesResult(List<String> extraRoutes, NodeInfoQueryStatus status) {
this.extraRoutes = Collections.unmodifiableList(new ArrayList<String>(
extraRoutes == null ? Collections.<String>emptyList() : extraRoutes));
this.status = status;
}
public List<String> getExtraRoutes() {
return extraRoutes;
}
public NodeInfoQueryStatus getStatus() {
return status;
}
@Override
public int hashCode() {
return Objects.hash(extraRoutes, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ExtraRoutesResult)) {
return false;
}
ExtraRoutesResult other = (ExtraRoutesResult) obj;
return Objects.equals(extraRoutes, other.extraRoutes) && status == other.status;
}
@Override
public String toString() {
return "ExtraRoutesResult [extraRoutes=" + extraRoutes + ", status=" + status + "]";
}
}
@@ -1,29 +1,14 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.Serializable; import java.io.Serializable;
import java.net.Inet6Address;
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.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@@ -49,11 +34,12 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
private String data; private String data;
private static final int HEADER_LENGTH=3; private static final int HEADER_LENGTH=3;
private static final int MAX_JSON_PAYLOAD_LENGTH=65535-HEADER_LENGTH;
private static Gson gson; private static Gson gson;
static{ static{
GsonBuilder gb=new GsonBuilder(); GsonBuilder gb=new GsonBuilder();
MultipurposeSocketAddress.registerToGsonBuilder(gb); MultiProtocolSocketAddress.registerToGsonBuilder(gb);
gson=gb.create(); gson=gb.create();
} }
@@ -90,6 +76,9 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
@Override @Override
public void writeToChannel(WritableByteChannel dto) throws IOException { public void writeToChannel(WritableByteChannel dto) throws IOException {
byte[]bta=data.getBytes(Charset.forName("UTF-8")); byte[]bta=data.getBytes(Charset.forName("UTF-8"));
if(bta.length>MAX_JSON_PAYLOAD_LENGTH) {
throw new IOException("JSON payload exceeds " + MAX_JSON_PAYLOAD_LENGTH + " bytes");
}
getHeader().putChar(1,(char) bta.length); getHeader().putChar(1,(char) bta.length);
super.writeToChannel(dto); super.writeToChannel(dto);
dto.write(ByteBuffer.wrap(bta)); dto.write(ByteBuffer.wrap(bta));
@@ -14,6 +14,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
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;
@@ -21,6 +22,8 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@@ -43,6 +46,14 @@ public class KLALBRoutingProtocol extends Thread{
private RouterInfo selfRouterInfo; private RouterInfo selfRouterInfo;
private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>(); private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>();
private final AtomicLong selfCreateTime = new AtomicLong();
private final AtomicInteger pendingNodeInfoUpdateFlags = new AtomicInteger();
private static final int NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE = 64;
private final Map<IPv6Address, LinkedHashSet<Long>> seenNodeInfoUpdates = new HashMap<>();
public Map<IPv6Address, RouterInfo> getNetmap() {
return netmap;
}
private volatile Map<IPv6Address,Long>addresses; private volatile Map<IPv6Address,Long>addresses;
@@ -81,6 +92,7 @@ public class KLALBRoutingProtocol extends Thread{
private ReentrantLock sendLock=new ReentrantLock(); private ReentrantLock sendLock=new ReentrantLock();
private Set<BiConsumer<SocketAddress,JsonDataPacket>>receivers=new CopyOnWriteArraySet<BiConsumer<SocketAddress,JsonDataPacket>>(); private Set<BiConsumer<SocketAddress,JsonDataPacket>>receivers=new CopyOnWriteArraySet<BiConsumer<SocketAddress,JsonDataPacket>>();
private Set<BiConsumer<IPv6Address,Integer>> nodeInfoUpdateListeners = new CopyOnWriteArraySet<BiConsumer<IPv6Address,Integer>>();
public void addReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) { public void addReceiver(BiConsumer<SocketAddress,JsonDataPacket> rec) {
receivers.add(rec); receivers.add(rec);
@@ -90,6 +102,57 @@ public class KLALBRoutingProtocol extends Thread{
receivers.remove(rec); receivers.remove(rec);
} }
public void addNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> listener) {
nodeInfoUpdateListeners.add(listener);
}
public void removeNodeInfoUpdateListener(BiConsumer<IPv6Address,Integer> listener) {
nodeInfoUpdateListeners.remove(listener);
}
public void announceNodeInfoUpdate(int flags) {
int validFlags = flags & (RouterInfoPacket.NODE_INFO_TINY_UPDATE_REQUIRED
| RouterInfoPacket.NODE_INFO_FULL_UPDATE_REQUIRED);
if(validFlags != 0) {
pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | validFlags);
}
}
private boolean markNodeInfoUpdateSeen(IPv6Address origin, long createTime) {
synchronized(seenNodeInfoUpdates) {
LinkedHashSet<Long> createTimes = seenNodeInfoUpdates.get(origin);
if(createTimes == null) {
createTimes = new LinkedHashSet<>();
seenNodeInfoUpdates.put(origin, createTimes);
}
if(!createTimes.add(createTime)) {
return false;
}
while(createTimes.size() > NODE_INFO_UPDATE_REPLAY_WINDOW_SIZE) {
createTimes.remove(createTimes.iterator().next());
}
return true;
}
}
private void removeNodeInfoUpdateWindow(IPv6Address origin) {
synchronized(seenNodeInfoUpdates) {
seenNodeInfoUpdates.remove(origin);
}
}
private void notifyNodeInfoUpdate(IPv6Address origin, int flags) {
for(BiConsumer<IPv6Address,Integer> listener : nodeInfoUpdateListeners) {
try {
listener.accept(origin, flags);
} catch(Throwable e) {
if(debug) {
e.printStackTrace();
}
}
}
}
@Override @Override
public void run() { public void run() {
Thread.currentThread().setName("KLALB路由协议接收线程"); Thread.currentThread().setName("KLALB路由协议接收线程");
@@ -104,6 +167,17 @@ public class KLALBRoutingProtocol extends Thread{
selfRouterInfo = getSelfRouterInfo(); selfRouterInfo = getSelfRouterInfo();
RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo); RouterInfo oslf=netmap.put(selfRouterInfo.getLocator().getAddress(), selfRouterInfo);
noticeUpdate(); noticeUpdate();
int nodeInfoUpdateFlags = pendingNodeInfoUpdateFlags.getAndSet(0);
if(nodeInfoUpdateFlags != 0) {
RouterInfoPacket updatePacket = new RouterInfoPacket(getSelfRouterInfo(), true, -1);
updatePacket.setNodeInfoUpdateFlags(nodeInfoUpdateFlags);
try {
floodPacket(null, updatePacket);
} catch(IOException e) {
pendingNodeInfoUpdateFlags.getAndUpdate(old -> old | nodeInfoUpdateFlags);
e.printStackTrace();
}
}
/*if(oslf==null||(!selfRouterInfo.equals(oslf))) { /*if(oslf==null||(!selfRouterInfo.equals(oslf))) {
long cur=System.nanoTime(); long cur=System.nanoTime();
if(cur-floodTimer2>5000000000L) { if(cur-floodTimer2>5000000000L) {
@@ -144,8 +218,10 @@ public class KLALBRoutingProtocol extends Thread{
timeout=2000000000L; timeout=2000000000L;
} }
if(val.checkTimeOut(timeout)) { if(val.checkTimeOut(timeout)) {
iterator.remove(); if(netmap.remove(type.getKey(), val)) {
noticeUpdate(); removeNodeInfoUpdateWindow(type.getKey());
noticeUpdate();
}
} }
/*List<NeighborInfo> ads=val.getNeighborAddresses(); /*List<NeighborInfo> ads=val.getNeighborAddresses();
for (Iterator<NeighborInfo> iterator2 = ads.iterator(); iterator2.hasNext();) { for (Iterator<NeighborInfo> iterator2 = ads.iterator(); iterator2.hasNext();) {
@@ -212,7 +288,7 @@ public class KLALBRoutingProtocol extends Thread{
ds.receive(dgp); ds.receive(dgp);
//System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength()))); //System.out.println(Arrays.toString( Arrays.copyOf( dgp.getData(),dgp.getLength())));
ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,dgp.getLength()); ByteArrayInputStream bi=new ByteArrayInputStream(dgp.getData(),0,dgp.getLength());
KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi)); KLALBRoutingProtocolPacket kp=KLALBRoutingProtocolPacket.readKLALBPacketFromChannel(Channels.newChannel(bi), dgp.getLength());
switch(kp.getType()) { switch(kp.getType()) {
case KLALBRoutingProtocolPacket.RINFO_REQ: case KLALBRoutingProtocolPacket.RINFO_REQ:
@@ -233,24 +309,30 @@ public class KLALBRoutingProtocol extends Thread{
RouterInfoPacket rifp=(RouterInfoPacket) kp; RouterInfoPacket rifp=(RouterInfoPacket) kp;
RouterInfo rif=rifp.getRinfo(); RouterInfo rif=rifp.getRinfo();
RouterInfo oldrif=netmap.get(rif.getLocator().getAddress()); RouterInfo oldrif=netmap.get(rif.getLocator().getAddress());
IPv6Address origin = rif.getLocator().getAddress();
int nodeInfoUpdateFlags = rifp.getNodeInfoUpdateFlags();
boolean newNodeInfoUpdate = nodeInfoUpdateFlags != 0
&& markNodeInfoUpdateSeen(origin, rif.getCreateTime());
if(debug) if(debug)
System.out.println(rif); System.out.println(rif);
if(oldrif==null||oldrif.getCreateTime()<rif.getCreateTime()) { boolean topologyUpdated = oldrif==null||oldrif.getCreateTime()<rif.getCreateTime();
if(topologyUpdated) {
if(debug) if(debug)
System.out.println("update RouterInfo"); System.out.println("update RouterInfo");
netmap.put(rif.getLocator().getAddress(), rif); netmap.put(rif.getLocator().getAddress(), rif);
noticeUpdate(); noticeUpdate();
if(rifp.isFlood()) {
floodPacket( dgp.getSocketAddress(), rifp);
}
}else { }else {
if(debug) if(debug)
System.out.println("dispose RouterInfo"); System.out.println("dispose RouterInfo");
} }
if(newNodeInfoUpdate) {
notifyNodeInfoUpdate(origin, nodeInfoUpdateFlags);
}
if(rifp.isFlood() && (topologyUpdated || newNodeInfoUpdate)) {
floodPacket(dgp.getSocketAddress(), rifp);
}
computeShortestPathIfUpdated(); computeShortestPathIfUpdated();
break; break;
case KLALBRoutingProtocolPacket.JSON_DATA: case KLALBRoutingProtocolPacket.JSON_DATA:
@@ -313,6 +395,7 @@ public class KLALBRoutingProtocol extends Thread{
}catch(BindException e) { }catch(BindException e) {
e.printStackTrace(); e.printStackTrace();
System.err.println("Cannot assign:"+dest); System.err.println("Cannot assign:"+dest);
throw e;
}finally { }finally {
sendLock.unlock(); sendLock.unlock();
} }
@@ -544,7 +627,9 @@ public class KLALBRoutingProtocol extends Thread{
private RouterInfo getSelfRouterInfo() { private RouterInfo getSelfRouterInfo() {
List<IPv6NetworkLink>links=router.getLinkTabel(); List<IPv6NetworkLink>links=router.getLinkTabel();
Object[] nls=links.toArray(); Object[] nls=links.toArray();
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN()); long currentTime = System.currentTimeMillis();
long createTime = selfCreateTime.updateAndGet(previous -> Math.max(currentTime, previous + 1));
RouterInfo ri=new RouterInfo(createTime,router.getLocator(),router.getASN());
for(int i=0;i<nls.length;i++) { for(int i=0;i<nls.length;i++) {
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i]; IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
if((!nl.isLoopBack())&&nl.isUp()) { if((!nl.isLoopBack())&&nl.isUp()) {
@@ -595,6 +680,13 @@ public class KLALBRoutingProtocol extends Thread{
public Map<IPv6Address, Long> getAddresses() { public Map<IPv6Address, Long> getAddresses() {
return addresses; return addresses;
} }
/**
* 查询某地址广播的设备名称未知返回null
*/
public String getDeviceName(IPv6Address address) {
return null;
}
public Map<IPv6Address, List<LinkDirection>> getPaths() { public Map<IPv6Address, List<LinkDirection>> getPaths() {
return paths; return paths;
} }
@@ -3,16 +3,19 @@ package org.kne.cloud.network.srv6;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.InetAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.kne.cloud.network.MultipurposeSocketAddress; import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.congestion.NOCongestionAlgorithm; import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow; import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.cloud.network.klalb.SendItem;
import org.kne.opencl64.Releaser; import org.kne.opencl64.Releaser;
public class KLALBRoutingProtocolAPIClient { public class KLALBRoutingProtocolAPIClient {
@@ -24,31 +27,106 @@ public class KLALBRoutingProtocolAPIClient {
private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> { private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
KLALBRoutingProtocolJsonData dataobj = data.getDecodedData(); KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
InetSocketAddress addrs = (InetSocketAddress) addr;
UUID ruid = dataobj.getUuid(); UUID ruid = dataobj.getUuid();
JsonDataPacket relate = null;
//System.out.println(ruid + " " + window.getSendmap());
switch (dataobj.getType()) { switch (dataobj.getType()) {
case KLALBRoutingProtocolJsonData.OPEN_LINES_RESP: case KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP:
PendingRequest profileRequest = acknowledgeResponse(ruid,
if ((relate = window.ack(ruid)) != null) { KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr);
List<?> connects = (List<?>) dataobj.getData(); if (profileRequest != null) {
List<MultipurposeSocketAddress> connectsm = new ArrayList<MultipurposeSocketAddress>(connects.size()); ((Consumer<NodeProfile>) profileRequest.callback)
for (Object open : connects) { .accept(new NodeProfile(dataobj.getDeviceName(), dataobj.getDeviceDescription()));
if (open instanceof MultipurposeSocketAddress) { }
connectsm.add((MultipurposeSocketAddress) open); break;
} else { case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP:
connectsm.add(new MultipurposeSocketAddress((String) open)); PendingRequest endpointsRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr);
} if (endpointsRequest != null) {
} ((Consumer<ExternalEndpointsResult>) endpointsRequest.callback).accept(
((Consumer<List<MultipurposeSocketAddress>>) relate.getUserCallback()).accept(connectsm); new ExternalEndpointsResult(decodeExternalEndpoints(dataobj.getData()), dataobj.getStatus()));
}
break;
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP:
PendingRequest routesRequest = acknowledgeResponse(ruid,
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr);
if (routesRequest != null) {
((Consumer<ExtraRoutesResult>) routesRequest.callback).accept(
new ExtraRoutesResult(decodeExtraRoutes(dataobj.getData()), dataobj.getStatus()));
} }
break; break;
} }
}; };
private PendingRequest acknowledgeResponse(UUID uuid, String expectedRequestType, SocketAddress sourceAddress) {
SendItem<JsonDataPacket> pending = window.getSendmap().get(uuid);
if (pending == null) {
return null;
}
KLALBRoutingProtocolJsonData request = pending.getPacket().getDecodedData();
if (request == null || !expectedRequestType.equals(request.getType())) {
return null;
}
Object callback = pending.getPacket().getUserCallback();
if (!(callback instanceof PendingRequest)) {
return null;
}
PendingRequest pendingRequest = (PendingRequest) callback;
if (!expectedRequestType.equals(pendingRequest.expectedRequestType)
|| !sameDestination(pendingRequest.expectedDestination, sourceAddress)) {
return null;
}
return window.ack(uuid) == null ? null : pendingRequest;
}
private static boolean sameDestination(SocketAddress expected, SocketAddress actual) {
if (expected instanceof InetSocketAddress && actual instanceof InetSocketAddress) {
InetSocketAddress expectedInet = (InetSocketAddress) expected;
InetSocketAddress actualInet = (InetSocketAddress) actual;
if (expectedInet.getPort() != actualInet.getPort()) return false;
InetAddress expectedAddress = expectedInet.getAddress();
InetAddress actualAddress = actualInet.getAddress();
if (expectedAddress != null && actualAddress != null) return expectedAddress.equals(actualAddress);
if (expectedAddress == null && actualAddress == null) {
return expectedInet.getHostString().equalsIgnoreCase(actualInet.getHostString());
}
return false;
}
return expected != null && expected.equals(actual);
}
private List<MultiProtocolSocketAddress> decodeExternalEndpoints(Object data) {
if (!(data instanceof List<?>)) {
return new ArrayList<MultiProtocolSocketAddress>();
}
List<?> encoded = (List<?>) data;
List<MultiProtocolSocketAddress> endpoints = new ArrayList<MultiProtocolSocketAddress>(encoded.size());
for (Object value : encoded) {
if (value instanceof MultiProtocolSocketAddress) {
endpoints.add((MultiProtocolSocketAddress) value);
} else if (value instanceof String) {
try {
endpoints.add(new MultiProtocolSocketAddress((String) value));
} catch (RuntimeException ignored) {
// Ignore malformed entries while preserving the rest of the response.
}
}
}
return endpoints;
}
private List<String> decodeExtraRoutes(Object data) {
if (!(data instanceof List<?>)) {
return new ArrayList<String>();
}
List<?> encoded = (List<?>) data;
List<String> routes = new ArrayList<String>(encoded.size());
for (Object value : encoded) {
if (value instanceof String) {
routes.add((String) value);
}
}
return routes;
}
public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) { public KLALBRoutingProtocolAPIClient(KLALBRoutingProtocol routingProtocol) {
this.routingProtocol = routingProtocol; this.routingProtocol = routingProtocol;
routingProtocol.addReceiver(rec); routingProtocol.addReceiver(rec);
@@ -56,16 +134,29 @@ public class KLALBRoutingProtocolAPIClient {
clr.register(this, releaser); clr.register(this, releaser);
} }
public void requestOpenLines(SocketAddress addr, Consumer<List<MultipurposeSocketAddress>> callback) public void requestNodeProfile(SocketAddress addr, Consumer<NodeProfile> callback) throws IOException {
throws IOException { sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ, addr, callback);
UUID suid = UUID.randomUUID(); }
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.OPEN_LINES_REQ, suid, null);
JsonDataPacket packet = new JsonDataPacket(json);
packet.setUserCallback(callback);
window.put(suid, packet);
routingProtocol.sendJsonPacketToAddress(packet, addr);
public void requestExternalEndpoints(SocketAddress addr, Consumer<ExternalEndpointsResult> callback)
throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ, addr, callback);
}
public void requestExtraRoutes(SocketAddress addr, Consumer<ExtraRoutesResult> callback) throws IOException {
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ, addr, callback);
}
private void sendNodeInfoRequest(String type, SocketAddress addr, Object callback) throws IOException {
UUID suid = UUID.randomUUID();
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null);
JsonDataPacket packet = new JsonDataPacket(json);
packet.setUserCallback(new PendingRequest(addr, type, callback));
synchronized (this) {
if (closed.get() || releaser.isReleased()) throw new IOException("Node info client is closed");
window.put(suid, packet);
routingProtocol.sendJsonPacketToAddress(packet, addr);
}
} }
public KLALBRoutingProtocol getRoutingProtocol() { public KLALBRoutingProtocol getRoutingProtocol() {
@@ -74,13 +165,27 @@ public class KLALBRoutingProtocolAPIClient {
private KLALBRoutingProtocolAPIClientReleaser releaser; private KLALBRoutingProtocolAPIClientReleaser releaser;
public void close() { public synchronized void close() {
releaser.run(); if (closed.compareAndSet(false, true)) releaser.run();
} }
public boolean isClosed() { public boolean isClosed() {
return releaser.isReleased(); return closed.get() || releaser.isReleased();
} }
private static final class PendingRequest {
private final SocketAddress expectedDestination;
private final String expectedRequestType;
private final Object callback;
private PendingRequest(SocketAddress expectedDestination, String expectedRequestType, Object callback) {
this.expectedDestination = expectedDestination;
this.expectedRequestType = expectedRequestType;
this.callback = callback;
}
}
private final AtomicBoolean closed = new AtomicBoolean();
} }
class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> { class KLALBRoutingProtocolAPIClientReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
@@ -2,15 +2,10 @@ package org.kne.cloud.network.srv6;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Cleaner; import java.lang.ref.Cleaner;
import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.cloud.network.klalb.KLALBController; import org.kne.cloud.network.klalb.KLALBController;
import org.kne.opencl64.Releaser; import org.kne.opencl64.Releaser;
@@ -18,31 +13,52 @@ public class KLALBRoutingProtocolAPIServer {
private KLALBController controller; private KLALBController controller;
private KLALBRoutingProtocol routingProtocol; private KLALBRoutingProtocol routingProtocol;
private static final Cleaner clr = Cleaner.create();
private static final Cleaner clr=Cleaner.create(); private BiConsumer<SocketAddress, JsonDataPacket> rec = (addr, data) -> {
private BiConsumer<SocketAddress, JsonDataPacket> rec=(addr,data)->{
try { try {
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData(); KLALBRoutingProtocolJsonData dataobj = data.getDecodedData();
InetSocketAddress addrs=(InetSocketAddress) addr; switch (dataobj.getType()) {
switch(dataobj.getType()){ case KLALBRoutingProtocolJsonData.NODE_PROFILE_REQ:
case KLALBRoutingProtocolJsonData.OPEN_LINES_REQ: KLALBController.PublishedNodeInfo profilePublished = controller.getPublishedNodeInfo();
if(controller.getConfigItem()==null||(!controller.getConfigItem().isDenyLineTableQuery())) { KLALBRoutingProtocolJsonData profile = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.OPEN_LINES_RESP,dataobj.getUuid(),controller.getSelflineTable()); KLALBRoutingProtocolJsonData.NODE_PROFILE_RESP, dataobj.getUuid(), null);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr); profile.setDeviceName(profilePublished.getDeviceName());
profile.setDeviceDescription(profilePublished.getDeviceDescription());
profile.setStatus(NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(profile), addr);
break;
case KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_REQ:
boolean denyEndpoints = controller.getConfigItem() != null
&& controller.getConfigItem().isDenyExternalEndpointQuery();
KLALBController.PublishedNodeInfo endpointsPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData endpoints = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.NODE_EXTERNAL_ENDPOINTS_RESP, dataobj.getUuid(),
denyEndpoints ? new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>()
: new ArrayList<org.kne.cloud.network.MultiProtocolSocketAddress>(
endpointsPublished.getExternalEndpoints()));
endpoints.setStatus(denyEndpoints ? NodeInfoQueryStatus.DENIED : NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(endpoints), addr);
break;
case KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_REQ:
KLALBController.PublishedNodeInfo routesPublished = controller.getPublishedNodeInfo();
KLALBRoutingProtocolJsonData routes = new KLALBRoutingProtocolJsonData(
KLALBRoutingProtocolJsonData.NODE_EXTRA_ROUTES_RESP, dataobj.getUuid(),
new ArrayList<String>(routesPublished.getExtraRoutes()));
routes.setStatus(NodeInfoQueryStatus.OK);
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(routes), addr);
break;
} }
break;
}
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
}; };
public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol,KLALBController controller) { public KLALBRoutingProtocolAPIServer(KLALBRoutingProtocol routingProtocol, KLALBController controller) {
this.controller=controller; this.controller = controller;
this.routingProtocol=routingProtocol; this.routingProtocol = routingProtocol;
routingProtocol.addReceiver(rec); routingProtocol.addReceiver(rec);
this.releaser=new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol,rec); this.releaser = new KLALBRoutingProtocolAPIServerReleaser(this.routingProtocol, rec);
clr.register(this, releaser); clr.register(this, releaser);
} }
@@ -54,26 +70,25 @@ public class KLALBRoutingProtocolAPIServer {
return routingProtocol; return routingProtocol;
} }
private KLALBRoutingProtocolAPIServerReleaser releaser;
private KLALBRoutingProtocolAPIServerReleaser releaser;
public void close() { public void close() {
releaser.run(); releaser.run();
} }
public boolean isClosed() { public boolean isClosed() {
return releaser.isReleased(); return releaser.isReleased();
} }
} }
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>>{
class KLALBRoutingProtocolAPIServerReleaser extends Releaser<BiConsumer<SocketAddress, JsonDataPacket>> {
private KLALBRoutingProtocol routingProtocol; private KLALBRoutingProtocol routingProtocol;
public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,BiConsumer<SocketAddress, JsonDataPacket> resource) { public KLALBRoutingProtocolAPIServerReleaser(KLALBRoutingProtocol routingProtocol,
BiConsumer<SocketAddress, JsonDataPacket> resource) {
super(resource); super(resource);
this.routingProtocol=routingProtocol; this.routingProtocol = routingProtocol;
} }
@Override @Override
@@ -1,66 +1,89 @@
package org.kne.cloud.network.srv6; package org.kne.cloud.network.srv6;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
public class KLALBRoutingProtocolJsonData { public class KLALBRoutingProtocolJsonData {
public static final String OPEN_LINES_REQ="openlinesreq"; public static final String NODE_PROFILE_REQ = "nodeprofilereq";
public static final String OPEN_LINES_RESP="openlinesresp"; public static final String NODE_PROFILE_RESP = "nodeprofileresp";
public static final String NODE_EXTERNAL_ENDPOINTS_REQ = "nodeexternalendpointsreq";
public static final String NODE_EXTERNAL_ENDPOINTS_RESP = "nodeexternalendpointsresp";
public static final String NODE_EXTRA_ROUTES_REQ = "nodeextraroutesreq";
public static final String NODE_EXTRA_ROUTES_RESP = "nodeextraroutesresp";
private String type; private String type;
private UUID uuid; private UUID uuid;
private Object data; private Object data;
public String getType() { private String deviceName;
return type; private String deviceDescription;
} private NodeInfoQueryStatus status;
public Object getData() {
return data;
}
public UUID getUuid() {
return uuid;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((data == null) ? 0 : data.hashCode());
result = prime * result + ((type == null) ? 0 : type.hashCode());
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
if (data == null) {
if (other.data != null)
return false;
} else if (!data.equals(other.data))
return false;
if (type == null) {
if (other.type != null)
return false;
} else if (!type.equals(other.type))
return false;
if (uuid == null) {
if (other.uuid != null)
return false;
} else if (!uuid.equals(other.uuid))
return false;
return true;
}
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) { public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
super();
this.type = type; this.type = type;
this.uuid = uuid; this.uuid = uuid;
this.data = data; this.data = data;
} }
@Override
public String toString() { public String getType() {
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + "]"; return type;
} }
public UUID getUuid() {
return uuid;
}
public Object getData() {
return data;
}
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
public void setDeviceDescription(String deviceDescription) {
this.deviceDescription = deviceDescription;
}
public NodeInfoQueryStatus getStatus() {
return status;
}
public void setStatus(NodeInfoQueryStatus status) {
this.status = status;
}
@Override
public int hashCode() {
return Objects.hash(type, uuid, data, deviceName, deviceDescription, status);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof KLALBRoutingProtocolJsonData)) {
return false;
}
KLALBRoutingProtocolJsonData other = (KLALBRoutingProtocolJsonData) obj;
return Objects.equals(type, other.type) && Objects.equals(uuid, other.uuid)
&& Objects.equals(data, other.data) && Objects.equals(deviceName, other.deviceName)
&& Objects.equals(deviceDescription, other.deviceDescription)
&& Objects.equals(status, other.status);
}
@Override
public String toString() {
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data
+ ", deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + ", status=" + status
+ "]";
}
} }
@@ -96,6 +96,10 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
} }
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException { public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in) throws IOException {
return readKLALBPacketFromChannel(in, -1);
}
public static KLALBRoutingProtocolPacket readKLALBPacketFromChannel(ReadableByteChannel in, long packetLength) throws IOException {
while(true) { while(true) {
ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40); ByteBuffer bb=NetworkPacket.bufferAllocator.allocate(40);
bb.limit(1); bb.limit(1);
@@ -111,11 +115,13 @@ public abstract class KLALBRoutingProtocolPacket extends NetworkPacket {
switch(type) { switch(type) {
case RINFO_REQ: case RINFO_REQ:
klp=new RouterInfoRequestPacket(bb); klp=new RouterInfoRequestPacket(bb);
klp.readFromChannel(in); if(packetLength < 0) klp.readFromChannel(in);
else klp.readFromChannel(in, packetLength);
return klp; return klp;
case RINFO: case RINFO:
klp=new RouterInfoPacket(bb); klp=new RouterInfoPacket(bb);
klp.readFromChannel(in); if(packetLength < 0) klp.readFromChannel(in);
else klp.readFromChannel(in, packetLength);
return klp; return klp;
case JSON_DATA: case JSON_DATA:
klp=new JsonDataPacket(bb); klp=new JsonDataPacket(bb);
@@ -0,0 +1,6 @@
package org.kne.cloud.network.srv6;
public enum NodeInfoQueryStatus {
OK,
DENIED
}
@@ -0,0 +1,44 @@
package org.kne.cloud.network.srv6;
import java.util.Objects;
public final class NodeProfile {
private final String deviceName;
private final String deviceDescription;
public NodeProfile(String deviceName, String deviceDescription) {
this.deviceName = deviceName;
this.deviceDescription = deviceDescription;
}
public String getDeviceName() {
return deviceName;
}
public String getDeviceDescription() {
return deviceDescription;
}
@Override
public int hashCode() {
return Objects.hash(deviceName, deviceDescription);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof NodeProfile)) {
return false;
}
NodeProfile other = (NodeProfile) obj;
return Objects.equals(deviceName, other.deviceName)
&& Objects.equals(deviceDescription, other.deviceDescription);
}
@Override
public String toString() {
return "NodeProfile [deviceName=" + deviceName + ", deviceDescription=" + deviceDescription + "]";
}
}
+13 -1
View File
@@ -68,6 +68,7 @@ public class RouterInfo implements Serializable{
} }
private static final long INFO_UPDATETIME=10000000000L; private static final long INFO_UPDATETIME=10000000000L;
private static final long INFO_TIMEOUT=60000000000L; private static final long INFO_TIMEOUT=60000000000L;
private static final int MAX_NEIGHBOR_COUNT = 512;
private long putTime=System.nanoTime(); private long putTime=System.nanoTime();
public boolean checkUpdateTime() { public boolean checkUpdateTime() {
return System.nanoTime()-putTime>INFO_UPDATETIME; return System.nanoTime()-putTime>INFO_UPDATETIME;
@@ -99,13 +100,19 @@ public class RouterInfo implements Serializable{
public RouterInfo() { public RouterInfo() {
} }
public void writeToStream(DataOutputStream out) throws IOException { public void writeToStream(DataOutputStream out) throws IOException {
int neighborCount = neighborAddresses == null ? -1 : neighborAddresses.size();
if(neighborCount<0 || neighborCount>MAX_NEIGHBOR_COUNT) {
throw new IOException("Invalid neighbor count: " + neighborCount);
}
locator.writeToStream(out); locator.writeToStream(out);
out.writeLong(asn); out.writeLong(asn);
out.writeLong(createTime); out.writeLong(createTime);
out.writeInt(neighborAddresses.size()); out.writeInt(neighborCount);
for( NeighborInfo neighborInfo :neighborAddresses) { for( NeighborInfo neighborInfo :neighborAddresses) {
neighborInfo.writeToStream(out); neighborInfo.writeToStream(out);
} }
// Keep the legacy UTF slot so older RINFO decoders stay aligned.
out.writeUTF("");
} }
protected void writeToChannel(WritableByteChannel dto) throws IOException { protected void writeToChannel(WritableByteChannel dto) throws IOException {
@@ -119,6 +126,9 @@ public class RouterInfo implements Serializable{
asn=in.readLong(); asn=in.readLong();
createTime=in.readLong(); createTime=in.readLong();
int size=in.readInt(); int size=in.readInt();
if(size<0 || size>MAX_NEIGHBOR_COUNT) {
throw new IOException("Invalid neighbor count: " + size);
}
neighborAddresses=new ArrayList<>(size); neighborAddresses=new ArrayList<>(size);
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
NeighborInfo nif=new NeighborInfo(); NeighborInfo nif=new NeighborInfo();
@@ -126,6 +136,8 @@ public class RouterInfo implements Serializable{
neighborAddresses.add(nif); neighborAddresses.add(nif);
} }
// Consume the legacy device-name slot; names are no longer broadcast here.
in.readUTF();
} }
} }
@@ -2,6 +2,7 @@ package org.kne.cloud.network.srv6;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.ByteArrayInputStream;
import java.io.Externalizable; import java.io.Externalizable;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInput; import java.io.ObjectInput;
@@ -23,6 +24,11 @@ import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ipv6.IPv6AddressGroup; import org.kne.cloud.network.ipv6.IPv6AddressGroup;
public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Serializable{ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Serializable{
public static final int NODE_INFO_TINY_UPDATE_REQUIRED = 1;
public static final int NODE_INFO_FULL_UPDATE_REQUIRED = 2;
private static final int NODE_INFO_UPDATE_FLAGS_MASK = NODE_INFO_TINY_UPDATE_REQUIRED
| NODE_INFO_FULL_UPDATE_REQUIRED;
private static final int NODE_INFO_UPDATE_MAGIC = 0xA5;
public RouterInfoPacket(RouterInfo routerInfo, boolean flood,long asn) { public RouterInfoPacket(RouterInfo routerInfo, boolean flood,long asn) {
super(RINFO); super(RINFO);
@@ -35,6 +41,7 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
} }
private RouterInfo rinfo; private RouterInfo rinfo;
private transient int nodeInfoUpdateFlags;
public boolean isFlood() { public boolean isFlood() {
return (getHeader().get(1)&1)==1; return (getHeader().get(1)&1)==1;
@@ -47,6 +54,14 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
public RouterInfo getRinfo() { public RouterInfo getRinfo() {
return rinfo; return rinfo;
} }
public int getNodeInfoUpdateFlags() {
return nodeInfoUpdateFlags;
}
public void setNodeInfoUpdateFlags(int flags) {
nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK;
}
@Override @Override
protected long getHeaderSize() { protected long getHeaderSize() {
return super.getHeaderSize()+9; return super.getHeaderSize()+9;
@@ -68,12 +83,43 @@ public class RouterInfoPacket extends KLALBRoutingProtocolPacket implements Seri
public void writeToChannel(WritableByteChannel dto) throws IOException { public void writeToChannel(WritableByteChannel dto) throws IOException {
super.writeToChannel(dto); super.writeToChannel(dto);
rinfo.writeToChannel(dto); rinfo.writeToChannel(dto);
int flags = nodeInfoUpdateFlags & NODE_INFO_UPDATE_FLAGS_MASK;
if(flags != 0) {
ByteBuffer extension = ByteBuffer.wrap(new byte[] {(byte) NODE_INFO_UPDATE_MAGIC, (byte) flags});
while(extension.hasRemaining()) {
dto.write(extension);
}
}
} }
@Override @Override
public void readFromChannel(ReadableByteChannel din, long length) throws IOException { public void readFromChannel(ReadableByteChannel din, long length) throws IOException {
super.readFromChannel(din, length); super.readFromChannel(din, length);
rinfo=new RouterInfo(); rinfo=new RouterInfo();
rinfo.readFromChannel(din, length); if(length < 0) {
rinfo.readFromChannel(din, length);
nodeInfoUpdateFlags = 0;
return;
}
long payloadLength = length - getHeaderSize();
if(payloadLength < 0 || payloadLength > Integer.MAX_VALUE) {
throw new IOException("Invalid RINFO packet length: " + length);
}
byte[] payload = new byte[(int) payloadLength];
ByteBuffer payloadBuffer = ByteBuffer.wrap(payload);
while(payloadBuffer.hasRemaining()) {
if(din.read(payloadBuffer) == -1) {
throw new java.io.EOFException();
}
}
ByteArrayInputStream payloadInput = new ByteArrayInputStream(payload);
rinfo.readFromStream(new DataInputStream(payloadInput));
nodeInfoUpdateFlags = 0;
int marker = payloadInput.read();
int flags = payloadInput.read();
int trailing = payloadInput.read();
if(marker == NODE_INFO_UPDATE_MAGIC && flags >= 0 && trailing == -1) {
nodeInfoUpdateFlags = flags & NODE_INFO_UPDATE_FLAGS_MASK;
}
} }
} }
+42 -21
View File
@@ -46,6 +46,8 @@ import org.kne.cloud.network.ipv6.PostcardEntry;
import org.kne.cloud.network.ipv6.RouteItem; import org.kne.cloud.network.ipv6.RouteItem;
import org.kne.cloud.network.klalb.KLALBRemoteLink; import org.kne.cloud.network.klalb.KLALBRemoteLink;
import org.kne.cloud.network.klalb.KLALBUtils; import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.klalb.PerformanceStrategy;
import org.kne.cloud.network.klalb.ui.PerformanceStrategyItem;
import org.kne.concurrent.HighPerformanceExecutor2; import org.kne.concurrent.HighPerformanceExecutor2;
import org.kne.concurrent.TimeoutConcurrentHashMap; import org.kne.concurrent.TimeoutConcurrentHashMap;
import org.pcap4j.packet.IllegalRawDataException; import org.pcap4j.packet.IllegalRawDataException;
@@ -62,6 +64,8 @@ public class SRv6Router {
private static final int IPv6_BITS = 128; // IPv6地址位数 private static final int IPv6_BITS = 128; // IPv6地址位数
private PerformanceStrategy performanceStrategy=PerformanceStrategy.MULTI_FILL;
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>( /*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
60000000000L); 60000000000L);
*/ */
@@ -161,6 +165,14 @@ public class SRv6Router {
}; };
public void setPerformanceStrategy(PerformanceStrategy performanceStrategy) {
this.performanceStrategy=performanceStrategy;
}
public PerformanceStrategy getPerformanceStrategy(){
return performanceStrategy;
}
// 链路状态监听器实现 // 链路状态监听器实现
private class LinkListener implements IPv6LinkStateListener { private class LinkListener implements IPv6LinkStateListener {
@@ -282,6 +294,15 @@ public class SRv6Router {
private IPv6AddressGroup locator; // SRv6定位器 private IPv6AddressGroup locator; // SRv6定位器
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号 private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
private volatile String deviceName; // 设备名称随路由信息广播
public String getDeviceName() {
return deviceName;
}
public void setDeviceName(String deviceName) {
this.deviceName = deviceName;
}
public IPv6AddressGroup getLocator() { public IPv6AddressGroup getLocator() {
return locator; return locator;
@@ -765,44 +786,44 @@ public class SRv6Router {
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory()); //private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
public static HighPerformanceExecutor2 getDefaultExecutor() { /*public static HighPerformanceExecutor2 getDefaultExecutor() {
return defaultExecutor; return defaultExecutor;
} }*/
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new // private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
// ArrayBlockingQueue<>(10000); // ArrayBlockingQueue<>(10000);
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) { public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
// packetQueue.add(object); // packetQueue.add(object);
defaultExecutor.executeWithCongestionReport((state) -> { switch(performanceStrategy) {
try { case SINGLE_CORE:
long start=System.nanoTime(); runPacketSendTask0(sendTask, false);
IPv6Packet packet=sendTask.get(); break;
if(state) { case MULTI_FILL:
packet.markCE(); case MULTI_SCATTER:
} defaultExecutor.executeWithCongestionReport((state) -> {
insertSRHandRoutePacket(null, packet); // 插入SRH并路由 runPacketSendTask0(sendTask, state);
long time=System.nanoTime()-start; }, 20,performanceStrategy);
backplaneCount(time,state);
} catch (Exception e) { break;
e.printStackTrace(); }
}
},20);
} }
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) { public void runPacketSendTask0(Supplier<IPv6Packet> sendTask,boolean ecn) {
try { try {
boolean state=false;
long start=System.nanoTime(); long start=System.nanoTime();
IPv6Packet packet=sendTask.get(); IPv6Packet packet=sendTask.get();
if(state) { if(ecn) {
packet.markCE(); packet.markCE();
} }
insertSRHandRoutePacket(null, packet); // 插入SRH并路由 insertSRHandRoutePacket(null, packet); // 插入SRH并路由
long time=System.nanoTime()-start; long time=System.nanoTime()-start;
backplaneCount(time,state); backplaneCount(time,ecn);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
runPacketSendTask0(sendTask,false);
}
public void onReceive(IPv6NetworkLink link,IPv6Packet t){ public void onReceive(IPv6NetworkLink link,IPv6Packet t){
if (unduplicate(t)) { if (unduplicate(t)) {
@@ -824,7 +845,7 @@ public class SRv6Router {
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
},20); },20,performanceStrategy);
} }
public void enqueuePacket(IPv6Packet packet) { public void enqueuePacket(IPv6Packet packet) {

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