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

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

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+290
View File
@@ -0,0 +1,290 @@
# shadcn CLI Reference
Configuration is read from `components.json`.
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
## Contents
- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
- Templates: next, vite, start, react-router, astro
- Presets: named, code, URL formats and fields
- Switching presets
---
## Commands
### `init` — Initialize or create a project
```bash
npx shadcn@latest init [components...] [options]
```
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
| Flag | Short | Description | Default |
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
| `--yes` | `-y` | Skip confirmation prompt | `true` |
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
| `--force` | `-f` | Force overwrite existing configuration | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--name <name>` | `-n` | Name for new project | — |
| `--silent` | `-s` | Mute output | `false` |
| `--rtl` | | Enable RTL support | — |
| `--reinstall` | | Re-install existing UI components | `false` |
| `--monorepo` | | Scaffold a monorepo project | — |
| `--no-monorepo` | | Skip the monorepo prompt | — |
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
### `apply` — Apply a preset to an existing project
```bash
npx shadcn@latest apply [preset] [options]
```
Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------ | ------- |
| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
| `--yes` | `-y` | Skip confirmation prompt | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--silent` | `-s` | Mute output | `false` |
`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
### `add` — Add components
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
```bash
npx shadcn@latest add [components...] [options]
```
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`),
GitHub item addresses (`owner/repo/item`), URLs, or local paths.
| Flag | Short | Description | Default |
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
| `--yes` | `-y` | Skip confirmation prompt | `false` |
| `--overwrite` | `-o` | Overwrite existing files | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
| `--all` | `-a` | Add all available components | `false` |
| `--path <path>` | `-p` | Target path for the component | — |
| `--silent` | `-s` | Mute output | `false` |
| `--dry-run` | | Preview all changes without writing files | `false` |
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
#### Dry-Run Mode
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
```bash
# Preview all changes.
npx shadcn@latest add button --dry-run
# Show diffs for all files (top 5).
npx shadcn@latest add button --diff
# Show the diff for a specific file.
npx shadcn@latest add button --diff button.tsx
# Show contents for all files (top 5).
npx shadcn@latest add button --view
# Show the full content of a specific file.
npx shadcn@latest add button --view button.tsx
# Works with URLs too.
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
# Works with public GitHub registries too.
npx shadcn@latest add owner/repo/item --dry-run
# CSS diffs.
npx shadcn@latest add button --diff globals.css
```
**When to use dry-run:**
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
- Before overwriting existing components — use `--diff` to preview the changes first.
- When the user wants to inspect component source code without installing — use `--view`.
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
#### Smart Merge from Upstream
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
### `search` — Search registries
```bash
npx shadcn@latest search [registries...] [options]
```
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`.
Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`),
and registry catalog URLs. Without `-q`, lists all items. When no registries are
passed, searches every registry configured in `components.json`.
| Flag | Short | Description | Default |
| ------------------- | ----- | ------------------------------------------------- | ------- |
| `--query <query>` | `-q` | Search query | — |
| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |
| `--limit <number>` | `-l` | Max items to display | `100` |
| `--offset <number>` | `-o` | Items to skip | `0` |
| `--json` | | Output as JSON | `false` |
| `--cwd <cwd>` | `-c` | Working directory | current |
### `view` — View item details
```bash
npx shadcn@latest view <items...> [options]
```
Displays item info including file contents. Examples:
`npx shadcn@latest view @shadcn/button`,
`npx shadcn@latest view owner/repo/item`.
### `docs` — Get component documentation URLs
```bash
npx shadcn@latest docs <components...> [options]
```
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
Example output for `npx shadcn@latest docs input button`:
```
base radix
input
docs https://ui.shadcn.com/docs/components/radix/input
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
button
docs https://ui.shadcn.com/docs/components/radix/button
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
```
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
### `diff` — Check for updates
Do not use this command. Use `npx shadcn@latest add --diff` instead.
### `info` — Project information
```bash
npx shadcn@latest info [options]
```
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
| Flag | Short | Description | Default |
| ------------- | ----- | ----------------- | ------- |
| `--cwd <cwd>` | `-c` | Working directory | current |
**Project Info fields:**
| Field | Type | Meaning |
| -------------------- | --------- | ------------------------------------------------------------------ |
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
| `isRSC` | `boolean` | Whether React Server Components are enabled |
| `isTsx` | `boolean` | Whether the project uses TypeScript |
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
| `tailwindCssFile` | `string` | Path to the global CSS file |
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
**Components.json fields:**
| Field | Type | Meaning |
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
| `rsc` | `boolean` | RSC flag from config |
| `tsx` | `boolean` | TypeScript flag |
| `tailwind.config` | `string` | Tailwind config path |
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
| `registries` | `object` | Configured custom registries |
**Links fields:**
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
### `build` — Build a custom registry
```bash
npx shadcn@latest build [registry] [options]
```
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
For authoring rules, `include`, item definitions, `registryDependencies`, and
GitHub registry behavior, see [registry.md](./registry.md).
| Flag | Short | Description | Default |
| ----------------- | ----- | ----------------- | ------------ |
| `--output <path>` | `-o` | Output directory | `./public/r` |
| `--cwd <cwd>` | `-c` | Working directory | current |
---
## Templates
| Value | Framework | Monorepo support |
| -------------- | -------------- | ---------------- |
| `next` | Next.js | Yes |
| `vite` | Vite | Yes |
| `start` | TanStack Start | Yes |
| `react-router` | React Router | Yes |
| `astro` | Astro | Yes |
| `laravel` | Laravel | No |
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
---
## Presets
Three ways to specify a preset via `--preset`:
1. **Named:** `--preset nova` or `--preset lyra`
2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
> Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
## Switching Presets
Ask the user first: **overwrite**, **merge**, or **skip** existing components?
- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
+209
View File
@@ -0,0 +1,209 @@
# Customization & Theming
Components reference semantic CSS variable tokens. Change the variables to change every component.
## Contents
- How it works (CSS variables → Tailwind utilities → components)
- Color variables and OKLCH format
- Dark mode setup
- Changing the theme (presets, CSS variables)
- Adding custom colors (Tailwind v3 and v4)
- Border radius
- Customizing components (variants, className, wrappers)
- Checking for updates
---
## How It Works
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
3. Components use these utilities — changing a variable changes all components that reference it.
---
## Color Variables
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
| Variable | Purpose |
| -------------------------------------------- | -------------------------------- |
| `--background` / `--foreground` | Page background and default text |
| `--card` / `--card-foreground` | Card surfaces |
| `--primary` / `--primary-foreground` | Primary buttons and actions |
| `--secondary` / `--secondary-foreground` | Secondary actions |
| `--muted` / `--muted-foreground` | Muted/disabled states |
| `--accent` / `--accent-foreground` | Hover and accent states |
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
| `--border` | Default border color |
| `--input` | Form input borders |
| `--ring` | Focus ring color |
| `--chart-1` through `--chart-5` | Chart/data visualization |
| `--sidebar-*` | Sidebar-specific colors |
| `--surface` / `--surface-foreground` | Secondary surface |
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (01), chroma (0 = gray), and hue (0360).
---
## Dark Mode
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
```tsx
import { ThemeProvider } from "next-themes"
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
{children}
</ThemeProvider>
```
---
## Changing the Theme
```bash
# Apply a preset code from ui.shadcn.com.
npx shadcn@latest apply --preset a2r6bw
# Positional shorthand also works.
npx shadcn@latest apply a2r6bw
# Switch to a named preset and overwrite existing components.
npx shadcn@latest apply --preset nova
# Preserve existing components instead.
npx shadcn@latest init --preset nova --force --no-reinstall
# Use a custom theme URL.
npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
```
Or edit CSS variables directly in `globals.css`.
---
## Adding Custom Colors
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
```css
/* 1. Define in the global CSS file. */
:root {
--warning: oklch(0.84 0.16 84);
--warning-foreground: oklch(0.28 0.07 46);
}
.dark {
--warning: oklch(0.41 0.11 46);
--warning-foreground: oklch(0.99 0.02 95);
}
```
```css
/* 2a. Register with Tailwind v4 (@theme inline). */
@theme inline {
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
}
```
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
```js
// 2b. Register with Tailwind v3 (tailwind.config.js).
module.exports = {
theme: {
extend: {
colors: {
warning: "oklch(var(--warning) / <alpha-value>)",
"warning-foreground":
"oklch(var(--warning-foreground) / <alpha-value>)",
},
},
},
}
```
```tsx
// 3. Use in components.
<div className="bg-warning text-warning-foreground">Warning</div>
```
---
## Border Radius
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
---
## Customizing Components
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
Prefer these approaches in order:
### 1. Built-in variants
```tsx
<Button variant="outline" size="sm">
Click
</Button>
```
### 2. Tailwind classes via `className`
```tsx
<Card className="mx-auto max-w-md">...</Card>
```
### 3. Add a new variant
Edit the component source to add a variant via `cva`:
```tsx
// components/ui/button.tsx
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
```
### 4. Wrapper components
Compose shadcn/ui primitives into higher-level components:
```tsx
export function ConfirmDialog({ title, description, onConfirm, children }) {
return (
<AlertDialog>
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{title}</AlertDialogTitle>
<AlertDialogDescription>{description}</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
---
## Checking for Updates
```bash
npx shadcn@latest add button --diff
```
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
```bash
npx shadcn@latest add button --dry-run # see all affected files
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
```
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
+77
View File
@@ -0,0 +1,77 @@
{
"skill_name": "shadcn",
"evals": [
{
"id": 1,
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
"files": [],
"expectations": [
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
"No manual dark: color overrides"
]
},
{
"id": 2,
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
"files": [],
"expectations": [
"Includes DialogTitle for accessibility (visible or with sr-only class)",
"Avatar component includes AvatarFallback",
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
"Uses asChild for custom triggers (radix preset)"
]
},
{
"id": 3,
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
"files": [],
"expectations": [
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
"Uses Badge component for percentage change instead of custom styled spans",
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
"Uses gap-* instead of space-y-* or space-x-* for spacing",
"Uses size-* when width and height are equal instead of separate w-* h-*"
]
},
{
"id": 4,
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.",
"expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.",
"files": [],
"expectations": [
"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea",
"Wraps each row in MessageScrollerItem inside MessageScrollerContent",
"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs",
"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary",
"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card",
"Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label",
"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*",
"Includes \"use client\" when the component uses state or event handlers (isRSC)"
]
},
{
"id": 5,
"prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.",
"expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.",
"files": [],
"expectations": [
"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring",
"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state",
"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation",
"Wraps each message row in MessageScrollerItem inside MessageScrollerContent",
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
]
}
]
}
+105
View File
@@ -0,0 +1,105 @@
# shadcn MCP Server
The CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.
---
## Setup
```bash
shadcn mcp # start the MCP server (stdio)
shadcn mcp init # write config for your editor
```
Editor config files:
| Editor | Config file |
| ----------- | ------------------------------- |
| Claude Code | `.mcp.json` |
| Cursor | `.cursor/mcp.json` |
| VS Code | `.vscode/mcp.json` |
| OpenCode | `opencode.json` |
| Codex | `~/.codex/config.toml` (manual) |
---
## Tools
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
### `shadcn:get_project_registries`
Returns registry names from `components.json`. Errors if no `components.json` exists.
**Input:** none
### `shadcn:list_items_in_registries`
Lists all items from one or more registries. Registries can be configured
namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or
registry catalog URLs. Omit `registries` to list from every registry configured
in `components.json`.
**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
### `shadcn:search_items_in_registries`
Fuzzy search across registries. Registries can be configured namespaces, public
GitHub sources, or registry catalog URLs. Omit `registries` to search every
registry configured in `components.json` — e.g. "find me a hero" across all
configured registries.
**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
### `shadcn:view_items_in_registries`
View item details including full file contents.
**Input:** `items` (string[]) — e.g.
`["@shadcn/button", "@shadcn/card", "owner/repo/item"]`
### `shadcn:get_item_examples_from_registries`
Find usage examples and demos with source code. Omit `registries` to search
every registry configured in `components.json`.
**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
### `shadcn:get_add_command_for_items`
Returns the CLI install command.
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
### `shadcn:get_audit_checklist`
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
**Input:** none
---
## Configuring Registries
Namespaced and authenticated registries are set in `components.json`. The
`@shadcn` registry is always built-in. Public GitHub registries can also be used
directly as `owner/repo` registry sources when the repository has a root
`registry.json`; they do not need `components.json` configuration.
```json
{
"registries": {
"@acme": "https://acme.com/r/{name}.json",
"@private": {
"url": "https://private.com/r/{name}.json",
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
}
}
}
```
- Names must start with `@`.
- URLs must contain `{name}`.
- `${VAR}` references are resolved from environment variables.
Community registry index: `https://ui.shadcn.com/r/registries.json`
+277
View File
@@ -0,0 +1,277 @@
# Registry Authoring and Addresses
Use this reference when the user wants to create, fix, publish, or reason about
a shadcn registry.
## Mental Model
A registry has two forms:
- **Source registry**: an authored `registry.json` in a project or repository.
It may use `include` and file paths that point at source files.
- **Built registry**: generated JSON files served to CLI consumers, usually
from `public/r`. Use `npx shadcn@latest build` to create this form.
The CLI installer consumes registry item payloads. A source registry is a way to
author those payloads from real files.
Registry items are not limited to React components. They can distribute
components, hooks, utilities, design tokens, pages, config files, docs, rules,
workflows, templates, MCP files, and other project files.
## Root `registry.json`
The root registry file should define registry metadata and either `items` or
`include`.
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme",
"homepage": "https://acme.com",
"items": [
{
"name": "absolute-url",
"type": "registry:lib",
"title": "Absolute URL",
"description": "A utility to turn any path into an absolute URL.",
"files": [
{
"path": "lib/absolute-url.ts",
"type": "registry:lib"
}
]
}
]
}
```
Root registry rules:
- Root `registry.json` must include `name` and `homepage`.
- `items` is an array of registry item definitions.
- `include` may be used to split the source registry into multiple files.
- Included registry files may omit `name` and `homepage`.
## Include
Use `include` to keep large registries modular.
```json
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "acme",
"homepage": "https://acme.com",
"include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
}
```
Include rules:
- Include paths are relative to the `registry.json` that declares them.
- Include paths must explicitly point to a `registry.json` file.
- Do not use remote URLs, absolute paths, or parent traversal (`..`).
- Item file paths are relative to the registry file that declares the item.
- Duplicate item names fail across the resolved registry.
Example included file:
```json
{
"items": [
{
"name": "button",
"type": "registry:ui",
"files": [
{
"path": "button.tsx",
"type": "registry:ui"
}
]
}
]
}
```
If this file is at `registry/ui/registry.json`, then `button.tsx` is read from
`registry/ui/button.tsx`, and the built item path is emitted relative to the
root registry.
## Item Definitions
Common item fields:
```json
{
"name": "login-form",
"type": "registry:block",
"title": "Login Form",
"description": "A login form with email and password fields.",
"dependencies": ["zod"],
"registryDependencies": ["button", "input", "label"],
"files": [
{
"path": "blocks/login-form.tsx",
"type": "registry:block"
}
],
"cssVars": {
"light": {
"brand": "oklch(0.62 0.18 250)"
},
"dark": {
"brand": "oklch(0.72 0.16 250)"
}
}
}
```
Important fields:
- `name`: the installable item name. It is not necessarily a file path.
- `type`: one of the registry item types, such as `registry:ui`,
`registry:block`, `registry:lib`, `registry:hook`, `registry:file`,
`registry:page`, `registry:theme`, `registry:style`, `registry:font`, or
`registry:item`.
- `files`: source files copied or generated by the item.
- `dependencies`: npm runtime dependencies.
- `devDependencies`: npm development dependencies.
- `registryDependencies`: other registry items required by this item.
- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time
additions.
File rules:
- File paths are relative to the declaring `registry.json`.
- `registry:file` and `registry:page` files require a `target`.
- Do not use remote file URLs in source registry file paths.
- Keep source files copy-pasteable: no hidden app-only imports.
## Registry Dependencies
`registryDependencies` entries are item addresses, not file paths.
```json
{
"name": "login-form",
"type": "registry:block",
"registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
"files": [
{
"path": "blocks/login-form.tsx",
"type": "registry:block"
}
]
}
```
Dependency rules:
- Bare names such as `"button"` mean official shadcn items.
- Bare names never mean same-registry or same-repository items.
- Namespaced dependencies use `@namespace/item-name`.
- GitHub dependencies use `owner/repo/item-name`.
- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.
- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same
repo at `v2`, write `owner/repo/bar#v2`.
- Do not use relative dependencies such as `"./bar"`.
## Address Schemes
When reasoning about a registry item string, classify it first.
| Address | Scheme | Meaning |
| ----------------------------------- | --------- | ------------------------------------------------------------ |
| `button` | shadcn | Official shadcn item named `button`. |
| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |
| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |
| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |
| `./button.json` | file | Built registry item JSON on disk. |
| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |
| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |
For namespace and GitHub addresses, slashful item names are allowed and are item
names, not file paths. Addresses ending in `.json` keep file-address
precedence, so `acme/ui/data/schema.json` is treated as a file path, not a
GitHub item address.
## GitHub Registries
A public GitHub repository can act as a source registry when it has a root
`registry.json`.
```txt
owner/repo/item-name[#ref]
```
Rules:
- The first two path segments are GitHub owner and repo.
- All remaining path segments are the registry item name.
- The source entrypoint is always root `registry.json`.
- GitHub registries are source registries consumed directly by the CLI. They do
not require `shadcn build` or generated item JSON files.
- `include` follows the same source-registry rules as local registries.
- Currently, GitHub addresses support public `github.com` repositories only.
- Private repos and GitHub Enterprise require explicit product decisions.
When implementing GitHub registry fetching, resolve refs to a commit SHA before
reading source files. Do not read moving refs directly from
`raw.githubusercontent.com`, because branch-like refs can be cached for several
minutes.
Preferred flow:
```txt
owner/repo[#ref]
-> resolve ref with git ls-remote
-> commit SHA
-> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
-> read includes and item files from the same SHA
```
This keeps a command on one consistent repository snapshot.
Full 40-character commit SHAs are already stable and can be used directly.
Branches, tags, and short refs require Git so the CLI can resolve them to a
commit SHA first.
## Build and Verify
Use the CLI to build source registries:
```bash
npx shadcn@latest build
npx shadcn@latest build registry.json --output public/r
```
Use CLI commands to inspect the result:
```bash
npx shadcn@latest list @acme
npx shadcn@latest search @acme -q "login"
npx shadcn@latest view @acme/login-form
npx shadcn@latest add @acme/login-form --dry-run
npx shadcn@latest registry validate ./registry.json
```
Use GitHub addresses directly for public GitHub registries:
```bash
npx shadcn@latest list owner/repo
npx shadcn@latest search owner/repo -q "login"
npx shadcn@latest view owner/repo/item
npx shadcn@latest add owner/repo/item --dry-run
npx shadcn@latest registry validate owner/repo
```
When working on registry implementation in the shadcn/ui codebase:
- Keep address parsing pure and testable.
- Do not add side effects to validators.
- Preserve existing behavior for official shadcn, namespace, URL, and file
schemes.
- Add tests for address parsing, source loading, dependency resolution, list,
search, view, and add paths.
- Prefer small source-reader abstractions over a plugin system until there are
multiple real providers.
@@ -0,0 +1,306 @@
# Base vs Radix
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
## Contents
- Composition: asChild vs render
- Button / trigger as non-button element
- Select (items prop, placeholder, positioning, multiple, object values)
- ToggleGroup (type vs multiple)
- Slider (scalar vs array)
- Accordion (type and defaultValue)
---
## Composition: asChild (radix) vs render (base)
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
**Incorrect:**
```tsx
<DialogTrigger>
<div>
<Button>Open</Button>
</div>
</DialogTrigger>
```
**Correct (radix):**
```tsx
<DialogTrigger asChild>
<Button>Open</Button>
</DialogTrigger>
```
**Correct (base):**
```tsx
<DialogTrigger render={<Button />}>Open</DialogTrigger>
```
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
---
## Button / trigger as non-button element (base only)
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
**Incorrect (base):** missing `nativeButton={false}`.
```tsx
<Button render={<a href="/docs" />}>Read the docs</Button>
```
**Correct (base):**
```tsx
<Button render={<a href="/docs" />} nativeButton={false}>
Read the docs
</Button>
```
**Correct (radix):**
```tsx
<Button asChild>
<a href="/docs">Read the docs</a>
</Button>
```
Same for triggers whose `render` is not a `Button`:
```tsx
// base.
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
Pick date
</PopoverTrigger>
```
---
## Select
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
**Incorrect (base):**
```tsx
<Select>
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
</Select>
```
**Correct (base):**
```tsx
const items = [
{ label: "Select a fruit", value: null },
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
]
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{items.map((item) => (
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
```
**Correct (radix):**
```tsx
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
```
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
```tsx
// base.
<SelectContent alignItemWithTrigger={false} side="bottom">
// radix.
<SelectContent position="popper">
```
---
## Select — multiple selection and object values (base only)
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
**Correct (base — multiple selection):**
```tsx
<Select items={items} multiple defaultValue={[]}>
<SelectTrigger>
<SelectValue>
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
</SelectValue>
</SelectTrigger>
...
</Select>
```
**Correct (base — object values):**
```tsx
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
<SelectTrigger>
<SelectValue>{(value) => value.name}</SelectValue>
</SelectTrigger>
...
</Select>
```
---
## ToggleGroup
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
**Incorrect (base):**
```tsx
<ToggleGroup type="single" defaultValue="daily">
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
</ToggleGroup>
```
**Correct (base):**
```tsx
// Single (no prop needed), defaultValue is always an array.
<ToggleGroup defaultValue={["daily"]} spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// Multi-selection.
<ToggleGroup multiple>
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**Correct (radix):**
```tsx
// Single, defaultValue is a string.
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
</ToggleGroup>
// Multi-selection.
<ToggleGroup type="multiple">
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
</ToggleGroup>
```
**Controlled single value:**
```tsx
// base — wrap/unwrap arrays.
const [value, setValue] = React.useState("normal")
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
// radix — plain string.
const [value, setValue] = React.useState("normal")
<ToggleGroup type="single" value={value} onValueChange={setValue}>
```
---
## Slider
Base accepts a plain number for a single thumb. Radix always requires an array.
**Incorrect (base):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
**Correct (base):**
```tsx
<Slider defaultValue={50} max={100} step={1} />
```
**Correct (radix):**
```tsx
<Slider defaultValue={[50]} max={100} step={1} />
```
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
```tsx
// base.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
// radix.
const [value, setValue] = React.useState([0.3, 0.7])
<Slider value={value} onValueChange={setValue} />
```
---
## Accordion
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
**Incorrect (base):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
**Correct (base):**
```tsx
<Accordion defaultValue={["item-1"]}>
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
// Multi-select.
<Accordion multiple defaultValue={["item-1", "item-2"]}>
<AccordionItem value="item-1">...</AccordionItem>
<AccordionItem value="item-2">...</AccordionItem>
</Accordion>
```
**Correct (radix):**
```tsx
<Accordion type="single" collapsible defaultValue="item-1">
<AccordionItem value="item-1">...</AccordionItem>
</Accordion>
```
+224
View File
@@ -0,0 +1,224 @@
# Chat & Messaging
Components for conversation and chat UI. Compose these instead of hand-rolling
bubbles, scroll containers, dividers, or attachment cards.
Install: `npx shadcn@latest add message-scroller message bubble attachment marker`
The same component names and props ship for both `base` and `radix`; only
composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).
## Contents
- Scrollable threads use MessageScroller
- Message rows use Message
- Message surfaces use Bubble
- Attachments use Attachment
- System notes and dividers use Marker
- Streaming, anchoring, and jump-to-latest are built in
- Escape hatch: the scroller hooks
---
## Scrollable threads use MessageScroller
A conversation that scrolls, follows new messages, restores position, or jumps
to a message uses `MessageScroller`. Don't build a raw overflow container with
manual scroll wiring, and don't reach for `ScrollArea`.
The parts nest in a fixed order. Every direct child of the content is wrapped in
a `MessageScrollerItem` so the scroller can measure, anchor, preserve position,
track visibility, and jump to it. `MessageScrollerButton` sits inside
`MessageScroller`, after the viewport.
**Incorrect:**
```tsx
// Hand-rolled scroll container with manual stick-to-bottom logic.
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto">
<div className="flex flex-col gap-6 p-4">
{messages.map((m) => (
<ChatMessage key={m.id} message={m} />
))}
</div>
</div>
```
**Correct:**
```tsx
<MessageScrollerProvider autoScroll>
<MessageScroller>
<MessageScrollerViewport>
<MessageScrollerContent>
{messages.map((message) => (
<MessageScrollerItem
key={message.id}
messageId={message.id}
scrollAnchor={message.role === "user"}
>
<Message align={message.role === "user" ? "end" : "start"}>
{/* ...message content... */}
</Message>
</MessageScrollerItem>
))}
</MessageScrollerContent>
</MessageScrollerViewport>
<MessageScrollerButton />
</MessageScroller>
</MessageScrollerProvider>
```
---
## Message rows use Message
`Message` lays out a single row: avatar, header, content, footer, with
alignment. Group consecutive rows from one sender with `MessageGroup`. Don't
rebuild the row from flex divs.
`align="end"` is the current user's side; `align="start"` is everyone else.
```tsx
<Message align="start">
<MessageAvatar>
<Avatar>
<AvatarImage src={sender.avatar} alt={sender.name} />
<AvatarFallback>{initials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<MessageHeader>{sender.name}</MessageHeader>
<Bubble>
<BubbleContent>{text}</BubbleContent>
</Bubble>
<MessageFooter>{time}</MessageFooter>
</MessageContent>
</Message>
```
---
## Message surfaces use Bubble
The colored message surface is `Bubble` + `BubbleContent`, never a styled `div`
with `bg-muted` / `bg-primary` and hand-managed corners.
- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.
- `align`: `start` or `end` (matches the `Message` side).
`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and
`align` (`start` | `end`) position it against the bubble. Don't lay reactions out
with absolutely-positioned `Badge`s.
**Incorrect:**
```tsx
<div className="w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground">
{text}
</div>
```
**Correct:**
```tsx
<Bubble variant="default" align="end">
<BubbleContent>{text}</BubbleContent>
<BubbleReactions side="bottom" align="end">
<Badge variant="secondary">👍 2</Badge>
</BubbleReactions>
</Bubble>
```
---
## Attachments use Attachment
File and image attachments use `Attachment`, not `Item` or a custom card. It
carries upload state, so wire `state` to the real status rather than rendering a
separate spinner.
- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and
`processing` apply the `shimmer` animation to the title automatically.
- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.
- Use `AttachmentGroup` to lay out several attachments in a scrolling row.
```tsx
<Attachment state="done">
<AttachmentMedia variant="icon">
<FileTextIcon />
</AttachmentMedia>
<AttachmentContent>
<AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
</AttachmentContent>
<AttachmentActions>
<AttachmentAction>
<DownloadIcon />
</AttachmentAction>
</AttachmentActions>
</Attachment>
```
For an image, use `<AttachmentMedia variant="image">` with an `img` child.
---
## System notes and dividers use Marker
Status lines ("Sarah joined the conversation"), date dividers ("Today"), and
labeled separators are `Marker`, not a `Separator` plus a centered span.
- `variant`: `default` (plain row), `separator` (centered label with rules on
each side), `border` (bottom-bordered row).
- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.
**Incorrect:**
```tsx
<div className="flex items-center gap-3 py-2">
<Separator className="flex-1" />
<span className="text-xs text-muted-foreground">Today</span>
<Separator className="flex-1" />
</div>
```
**Correct:**
```tsx
<Marker variant="separator">
<MarkerContent>Today</MarkerContent>
</Marker>
```
---
## Streaming, anchoring, and jump-to-latest are built in
`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't
write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.
- **Follow the live edge while streaming.** `MessageScrollerProvider` with
`autoScroll` keeps the view pinned to new content and yields the moment the
user scrolls up. Streaming token updates that grow the last message are
followed automatically.
- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to
hold in view (typically the user's message that started the turn).
- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away
and scrolls back on click. `direction="end"` (default) or `direction="start"`.
It is a self-managing control, so don't gate it behind your own scroll-position
state.
For a "thinking…" indicator while the model generates, apply the `shimmer`
utility to text. Don't author a custom keyframe animation. See
[styling.md](./styling.md).
---
## Escape hatch: the scroller hooks
For behavior the parts don't expose, read state from the hooks rather than
re-implementing the scroller: `useMessageScroller`,
`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come
from the auto-installed `@shadcn/react` dependency, so there's nothing extra to
install. Reach for them only when composition can't express what you need.
+213
View File
@@ -0,0 +1,213 @@
# Component Composition
## Contents
- Items always inside their Group component
- Callouts use Alert
- Empty states use Empty component
- Toast notifications follow the project base
- Choosing between overlay components
- Dialog, Sheet, and Drawer always need a Title
- Card structure
- Button has no isPending or isLoading prop
- TabsTrigger must be inside TabsList
- Avatar always needs AvatarFallback
- Use Separator instead of raw hr or border divs
- Use Skeleton for loading placeholders
- Use Badge instead of custom styled spans
---
## Items always inside their Group component
Never render items directly inside the content container.
**Incorrect:**
```tsx
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectContent>
```
**Correct:**
```tsx
<SelectContent>
<SelectGroup>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
</SelectGroup>
</SelectContent>
```
This applies to all group-based components:
| Item | Group |
|------|-------|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
| `MenubarItem` | `MenubarGroup` |
| `ContextMenuItem` | `ContextMenuGroup` |
| `CommandItem` | `CommandGroup` |
| `MessageScrollerItem` | `MessageScrollerContent` |
| `Message` (consecutive, same sender) | `MessageGroup` |
| `Bubble` (stacked) | `BubbleGroup` |
| `Attachment` (in a row) | `AttachmentGroup` |
Chat components nest in a fixed order (`MessageScrollerProvider``MessageScroller``MessageScrollerViewport``MessageScrollerContent``MessageScrollerItem`). See [chat.md](./chat.md).
---
## Callouts use Alert
```tsx
<Alert>
<AlertTitle>Warning</AlertTitle>
<AlertDescription>Something needs attention.</AlertDescription>
</Alert>
```
---
## Empty states use Empty component
```tsx
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
<EmptyTitle>No projects yet</EmptyTitle>
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
</EmptyHeader>
<EmptyContent>
<Button>Create Project</Button>
</EmptyContent>
</Empty>
```
---
## Toast notifications follow the project base
For Base UI projects, use the `toast` component:
```tsx
import { toast } from "@/components/ui/toast"
toast.add({
title: "Changes saved.",
})
```
For Radix and React Aria projects, use Sonner:
```tsx
import { toast } from "sonner"
toast.success("Changes saved.")
toast.error("Something went wrong.")
toast("File deleted.", {
action: { label: "Undo", onClick: () => undoDelete() },
})
```
---
## Choosing between overlay components
| Use case | Component |
|----------|-----------|
| Focused task that requires input | `Dialog` |
| Destructive action confirmation | `AlertDialog` |
| Side panel with details or filters | `Sheet` |
| Mobile-first bottom panel | `Drawer` |
| Quick info on hover | `HoverCard` |
| Small contextual content on click | `Popover` |
---
## Dialog, Sheet, and Drawer always need a Title
`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
```tsx
<DialogContent>
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
<DialogDescription>Update your profile.</DialogDescription>
</DialogHeader>
...
</DialogContent>
```
---
## Card structure
Use full composition — don't dump everything into `CardContent`:
```tsx
<Card>
<CardHeader>
<CardTitle>Team Members</CardTitle>
<CardDescription>Manage your team.</CardDescription>
</CardHeader>
<CardContent>...</CardContent>
<CardFooter>
<Button>Invite</Button>
</CardFooter>
</Card>
```
---
## Button has no isPending or isLoading prop
Compose with `Spinner` + `data-icon` + `disabled`:
```tsx
<Button disabled>
<Spinner data-icon="inline-start" />
Saving...
</Button>
```
---
## TabsTrigger must be inside TabsList
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
```tsx
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">...</TabsContent>
</Tabs>
```
---
## Avatar always needs AvatarFallback
Always include `AvatarFallback` for when the image fails to load:
```tsx
<Avatar>
<AvatarImage src="/avatar.png" alt="User" />
<AvatarFallback>JD</AvatarFallback>
</Avatar>
```
---
## Use existing components instead of custom markup
| Instead of | Use |
|---|---|
| `<hr>` or `<div className="border-t">` | `<Separator />` |
| `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
+192
View File
@@ -0,0 +1,192 @@
# Forms & Inputs
## Contents
- Forms use FieldGroup + Field
- InputGroup requires InputGroupInput/InputGroupTextarea
- Buttons inside inputs use InputGroup + InputGroupAddon
- Option sets (27 choices) use ToggleGroup
- FieldSet + FieldLegend for grouping related fields
- Field validation and disabled states
---
## Forms use FieldGroup + Field
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
```tsx
<FieldGroup>
<Field>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" type="email" />
</Field>
<Field>
<FieldLabel htmlFor="password">Password</FieldLabel>
<Input id="password" type="password" />
</Field>
</FieldGroup>
```
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
**Choosing form controls:**
- Simple text input → `Input`
- Dropdown with predefined options → `Select`
- Searchable dropdown → `Combobox`
- Native HTML select (no JS) → `native-select`
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
- Single choice from few options → `RadioGroup`
- Toggle between 25 options → `ToggleGroup` + `ToggleGroupItem`
- OTP/verification code → `InputOTP`
- Multi-line text → `Textarea`
---
## InputGroup requires InputGroupInput/InputGroupTextarea
Never use raw `Input` or `Textarea` inside an `InputGroup`.
**Incorrect:**
```tsx
<InputGroup>
<Input placeholder="Search..." />
</InputGroup>
```
**Correct:**
```tsx
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="Search..." />
</InputGroup>
```
---
## Buttons inside inputs use InputGroup + InputGroupAddon
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
**Incorrect:**
```tsx
<div className="relative">
<Input placeholder="Search..." className="pr-10" />
<Button className="absolute right-0 top-0" size="icon">
<SearchIcon />
</Button>
</div>
```
**Correct:**
```tsx
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
<InputGroup>
<InputGroupInput placeholder="Search..." />
<InputGroupAddon>
<Button size="icon">
<SearchIcon data-icon="inline-start" />
</Button>
</InputGroupAddon>
</InputGroup>
```
---
## Option sets (27 choices) use ToggleGroup
Don't manually loop `Button` components with active state.
**Incorrect:**
```tsx
const [selected, setSelected] = useState("daily")
<div className="flex gap-2">
{["daily", "weekly", "monthly"].map((option) => (
<Button
key={option}
variant={selected === option ? "default" : "outline"}
onClick={() => setSelected(option)}
>
{option}
</Button>
))}
</div>
```
**Correct:**
```tsx
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
<ToggleGroup spacing={2}>
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
</ToggleGroup>
```
Combine with `Field` for labelled toggle groups:
```tsx
<Field orientation="horizontal">
<FieldTitle id="theme-label">Theme</FieldTitle>
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
<ToggleGroupItem value="light">Light</ToggleGroupItem>
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
<ToggleGroupItem value="system">System</ToggleGroupItem>
</ToggleGroup>
</Field>
```
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
---
## FieldSet + FieldLegend for grouping related fields
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
```tsx
<FieldSet>
<FieldLegend variant="label">Preferences</FieldLegend>
<FieldDescription>Select all that apply.</FieldDescription>
<FieldGroup className="gap-3">
<Field orientation="horizontal">
<Checkbox id="dark" />
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
</Field>
</FieldGroup>
</FieldSet>
```
---
## Field validation and disabled states
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
```tsx
// Invalid.
<Field data-invalid>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" aria-invalid />
<FieldDescription>Invalid email address.</FieldDescription>
</Field>
// Disabled.
<Field data-disabled>
<FieldLabel htmlFor="email">Email</FieldLabel>
<Input id="email" disabled />
</Field>
```
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
+101
View File
@@ -0,0 +1,101 @@
# Icons
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide``lucide-react`, `tabler``@tabler/icons-react`, etc. Never assume `lucide-react`.
---
## Icons in Button use data-icon attribute
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
**Incorrect:**
```tsx
<Button>
<SearchIcon className="mr-2 size-4" />
Search
</Button>
```
**Correct:**
```tsx
<Button>
<SearchIcon data-icon="inline-start"/>
Search
</Button>
<Button>
Next
<ArrowRightIcon data-icon="inline-end"/>
</Button>
```
---
## No sizing classes on icons inside components
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
**Incorrect:**
```tsx
<Button>
<SearchIcon className="size-4" data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon className="mr-2 size-4" />
Settings
</DropdownMenuItem>
```
**Correct:**
```tsx
<Button>
<SearchIcon data-icon="inline-start" />
Search
</Button>
<DropdownMenuItem>
<SettingsIcon />
Settings
</DropdownMenuItem>
```
---
## Pass icons as component objects, not string keys
Use `icon={CheckIcon}`, not a string key to a lookup map.
**Incorrect:**
```tsx
const iconMap = {
check: CheckIcon,
alert: AlertIcon,
}
function StatusBadge({ icon }: { icon: string }) {
const Icon = iconMap[icon]
return <Icon />
}
<StatusBadge icon="check" />
```
**Correct:**
```tsx
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
import { CheckIcon } from "lucide-react"
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
return <Icon />
}
<StatusBadge icon={CheckIcon} />
```
+185
View File
@@ -0,0 +1,185 @@
# Styling & Customization
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
## Contents
- Semantic colors
- Built-in variants first
- className for layout only
- No space-x-* / space-y-*
- Prefer size-* over w-* h-* when equal
- Prefer truncate shorthand
- No manual dark: color overrides
- Use cn() for conditional classes
- No manual z-index on overlay components
- Use shimmer / scroll-fade utilities, not custom animations
---
## Semantic colors
**Incorrect:**
```tsx
<div className="bg-blue-500 text-white">
<p className="text-gray-600">Secondary text</p>
</div>
```
**Correct:**
```tsx
<div className="bg-primary text-primary-foreground">
<p className="text-muted-foreground">Secondary text</p>
</div>
```
---
## No raw color values for status/state indicators
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
**Incorrect:**
```tsx
<span className="text-emerald-600">+20.1%</span>
<span className="text-green-500">Active</span>
<span className="text-red-600">-3.2%</span>
```
**Correct:**
```tsx
<Badge variant="secondary">+20.1%</Badge>
<Badge>Active</Badge>
<span className="text-destructive">-3.2%</span>
```
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
---
## Built-in variants first
**Incorrect:**
```tsx
<Button className="border border-input bg-transparent hover:bg-accent">
Click me
</Button>
```
**Correct:**
```tsx
<Button variant="outline">Click me</Button>
```
---
## className for layout only
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
**Incorrect:**
```tsx
<Card className="bg-blue-100 text-blue-900 font-bold">
<CardContent>Dashboard</CardContent>
</Card>
```
**Correct:**
```tsx
<Card className="max-w-md mx-auto">
<CardContent>Dashboard</CardContent>
</Card>
```
To customize a component's appearance, prefer these approaches in order:
1. **Built-in variants**`variant="outline"`, `variant="destructive"`, etc.
2. **Semantic color tokens**`bg-primary`, `text-muted-foreground`.
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
---
## No space-x-* / space-y-*
Use `gap-*` instead. `space-y-4``flex flex-col gap-4`. `space-x-2``flex gap-2`.
```tsx
<div className="flex flex-col gap-4">
<Input />
<Input />
<Button>Submit</Button>
</div>
```
---
## Prefer size-* over w-* h-* when equal
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
---
## Prefer truncate shorthand
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
---
## No manual dark: color overrides
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
---
## Use cn() for conditional classes
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
**Incorrect:**
```tsx
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
```
**Correct:**
```tsx
import { cn } from "@/lib/utils"
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
```
---
## No manual z-index on overlay components
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
---
## Use shimmer / scroll-fade utilities, not custom animations
For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.
For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.
**Incorrect:**
```tsx
<span className="animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]">
Thinking
</span>
```
**Correct:**
```tsx
<span className="shimmer">Thinking</span>
```
+4 -2
View File
@@ -4,7 +4,6 @@
<classpathentry kind="lib" path="lib/gson-2.1.jar"/>
<classpathentry kind="lib" path="lib/javassist.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1686963856070/source/javassist-3.29.2-GA-sources.jar"/>
<classpathentry kind="lib" path="lib/ini4j.jar"/>
<classpathentry kind="lib" path="lib/JFreeChart1.5.2.jar"/>
<classpathentry kind="lib" path="lib/toml4j-0.7.1.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-core-1.8.2.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1725520274095/source/pcap4j-core-1.8.2-sources.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar"/>
@@ -24,10 +23,13 @@
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.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>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+13
View File
@@ -3,3 +3,16 @@
/klalbs4.json
/klalbs.json
/klalbs2.json
# Dashboard / Frontend
dashboard/node_modules/
dashboard/dist/
dashboard/.pnpm-store/
.pnpm-debug.log*
dashboard/.env.local
dashboard/.env.*.local
/klalb-config.json
/klalbconfig-old.json
/.slim/
+3
View File
@@ -0,0 +1,3 @@
[submodule "dashboard"]
path = dashboard
url = https://git.code.cq.cn/SerinaNya/KLALB-dashboard.git
+38
View File
@@ -0,0 +1,38 @@
<component name="libraryTable">
<library name="jfreechart-1.5.3">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/gson-2.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javassist.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNEOptimize.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-core-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-static-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packettest-1.8.2-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-sample-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1-sources.jar!/" />
</SOURCES>
</library>
</component>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2">
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="jdk-26.0.1" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
</project>
+11
View File
@@ -14,4 +14,15 @@
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</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>
+27
View File
@@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "KLALBMain (VS Code 自动构建)",
"request": "launch",
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
"projectName": "KLALB",
"cwd": "${workspaceFolder}",
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
},
{
"type": "java",
"name": "KLALBMain (直接用 bin 目录)",
"request": "launch",
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
"cwd": "${workspaceFolder}",
"classPaths": [
"${workspaceFolder}/bin",
"${workspaceFolder}/src",
"${workspaceFolder}/lib/*"
],
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
}
]
}
+9
View File
@@ -0,0 +1,9 @@
{
"java.configuration.runtimes": [
{
"name": "JavaSE-25",
"path": "C:\\Program Files\\Zulu\\zulu-25",
"default": true
}
]
}
+101
View File
@@ -0,0 +1,101 @@
# AGENTS.md
KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system that merges multiple WAN links into one virtual IPv6/SRv6 network. Version constant lives in `src/org/kne/cloud/network/klalb/CONST.java`. Protocol specs and manuals are the Chinese `.docx` files in the repo root.
## Build & run
No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`.
Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text):
```powershell
& "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
```
Warnings about `ThreadTool` varargs / deprecated `finalize` are pre-existing and expected — success = exit code 0. After recompiling, restart the running app (IDE-debugged JVMs keep old classes).
Run from the repo root — CWD matters:
- reads `klalb-config.json` from CWD
- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support)
- classpath must include `src` as well as `bin`: i18n bundles (`/klalb_*.properties`) and images (`/assets/*`) are classpath resources that Eclipse copies to `bin` but manual `javac` does not
```powershell
java --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
```
IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JDK 25. `.classpath` now references the standard container `JavaSE-25` — an execution-environment spec that any JDK ≥25 satisfies, so it works unchanged on JDK 26 machines too (the original named `jdk-26.0.1` VM broke VS Code import on machines without it). `.vscode/settings.json` maps `JavaSE-25` to the locally installed Adoptium JDK; register every installed JDK there when adding another one. Keep compiler compliance ≤25 (`.settings` pins 19) so both JDKs stay usable.
Runtime gotchas (all verified):
- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs).
- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work.
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"enableTUN": false` in `klalb-config.json` or toggle off "启用 TUN 虚拟网卡" in GUI/Web settings.
- Routing broadcast (`RouterInfo`) transmits `deviceName`, which topology and node overview panels display. `deviceDescription` is NOT broadcast — it only leaves the node in full node-info query responses (see srv6 API below); `ExtraRoutes` remain local controller configs.
## Verification
No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `main()` harnesses requiring real network peers. Practical check = compile succeeds + app launches.
## Architecture
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
- API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routing-table`, `/api/nodes` (topology graph), `/api/node-info?address=<ipv6>` (on-demand full node info), `/api/config`.
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config.
- `...network.kltp` — custom reliable transport protocol (packets/streams).
- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation.
- **Node-info query API** (`...network.srv6`, JSON datagrams on `KLALBRoutingProtocol.DEFAULT_PORT=1001`): `KLALBRoutingProtocolAPIServer/Client` speak two request types —
- `nodeinfotinyreq/resp` → device name ONLY; never gated by any flag (name is public via broadcast anyway).
- `nodeinfofullreq/resp` → externalEndpoints + deviceName + deviceDescription. `denyExternalEndpointQuery=true` hides ONLY the endpoint list (`data=null`); name/description still answer.
- GUI rule: opening `NodeInformationPanel` = Full query; use `requestNodeInfoTiny` for lightweight/background lookups. Legacy `openlines*` message types were removed — mixed-version meshes get silence, so upgrade the whole network together.
- `JsonDataPacket` stores its UTF-8 payload length in a 2-byte header field: keep every JSON message under 64 KiB.
- `...network.frpc` — frp client integration.
- `...klalb.ui` — all Swing UI code.
## Frontend (Dashboard)
Located in `dashboard/`:
- **Git layout**: `dashboard/` is a separate git repo wired in as a submodule (own origin on `git.code.cq.cn`). Commit frontend changes inside `dashboard/` first, then bump the submodule pointer in the parent repo — parent-repo commits alone do not capture them.
- **Stack**: Vite + React 19 + TypeScript + Tailwind CSS v4 + `@base-ui/react` (style: `base-nova`, icons: `lucide-react`, toasts: `@base-ui/react/toast`).
- **Routing**: Hash-based routing (`#/overview`, `#/connections`, `#/routing-table`, `#/topology`, `#/settings`) for seamless SPA hosting under Java `KLALBWebServer`.
- **Package Manager**: `pnpm` (run all commands from `dashboard/` directory).
- **Component installation**: **Must** use CLI via `pnpm dlx shadcn@latest add <component>` (e.g. `pnpm dlx shadcn@latest add alert card badge toast`). Never create or fake shadcn components manually. Non-shadcn libs (`@xyflow/react`, `d3-force`) are installed via plain `pnpm add`.
- **Commands**:
- `pnpm dev` — Start Vite dev server (proxies `/api` to backend `http://127.0.0.1:4665`).
- `pnpm build` — Typecheck and build SPA to `dashboard/dist` (which Java `KLALBWebServer` serves directly).
- `pnpm lint` / `pnpm typecheck` — Verification.
- **Pages & data flow**:
- Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links).
- Settings loads/saves `/api/config` (`use-klalb-config.ts`); save payload must keep legacy field aliases alongside new names for compatibility.
- Routing table polls `/api/routing-table` every 1s (`use-routing-table.ts`); `cost` is delay-derived and displayed in milliseconds.
- Topology polls `/api/nodes` every 1s (`use-topology.ts`) — SSE does NOT carry topology.
- Selecting a topology node queries `/api/node-info`; remote node descriptions require a full SRv6 node-info request and can time out after 3s.
- Topology layout: `d3-force` headless simulation (recomputed only when node/edge structure changes) rendered by `@xyflow/react` with custom `device-node` / `link-edge` components in `src/components/topology/`.
- React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern).
## Config
`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`).
Key controller config fields:
- `externalEndpoints` / `autoConnections`: published vs auto-connect endpoint lists (renamed from `openConnections`, which itself replaced legacy `LineTable`; the old name was a developer naming mistake — these addresses are this node's externally published endpoints, not "connections").
- `ntpServers`: time server list (replaces `ntpServerTable`).
- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`).
- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name).
- `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input.
Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names.
Gson quirks:
- **gson-2.1 (vendored) is ancient**: its `JSON_ELEMENT` adapter factory only matches exact `JsonElement.class`, NOT subclasses. Calling `gson.toJson(Object)` with a runtime `JsonObject`/`JsonArray` reflectively serializes the internal field as `{"members": {...}}`. `KLALBWebServer.sendJsonResponse` guards against this by using `JsonElement.toString()` for JsonElement instances — keep that guard when adding new response paths. SSE avoids the issue entirely via `JsonObject.toString()`.
- `/api/config` GET/POST is parsed field-by-field in `KLALBWebServer.handleConfig` (NOT whole-object Gson reflection) because polymorphic fields (`List<InetAddress>`, `List<MultiProtocolSocketAddress>`) break reflective mapping. Keep new config fields in sync there, accepting both legacy and new JSON key names.
- `InetAddress`, `MultiProtocolSocketAddress`, and `KLALBConfigItem` custom adapters are registered on the shared Gson in `KLALBProxySystem`; the web server reuses that instance via `proxySystem.getGson()`.
- Saving via web API persists through `KLALBProxySystem.saveConfigToFile()` (GUI save consumer takes precedence when present).
## Conventions
- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese.
- UI strings go through `UIEnv.getRsb().getString(...)`; add keys to **both** `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
- `client.cfg`, `server.cfg`, `linetable.txt` at the root are example line-table/port-rule files loaded via the GUI file picker — not hardwired paths.
+1
View File
@@ -249,5 +249,6 @@
</library>
</orderEntry>
<orderEntry type="jdk" jdkName="jdk-26.0.1" jdkType="JavaSDK" />
<orderEntry type="library" name="jfreechart-1.5.3" level="project" />
</component>
</module>
-20
View File
@@ -1,20 +0,0 @@
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
Submodule
+1
Submodule dashboard added at 885a0c2d89
BIN
View File
Binary file not shown.
-123
View File
@@ -1,123 +0,0 @@
[
{
"language": "ZH_CN",
"nogui": false,
"VirtualAddress": "2486:1:0:0:0:0:0:8888",
"VirtualASN": 2142606939373348329,
"DNS": [
"2486:1:0:0:0:0:0:8888"
],
"TCPListen": "0.0.0.0:4565",
"UDPListen": "{UDP}0.0.0.0:4572",
"VirtualSocketName": "KLALB_Stream",
"LineTable": [
"07f4acdef99b.ofalias.net:4565"
],
"ConnectLineTable": [
"home-ipv6.sliveridc1.cn:4565",
"home-ltipv6.sliveridc1.cn:4565",
"cn-gd-gz.sliveridc1.cn:4565"
],
"ntpServerTable": [
"{UDP}ntp1.aliyun.com:123",
"{UDP}ntp2.aliyun.com:123",
"{UDP}ntp3.aliyun.com:123",
"{UDP}ntp4.aliyun.com:123",
"{UDP}ntp5.aliyun.com:123",
"{UDP}ntp6.aliyun.com:123",
"{UDP}ntp7.aliyun.com:123",
"{UDP}ntp1.tencent.com:123",
"{UDP}ntp2.tencent.com:123",
"{UDP}ntp3.tencent.com:123",
"{UDP}ntp4.tencent.com:123",
"{UDP}ntp5.tencent.com:123",
"{UDP}time.google.com:123",
"{UDP}time.apple.com:123",
"{UDP}pool.ntp.org:123",
"{UDP}ntp.ntsc.ac.cn:123",
"{UDP}us.ntp.org.cn:123"
],
"denyLineTableQuery": true,
"denyLineTableBroadcast": true,
"congestionAlgorithm": "BBR",
"burstLimit": 1.5,
"delayUpperBound": 1.3,
"delayLowerBound": 1.3,
"nagleDelayTime": 1000000,
"linkNagleDelayTime": 0,
"linkConnectionsCount": 1,
"TUNName": "KLALB_SRv6",
"NetworkInterfaceExcepts": [],
"Type": "KLALBController"
},
{
"Listen": "0.0.0.0:4572",
"Bridge": {
"KLALB": "SocketBridge",
"RDP": "SocketBridge",
"HTTP": "SocketBridge",
"HTTPS": "SocketBridge",
"DEFAULT": "MinecraftSocketBridge23332"
},
"Connect": {
"KLALB": "127.0.0.1:4565",
"RDP": "127.0.0.1:3389",
"HTTP": "127.0.0.1:5212",
"HTTPS": "192.168.1.235:443",
"DEFAULT": "127.0.0.1:36555"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23332",
"Bridge": {
"DEFAULT": "MinecraftSocketBridge23332"
},
"Connect": {
"DEFAULT": "127.0.0.1:36555"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23333",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "127.0.0.1:5212"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:25565",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "127.0.0.1:25566"
},
"Type": "SocketBridge"
},
{
"Listen": "0.0.0.0:25561",
"Bridge": {
"KLALB": "SocketBridge",
"DEFAULT": "MinecraftSocketBridge23330"
},
"Connect": {
"KLALB": "127.0.0.1:4565",
"DEFAULT": "127.0.0.1:25562"
},
"Type": "SocketBridge"
},
{
"Listen": "{KLALB_Stream}[::0]:23330",
"Bridge": {
"DEFAULT": "MinecraftSocketBridge23330"
},
"Connect": {
"DEFAULT": "127.0.0.1:25562"
},
"Type": "SocketBridge"
}
]
View File
-79
View File
@@ -1,79 +0,0 @@
[
{"Type":"KLALBController" ,
"TCPListen" : "0.0.0.0:4565",
"UDPListen" : "{UDP}0.0.0.0:4572",
"VirtualSocketName" : "KLALB_Stream",
"LineTable":["43.248.189.107:65529",
"cn-bj-bgp-3.openfrp.top:65529",
"180.76.147.250:65529",
"cn-ah-dx-1.natfrp.cloud:65529",
"cn-nn-dx-1.natfrp.cloud:65529",
"cn-wh-dx-1.natfrp.cloud:65529",
"cn-zz-bgp-10.natfrp.cloud:23330",
"cn-zz-bgp-7.natfrp.cloud:33336",
"43.143.109.64:49965",
"frp.104300.xyz:49965",
"us.afrps.cn:49966",
"hk.afrps.cn:49966",
"la.afrps.cn:49966",
"frp.freefrp.net:49965",
"frp1.freefrp.net:49965",
"frp2.freefrp.net:49965",
"frp4.freefrp.net:49965",
"cn-he-plc-2.openfrp.top:4572",
"{UDP}cn-he-plc-2.openfrp.top:4572",
"frp-mom.top:22226",
"frp-man.top:36555",
"us-sjc-bgp-2.openfrp.top:36555",
"us-sjc-bgp-1.openfrp.top:36555",
"[240e:340:d856:b700:6d02:6a6a:f9fc:865b]:4572"]
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:4572",
"Bridge":{
"KLALB" : "SocketBridge",
"RDP":"SocketBridge",
"HTTP":"SocketBridge",
"HTTPS":"SocketBridge",
"DEFAULT":"MinecraftSocketBridge23332"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"RDP":"127.0.0.1:3389",
"HTTP":"127.0.0.1:5212",
"HTTPS":"192.168.1.235:443",
"DEFAULT":"127.0.0.1:36555"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23332",
"Bridge":"MinecraftSocketBridge23332",
"Connect" : "127.0.0.1:36555"
},
{ "Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23333",
"Bridge":"SocketBridge",
"Connect" : "127.0.0.1:5212"
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:25561",
"Bridge":{
"KLALB" : "SocketBridge",
"DEFAULT":"MinecraftSocketBridge23330"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"DEFAULT":"127.0.0.1:25562"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23330",
"Bridge":"MinecraftSocketBridge23330",
"Connect" : "127.0.0.1:25562"
}
]
-3
View File
@@ -1,3 +0,0 @@
virtualip=cefe:49b8:f837:4b00:b9cd:dbdd:c299:349
bind=0.0.0.0:4569
local=127.0.0.1:5212
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-29
View File
@@ -1,29 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
-18
View File
@@ -1,18 +0,0 @@
43.248.189.107:65529
cn-bj-bgp-3.openfrp.top:65529
180.76.147.250:65529
cn-ah-dx-1.natfrp.cloud:65529
cn-nn-dx-1.natfrp.cloud:65529
cn-wh-dx-1.natfrp.cloud:65529
cn-zz-bgp-10.natfrp.cloud:23330
cn-zz-bgp-7.natfrp.cloud:33336
43.143.109.64:49965
frp.104300.xyz:49965
us.afrps.cn:49966
hk.afrps.cn:49966
la.afrps.cn:49966
frp.freefrp.net:49965
frp1.freefrp.net:49965
frp2.freefrp.net:49965
frp4.freefrp.net:49965
cn-he-plc-2.openfrp.top:4569
View File
-8
View File
@@ -1,8 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
+11
View File
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"shadcn": {
"source": "shadcn/ui",
"sourceType": "github",
"skillPath": "skills/shadcn/SKILL.md",
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
}
}
}
+18 -1
View File
@@ -3,7 +3,10 @@ networkgraph=Network graph
language=Language
basicsettings=Basic settings
ipv6addr=IPv6 address
devicename=Device name
devicedescription=Device description
dnsserver=DNS server
extraroutes=Extra routes
asnumber=AS number
tcplistening=TCP listening
udplistening=UDP listening
@@ -34,6 +37,7 @@ saveconfigsuccess=Save config success
warning=Warning
invaildipv6addr=IPv6 address:Invaild Input
invailddnsserver=DNS server:Invaild Input
invaildextraroutes=Extra routes:Invaild Input
invaildasnumber=AS number:Invaild Input
invaildtcplisten=TCP listen:Invaild Input
invaildudplisten=UDP listen:Invaild Input
@@ -104,4 +108,17 @@ downloadbasedelay=Download Base
uploadbasedelay=Upload Base
burstlimit=Burst limit
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
congestionmonitor=Congestion control monitor
rttbaseline=RTT baseline
rtt=RTT
rttmax=RTT max
rttmin=RTT min
+18 -1
View File
@@ -3,7 +3,10 @@ networkgraph=网络拓扑图
language=语言
basicsettings=基本设置
ipv6addr=IPv6地址
devicename=设备名称
devicedescription=设备描述
dnsserver=DNS服务器
extraroutes=额外路由
asnumber=AS号码
tcplistening=TCP监听端口
udplistening=UDP监听端口
@@ -34,6 +37,7 @@ saveconfigsuccess=保存配置成功
warning=警告
invaildipv6addr=IPv6地址:非法输入
invailddnsserver=DNS服务器:非法输入
invaildextraroutes=额外路由:非法输入
invaildasnumber=AS号码:非法输入
invaildtcplisten=TCP监听端口:非法输入
invaildudplisten=UDP监听端口:非法输入
@@ -104,4 +108,17 @@ downloadbasedelay=下载延迟基线
uploadbasedelay=上传延迟基线
burstlimit=突发限制
performancesettings=性能设置
performancestrategy=性能策略
performancestrategy=性能策略
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API 设置
enablewebapi=启用 Web API
weblistenaddr=Web API 监听地址:端口
invaildweblistenaddr=无效的 Web API 监听地址:端口
enabletun=启用TUN虚拟网卡
congestionmonitor=拥塞控制监视器
rttbaseline=往返延迟基线
rtt=往返延迟
rttmax=往返延迟上限
rttmin=往返延迟下限
@@ -5,6 +5,9 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.kne.math.Long128;
@@ -256,4 +259,27 @@ public class NTPTimestamps {
.setBit(63);
}
}
public static Long128 median(List<Long128> values) {
Objects.requireNonNull(values);
if ( values.isEmpty()) {
throw new IllegalArgumentException("列表不能为空");
}
// 1. 创建副本并排序
List<Long128> sorted = new ArrayList<>(values);
sorted.sort(Long128::compareTo);
// 2. 计算中位数
int size = sorted.size();
if (size % 2 == 1) {
// 奇数个:取中间值
return sorted.get(size / 2);
} else {
// 偶数个:取中间两个的平均值
Long128 left = sorted.get(size / 2 - 1);
Long128 right = sorted.get(size / 2);
return left.add(right).divide(Long128.valueOf(2));
}
}
}
@@ -3,14 +3,9 @@ package org.kne.cloud.network;
import java.io.Closeable;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.function.Consumer;
import javax.net.ServerSocketFactory;
public class DatagramSocketListener implements Closeable,AutoCloseable{
private DatagramServerSocket serverSocket;
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 {
this.multipurposeSocketAddress=multipurposeSocketAddress;
public DatagramSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open();
}
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
@@ -49,7 +44,7 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
private void open() throws IOException {
if(serverSocket==null)
serverSocket=multipurposeSocketAddress.listenDatagramServerSocket();
serverSocket= multiProtocolSocketAddress.listenDatagramServerSocket();
//new Thread(r).start();
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
}
@@ -63,8 +58,8 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress;
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multiProtocolSocketAddress;
}
public Consumer<DatagramSocket> getCon() {
+3 -4
View File
@@ -1,9 +1,8 @@
package org.kne.cloud.network;
import java.util.HashMap;
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;
public MultipurposeSocketAddress putN$H(String str) {
public MultiProtocolSocketAddress putN$H(String str) {
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.EOFException;
import java.io.IOException;
import java.net.DatagramPacket;
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.net.*;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.UUID;
@@ -29,12 +21,11 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
private NetworkInterface ninterface;
private volatile boolean closed = false;
private InetSocketAddress group;
private InetSocketAddress bind;
private int type;
private List<MultipurposeSocketAddress> msas;
private List<MultiProtocolSocketAddress> msas;
private UUID selfUUID;
private volatile Consumer<MultipurposeSocketAddress> con;
private volatile Consumer<MultiProtocolSocketAddress> con;
private long timeInterval;
public long getTimeInterval() {
@@ -45,7 +36,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
this.timeInterval = timeInterval;
}
public NetworkInterface getNinterface() {
public NetworkInterface getInterface() {
return ninterface;
}
@@ -54,22 +45,24 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
}
public InetSocketAddress getBind() {
return bind;
return (InetSocketAddress) soc.getLocalSocketAddress();
}
public Consumer<MultipurposeSocketAddress> getCon() {
public Consumer<MultiProtocolSocketAddress> getCon() {
return con;
}
public void setCon(Consumer<MultipurposeSocketAddress> con) {
public void setCon(Consumer<MultiProtocolSocketAddress> con) {
this.con = con;
}
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException {
soc = new MulticastSocket(bind);
List<MultiProtocolSocketAddress> msas, UUID node, long timeInterval) throws IOException {
soc = new MulticastSocket(null);
soc.setReuseAddress(true);
soc.bind(bind);
soc.setNetworkInterface(ninterface);
soc.joinGroup(group, ninterface);
this.bind = bind;
this.group = group;
this.ninterface = ninterface;
this.msas = msas;
@@ -82,46 +75,22 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
try {
while (!closed) {
HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>();
synchronized (msas) {
for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator
.next();
/*MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());*/
if(msas!=null) {
synchronized (msas) {
for (Iterator<MultiProtocolSocketAddress> iterator = msas.iterator(); iterator.hasNext(); ) {
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator
.next();
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());
try {
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;
try {
send(multiProtocolSocketAddress);
} catch (NoRouteToHostException | UnknownHostException e) {
}
}
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);
}
} catch (IOException e) {
@@ -143,28 +112,9 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
try {
loop:while (!closed) {
try {
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);
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();
MultiProtocolSocketAddress mpsa = receive();
if (mpsa == null) continue;
InetAddress mpsai=mpsa.getInetAddress();
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
@@ -186,7 +136,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
if(!isClosed())
e.printStackTrace();
}catch (IOException e) {
System.out.println(bind + " " + group + " " + ninterface);
System.out.println(getBind() + " " + group + " " + ninterface);
e.printStackTrace();
} finally {
try {
@@ -196,7 +146,48 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
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
@@ -1,471 +1,477 @@
package org.kne.cloud.network;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.net.DatagramSocket;
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.SocketChannel;
import java.util.*;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
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;
public class MultipurposeSocketAddress implements Serializable{
/**
*
*/
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
static {
socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory()));
socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory()));
}
public static Map<String, SocketType> getSocketTypeRegister() {
return socketTypeRegister;
}
private static final long serialVersionUID = 1L;
private String type;
private String host;
private ProtocolStack ps=new ProtocolStack();
private int port;
public MultipurposeSocketAddress(String hostport) {
this(hostport,"TCP");
}
public MultipurposeSocketAddress(String hostport,String defaulttype) {
hostport=hostport.trim();
if (hostport.startsWith("{")) {
int i=hostport.indexOf("}");
if(i==-1) {
throw new IllegalArgumentException("missing }");
}
type=hostport.substring(1, i);
hostport=hostport.substring(i+1);
}else {
type=defaulttype;
}
int index =hostport.lastIndexOf(":");
host=hostport.substring(0,index);
host=host.replace("[", "");
host=host.replace("]", "");
String tps=hostport.substring(index+1);
int v=tps.indexOf("(");
if(v!=-1) {
ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
tps=tps.substring(0, v);
}
port=Integer.parseInt(tps);
if(port<0) {
throw new IllegalArgumentException("port < 0");
}
}
@Override
public int hashCode() {
return Objects.hash(host, port, ps, type);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(type, other.type);
}
public boolean equals2(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
InetAddress addra=null;
try {
addra = InetAddress.getByName(host);
} catch (UnknownHostException e) {
}
InetAddress addrb=null;
try {
addrb = InetAddress.getByName(other.host);
} catch (UnknownHostException e) {
}
boolean hostequals=Objects.equals(addra, addrb);
return hostequals && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(type, other.type);
}
public MultipurposeSocketAddress(String host2, int port2) {
this("TCP", host2, port2);
}
public MultipurposeSocketAddress(String type,String host2, int port2) {
host=host2;
port=port2;
this.type=type;
}
public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) {
this("TCP",remoteSocketAddress);
}
public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) {
host=remoteSocketAddress.getAddress().getHostAddress();
port=remoteSocketAddress.getPort();
this.type=type;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getType() {
return type;
}
public InetSocketAddress getSocketAddress() {
return new InetSocketAddress(host, port);
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
if(!"TCP".equals(type)) {
sb.append('{').append(type).append('}');
}
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 {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port),timeout);
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket() throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.connect(new InetSocketAddress(host, port),timeout);
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
private static TimeoutTimer tmoTimer=new TimeoutTimer();
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
TimerTask tt=null;
try {
s.bind(new InetSocketAddress(bindip, bindport));
tt=tmoTimer.createTimeOutTask(s, timeout);
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}finally {
if(tt!=null)
tt.cancel();
}
return s;
}
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
TimerTask tt=tmoTimer.createTimeOutTask(s, timeout);
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}finally {
if(tt!=null)
tt.cancel();
}
return s;
}
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocket sk=srf.createServerSocket(port, 50, InetAddress.getByName(host));
return sk;
}
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocket sk=srf.createServerSocket(port, backlog, InetAddress.getByName(host));
return sk;
}
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocketChannel sk=srf.createServerSocketChannel(port, 50, InetAddress.getByName(host));
return sk;
}
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocketChannel sk=srf.createServerSocketChannel(port, backlog, InetAddress.getByName(host));
return sk;
}
public boolean isStream() {
return checkIsStream(type);
}
public static boolean checkIsStream(String type2) {
return socketTypeRegister.get(type2).isStream();
}
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.bind(new InetSocketAddress(bindip, bindport));
dgd.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramSocket connectDatagramSocket() throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.bind(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
}
DatagramServerSocket sk=srf.createDatagramServerSocket(port, 50, InetAddress.getByName(host));
return sk;
}
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
}
DatagramServerSocket sk=srf.createDatagramServerSocket(port, backlog, InetAddress.getByName(host));
return sk;
}
public InetAddress getInetAddress() throws UnknownHostException {
return InetAddress.getByName(host);
}
public InetAddress[] getAllInetAddress() throws UnknownHostException {
return InetAddress.getAllByName(host);
}
public boolean supportNIO() {
SocketType stp= socketTypeRegister.get(getType());
if(stp==null)
return false;
if(stp.getSocketChannelFactory()==null) {
return false;
}else {
return true;
}
}
public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){
return new JsonDeserializer<MultipurposeSocketAddress>() {
@Override
public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
if(arg0.isJsonPrimitive()) {
JsonPrimitive jp=(JsonPrimitive) arg0;
if(jp.isString()) {
return new MultipurposeSocketAddress(jp.getAsString());
}
}
throw new JsonParseException("not a string:"+arg0);
}
};
}
public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){
return new JsonSerializer<MultipurposeSocketAddress>() {
@Override
public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
return new JsonPrimitive(arg0.toString());
}
};
}
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer());
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer());
}
}
package org.kne.cloud.network;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.net.*;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.*;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
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 org.kne.cloud.network.ntp.NTPSocketType;
public class MultiProtocolSocketAddress implements Serializable{
/**
*
*/
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
static {
socketTypeRegister.put("tcp", TCPSocketType.getInstance());
socketTypeRegister.put("udp", UDPSocketType.getInstance());
socketTypeRegister.put("ntp", NTPSocketType.getInstance());
}
public static Map<String, SocketType> getSocketTypeRegister() {
return socketTypeRegister;
}
private static final long serialVersionUID = 1L;
private String protocol;
private String host;
private ProtocolStack ps=new ProtocolStack();
private int port;
public MultiProtocolSocketAddress(String hostport) {
this(hostport,"tcp");
}
public MultiProtocolSocketAddress(String hostport, String defaultprotocol) {
try {
if (!hostport.contains("://")) {
hostport = defaultprotocol.toLowerCase() + "://" + hostport;
}
URI uri = new URI(hostport);
this.protocol = uri.getScheme().toLowerCase();
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);
}
}
private int resolvePort(URI uri, String protocol) {
int port = uri.getPort();
if (port != -1) {
return port;
}
SocketType socketType = socketTypeRegister.get(protocol);
if (socketType != null) {
int defaultPort = socketType.getDefaultPort();
if (defaultPort != -1) {
return defaultPort;
}
}
throw new IllegalArgumentException(
"未指定端口号,且协议 " + protocol + " 没有默认端口: " + uri
);
}
@Override
public int hashCode() {
return Objects.hash(host, port, ps, protocol);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(protocol, other.protocol);
}
public boolean equals2(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
InetAddress addra=null;
try {
addra = InetAddress.getByName(host);
} catch (UnknownHostException e) {
}
InetAddress addrb=null;
try {
addrb = InetAddress.getByName(other.host);
} catch (UnknownHostException e) {
}
boolean hostequals=Objects.equals(addra, addrb);
return hostequals && port == other.port && Objects.equals(ps, other.ps)
&& Objects.equals(protocol, other.protocol);
}
public MultiProtocolSocketAddress(String host2, int port2) {
this("tcp", host2, port2);
}
public MultiProtocolSocketAddress(String protocol, String host2, int port2) {
host=host2;
port=port2;
this.protocol = protocol;
}
public MultiProtocolSocketAddress(InetSocketAddress remoteSocketAddress) {
this("tcp",remoteSocketAddress);
}
public MultiProtocolSocketAddress(String protocol, InetSocketAddress remoteSocketAddress) {
host=remoteSocketAddress.getAddress().getHostAddress();
port=remoteSocketAddress.getPort();
this.protocol = protocol;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getProtocol() {
return protocol;
}
public InetSocketAddress getSocketAddress() {
return new InetSocketAddress(host, port);
}
@Override
public String toString() {
try {
// 获取协议的默认端口
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;
}
}
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port),timeout);
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket() throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
Socket s=sf.createSocket();
try {
s.connect(new InetSocketAddress(host, port),timeout);
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
private static TimeoutTimer tmoTimer=new TimeoutTimer();
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
TimerTask tt=null;
try {
s.bind(new InetSocketAddress(bindip, bindport));
tt=tmoTimer.createTimeOutTask(s, timeout);
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}finally {
if(tt!=null)
tt.cancel();
}
return s;
}
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
try {
s.bind(new InetSocketAddress(bindip, bindport));
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}
return s;
}
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
if(sf==null) {
throw new UnsupportedOperationException("Socket Unsupported");
}
SocketChannel s=sf.createSocketChannel();
TimerTask tt=tmoTimer.createTimeOutTask(s, timeout);
try {
s.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
s.close();
throw e;
}finally {
if(tt!=null)
tt.cancel();
}
return s;
}
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocket sk=srf.createServerSocket(port, 50, InetAddress.getByName(host));
return sk;
}
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocket sk=srf.createServerSocket(port, backlog, InetAddress.getByName(host));
return sk;
}
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocketChannel sk=srf.createServerSocketChannel(port, 50, InetAddress.getByName(host));
return sk;
}
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
if(srf==null) {
throw new UnsupportedOperationException("ServerSocket Unsupported");
}
ServerSocketChannel sk=srf.createServerSocketChannel(port, backlog, InetAddress.getByName(host));
return sk;
}
public boolean isStream() {
return checkIsStream(protocol);
}
public static boolean checkIsStream(String type2) {
return socketTypeRegister.get(type2).isStream();
}
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.bind(new InetSocketAddress(bindip, bindport));
dgd.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramSocket connectDatagramSocket() throws IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.connect(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
if(dgs==null) {
throw new UnsupportedOperationException("DatagramSocket Unsupported");
}
DatagramSocket dgd=dgs.createSocket();
try {
dgd.bind(new InetSocketAddress(host, port));
}catch(Exception e) {
dgd.close();
throw e;
}
return dgd;
}
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
}
DatagramServerSocket sk=srf.createDatagramServerSocket(port, 50, InetAddress.getByName(host));
return sk;
}
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
if(srf==null) {
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
}
DatagramServerSocket sk=srf.createDatagramServerSocket(port, backlog, InetAddress.getByName(host));
return sk;
}
public InetAddress getInetAddress() throws UnknownHostException {
return InetAddress.getByName(host);
}
public InetAddress[] getAllInetAddress() throws UnknownHostException {
return InetAddress.getAllByName(host);
}
public boolean supportNIO() {
SocketType stp= socketTypeRegister.get(getProtocol());
if(stp==null)
return false;
if(stp.getSocketChannelFactory()==null) {
return false;
}else {
return true;
}
}
public static JsonDeserializer<MultiProtocolSocketAddress>getDefaultJsonDeserializer(){
return new JsonDeserializer<MultiProtocolSocketAddress>() {
@Override
public MultiProtocolSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
throws JsonParseException {
if(arg0.isJsonPrimitive()) {
JsonPrimitive jp=(JsonPrimitive) arg0;
if(jp.isString()) {
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(jp.getAsString());
// System.out.println(msa);
return msa;
}
}
throw new JsonParseException("not a string:"+arg0);
}
};
}
public static JsonSerializer<MultiProtocolSocketAddress>getDefaultJsonSerializer(){
return new JsonSerializer<MultiProtocolSocketAddress>() {
@Override
public JsonElement serialize(MultiProtocolSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
return new JsonPrimitive(arg0.toString());
}
};
}
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonDeserializer());
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonSerializer());
}
}
@@ -1,9 +1,9 @@
package org.kne.cloud.network;
public interface NetworkService {
public void listen(MultipurposeSocketAddress msa);
public void unlisten(MultipurposeSocketAddress msc);
public void listen(MultiProtocolSocketAddress msa);
public void unlisten(MultiProtocolSocketAddress msc);
public void connect(MultipurposeSocketAddress msa);
public void unconnect(MultipurposeSocketAddress msc);
public void connect(MultiProtocolSocketAddress msa);
public void unconnect(MultiProtocolSocketAddress msc);
}
+3 -9
View File
@@ -1,13 +1,7 @@
package org.kne.cloud.network;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.AbstractInterruptibleChannel;
import java.util.*;
public class PortRelay {
private int port;
@@ -16,8 +10,8 @@ public class PortRelay {
public PortRelay(int port, HostPortMap services) throws IOException {
this.services = services;
this.port = port;
MultipurposeSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
ssc=new SocketListener(new MultipurposeSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
MultiProtocolSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
ssc=new SocketListener(new MultiProtocolSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
}
public void start() throws IOException {
ssc.setCon((s)->{
@@ -30,7 +24,7 @@ public class PortRelay {
}else {
nx=pds.getProtocolStack().pop().getName();
}
MultipurposeSocketAddress hp=services.get(nx);
MultiProtocolSocketAddress hp=services.get(nx);
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
System.out.println(sa.getAddress().getHostAddress() + ":" + port
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
@@ -1,23 +1,19 @@
package org.kne.cloud.network;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class ServiceElement {
public String name;
public String protocol;
public MultipurposeSocketAddress ipport;
public MultiProtocolSocketAddress ipport;
public ServiceElement(String ini) throws UnknownHostException {
String[]t=ini.split("\\$");
if(t.length==1) {
protocol="";
ipport=new MultipurposeSocketAddress(t[0]);
ipport=new MultiProtocolSocketAddress(t[0]);
}else {
protocol=t[0];
ipport=new MultipurposeSocketAddress(t[1]);
ipport=new MultiProtocolSocketAddress(t[1]);
}
}
public ServiceElement() {
@@ -1,21 +1,20 @@
package org.kne.cloud.network;
import java.io.IOException;
import java.util.function.Consumer;
public class ServiceToSocketProxy extends Proxy{
public NetworkService getSrc() {
return src;
}
public MultipurposeSocketAddress getDes() {
public MultiProtocolSocketAddress getDes() {
return des;
}
public ServiceToSocketProxy(String l, String r) {
src=getRegister().get(l.substring(1, l.length()-1));
des=new MultipurposeSocketAddress(r);
des=new MultiProtocolSocketAddress(r);
}
private NetworkService src;
private MultipurposeSocketAddress des;
private MultiProtocolSocketAddress des;
@Override
public void close() throws IOException {
// TODO 自动生成的方法存根
@@ -50,10 +50,10 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
}
}
};
private MultipurposeSocketAddress multipurposeSocketAddress;
private MultiProtocolSocketAddress multiProtocolSocketAddress;
public SocketChannelListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
this.multipurposeSocketAddress=multipurposeSocketAddress;
public SocketChannelListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open();
}
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
@@ -62,9 +62,9 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
}
protected void open() throws UnknownHostException, IOException {
if(serverSocketChannel==null)
serverSocketChannel=multipurposeSocketAddress.listenServerSocketChannel();
serverSocketChannel= multiProtocolSocketAddress.listenServerSocketChannel();
if(serverSocketChannel==null) {
throw new IOException(multipurposeSocketAddress.getType()+" unsupport SocketChannel");
throw new IOException(multiProtocolSocketAddress.getProtocol()+" unsupport SocketChannel");
}
ThreadTool.makeVThread("端口监听线程", r).start();
}
@@ -82,8 +82,8 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress;
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multiProtocolSocketAddress;
}
public Consumer<SocketChannel> getCon() {
@@ -2,15 +2,12 @@ package org.kne.cloud.network;
import java.io.Closeable;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.function.Consumer;
import javax.net.ServerSocketFactory;
public class SocketListener implements Closeable,AutoCloseable{
protected ServerSocket serverSocket;
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 {
this.multipurposeSocketAddress=multipurposeSocketAddress;
public SocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
open();
}
public SocketListener(ServerSocket tserverSocket) throws IOException {
@@ -65,7 +62,7 @@ public class SocketListener implements Closeable,AutoCloseable{
}
protected void open() throws UnknownHostException, IOException {
if(serverSocket==null)
serverSocket=multipurposeSocketAddress.listenServerSocket();
serverSocket= multiProtocolSocketAddress.listenServerSocket();
ThreadTool.makeVThread("端口监听线程", r).start();
}
public void close() {
@@ -82,8 +79,8 @@ public class SocketListener implements Closeable,AutoCloseable{
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
return multipurposeSocketAddress;
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
return multiProtocolSocketAddress;
}
public Consumer<Socket> getCon() {
@@ -1,21 +1,19 @@
package org.kne.cloud.network;
import java.io.IOException;
import java.net.Socket;
import java.util.function.Consumer;
public class SocketToServiceProxy extends Proxy {
public SocketToServiceProxy(String l, String r) {
src=new MultipurposeSocketAddress(l);
src=new MultiProtocolSocketAddress(l);
des=getRegister().get(r.substring(1, r.length()-1)) ;
}
public MultipurposeSocketAddress getSrc() {
public MultiProtocolSocketAddress getSrc() {
return src;
}
public NetworkService getDes() {
return des;
}
private MultipurposeSocketAddress src;
private MultiProtocolSocketAddress src;
private NetworkService des;
@Override
public void close() throws IOException {
@@ -4,10 +4,7 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketOption;
import java.net.SocketOptions;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.channels.SocketChannel;
import java.util.Map;
@@ -15,8 +12,8 @@ public class SocketToSocketProxy extends Proxy {
private SocketListener sl;
private SocketChannelListener scl;
private DatagramSocketListener dsl;
private MultipurposeSocketAddress listen, cbind, defaultConnect;
private Map<String, MultipurposeSocketAddress> detectedConnect;
private MultiProtocolSocketAddress listen, cbind, defaultConnect;
private Map<String, MultiProtocolSocketAddress> detectedConnect;
private SocketBridgeFactory defaultFactory;
private SocketChannelBridgeFactory defaultChannelFactory;
private Map<String, SocketBridgeFactory> detectedFactory;
@@ -30,28 +27,28 @@ public class SocketToSocketProxy extends Proxy {
this.defaultSoTimeout = defaultSoTimeout;
}
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) throws IOException {
this(listen, new MultipurposeSocketAddress(new InetSocketAddress(0)), connect);
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress connect) throws IOException {
this(listen, new MultiProtocolSocketAddress(new InetSocketAddress(0)), connect);
}
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,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect)
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect)
throws IOException {
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
}
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory)
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory)
throws IOException {
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
}
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory,SocketChannelBridgeFactory defaultChannelFactory)
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory, SocketChannelBridgeFactory defaultChannelFactory)
throws IOException {
this.listen = listen;
this.cbind = cbind;
@@ -66,8 +63,8 @@ public class SocketToSocketProxy extends Proxy {
open();
}
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
MultipurposeSocketAddress defaultConnect) throws IOException {
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
MultiProtocolSocketAddress defaultConnect) throws IOException {
this(listen, cbind, defaultConnect,null);
}
@@ -112,7 +109,7 @@ public class SocketToSocketProxy extends Proxy {
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
if (!ps.isEmpty()) {
String pname=ps.pop().getName();
MultipurposeSocketAddress pmsa = detectedConnect.get(pname);
MultiProtocolSocketAddress pmsa = detectedConnect.get(pname);
if(pmsa!=null) {
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
}else {
@@ -178,19 +175,19 @@ public class SocketToSocketProxy extends Proxy {
sb.run();
}
public MultipurposeSocketAddress getListen() {
public MultiProtocolSocketAddress getListen() {
return listen;
}
public MultipurposeSocketAddress getCbind() {
public MultiProtocolSocketAddress getCbind() {
return cbind;
}
public MultipurposeSocketAddress getDefaultConnect() {
public MultiProtocolSocketAddress getDefaultConnect() {
return defaultConnect;
}
public Map<String, MultipurposeSocketAddress> getDetectedConnect() {
public Map<String, MultiProtocolSocketAddress> getDetectedConnect() {
return detectedConnect;
}
+4 -1
View File
@@ -15,6 +15,7 @@ public class SocketType {
private DatagramSocketFactory datagramSocketFactory;
private DatagramServerSocketFactory datagramServerSocketFactory;
private boolean stream;
private int defaultPort=-1;
public SocketFactory Channel() {
return socketFactory;
@@ -74,5 +75,7 @@ public class SocketType {
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;
}
}
@@ -136,4 +136,19 @@ public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetConges
return "BBR";
}
@Override
public double getUpperDelayBound() {
return MAX_GAIN;
}
@Override
public double getLowerDelayBound() {
return MIN_GAIN;
}
@Override
public double getBurstLimit() {
return burstLimit;
}
}
@@ -88,7 +88,7 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
}
}else {
if(windowUsed*3L>=congressWindowSize) {
congressWindowSize+=4000;
congressWindowSize+=4096;
updateWindowSize();
}
}
@@ -4,4 +4,7 @@ public interface DetnetCongestionAlgorithm extends CongestionAlgorithm {
public void setUpperDelayBound(double upper);
public void setLowerDelayBound(double lower);
public void setBurstLimit(double limit);
public double getUpperDelayBound( );
public double getLowerDelayBound();
public double getBurstLimit();
}
@@ -2,6 +2,8 @@ package org.kne.cloud.network.congestion;
import java.util.function.Consumer;
import org.kne.cloud.network.monitor.DelaySampler;
public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCongestionAlgorithm {
// 可配置的时延膨胀比上下限 - Vegas2.0核心参数
@@ -17,6 +19,7 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
private Consumer<Long> windowControlConsumer;
private long MIN_RTTVAR = 30000000L;
private volatile long RTTCurr = 1000000000L; // RTT
private volatile long RTTMin = 1000000000L; // BaseRTT
private volatile long RTTVar = 1000000000L;
private volatile long RTTAvg = 1000000000L;
@@ -25,11 +28,6 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
private volatile long RTTCount = 0;
private volatile long RTO = 1000000000L;
private volatile long OWDTotal = 0;
private volatile long OWDCount = 0;
private volatile long OWDAvg2 = 1000000000L; // 当前平滑OWD
private volatile long OWDMin = 1000000000L; // BaseRTT
private volatile long window = MIN_WINDOW;
private volatile long window2 = MIN_WINDOW;
@@ -58,106 +56,14 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
@Override
public void putAck(long packetSize, long RTTns, long OWDup, boolean ecn) {
if(RTTns<0) {
throw new IllegalArgumentException("RTT:"+ RTTns+" is negative!");
}
// 处理ECN信号 - 将其视为强烈的拥塞信号
if (ecn) {
// 当收到ECN时,更激进地减少窗口
window2 = Math.max(0, window2 * 3 / 4);
if (windowControlConsumer != null) {
windowControlConsumer.accept(window2);
}
}
// 更新最小RTTBaseRTT
if (RTTns <= RTTMin) {
RTTMin = RTTns;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
RTTMin = (RTTMin * 999 + RTTns) / 1000;
}
// 更新最小OWDBaseOWD
if (OWDup <= OWDMin) {
OWDMin = OWDup;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
OWDMin = (OWDMin * 999 + OWDup) / 1000;
}
// 更新平滑RTT估计
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - RTTns)) / 4;
RTTAvg = (RTTAvg * 7 + RTTns) / 8;
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);
RTTTotal += RTTns;
RTTCount++;
OWDTotal += OWDup;
OWDCount++;
long curr = System.nanoTime();
// 每个RTT周期调整一次窗口(使用当前估计的RTT)
if (curr - RTTstartTime > Math.max(BIAS, 2*RTTMin)) {
long RTTCountx=RTTCount;
if (RTTCountx > 0) {
long currentRTT = RTTTotal / RTTCountx;
//RTTAvg2 = (RTTAvg2 * 3 + currentRTT) / 4; // 平滑当前RTT
RTTAvg2=currentRTT;
RTTTotal = 0;
RTTCount = 0;
}
long OWDCountx=OWDCount;
if (OWDCountx > 0) {
long currentOWD = OWDTotal / OWDCountx;
OWDAvg2=currentOWD;
OWDTotal = 0;
OWDCount = 0;
}
// ================== Vegas2.0核心逻辑 ==================
// 1. 计算时延膨胀比
double delayRatio = (double) OWDAvg2 / (double) Math.max(BIAS, OWDMin);
//System.out.println(delayRatio+" "+RTTAvg2/1000000.0+" "+RTTMin/1000000.0);
// 2. 基于比值的窗口调整(取代原来的基于差值的调整)
if (delayRatio > delayUpperBound) {
// 时延过高:减小窗口,减少幅度与超标程度成正比
window2 -= 4096;
} else if (delayRatio < delayLowerBound) {
// 时延过低:增大窗口,增加幅度与低于目标程度成正比
window2 += 4096;
} else {
}
// ===================================================
updateWindowSize();
RTTstartTime = curr;
}
}
private DelaySampler dsp=new DelaySampler();
@Override
public void putAck(long packetSize, long RTTns, boolean ecn) {
if(RTTns<0) {
throw new IllegalArgumentException("RTT:"+ RTTns+" is negative!");
}
this.RTTCurr=RTTns;
// 处理ECN信号 - 将其视为强烈的拥塞信号
if (ecn) {
// 当收到ECN时,更激进地减少窗口
@@ -167,14 +73,7 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
}
}
// 更新最小RTTBaseRTT
if (RTTns <= RTTMin) {
RTTMin = RTTns;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
RTTMin = (RTTMin * 999 + RTTns) / 1000;
}
dsp.recordDelay(RTTns);
// 更新平滑RTT估计
@@ -190,6 +89,15 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
long curr = System.nanoTime();
// 每个RTT周期调整一次窗口(使用当前估计的RTT)
if (curr - RTTstartTime > Math.max(BIAS, 2*RTTMin)) {
dsp.update();
long rmin=dsp.getMinDelayNanos();
if(rmin<=RTTMin) {
RTTMin=rmin;
}else {
RTTMin=Math.min(rmin, RTTMin+1000);
}
long RTTCountx=RTTCount;
if (RTTCountx > 0) {
long currentRTT = RTTTotal / RTTCountx;
@@ -206,12 +114,12 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
// 2. 基于比值的窗口调整(取代原来的基于差值的调整)
if (delayRatio > delayUpperBound) {
// 时延过高:减小窗口,减少幅度与超标程度成正比
window2 -= 4096;
window2 =window2-4096;
} else if (delayRatio < delayLowerBound) {
// 时延过低:增大窗口,增加幅度与低于目标程度成正比
window2 += 4096;
window2 =window2+4096;
} else {
//window2=window2+4096
}
// ===================================================
@@ -271,10 +179,12 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
public long getBaseRTT() {
return RTTMin;
}
public long getCurrentRTT() {
public long getAvgRTT() {
return RTTAvg2;
}
public long getCurrentRTT() {
return RTTCurr;
}
@Override
public void setUpperDelayBound(double upper) {
@@ -295,4 +205,19 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
return "Vegas2";
}
@Override
public double getUpperDelayBound() {
return delayUpperBound;
}
@Override
public double getLowerDelayBound() {
return delayLowerBound;
}
@Override
public double getBurstLimit() {
return burstLimit;
}
}
@@ -4,16 +4,13 @@ import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketImpl;
import java.net.UnknownHostException;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import org.ini4j.Profile.Section;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.MultiProtocolSocketAddress;
public class FrpcServerSocket extends ServerSocket {
private FrpcProcess frpc;
@@ -76,10 +73,10 @@ public class FrpcServerSocket extends ServerSocket {
return frpcini.getRemotePort(tunnel);
}
public MultipurposeSocketAddress getTunnelSocketAddress() {
public MultiProtocolSocketAddress getTunnelSocketAddress() {
if (!isBound())
return null;
return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
}
@Override
@@ -1,5 +1,8 @@
package org.kne.cloud.network.ipv6;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -8,10 +11,17 @@ import org.kne.cloud.network.srv6.SRv6Router;
public abstract class AbstractIPv6NetworkLink implements IPv6NetworkLink {
private CopyOnWriteArraySet<IPv6LinkStateListener> listeners = new CopyOnWriteArraySet<>();
private SRv6Router srv6Router;
private final boolean loopback;
public AbstractIPv6NetworkLink(boolean loopback) {
super();
this.loopback = loopback;
}
public SRv6Router getSrv6Router() {
return srv6Router;
}
@@ -53,8 +63,49 @@ public abstract class AbstractIPv6NetworkLink implements IPv6NetworkLink {
}
private BiConsumer<IPv6NetworkLink, Supplier<IPv6Packet>> receiveConsumer;
private Supplier<Long> costSupplier;
@Override
public void setReceiveConsumer(BiConsumer<IPv6NetworkLink,Supplier< IPv6Packet>> receiveConsumer) {
this.receiveConsumer = receiveConsumer;
}
@Override
public boolean isLoopBack() {
return loopback;
}
public Supplier<Long> getCostSupplier() {
return costSupplier;
}
public void setCostSupplier(Supplier<Long> costSupplier) {
this.costSupplier = costSupplier;
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
if(isLoopBack()) {
for(IPv6AddressGroup group:getAddressGroups()) {
rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128),
group.getAddress(), this, RouteItem.DIRECT, 0, 0, null, "D"));
}
}
if(isUp()) {
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this,RouteItem.SRv6_ENDXSID, 0, 128, costSupplier, "D");
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(),
addresses.getLocator().getAddress(), this, RouteItem.SRv6_ENDSID, 13, 128,
costSupplier, "D");
rlist.add(ris);
}
}
return rlist;
}
}
@@ -61,6 +61,10 @@ public final class IPv6Address implements Comparable<IPv6Address> {
((long)(bytes[15] & 0xFF));
}
public IPv6Address(String string) throws UnknownHostException {
this((Inet6Address) Inet6Address.getByName(string));
}
/**
* 从两个long创建IPv6地址
*/
@@ -67,7 +67,16 @@ public class IPv6AddressGroup implements Comparable<IPv6AddressGroup>{
prefixLength=in.read();
}
public boolean checkMatch(IPv6Address address2) {
return address2.equals(address.maskWith(IPv6Address.createMask(prefixLength)));
IPv6Address cmsk= IPv6Address.createMask(prefixLength);
return address2.maskWith(cmsk).equals(address.maskWith(cmsk));
}
/**
* 返回裁剪后的路由表地址(主机位清零)。
* 例如 2001:db8:1::100/64 → 2001:db8:1::/64
*/
public IPv6AddressGroup toNetworkRoute() {
IPv6Address mask = IPv6Address.createMask(prefixLength);
IPv6Address maskedAddr = address.maskWith(mask);
return new IPv6AddressGroup(maskedAddr, prefixLength);
}
}
@@ -1378,7 +1378,7 @@ public class IPv6Packet extends NetworkPacket {
// 重路由计数器
private AtomicInteger rerouteCounter = new AtomicInteger(0);
public AtomicInteger getRerouteCounter() {
public AtomicInteger getRouteCounter() {
return rerouteCounter;
}
@@ -41,6 +41,7 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
private Lock slok = new SpinLock();
public IPv6TUNLoopbackNetworkLink(String name,IPv6AddressGroup hostAddress, int mtu, List<InetAddress> dns) throws IOException {
super(true);
if (tun != null)
throw new IllegalStateException("already open!");
try {
@@ -204,13 +205,6 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
LockSupport.unpark(tr);
}
@Override
public boolean isLoopBack() {
return true;
}
@Override
public List<Neighbor> getNeighborsInfo() {
List<Neighbor> hs = new ArrayList<>();
@@ -219,10 +213,6 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
return hs;
}
/*
* @Override public boolean isCongress(IPv6Packet iPv6Packet,double scale) {
* return sendQueue.size() > 1000*scale; }
*/
@Override
public String getName() {
@@ -255,27 +245,4 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
return List.of(hostAddress);
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
for (IPv6AddressGroup grp : getAddressGroups()) {
rlist.add(new RouteItem(new IPv6AddressGroup(grp.getAddress(), 128), grp.getAddress(), this, "Direct", 0, 1,
null, "D", true));
}
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D", false);
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
"KLALB SRv6", 13, 128, null, "D", false);
rlist.add(ris);
}
return rlist;
}
}
@@ -32,6 +32,7 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
//new Inet6AddressGroup(loopbackAddress, 128) new Inet6AddressGroup((Inet6Address) Inet6Address.getByName("::1"), 128)
public LoopbackIPv6NetworkLink(List<IPv6AddressGroup> addressGroupsx,SRv6Router router) {
super(true);
this.addressGroups .addAll( addressGroupsx);
}
@@ -54,10 +55,7 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
}
}
@Override
public boolean isLoopBack() {
return true;
}
@Override
public List<Neighbor> getNeighborsInfo() {
@@ -93,29 +91,6 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
return rerouteConsumer;
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
for(IPv6AddressGroup group:addressGroups) {
rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128),
group.getAddress(), this, "Direct", 0, 0, null, "D", true));
}
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D",
false);
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(),
addresses.getLocator().getAddress(), this, "KLALB SRv6", 13, 128,
null, "D", false);
rlist.add(ris);
}
return rlist;
}
public SRv6Router getRouter() {
+9 -10
View File
@@ -4,6 +4,11 @@ import java.util.Objects;
import java.util.function.Supplier;
public class RouteItem implements Comparable<RouteItem>,Cloneable{
public static final String DIRECT="Direct";
public static final String SRv6_ENDSID="SRv6 ENDSID";
public static final String SRv6_ENDXSID="SRv6 ENDXSID";
public static final String KLALB_SRv6 = "KLALB_SRv6";
private IPv6AddressGroup destination;
private IPv6Address nexthop;
private IPv6NetworkLink destlink;
@@ -12,7 +17,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
private long cost;
private String flag;
private Supplier<Long> costSupplier;
private boolean isLoopback;
public String getFlag() {
return flag;
}
@@ -34,21 +38,20 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
&& pre == other.pre && Objects.equals(proto, other.proto);
}
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,String flag,boolean isLoopback) {
long cost,String flag) {
super();
this.destination = destination;
this.destination = destination.toNetworkRoute();
this.nexthop = nexthop;
this.destlink = destlink;
this.proto = proto;
this.pre = pre;
this.cost = cost;
this.flag=flag;
this.isLoopback=isLoopback;
}
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,Supplier<Long> costSupplier,String flag,boolean isLoopback) {
long cost,Supplier<Long> costSupplier,String flag) {
super();
this.destination = destination;
this.destination = destination.toNetworkRoute();
this.nexthop = nexthop;
this.destlink = destlink;
this.proto = proto;
@@ -56,7 +59,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
this.cost = cost;
this.costSupplier=costSupplier;
this.flag=flag;
this.isLoopback=isLoopback;
}
public IPv6AddressGroup getDestination() {
return destination;
@@ -73,9 +75,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
public int getPre() {
return pre;
}
public boolean isLoopback() {
return isLoopback;
}
@Override
public String toString() {
return getDestination()+"\t"+getProto()+"\t"+getPre()+"\t"+getCost()+"\t"+getFlag()+"\t"+getNexthop()+"\t"+getDestlink().getName();
@@ -13,6 +13,10 @@ import org.kne.cloud.network.ipv6.RouteItem;
public abstract class AbstractControlledIPv6NetworkLink extends AbstractIPv6NetworkLink implements ControlledIPv6NetworkLink{
public AbstractControlledIPv6NetworkLink(boolean loopback) {
super(loopback);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ public class CONST {
public static final String KLALB_S_RV6 = "KLALB_SRv6";
public static final String KLTP_VIRTUAL_SOCKET_NAME="kltp";
public static final int itemwidth = 1000;
public static final int linepanelheight = 41;
@@ -116,9 +116,7 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
}*/
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.net.DatagramPacket;
@@ -128,15 +126,14 @@ import java.net.SocketException;
import java.nio.ByteBuffer;
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.SpeedLimiter;
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
@Override
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;
@@ -1,39 +1,31 @@
package org.kne.cloud.network.klalb;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import javax.annotation.processing.Filer;
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 org.kne.cloud.network.MultiProtocolSocketAddress;
import com.google.gson.Gson;
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.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.FileNotFoundException;
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 {
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
MultipurposeSocketAddress.registerToGsonBuilder(gb);
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
KLALBConfigItem.registerToGsonBuilder(gb);
Gson gson=gb.create();
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
@@ -1,22 +1,15 @@
package org.kne.cloud.network.klalb;
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 org.kne.cloud.network.MultipurposeSocketAddress;
import com.google.gson.Gson;
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.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.reflect.TypeToken;
@@ -69,6 +62,7 @@ public class KLALBConfigItem {
String s=jobj.get("Type").getAsString();
switch(s) {
case "KLALBController":
normalizeLegacyControllerKeys(jobj);
return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType());
case "SocketBridge":
return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType());
@@ -78,6 +72,39 @@ public class KLALBConfigItem {
}
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(){
@@ -33,7 +33,7 @@ import java.util.function.Consumer;
import org.kne.cloud.clock.HighAccuracyClock;
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.SocketType;
import org.kne.cloud.network.ThreadTool;
@@ -67,7 +67,7 @@ public class KLALBController {
public HighAccuracyClock getClock() {
return clock;
}
private final long TIME_WINDOW=5000000000L;
private final long TIME_WINDOW=2000000000L;
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
@@ -76,20 +76,27 @@ public class KLALBController {
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
private List<MultipurposeSocketAddress> selflineTable = new ArrayList<>();
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
public List<MultiProtocolSocketAddress> getListenSocketAddress() {
return listensSocketAddress;
}
private static final boolean debug=false;
private static final boolean showpacket = false;
private static final int PREFIX = 128;// 112
private static final int PREFIX = 128;
private static final int DISCOVERY_PORT = 4569;
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() {
return networkInterfaceExcept;
return networkInterfaceManager.getNetworkInterfaceExcept();
}
private List<InetAddress> dnsAddresses = new ArrayList<>();
@@ -111,55 +118,28 @@ public class KLALBController {
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() {
@Override
public void run() {
try {
List<InetAddress> localaddress = getAllNetworkInterfaceAddress();
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
for (InetAddress inetAddress : localaddress) {
if (!inetAddress.isLoopbackAddress())
for (Iterator<MultipurposeSocketAddress> iterator = listens.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress tcpl = (MultipurposeSocketAddress) iterator.next();
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
MultiProtocolSocketAddress tcpl = (MultiProtocolSocketAddress) iterator.next();
try {
if (tcpl.getInetAddress().isAnyLocalAddress()
|| tcpl.getInetAddress().equals(inetAddress)) {
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(tcpl.getType(),
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
inetAddress.getHostAddress(), tcpl.getPort());
// System.out.println(bind);
synchronized (selflineTable) {
if (!selflineTable.contains(bind)) {
selflineTable.add(bind);
synchronized (externalEndpoints) {
if (!externalEndpoints.contains(bind)) {
externalEndpoints.add(bind);
}
}
}
@@ -175,46 +155,45 @@ public class KLALBController {
try {
Set<MultipurposeSocketAddress> st = new HashSet<>();
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next();
if (link instanceof KLALBRemoteLink) {
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
if (multipurposeSocketAddress.getSocketAddress() != null)
st.add(multipurposeSocketAddress.getSocketAddress());
}
}
Set<MultiProtocolSocketAddress> st = new HashSet<>();
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
if (link instanceof KLALBRemoteLink) {
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
if (multipurposeSocketAddress.getSocketAddress() != null)
st.add(multipurposeSocketAddress.getSocketAddress());
}
}
for (Iterator<MultipurposeSocketAddress> iterator = st.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress target = (MultipurposeSocketAddress) iterator.next();
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 {
for (MultiProtocolSocketAddress target : st) {
addRemoteLines(target);
}
reml.close();
}
} catch (UnknownHostException e) {
reml.close();
srv6Router.getLinkTabel().remove(reml);
e.printStackTrace();
}
}
}
}
/*
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
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();
}
} catch (UnknownHostException e) {
reml.close();
srv6Router.getLinkTabel().remove(reml);
e.printStackTrace();
}
}
}
}
*/
} finally {
lineslock.writeLock().unlock();
}
@@ -228,112 +207,99 @@ public class KLALBController {
}
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();
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getNinterface().isUp())
|| (configItem != null && configItem.isDenyLineTableBroadcast())) {
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getInterface().isUp())
|| (configItem != null && configItem.isDenyExternalEndpointBroadcast())) {
iterator.remove();
try {
ipMulticastDiscovery.close();
} catch (IOException e) {
e.printStackTrace();
}
if(debug)
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getNinterface());
if (debug)
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getInterface());
}
}
if (configItem != null && configItem.isDenyLineTableBroadcast()) {
if (configItem != null && configItem.isDenyExternalEndpointBroadcast()) {
} else {
Enumeration<NetworkInterface> eu2 = NetworkInterface.getNetworkInterfaces();
while (eu2.hasMoreElements()) {
NetworkInterface networkInterface = (NetworkInterface) eu2.nextElement();
if (networkInterface.getDisplayName()
.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();
}
List<NetworkInterface> interfaceList = networkInterfaceManager.getAllAvaliableNetworkInterface();
for(NetworkInterface networkInterface:interfaceList){
List<InetAddress> addressList= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
loop:for (InetAddress bidr:addressList) {
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()
|| selflineTable.contains(inetAddress);
|| externalEndpoints.contains(inetAddress);
}
private boolean checkIsSelfLocator(InetAddress inetAddress) {
@@ -345,16 +311,16 @@ public class KLALBController {
@Override
public void run() {
List<MultipurposeSocketAddress> nt = ntptable;
List<MultiProtocolSocketAddress> nt = ntptable;
// System.out.println("srv6 ip"+nb);
if (nvc2 != null) {
Set<NTPPeer> sp = nvc2.getPeers();
for (MultipurposeSocketAddress server : nt) {
for (MultiProtocolSocketAddress server : nt) {
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
}
sp.removeIf((p) -> {
for (MultipurposeSocketAddress server : nt) {
for (MultiProtocolSocketAddress server : nt) {
if (server.equals(p.getAddress())) {
return false;
}
@@ -365,33 +331,30 @@ public class KLALBController {
}
};
private TimerTask tsk3 = new TimerTask() {
@Override
public void run() {
if (srv6Router != null && routingProtocol != null) {
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next();
try {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
KLALBRoutingProtocol.DEFAULT_PORT);
apiClient.requestOpenLines(iaddr, (v) -> {
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
for (MultipurposeSocketAddress msa : v) {
// System.out.print(msa);
addRemoteLines(msa);
}
}).start();
});
} catch (IOException e) {
e.printStackTrace();
}
public void tryConnectMore(){
if (srv6Router != null && routingProtocol != null) {
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
IPv6AddressGroup neighbor = (IPv6AddressGroup) iterator.next();
try {
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
KLALBRoutingProtocol.DEFAULT_PORT);
apiClient.requestNodeInfoFull(iaddr, (v) -> {
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
for (MultiProtocolSocketAddress msa : v.getOpenLines()) {
// System.out.print(msa);
addRemoteLines(msa);
}
}).start();
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
}
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
return linkMonitor;
@@ -403,6 +366,8 @@ public class KLALBController {
private SocketType streamSocketType = new KLALBStreamSocketType();
public class KLALBStreamSocketType extends SocketType {
public KLALBStreamSocketType() {
@@ -436,8 +401,8 @@ public class KLALBController {
}
public List<MultipurposeSocketAddress> getSelflineTable() {
return selflineTable;
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
@@ -472,13 +437,13 @@ public class KLALBController {
private KLALBControllerConfigItem configItem;
public void addRemoteLines(List<MultipurposeSocketAddress> select) {
for (MultipurposeSocketAddress target : select) {
public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
for (MultiProtocolSocketAddress target : select) {
addRemoteLines(target);
}
}
public List<KLALBRemoteLink> addRemoteLines(MultipurposeSocketAddress target) {
public List<KLALBRemoteLink> addRemoteLines(MultiProtocolSocketAddress target) {
lineslock.writeLock().lock();
try {
List<KLALBRemoteLink> added = new ArrayList<>();
@@ -492,7 +457,7 @@ public class KLALBController {
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
try {
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(
inetAddress.getHostAddress(), 0);
try {
if (target.getInetAddress().isLoopbackAddress()
@@ -553,7 +518,7 @@ public class KLALBController {
}
}
public List<KLALBRemoteLink> removeRemoteLines(MultipurposeSocketAddress mpsa) {
public List<KLALBRemoteLink> removeRemoteLines(MultiProtocolSocketAddress mpsa) {
List<KLALBRemoteLink> rmved = new ArrayList<>();
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
@@ -569,7 +534,7 @@ public class KLALBController {
return rmved;
}
public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException {
public Inet6Address getRemoteVaddrBySocketAddress(MultiProtocolSocketAddress target) throws SocketTimeoutException {
KLALBRemoteLink kr = null;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next();
@@ -593,7 +558,7 @@ public class KLALBController {
return kr.getRemoteVaddr().getAddress().toInet6Address();
}
private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target, MultipurposeSocketAddress bind) {
private boolean checkContainsTargetAndBind(MultiProtocolSocketAddress target, MultiProtocolSocketAddress bind) {
boolean b = false;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next();
@@ -609,7 +574,7 @@ public class KLALBController {
return b;
}
private boolean checkContainsTarget(MultipurposeSocketAddress target) {
private boolean checkContainsTarget(MultiProtocolSocketAddress target) {
boolean b = false;
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
IPv6NetworkLink link = iterator.next();
@@ -628,19 +593,19 @@ public class KLALBController {
lineslock.writeLock().lock();
try {
krs.startIO();
String selflineTable = generateSelfLineTable();
if (selflineTable != null && !selflineTable.equals(""))
krs.sendPacket(new ADDLINESPacket(selflineTable));
String externalEndpointsText = generateExternalEndpointsString();
if (externalEndpointsText != null && !externalEndpointsText.equals(""))
krs.sendPacket(new ADDLINESPacket(externalEndpointsText));
srv6Router.getLinkTabel().add(krs);
} finally {
lineslock.writeLock().unlock();
}
}
private String generateSelfLineTable() {
private String generateExternalEndpointsString() {
StringBuilder sbd = new StringBuilder();
for (Iterator<MultipurposeSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) {
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next();
for (Iterator<MultiProtocolSocketAddress> iterator = externalEndpoints.iterator(); iterator.hasNext();) {
MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
sbd.append(klalbRemoteLine.toString());
sbd.append('\n');
}
@@ -668,7 +633,13 @@ public class KLALBController {
}
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
srv6Router = new SRv6Router(selfx, clock);
networkInterfaceManager.getInetAddressesExcept().add(selfx.getAddress().toInet6Address());
IPv6AddressGroup group=new IPv6AddressGroup(selfx.getAddress(),32);
srv6Router = new SRv6Router(selfx,group, clock);
if(configItem!=null) {
srv6Router.setPerformanceStrategy(PerformanceStrategy.fromDescription(configItem.getPerformanceStrategy()));
srv6Router.setDeviceName(configItem.getDeviceName());
}
srv6Router.runKLALBRouteProtocol();
routingProtocol = srv6Router.getKlalbRouteProtol();
routingProtocol.addReceiver((addr, packet) -> {
@@ -677,12 +648,13 @@ public class KLALBController {
rawPortBinder= new PortBinder(this.getSelf().getAddress());
System.out.println(" Loaded: SRv6 Stack");
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6;
if (enableVirtualAdapter&&(name!=null)) {
String name = (configItem != null && configItem.getTUNName() != null) ? configItem.getTUNName() : CONST.KLALB_S_RV6;
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(()->{
try {
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name,
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name.trim(),group, SRv6Router.MTU, dnsAddresses);
tunlink.setMonitor(datatMonitor);
// srv6Router.getLinkTabel().add(tunlink);
srv6Router.getinLoopback().setFallbackLink(tunlink);
@@ -695,7 +667,7 @@ public class KLALBController {
});
t.start();
}else {
System.out.println(" Tun Adapter Disbaled");
System.out.println(" Tun Adapter Disabled");
}
udpr=new UDPProtocolRegister(this);
@@ -715,7 +687,7 @@ public class KLALBController {
// System.out.println(context);
try {
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() {
@Override
@@ -726,7 +698,7 @@ public class KLALBController {
for (Neighbor neighbor : nb) {
if (neighbor.getLocator() != null)
sp.add(new NTPPeer(
new MultipurposeSocketAddress(iproxyname + "_Datagram",
new MultiProtocolSocketAddress(iproxyname + "datagram",
neighbor.getLocator().getAddress().toString(), 123),
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
@@ -747,7 +719,7 @@ public class KLALBController {
});
}
});
nvc2 = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
nvc2 = new NTPv4Protocol(context, new MultiProtocolSocketAddress("udp","0.0.0.0",0));
System.out.println(" Loaded: NTP Module");
} catch (UnknownHostException e) {
@@ -764,7 +736,6 @@ public class KLALBController {
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
twk.schedule(tsk1, 5000, 5000);
twk.schedule(tsk2, 5000, 5000);
twk.schedule(tsk3, 5000, 5000);
}
public KLALBController() {
@@ -830,18 +801,18 @@ public class KLALBController {
getIpv6Router().setASN(vasn);
}
List<MultipurposeSocketAddress> linele = configItem.getLineTable();
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
if (linele != null) {
getSelflineTable().addAll(linele);
}
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable();
getExternalEndpoints().addAll(linele);
}
List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
if (linetoc != null) {
linetoc.forEach((aline) -> {
addRemoteLines(aline);
});
}
List<MultipurposeSocketAddress> ntps = configItem.getNtpServerTable();
List<MultiProtocolSocketAddress> ntps = configItem.getNtpServers();
if (ntps != null) {
getNTPTable().addAll(ntps);
}
@@ -1004,24 +975,19 @@ public class KLALBController {
private List<MultipurposeSocketAddress> listens = new CopyOnWriteArrayList<>();
public void registerToProxyTypeAs(String proxyname) {
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "stream", streamSocketType);
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "datagram", datagramSocketType);
// ProxyProfileEntry.getRegister().put(proxyname, this);
}
public List<MultipurposeSocketAddress> getListenSocketAddress() {
return listens;
}
private List<MultipurposeSocketAddress> ntptable = new CopyOnWriteArrayList();
private List<MultiProtocolSocketAddress> ntptable = new CopyOnWriteArrayList();
private KLALBRoutingProtocol routingProtocol;
public List<MultipurposeSocketAddress> getNTPTable() {
public List<MultiProtocolSocketAddress> getNTPTable() {
return ntptable;
}
@@ -3,32 +3,88 @@ package org.kne.cloud.network.klalb;
import java.net.InetAddress;
import java.util.ArrayList;
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 {
private String language;
private boolean nogui;
private String VirtualAddress;
private String VirtualAddress=KLALBUtils.randomKLALBIPv6Address().toString();
private Long VirtualASN;
private List<InetAddress> DNS;
private MultipurposeSocketAddress TCPListen;
private MultipurposeSocketAddress UDPListen;
private MultiProtocolSocketAddress TCPListen=new MultiProtocolSocketAddress("0.0.0.0",4565);
private MultiProtocolSocketAddress UDPListen=new MultiProtocolSocketAddress("udp","0.0.0.0",4565);
private String VirtualSocketName;
private List<MultipurposeSocketAddress>LineTable=new ArrayList<>();
private List<MultipurposeSocketAddress>ConnectLineTable=new ArrayList<>();
private List<MultipurposeSocketAddress>ntpServerTable=new ArrayList<>();
private boolean denyLineTableQuery=false;
private boolean denyLineTableBroadcast=false;
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
private List<MultiProtocolSocketAddress> autoConnections = new ArrayList<>();
private List<MultiProtocolSocketAddress> ntpServers = new ArrayList<>();
private List<String> ExtraRoutes = new ArrayList<>();
private boolean denyExternalEndpointQuery = false;
private boolean denyExternalEndpointBroadcast = false;
private String congestionAlgorithm="BBR";
private double burstLimit=1.20;
private double burstLimit=1.50;
private double delayUpperBound=1.20;
private double delayLowerBound=1.15;
private long nagleDelayTime=1000000L;
private long linkNagleDelayTime=1000000L;
private int linkConnectionsCount=1;
private boolean enableTUN = true;
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) {
NetworkInterfaceExcepts = networkInterfaceExcepts;
}
@@ -52,8 +108,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
public KLALBControllerConfigItem(String type) {
super(type);
public KLALBControllerConfigItem() {
super("KLALBController");
}
@@ -124,63 +180,76 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
public MultipurposeSocketAddress getTCPListen() {
public MultiProtocolSocketAddress getTCPListen() {
return TCPListen;
}
public void setTCPListen(MultipurposeSocketAddress tCPListen) {
public void setTCPListen(MultiProtocolSocketAddress tCPListen) {
TCPListen = tCPListen;
}
public MultipurposeSocketAddress getUDPListen() {
public MultiProtocolSocketAddress getUDPListen() {
return UDPListen;
}
public void setUDPListen(MultipurposeSocketAddress uDPListen) {
public void setUDPListen(MultiProtocolSocketAddress uDPListen) {
UDPListen = uDPListen;
}
public List<MultipurposeSocketAddress> getLineTable() {
return LineTable;
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
return externalEndpoints;
}
public void setLineTable(List<MultipurposeSocketAddress> lineTable) {
LineTable = lineTable;
public void setExternalEndpoints(List<MultiProtocolSocketAddress> externalEndpoints) {
this.externalEndpoints = externalEndpoints;
}
public List<MultipurposeSocketAddress> getConnectLineTable() {
return ConnectLineTable;
public List<MultiProtocolSocketAddress> getAutoConnections() {
return autoConnections;
}
public void setConnectLineTable(List<MultipurposeSocketAddress> connectLineTable) {
ConnectLineTable = connectLineTable;
public void setAutoConnections(List<MultiProtocolSocketAddress> autoConnections) {
this.autoConnections = autoConnections;
}
public List<MultipurposeSocketAddress> getNtpServerTable() {
return ntpServerTable;
public List<MultiProtocolSocketAddress> getConnectLineTable() {
return autoConnections;
}
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) {
this.ntpServerTable = ntpServerTable;
public List<MultiProtocolSocketAddress> getNtpServerTable() {
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() {
return denyLineTableQuery;
public boolean isDenyExternalEndpointQuery() {
return denyExternalEndpointQuery;
}
public void setDenyLineTableQuery(boolean denyLineTableQuery) {
this.denyLineTableQuery = denyLineTableQuery;
public void setDenyExternalEndpointQuery(boolean denyExternalEndpointQuery) {
this.denyExternalEndpointQuery = denyExternalEndpointQuery;
}
public boolean isDenyLineTableBroadcast() {
return denyLineTableBroadcast;
public boolean isDenyExternalEndpointBroadcast() {
return denyExternalEndpointBroadcast;
}
public void setDenyLineTableBroadcast(boolean denyLineTableBroadcast) {
this.denyLineTableBroadcast = denyLineTableBroadcast;
public void setDenyExternalEndpointBroadcast(boolean denyExternalEndpointBroadcast) {
this.denyExternalEndpointBroadcast = denyExternalEndpointBroadcast;
}
@@ -306,146 +372,49 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
@Override
public String toString() {
return "KLALBControllerConfigItem [language=" + language + ", nogui=" + nogui + ", VirtualAddress="
+ VirtualAddress + ", VirtualASN=" + VirtualASN + ", DNS=" + DNS + ", TCPListen=" + TCPListen
+ ", UDPListen=" + UDPListen + ", VirtualSocketName=" + VirtualSocketName + ", LineTable=" + LineTable
+ ", ConnectLineTable=" + ConnectLineTable + ", ntpServerTable=" + ntpServerTable
+ ", denyLineTableQuery=" + denyLineTableQuery + ", denyLineTableBroadcast=" + denyLineTableBroadcast
+ ", congestionAlgorithm=" + congestionAlgorithm + ", delayUpperBound=" + delayUpperBound
+ ", delayLowerBound=" + delayLowerBound + ", nagleDelayTime=" + nagleDelayTime
+ ", linkNagleDelayTime=" + linkNagleDelayTime + ", linkConnectionsCount=" + linkConnectionsCount
+ ", TUNName=" + TUNName + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + "]";
return "KLALBControllerConfigItem{" +
"language='" + language + '\'' +
", nogui=" + nogui +
", VirtualAddress='" + VirtualAddress + '\'' +
", VirtualASN=" + VirtualASN +
", DNS=" + DNS +
", TCPListen=" + TCPListen +
", UDPListen=" + UDPListen +
", 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
public int hashCode() {
final int prime = 31;
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;
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);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
KLALBControllerConfigItem other = (KLALBControllerConfigItem) obj;
if (ConnectLineTable == null) {
if (other.ConnectLineTable != null)
return false;
} else if (!ConnectLineTable.equals(other.ConnectLineTable))
return false;
if (DNS == null) {
if (other.DNS != null)
return false;
} else if (!DNS.equals(other.DNS))
return false;
if (LineTable == null) {
if (other.LineTable != null)
return false;
} else if (!LineTable.equals(other.LineTable))
return false;
if (NetworkInterfaceExcepts == null) {
if (other.NetworkInterfaceExcepts != null)
return false;
} else if (!NetworkInterfaceExcepts.equals(other.NetworkInterfaceExcepts))
return false;
if (TCPListen == null) {
if (other.TCPListen != null)
return false;
} else if (!TCPListen.equals(other.TCPListen))
return false;
if (TUNName == null) {
if (other.TUNName != null)
return false;
} else if (!TUNName.equals(other.TUNName))
return false;
if (UDPListen == null) {
if (other.UDPListen != null)
return false;
} else if (!UDPListen.equals(other.UDPListen))
return false;
if (VirtualASN == null) {
if (other.VirtualASN != null)
return false;
} else if (!VirtualASN.equals(other.VirtualASN))
return false;
if (VirtualAddress == null) {
if (other.VirtualAddress != null)
return false;
} else if (!VirtualAddress.equals(other.VirtualAddress))
return false;
if (VirtualSocketName == null) {
if (other.VirtualSocketName != null)
return false;
} else if (!VirtualSocketName.equals(other.VirtualSocketName))
return false;
if (congestionAlgorithm == null) {
if (other.congestionAlgorithm != null)
return false;
} else if (!congestionAlgorithm.equals(other.congestionAlgorithm))
return false;
if (Double.doubleToLongBits(delayLowerBound) != Double.doubleToLongBits(other.delayLowerBound))
return false;
if (Double.doubleToLongBits(delayUpperBound) != Double.doubleToLongBits(other.delayUpperBound))
return false;
if (denyLineTableBroadcast != other.denyLineTableBroadcast)
return false;
if (denyLineTableQuery != other.denyLineTableQuery)
return false;
if (language == null) {
if (other.language != null)
return false;
} else if (!language.equals(other.language))
return false;
if (linkConnectionsCount != other.linkConnectionsCount)
return false;
if (linkNagleDelayTime != other.linkNagleDelayTime)
return false;
if (nagleDelayTime != other.nagleDelayTime)
return false;
if (nogui != other.nogui)
return false;
if (ntpServerTable == null) {
if (other.ntpServerTable != null)
return false;
} else if (!ntpServerTable.equals(other.ntpServerTable))
return false;
return true;
}
}
+64 -15
View File
@@ -2,7 +2,9 @@ package org.kne.cloud.network.klalb;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.Collections;
@@ -11,7 +13,7 @@ import java.util.List;
import java.util.NoSuchElementException;
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.ipv6.IPv6NetworkLink;
import org.kne.cloud.network.ipv6.RouteItem;
@@ -20,8 +22,7 @@ import org.kne.cloud.network.klalb.ui.UIEnv;
import org.kne.cloud.network.perf.Kperf;
import org.kne.cloud.network.perf.NodeBenchmark;
import org.kne.debug.TimeDebugger;
import org.kne.membandboost.MembandBenchmark;
import org.kne.membandboost.ObjectPoolBandwidthTest;
import org.kne.membandboost.benchmark.*;;
public class KLALBMain {
public static KLALBStateGUI3 ksg;
@@ -50,13 +51,15 @@ public class KLALBMain {
}catch(Throwable e) {
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.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());
kpsvr.bind(new InetSocketAddress("::0", 4564));
SocketChannelListener stlr=new SocketChannelListener(kpsvr);
@@ -82,11 +85,13 @@ public class KLALBMain {
case "help":
System.out.println(" help / ?: see help");
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-add <addr:port>: add link");
System.out.println(" links-remove <addr:port>: remove link");
System.out.println(" links-reconnect: reconnect all link");
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(" kperf <addr:port>: performance benchmark");
System.out.println(" exit: Exit");
@@ -99,6 +104,42 @@ public class KLALBMain {
e.printStackTrace();
}
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":
System.out.println("links state");
//System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动");
@@ -115,7 +156,7 @@ public class KLALBMain {
break;
case "links-add":
if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
if(addl.isEmpty()) {
//System.out.println("添加失败,线路已存在!");
@@ -135,7 +176,7 @@ public class KLALBMain {
break;
case "links-remove":
if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
if(rmvl.isEmpty()) {
System.out.println("No matched link has been found.");
@@ -161,6 +202,18 @@ public class KLALBMain {
case "kltp-state":
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
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":
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
Collections.sort(lri);
@@ -174,7 +227,7 @@ public class KLALBMain {
break;
case "kperf":
if(sc.length>=2) {
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
Kperf kp=new Kperf(mpsa);
kp.startPerfing();
}else {
@@ -185,10 +238,6 @@ public class KLALBMain {
case "membandbenchmark":
MembandBenchmark.main(args);
break;
case "memqueuebenchmark":
ObjectPoolBandwidthTest.main(args);
break;
//case "$$SYSTEM:":
//System.out.println();
//break;
@@ -2,12 +2,6 @@ package org.kne.cloud.network.klalb;
import java.io.IOException;
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 void writeKLALBPacket(KLALBPacket kp) throws IOException;
@@ -7,7 +7,7 @@ import java.net.UnknownHostException;
import java.util.function.Consumer;
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.ProtocolDetectorServerSocket;
import org.kne.cloud.network.ProtocolDetectorSocket;
@@ -30,8 +30,8 @@ public class KLALBProtocolDetectSocketListener extends SocketListener {
super.open();
}
public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException {
super(multipurposeSocketAddress);
public KLALBProtocolDetectSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress, KLALBController klalbc) throws IOException {
super(multiProtocolSocketAddress);
this.klalbController=klalbc;
}
@@ -5,34 +5,63 @@ import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.net.InetSocketAddress;
import java.lang.reflect.Type;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.HashSet;
import org.kne.cloud.network.*;
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
import org.kne.cloud.network.klalb.ui.Language;
import org.kne.cloud.network.klalb.ui.UIEnv;
import org.kne.cloud.network.klalb.web.KLALBWebServer;
import java.util.Set;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
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;
public class KLALBProxySystem {
private Set<Proxy> proxys=new HashSet<>();
private KLALBController klalbController;
private KLALBRemoteManagement krm;
private KLALBWebServer webServer;
private KLALBConfig config;
private Gson gson;
private File jsonFile;
{
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
MultipurposeSocketAddress.registerToGsonBuilder(gb);
MultiProtocolSocketAddress.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();
}
public Gson getGson() {
return gson;
}
public Set<Proxy> getProxys() {
return proxys;
}
@@ -55,42 +84,54 @@ public class KLALBProxySystem {
public KLALBProxySystem() {
}
public void enableRemoteManagement() throws IOException {
if(krm==null) {
krm=new KLALBRemoteManagement(this);
}else {
throw new IllegalStateException("Remote Management already enabled!");
public void enableWebServer() throws IOException {
KLALBControllerConfigItem cci = getControllerConfig();
MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
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 {
if(krm==null) {
krm=new KLALBRemoteManagement(this,bind);
}else {
throw new IllegalStateException("Remote Management already enabled!");
}
public boolean isWebServerEnabled() {
return webServer != null && webServer.isRunning();
}
public boolean isRemoteManagementEnabled() {
return krm!=null;
public KLALBWebServer getWebServer() {
return webServer;
}
public void disableRemoteManagement() {
if(krm!=null) {
krm.close();
krm=null;
public void disableWebServer() {
if (webServer != null) {
webServer.stop();
webServer = null;
}
}
public void loadConfigJson(File jsonFile) throws IOException {
this.jsonFile=jsonFile;
FileReader fr = null;
try {
fr=new FileReader(jsonFile);
loadConfigJson(fr);
}finally {
if(fr!=null)
fr.close();
if(jsonFile.exists()) {
FileReader fr = null;
try {
fr = new FileReader(jsonFile);
loadConfigJson(fr);
} finally {
if (fr != null)
fr.close();
}
}else{
loadConfig(KLALBConfig.getDefault());
}
}
@@ -127,10 +168,12 @@ public class KLALBProxySystem {
String vsne=kcci.getVirtualSocketName();
//if(vsne!=null) {
MultipurposeSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
//}
MultipurposeSocketAddress tcple=kcci.getTCPListen();
if(vsne!=null) {
MultiProtocolSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
}else{
MultiProtocolSocketAddress.getSocketTypeRegister().put(CONST.KLTP_VIRTUAL_SOCKET_NAME, klalbController.getStreamSocketType());
}
MultiProtocolSocketAddress tcple=kcci.getTCPListen();
if(tcple!=null) {
SocketChannelListener tcpl = null;
@@ -148,13 +191,13 @@ public class KLALBProxySystem {
}
});
MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(tcple.getType(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
MultiProtocolSocketAddress mpsa2=new MultiProtocolSocketAddress(tcple.getProtocol(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
klalbController.getListenSocketAddress().add(mpsa2);
} catch (IOException e1) {
e1.printStackTrace();
}
}
MultipurposeSocketAddress udple=kcci.getUDPListen();
MultiProtocolSocketAddress udple=kcci.getUDPListen();
if(udple!=null) {
DatagramSocketListener udpl = null;
try {
@@ -171,7 +214,7 @@ public class KLALBProxySystem {
}
});
MultipurposeSocketAddress mpsau2=new MultipurposeSocketAddress(udple.getType(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
MultiProtocolSocketAddress mpsau2=new MultiProtocolSocketAddress(udple.getProtocol(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
//klalbController.getListenSocketAddress().add(mpsau2);
} catch (IOException e1) {
e1.printStackTrace();
@@ -191,30 +234,24 @@ public class KLALBProxySystem {
}
public void saveConfigToFile() {
if (jsonFile != null && config != null) {
String json = gson.toJson(config);
try (FileWriter fw = new FileWriter(jsonFile)) {
fw.write(json);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private KLALBStateGUI3 kgui;
public KLALBStateGUI3 getKLALBGUI() {
if(kgui==null) {
kgui=new KLALBStateGUI3(klalbController);
kgui.loadConfig(config);
kgui.setSaveComsumer((cfg)->{
String json=gson.toJson(cfg);
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();
}
}
}
saveConfigToFile();
});
}
return kgui;
@@ -2,6 +2,9 @@ package org.kne.cloud.network.klalb;
import java.io.IOException;
import java.io.StreamCorruptedException;
import java.net.BindException;
import java.net.NoRouteToHostException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.UnresolvedAddressException;
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.ReliabilityBackoffTimeClock;
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.congestion.BBRCongestionAlgorithm;
import org.kne.cloud.network.congestion.CongestionAlgorithm;
@@ -100,8 +103,8 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
private LinkStatus status=new LinkStatus();
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
private volatile KLALBPacketLink kplink;
private MultipurposeSocketAddress bindAddress;
private MultipurposeSocketAddress socketAddress;
private MultiProtocolSocketAddress bindAddress;
private MultiProtocolSocketAddress socketAddress;
public void setKplink(KLALBPacketLink kplink) {
@@ -112,11 +115,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
return kplink;
}
public MultipurposeSocketAddress getSocketAddress() {
public MultiProtocolSocketAddress getSocketAddress() {
return socketAddress;
}
public MultipurposeSocketAddress getBindAddress() {
public MultiProtocolSocketAddress getBindAddress() {
return bindAddress;
}
@@ -134,20 +137,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
return peerAddress;
}
public KLALBRemoteLink(KLALBController controller, MultipurposeSocketAddress mpa, IPv6AddressGroup address) {
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
this(controller,mpa, null,address);
}
public KLALBRemoteLink(KLALBController controller,KLALBPacketLink kpl, IPv6AddressGroup address) {
super(false);
this.klalbController=controller;
sysclk = klalbController.getClock();
this.kplink = kpl;
this.addressGroup = address;
monitor=new SpeedAndTrafficAndDelayMonitorDataImpl(sysclk);
name=kpl.toString();
setCostSupplier( CostSupplierFactory.owdSupplier(monitor));
}
private KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr, IPv6AddressGroup address) {
private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
super(false);
this.klalbController=controller;
sysclk = klalbController.getClock();
this.socketAddress = mpa;
@@ -159,14 +165,15 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
} else {
name=bindaddr.toString() + "" + mpa.toString();
}
setCostSupplier( CostSupplierFactory.owdSupplier( monitor));
}
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bind) {
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
this(controller,mpa, bind, null);
}
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa) {
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
this(controller,mpa, (IPv6AddressGroup) null);
}
@@ -283,7 +290,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
sendLock.unlock();
}
}
tlock.parkNanos(1000000L);
tlock.parkNanos(10000000L);
}
} catch (StreamCorruptedException sce) {
@@ -464,16 +471,18 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
break;
case KLALBPacket.VADDR:
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
remoteVaddr = vdr;
sendPacket(new VADDRACKPacket());
if (fst.compareAndSet(true, false)) {
// coll.resetCoolingTime();
status.setState(LinkStatus.UP);
KLALBRemoteLink.this.onOnlineStateUpdate();
if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
close();
}else {
remoteVaddr = vdr;
sendPacket(new VADDRACKPacket());
if (fst.compareAndSet(true, false)) {
// coll.resetCoolingTime();
status.setState(LinkStatus.UP);
KLALBRemoteLink.this.onOnlineStateUpdate();
}
KLALBRemoteLink.this.onLocatorUpdate();
}
KLALBRemoteLink.this.onLocatorUpdate();
break;
case KLALBPacket.VADDRACK:
break;
@@ -567,7 +576,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
public void run() {
while (!closed) {
status.updateReliability();
monitor.update();
if(status.getState()!=LinkStatus.DOWN) {
monitor.update();
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
@@ -577,7 +588,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
}
}
}
private RemoteSender sender;
private RemoteReceiver receiver;
@@ -672,7 +683,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
}
} catch (StreamCorruptedException sce) {
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)
e.printStackTrace();
} catch (Exception e) {
@@ -831,10 +858,6 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
}
}
@Override
public boolean isLoopBack() {
return false;
}
BandwidthDistributer bandwidthDistributer=new BandwidthDistributer<>();
@Override
public List<Neighbor> getNeighborsInfo() {
@@ -923,29 +946,28 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
return (!isClosed()) && (status.getState() == LinkStatus.UP) ;
}
@Override
/*@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
IPv6AddressGroup adg = addressGroup;
/*if (adg != null)
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
null, "D", true));*/
if(isUp()) {
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
addresses.getAddress().getAddress(), this, RouteItem.SRv6_ENDXSID, 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D");
rlist.add(ri);
if (addresses.getLocator() != null) {
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
"KLALB SRv6", 13, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
RouteItem.SRv6_ENDSID, 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D");
rlist.add(ris);
}
}
return rlist;
}
return rlist;
}*/
@Override
public void setCongressCondition(Lock lock, Condition condition) {
@@ -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.FixedSizeStripedLongCounter;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
public class KLALBUtils {
@@ -51,6 +51,27 @@ public class KLALBUtils {
v[3] = 0x01;
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) {
System.out.println(uuidToIP(new UUID(-1,-1)));
}
@@ -208,7 +229,7 @@ public class KLALBUtils {
return col;
}
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, MultipurposeSocketAddress targetAddress,boolean buffered)
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress, MultiProtocolSocketAddress targetAddress, boolean buffered)
throws IOException {
if (targetAddress.isStream()) {
if (targetAddress.supportNIO()) {
@@ -235,8 +256,8 @@ public class KLALBUtils {
}
}
}
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress,
MultipurposeSocketAddress targetAddress,boolean buffered, int timeout) throws UnknownHostException, IOException {
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress,
MultiProtocolSocketAddress targetAddress, boolean buffered, int timeout) throws UnknownHostException, IOException {
if (targetAddress.isStream()) {
if (targetAddress.supportNIO()) {
if (bindAddress != null) {
@@ -0,0 +1,71 @@
package org.kne.cloud.network.klalb;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
public class NetworkInterfaceManager {
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
private List<InetAddress> inetAddressesExcept = new ArrayList<>();
public List<NetworkInterface> getNetworkInterfaceExcept() {
return networkInterfaceExcept;
}
public List<InetAddress> getInetAddressesExcept() {
return inetAddressesExcept;
}
public List<NetworkInterface> getAllAvaliableNetworkInterface() throws SocketException {
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
while (eu.hasMoreElements()) {
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
if (networkInterface.getDisplayName()
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
continue;
}
if (networkInterfaceExcept.contains(networkInterface)) {
continue;
}
if (networkInterface.isUp()) {
interfaces.add(networkInterface);
}
}
return interfaces;
}
public List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
List<NetworkInterface> interfaces=getAllAvaliableNetworkInterface();
List<InetAddress> addresses = new ArrayList<InetAddress>();
for (NetworkInterface nif:interfaces){
addresses.addAll( getNetworkInterfaceAddress(nif));
}
return addresses;
}
public List<InetAddress> getNetworkInterfaceAddress(NetworkInterface networkInterface) throws SocketException {
List<InetAddress> addresses = new ArrayList<InetAddress>();
// System.out.println(networkInterface+" "+networkInterface.isUp());
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
while (ei.hasMoreElements()) {
InetAddress inetAddress = (InetAddress) ei.nextElement();
if (inetAddress.isLoopbackAddress()){
continue;
}
if (inetAddressesExcept.contains( inetAddress)) {
continue;
}
addresses.add(inetAddress);
}
return addresses;
}
}
@@ -0,0 +1,35 @@
package org.kne.cloud.network.klalb;
import org.kne.cloud.network.klalb.ui.UIEnv;
public enum PerformanceStrategy {
SINGLE_CORE("singlecore"),
MULTI_FILL("multifill"),
MULTI_SCATTER("multiscatter");
private String description;
private PerformanceStrategy(String description) {
this.description = description;
}
@Override
public String toString() {
return description;
}
/**
* 根据 description 查找对应的枚举对象。
*
* @param description 描述文本(资源包key
* @return 对应的枚举对象
*/
public static PerformanceStrategy fromDescription(String description) {
for (PerformanceStrategy strategy : values()) {
if (strategy.description.equals(description)) {
return strategy;
}
}
return null;
}
}
@@ -1,40 +1,23 @@
package org.kne.cloud.network.klalb;
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.Map;
import java.util.Map.Entry;
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
import org.kne.cloud.network.DefaultSocketBridgeFactory;
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.SocketBridgeFactory;
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 {
private MultipurposeSocketAddress Listen;
private MultiProtocolSocketAddress Listen;
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
private HostPortMap Connect=new HostPortMap();
public SocketBridgeConfigItem( MultipurposeSocketAddress listen,
public SocketBridgeConfigItem( MultiProtocolSocketAddress listen,
LinkedHashMap<String, String> bridge, HostPortMap connect) {
super("SocketBridge");
Listen = listen;
@@ -85,12 +68,12 @@ public class SocketBridgeConfigItem extends KLALBConfigItem {
public Proxy createProxy(KLALBController controller) throws IOException {
HostPortMap mapp=new HostPortMap();
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
MultipurposeSocketAddress l=Listen;
MultiProtocolSocketAddress l=Listen;
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
MultipurposeSocketAddress r=getDefaultConnect(Connect,mapp);
return new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
MultiProtocolSocketAddress r=getDefaultConnect(Connect,mapp);
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)->{
mapp.put(en.getKey(), en.getValue());
});
@@ -116,29 +116,20 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
}*/
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.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
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.MultipurposeSocketAddress;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.PacketRebuilder;
import org.kne.cloud.network.PacketSpliter;
import org.kne.cloud.network.SpeedLimiter;
import org.kne.debug.TimeDebugger;
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
private PacketSpliter pslr;
@@ -146,7 +137,7 @@ public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink impl
@Override
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;
@@ -13,7 +13,7 @@ import java.nio.channels.SocketChannel;
import java.util.concurrent.atomic.AtomicLong;
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.ThreadTool;
import org.kne.io.KNEChannels;
@@ -45,7 +45,7 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
@Override
public String toString() {
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) {
e.printStackTrace();
return "?←?";
@@ -1,21 +1,18 @@
package org.kne.cloud.network.klalb;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.MultiProtocolSocketAddress;
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
@Override
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;
@@ -346,7 +346,16 @@ public class GraphPanel extends JPanel {
g.setFont(UIEnv.getFont().deriveFont(10.0f));
//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() {
@@ -20,7 +20,7 @@ import org.jfree.chart.plot.dial.*;
import org.jfree.data.general.DefaultValueDataset;
import org.kne.cloud.clock.HighAccuracyClock;
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.IPv6NetworkLink;
import org.kne.cloud.network.klalb.*;
@@ -75,12 +75,18 @@ public class KLALBStateGUI3 extends XFrame {
private JTextField addressField; // 地址显示框
private JTextField addressFieldSet; // 地址设置框
private JTextField asnFieldSet; // ASN设置框
private JCheckBox enableTUN; // 虚拟网卡启用禁用
private JTextField tunDeviceName; // 虚拟网卡名称设置框
private JTextField webListenSet; // Web API 监听地址设置框
private NetworkGraphPanel graph; // 网络图面板
private JTextField textFieldLocate; // 定位地址输入框
private JTextField asnField2; // ASN显示框
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 JTextField tcpListeningSet; // TCP监听设置
private JTextField udpListeningSet; // UDP监听设置
@@ -92,6 +98,7 @@ public class KLALBStateGUI3 extends XFrame {
private JTextArea ntpServerSet; // NTP服务器设置区域
private JCheckBox denyBroadcast;
private JCheckBox denyQuery;
private JCheckBox webApiEnabled; // 启用 Web API 开关
private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板
private JCheckBox nogui;
@@ -138,6 +145,7 @@ public class KLALBStateGUI3 extends XFrame {
* 初始化UI界面
*/
private void initUI(KLALBController kc, String title) {
kcontroller = kc;
// 设置窗口基本属性
setIconImage(UIEnv.getIcon());
setTitleColor(UIEnv.getDefaultTitleColor());
@@ -597,7 +605,11 @@ public class KLALBStateGUI3 extends XFrame {
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
btnNewButton.addActionListener(e -> {
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) {
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
JOptionPane.ERROR_MESSAGE);
@@ -781,18 +793,42 @@ public class KLALBStateGUI3 extends XFrame {
addressFieldSet = addr6.getTextField();
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服务器设置
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
CONST.itemwidth, CONST.settingheight * 5);
dnsAreaSet = dns.getTextArea();
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设置
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
CONST.itemwidth, CONST.settingheight);
asnFieldSet = asn.getTextField();
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"),
CONST.itemwidth, CONST.settingheight);
tunDeviceName = tunName.getTextField();
@@ -877,7 +913,22 @@ public class KLALBStateGUI3 extends XFrame {
CONST.itemwidth, CONST.settingheight);
denyBroadcast=denyBroadcastc.getCheckBox();
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"),
@@ -888,8 +939,10 @@ public class KLALBStateGUI3 extends XFrame {
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
CONST.itemwidth, CONST.settingheight);
comboPerformance = performanceStrategy.getComboBox();
comboPerformance.addItem("BBR");
comboPerformance.addItem("Vegas2");
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.SINGLE_CORE));
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_FILL));
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_SCATTER));
comboPerformance.setSelectedIndex(1);
settings.getView().add(performanceStrategy);
@@ -1004,7 +1057,7 @@ public class KLALBStateGUI3 extends XFrame {
private void saveConfig() {
if (config == null) {
config = new KLALBConfig();
config.add(new KLALBControllerConfigItem("KLALBController"));
config.add(new KLALBControllerConfigItem());
}
for (KLALBConfigItem item : config) {
@@ -1016,6 +1069,26 @@ public class KLALBStateGUI3 extends XFrame {
kck.setNogui(nogui.isSelected());
// 保存设备名称
String dnametext = deviceNameSet.getText().trim();
if (dnametext.equals("")) {
kck.setDeviceName(null);
} else {
kck.setDeviceName(dnametext);
}
if (kcontroller != null && kcontroller.getIpv6Router() != null) {
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName());
}
// 保存设备描述
String ddesctext = deviceDescriptionSet.getText().trim();
if (ddesctext.equals("")) {
kck.setDeviceDescription(null);
} else {
kck.setDeviceDescription(ddesctext);
}
// 保存IPv6地址
String ttext = addressFieldSet.getText().trim();
if (ttext.equals("")) {
@@ -1052,6 +1125,24 @@ public class KLALBStateGUI3 extends XFrame {
}
kck.setDNS(iaddr);
// 保存额外路由列表
String[] spltEr = extraRoutesSet.getText().split("\n");
List<String> eroutes = new ArrayList<>();
for (String str : spltEr) {
str = str.trim();
if (!str.isEmpty()) {
try {
eroutes.add(KLALBUtils.parseCidr(str));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildextraroutes"),
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
return;
}
}
}
kck.setExtraRoutes(eroutes);
// 保存ASN
String asntext = asnFieldSet.getText();
if (asntext.equals("")) {
@@ -1067,12 +1158,30 @@ public class KLALBStateGUI3 extends XFrame {
}
}
String tunNameText=tunDeviceName.getText();
if(tunNameText.equals("")) {
kck.setEnableTUN( enableTUN.isSelected());
String tunNameText=tunDeviceName.getText().trim();
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
kck.setTUNName(null);
}else {
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监听设置
String tcptext = tcpListeningSet.getText();
@@ -1080,7 +1189,7 @@ public class KLALBStateGUI3 extends XFrame {
kck.setTCPListen(null);
} else {
try {
kck.setTCPListen(new MultipurposeSocketAddress(tcptext));
kck.setTCPListen(new MultiProtocolSocketAddress(tcptext));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
@@ -1095,7 +1204,7 @@ public class KLALBStateGUI3 extends XFrame {
kck.setUDPListen(null);
} else {
try {
kck.setUDPListen(new MultipurposeSocketAddress(udptext));
kck.setUDPListen(new MultiProtocolSocketAddress(udptext,"udp"));
} catch (RuntimeException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
@@ -1106,13 +1215,13 @@ public class KLALBStateGUI3 extends XFrame {
// 保存开放线路表
String[] splt1 = openLineTabelSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr1 = new ArrayList<>();
List<MultiProtocolSocketAddress> iaddr1 = new ArrayList<>();
boolean show1 = true;
for (String str : splt1) {
try {
str = str.trim();
if (!str.isEmpty())
iaddr1.add(new MultipurposeSocketAddress(str));
iaddr1.add(new MultiProtocolSocketAddress(str));
} catch (RuntimeException e) {
e.printStackTrace();
if (show1) {
@@ -1123,17 +1232,17 @@ public class KLALBStateGUI3 extends XFrame {
}
}
}
kck.setLineTable(iaddr1);
kck.setExternalEndpoints(iaddr1);
// 保存自动连接线路表
String[] splt11 = connectLineTabelSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr11 = new ArrayList<>();
List<MultiProtocolSocketAddress> iaddr11 = new ArrayList<>();
boolean show11 = true;
for (String str : splt11) {
try {
str = str.trim();
if (!str.isEmpty())
iaddr11.add(new MultipurposeSocketAddress(str));
iaddr11.add(new MultiProtocolSocketAddress(str));
} catch (RuntimeException e) {
e.printStackTrace();
if (show11) {
@@ -1148,13 +1257,13 @@ public class KLALBStateGUI3 extends XFrame {
// 保存NTP服务器表
String[] splt111 = ntpServerSet.getText().split("\n");
List<MultipurposeSocketAddress> iaddr111 = new ArrayList<>();
List<MultiProtocolSocketAddress> iaddr111 = new ArrayList<>();
boolean show111 = true;
for (String str : splt111) {
try {
str = str.trim();
if (!str.isEmpty())
iaddr111.add(new MultipurposeSocketAddress(str));
iaddr111.add(new MultiProtocolSocketAddress(str,"ntp"));
} catch (RuntimeException e) {
e.printStackTrace();
if (show111) {
@@ -1176,10 +1285,15 @@ public class KLALBStateGUI3 extends XFrame {
kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue());
kck.setDenyLineTableQuery(denyQuery.isSelected());
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected());
kck.setDenyExternalEndpointQuery(denyQuery.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.setBurstLimit(1.0+burstLimit.getSlider().getValue()/1000.0);
@@ -1213,58 +1327,60 @@ public class KLALBStateGUI3 extends XFrame {
tsk = new TimerTask() {
@Override
public void run() {
Object[] kll = kc.getLines().toArray();
if (isVisible()) {
Object[] kll = kc.getLines().toArray();
// 遍历所有线路更新或添加TPanel2
for (Object obj : kll) {
IPv6NetworkLink link = (IPv6NetworkLink) obj;
if (!(link instanceof KLALBRemoteLink)) {
continue;
// 遍历所有线路更新或添加TPanel2
for (Object obj : kll) {
IPv6NetworkLink link = (IPv6NetworkLink) obj;
if (!(link instanceof KLALBRemoteLink)) {
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();
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 (kc.getLines().contains(((TPanel2) tp).getTunnel())) {
try {
if (tp.isVisible())
((TPanel2) tp).updateTraffic();
} catch (RuntimeException e) {
e.printStackTrace();
}
} else {
ysp.getView().remove(tp);
}
}
}
// 如果没有找到创建新面板
if (!found) {
TPanel2 tp = new TPanel2(ent, kc);
tp.setVisible(showoffline.isSelected() || (
ent.getState()!= LinkStatus.DOWN));
ysp.getView().add(tp);
}
ysp.updateScrool();
}
// 更新所有面板并移除不存在的线路
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);
@@ -1440,6 +1556,14 @@ public class KLALBStateGUI3 extends XFrame {
nogui.setSelected( kck.isNogui());
// 加载设备名称
String dname = kck.getDeviceName();
deviceNameSet.setText(dname != null ? dname : "");
// 加载设备描述
String ddesc = kck.getDeviceDescription();
deviceDescriptionSet.setText(ddesc != null ? ddesc : "");
// 加载IPv6地址
String vaddr = kck.getVirtualAddress();
addressFieldSet.setText(vaddr != null ? vaddr : "");
@@ -1448,31 +1572,45 @@ public class KLALBStateGUI3 extends XFrame {
List<InetAddress> dns = kck.getDNS();
dnsAreaSet.setText(listToStr(dns));
// 加载额外路由
List<String> ers = kck.getExtraRoutes();
extraRoutesSet.setText(listToStr2(ers));
// 加载ASN
Long vasn = kck.getVirtualASN();
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
enableTUN.setSelected(kck.isEnableTUN());
String tunname=kck.getTUNName();
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监听
MultipurposeSocketAddress mpat = kck.getTCPListen();
MultiProtocolSocketAddress mpat = kck.getTCPListen();
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
// 加载UDP监听
MultipurposeSocketAddress mpau = kck.getUDPListen();
MultiProtocolSocketAddress mpau = kck.getUDPListen();
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
// 加载开放线路表
List<MultipurposeSocketAddress> linet = kck.getLineTable();
List<MultiProtocolSocketAddress> linet = kck.getExternalEndpoints();
openLineTabelSet.setText(listToStr2(linet));
// 加载自动连接线路表
List<MultipurposeSocketAddress> clinet = kck.getConnectLineTable();
List<MultiProtocolSocketAddress> clinet = kck.getConnectLineTable();
connectLineTabelSet.setText(listToStr2(clinet));
// 加载NTP服务器表
List<MultipurposeSocketAddress> ntps = kck.getNtpServerTable();
List<MultiProtocolSocketAddress> ntps = kck.getNtpServerTable();
ntpServerSet.setText(listToStr2(ntps));
// 加载网络接口排除列表
@@ -1493,10 +1631,21 @@ public class KLALBStateGUI3 extends XFrame {
}
linkConnectionsCount.getSlider().setValue(conc);
denyQuery.setSelected( kck.isDenyLineTableQuery());
denyQuery.setSelected( kck.isDenyExternalEndpointQuery());
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;
}
}
}
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast());
String con=kck.getCongestionAlgorithm();
if(con==null) {
congestions.getComboBox().setSelectedItem("BBR");
@@ -1630,4 +1779,4 @@ public class KLALBStateGUI3 extends XFrame {
KLALBStateGUI3 kcg = new KLALBStateGUI3(new KLALBController());
kcg.setVisible(true);
}
}
}
@@ -8,7 +8,7 @@ import javax.swing.border.LineBorder;
import org.jfree.chart.plot.dial.DialTextAnnotation;
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.KLALBUtils;
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
@@ -143,9 +143,9 @@ public class KperfGUI extends XFrame{
if(kperf==null) {
try {
String tar=targetAddressField.getText();
MultipurposeSocketAddress msa=new MultipurposeSocketAddress(tar);
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(tar);
String sour=sourceAddressField.getText();
MultipurposeSocketAddress msas=new MultipurposeSocketAddress(sour);
MultiProtocolSocketAddress msas=new MultiProtocolSocketAddress(sour);
kperf=new Kperf(msa,msas);
kperf.setReports(reports);
kperf.startPerfing();
@@ -650,10 +650,10 @@ public class KperfGUI extends XFrame{
startButton.setText(UIEnv.getRsb().getString("stoptest"));
}
}
public void setTarget(MultipurposeSocketAddress target) {
public void setTarget(MultiProtocolSocketAddress target) {
targetAddressField.setText(target.toString());
}
public void setTarget(MultipurposeSocketAddress target,MultipurposeSocketAddress source) {
public void setTarget(MultiProtocolSocketAddress target, MultiProtocolSocketAddress source) {
targetAddressField.setText(target.toString());
sourceAddressField.setText(source.toString());
}
@@ -18,7 +18,10 @@ import org.jfree.chart.JFreeChart;
import org.jfree.data.time.Millisecond;
import org.jfree.data.time.TimeSeries;
import org.jfree.data.time.TimeSeriesCollection;
import org.kne.cloud.network.congestion.CongestionAlgorithm;
import org.kne.cloud.network.congestion.DetnetCongestionAlgorithm;
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
import org.kne.cloud.network.congestion.Vegas2CongestionAlgorithm;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
import org.kne.cloud.network.ipv6.IPv6Packet;
@@ -42,12 +45,14 @@ public class LineMonitorGUI extends XFrame {
private Timer timer1;
private Timer timer2;
private Timer timer3;
private Timer timer4;
private TimerTask tsk3;
private TimerTask tsk4, tsk5,tsk6;
private TimerTask tsk4, tsk5,tsk6,tsk7;
private TimerTask d1;
private TimerTask d2;
private TimerTask d3;
private TimerTask d4;
private TimeSeries spdup = new TimeSeries(UIEnv.getRsb().getString("uploadspeed"));
private TimeSeries spddown = new TimeSeries(UIEnv.getRsb().getString("downloadspeed"));
@@ -62,13 +67,21 @@ public class LineMonitorGUI extends XFrame {
private TimeSeries queueused = new TimeSeries(UIEnv.getRsb().getString("queueused"));
private TimeSeries buffermax = new TimeSeries(UIEnv.getRsb().getString("maxbuffer"));
private TimeSeries rttbaseline = new TimeSeries(UIEnv.getRsb().getString("rttbaseline"));
private TimeSeries rtt = new TimeSeries(UIEnv.getRsb().getString("rtt"));
private TimeSeries rttmin=new TimeSeries(UIEnv.getRsb().getString("rttmin"));
private TimeSeries rttmax=new TimeSeries(UIEnv.getRsb().getString("rttmax"));
private KLALBRemoteLink tr;
private JFreeChart jfc;
private JFreeChart jfce;
private JFreeChart jbfc;
private JFreeChart jbfcv;
private JLabel spdp;
private JLabel delp;
private JLabel sbpdp;
private JLabel sbpdpv;
private long timeRange = 5000;
private long[] values = new long[] { 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000 };
private JPanel panel_1;
@@ -155,6 +168,22 @@ public class LineMonitorGUI extends XFrame {
jp.add(sbpdp);
TimeSeriesCollection tbsc1 = new TimeSeriesCollection();
tbsc1.addSeries(rttbaseline);
tbsc1.addSeries(rtt);
tbsc1.addSeries(rttmax);
tbsc1.addSeries(rttmin);
jbfcv = ChartFactory.createTimeSeriesChart(UIEnv.getRsb().getString("congestionmonitor"),
UIEnv.getRsb().getString("time") + "(s)", UIEnv.getRsb().getString("delay") + "(ms)", tbsc1);
jbfcv.getXYPlot().setBackgroundPaint(Color.BLACK);
jbfcv.getXYPlot().getRenderer().setSeriesPaint(0, Color.CYAN);
jbfcv.getXYPlot().getRenderer().setSeriesPaint(1, Color.YELLOW);
jbfcv.getXYPlot().getRenderer().setSeriesPaint(2, Color.RED);
jbfcv.getXYPlot().getRenderer().setSeriesPaint(3, Color.GREEN);
changeFont(jbfcv);
sbpdpv = new JLabel();
sbpdpv.setBorder(new LineBorder(Color.DARK_GRAY));
jp.add(sbpdpv);
@@ -262,6 +291,14 @@ public class LineMonitorGUI extends XFrame {
buffermax.setMaximumItemCount((int) timeRange/5);
queueused.setMaximumItemCount((int) timeRange/5);
}
synchronized (jbfcv) {
jbfcv.getXYPlot().getDomainAxis().setFixedAutoRange(timeRange);
bufferused.setMaximumItemCount((int) timeRange/5);
buffermax.setMaximumItemCount((int) timeRange/5);
queueused.setMaximumItemCount((int) timeRange/5);
}
windowSelect.setText(timeRange / 1000 + "s");
}
@@ -328,6 +365,24 @@ public class LineMonitorGUI extends XFrame {
}
}
}
private void recordCongestionData() {
if (isVisible()) {
Millisecond ms = new Millisecond(
new Date(tr.getMonitor().getClock().getCurrentTimeMillis()));
synchronized (jbfcv) {
CongestionAlgorithm window=tr.getWindow().getAlgorithm();
if(window !=null&&window instanceof DetnetCongestionAlgorithm) {
Vegas2CongestionAlgorithm deta=(Vegas2CongestionAlgorithm) window;
double baseRTT=deta.getBaseRTT();
rttbaseline.addOrUpdate(ms, baseRTT / 1000000.0);
rtt.addOrUpdate(ms, deta.getCurrentRTT() / 1000000.0);
rttmax.addOrUpdate(ms, baseRTT*deta.getUpperDelayBound() / 1000000.0);
rttmin.addOrUpdate(ms, baseRTT*deta.getLowerDelayBound() / 1000000.0);
}
}
}
}
@Override
public void setVisible(boolean b) {
if (b) {
@@ -336,6 +391,7 @@ public class LineMonitorGUI extends XFrame {
timer1 = new Timer();
timer2 = new Timer();
timer3 = new Timer();
timer4 = new Timer();
tsk3 = new TimerTask() {
@Override
@@ -365,6 +421,13 @@ public class LineMonitorGUI extends XFrame {
recordBufferData();
}
};
tsk7 = new TimerTask() {
@Override
public void run() {
recordCongestionData();
}
};
d1 = new TimerTask() {
@Override
@@ -406,6 +469,20 @@ public class LineMonitorGUI extends XFrame {
}
}
};
d4 = new TimerTask() {
@Override
public void run() {
if (isVisible()) {
synchronized (jbfcv) {
ImageIcon i3 = new ImageIcon(
jbfcv.createBufferedImage(sbpdpv.getWidth(), sbpdpv.getHeight()));
sbpdpv.setIcon(i3);
}
}
}
};
timer.scheduleAtFixedRate(tsk3, 25, 25);
timer1.scheduleAtFixedRate(tsk4, 10, 10);
timer1.scheduleAtFixedRate(d1, 25, 25);
@@ -413,6 +490,8 @@ public class LineMonitorGUI extends XFrame {
timer2.scheduleAtFixedRate(d2, 25, 25);
timer3.scheduleAtFixedRate(tsk6, 10, 10);
timer3.scheduleAtFixedRate(d3, 25, 25);
timer4.scheduleAtFixedRate(tsk7, 25, 25);
timer4.scheduleAtFixedRate(d4, 25, 25);
}
} else {
@@ -422,12 +501,18 @@ public class LineMonitorGUI extends XFrame {
tsk4.cancel();
if (tsk5 != null)
tsk5.cancel();
if (tsk6 != null)
tsk6.cancel();
if (tsk7 != null)
tsk7.cancel();
if (d1 != null)
d1.cancel();
if (d2 != null)
d2.cancel();
if (d3 != null)
d3.cancel();
if (d4 != null)
d4.cancel();
if (timer != null)
timer.cancel();
if (timer1 != null)
@@ -438,6 +523,9 @@ public class LineMonitorGUI extends XFrame {
if (timer3 != null)
timer3.cancel();
if (timer4 != null)
timer4.cancel();
timer = null;
}
super.setVisible(b);
@@ -1,48 +1,25 @@
package org.kne.cloud.network.klalb.ui;
import java.awt.BasicStroke;
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.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Dimension2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.text.AttributedCharacterIterator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.border.LineBorder;
import org.kne.cloud.network.MultipurposeSocketAddress;
import org.kne.cloud.network.MultiProtocolSocketAddress;
import org.kne.cloud.network.ipv6.IPv6Address;
import org.kne.cloud.network.klalb.KLALBController;
import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
public class NetworkGraphPanel extends GraphPanel {
@@ -70,7 +47,7 @@ public class NetworkGraphPanel extends GraphPanel {
}
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
super(getText(address), color, x, y, ismarked);
super(getNodeText(address), color, x, y, ismarked);
this.address=address;
initign();
}
@@ -105,8 +82,8 @@ public class NetworkGraphPanel extends GraphPanel {
public void actionPerformed(ActionEvent e) {
KperfGUI kpfg=new KperfGUI();
kpfg.setVisible(true);
MultipurposeSocketAddress source=new MultipurposeSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
MultipurposeSocketAddress target=new MultipurposeSocketAddress("KLALB_Stream", address.toString(), 4564);
MultiProtocolSocketAddress source=new MultiProtocolSocketAddress("kltp",controller.getIpv6Router().getLocator().getAddress().toString(),0);
MultiProtocolSocketAddress target=new MultiProtocolSocketAddress("kltp", address.toString(), 4564);
kpfg.setTarget(target,source);
}
});
@@ -160,18 +137,34 @@ public class NetworkGraphPanel extends GraphPanel {
return address2.toCompressedString();
}
public void updateLabel() {
setText(getNodeText(address));
}
public IPv6Address getAddress() {
return address;
}
public void setAddress(IPv6Address address) {
this.address = address;
super.setText(getText(address));
super.setText(getNodeText(address));
}
}
private double nsPerPixel=1000L;
/**
* 节点标签显示广播得知的设备名称若有+IP地址
*/
private String getNodeText(IPv6Address address) {
StringBuilder sb=new StringBuilder();
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
if(dname!=null)
sb.append(dname).append('\n');
sb.append(InetGraphNode.getText(address));
return sb.toString();
}
private double nsPerPixel=5000L;
private class InetGraphEdgeGroup extends GraphEdgeGroup{
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
super(nodeA, nodeB);
@@ -201,6 +194,8 @@ public class NetworkGraphPanel extends GraphPanel {
IPv6Address inet6Address = (IPv6Address) iterator.next();
if(!addr.containsKey(inet6Address)) {
iterator.remove();
}else {
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
}
}
//System.out.println("------------------------------------");
@@ -5,13 +5,12 @@ import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
@@ -22,7 +21,7 @@ import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
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.klalb.KLALBController;
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
@@ -30,7 +29,6 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
import javax.swing.JTabbedPane;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
@@ -43,7 +41,7 @@ public class NodeInformationPanel extends JPanel {
private KLALBController controller;
private Image image;
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>();
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
public KLALBController getController() {
return controller;
}
@@ -69,11 +67,28 @@ public class NodeInformationPanel extends JPanel {
panel.setLayout(new BorderLayout(0, 0));
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
JTextArea overviewArea = new JTextArea();
overviewArea.setEditable(false);
overviewArea.setLineWrap(true);
overviewArea.setWrapStyleWord(true);
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
String ddesc=null;
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
ddesc=controller.getConfigItem().getDeviceDescription();
if(ddesc!=null&&ddesc.isEmpty()) {
ddesc=null;
}
}
overviewArea.setText(buildOverviewText(dname, ddesc));
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
JPanel panel_1 = new JPanel();
panel_1.setLayout(new BorderLayout(0, 0));
panel_1.add(scrollPane);
JList<MultipurposeSocketAddress> list = new JList<MultipurposeSocketAddress>(listModel);
JList<MultiProtocolSocketAddress> list = new JList<MultiProtocolSocketAddress>(listModel);
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
scrollPane.setViewportView(list);
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
@@ -81,7 +96,7 @@ public class NodeInformationPanel extends JPanel {
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
controller.addRemoteLines(select);
}
});
@@ -93,7 +108,7 @@ public class NodeInformationPanel extends JPanel {
@Override
public void valueChanged(ListSelectionEvent e) {
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
btnNewButton.setEnabled( !select.isEmpty()) ;
copy.setEnabled(!select.isEmpty());
}
@@ -103,9 +118,9 @@ public class NodeInformationPanel extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
StringBuilder sb=new StringBuilder();
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
for (MultipurposeSocketAddress multipurposeSocketAddress : select) {
sb.append(multipurposeSocketAddress);
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
for (MultiProtocolSocketAddress multiProtocolSocketAddress : select) {
sb.append(multiProtocolSocketAddress);
sb.append('\n');
}
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
@@ -150,16 +165,38 @@ public class NodeInformationPanel extends JPanel {
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
try {
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
listModel.clear();
for (MultipurposeSocketAddress multipurposeSocketAddress : result) {
listModel.addElement(multipurposeSocketAddress);
for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) {
listModel.addElement(multiProtocolSocketAddress);
}
// 用对端返回的设备名称/描述更新概览旧版本节点无该字段时保留原显示
String dn=info.getDeviceName();
if(dn==null||dn.isEmpty()) {
dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
}
String dd=info.getDeviceDescription();
if(dd!=null&&dd.isEmpty()) {
dd=null;
}
overviewArea.setText(buildOverviewText(dn, dd));
});
} catch (IOException e) {
e.printStackTrace();
}
}
private String buildOverviewText(String dname,String ddesc) {
StringBuilder sb=new StringBuilder();
sb.append(UIEnv.getRsb().getString("ipv6addr")).append(": ").append(address.toCompressedString()).append('\n');
if(dname!=null&&!dname.isEmpty()) {
sb.append('\n').append(UIEnv.getRsb().getString("devicename")).append(": ").append(dname).append('\n');
}
if(ddesc!=null&&!ddesc.isEmpty()) {
sb.append('\n').append(UIEnv.getRsb().getString("devicedescription")).append(":\n").append(ddesc).append('\n');
}
return sb.toString();
}
public Icon getIcon() {
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.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.klalb.CONST;
import org.kne.cloud.network.klalb.KLALBController;
@@ -219,7 +219,7 @@ public class TPanel2 extends JPanel {
@Override
public void actionPerformed(ActionEvent e) {
MultipurposeSocketAddress mtar=tunnel.getSocketAddress();
MultiProtocolSocketAddress mtar=tunnel.getSocketAddress();
if(mtar!=null) {
kc.removeRemoteLines(mtar);
}else {

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